Delegation WIP.
This commit is contained in:
parent
c5433bb95a
commit
38e3ce432e
3 changed files with 122 additions and 44 deletions
|
@ -36,4 +36,11 @@ module DNSManager
|
||||||
|
|
||||||
class TokenNotFoundException < ::Exception
|
class TokenNotFoundException < ::Exception
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The domain doesn't have delegation parameters, but a generation of a delegation file is asked.
|
||||||
|
class NoDelegation < ::Exception
|
||||||
|
property domain : String
|
||||||
|
def initialize(@domain)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
150
src/storage.cr
150
src/storage.cr
|
@ -6,6 +6,15 @@ require "./service.cr"
|
||||||
|
|
||||||
require "dodb"
|
require "dodb"
|
||||||
|
|
||||||
|
def safe_write(filename : String, &block)
|
||||||
|
filename_wip = "#{filename}.wip"
|
||||||
|
File.open(filename_wip, "w") do |file|
|
||||||
|
yield file
|
||||||
|
end
|
||||||
|
# Rename WIP filename to final file name.
|
||||||
|
File.rename filename_wip, filename
|
||||||
|
end
|
||||||
|
|
||||||
class DNSManager::Storage
|
class DNSManager::Storage
|
||||||
getter domains : DODB::Storage::Common(Domain)
|
getter domains : DODB::Storage::Common(Domain)
|
||||||
getter domains_by_name : DODB::Trigger::IndexCached(Domain)
|
getter domains_by_name : DODB::Trigger::IndexCached(Domain)
|
||||||
|
@ -23,6 +32,7 @@ class DNSManager::Storage
|
||||||
|
|
||||||
getter root : String
|
getter root : String
|
||||||
getter zonefiledir : String
|
getter zonefiledir : String
|
||||||
|
getter delegationdir : String
|
||||||
|
|
||||||
property dnsmanagerd : DNSManager::Service? = nil
|
property dnsmanagerd : DNSManager::Service? = nil
|
||||||
|
|
||||||
|
@ -66,6 +76,9 @@ class DNSManager::Storage
|
||||||
@zonefiledir = "#{@root}/bind9-zones"
|
@zonefiledir = "#{@root}/bind9-zones"
|
||||||
Dir.mkdir_p @zonefiledir
|
Dir.mkdir_p @zonefiledir
|
||||||
|
|
||||||
|
@delegationdir = "#{@root}/bind9-delegations"
|
||||||
|
Dir.mkdir_p @delegationdir
|
||||||
|
|
||||||
Baguette::Log.info "storage initialized"
|
Baguette::Log.info "storage initialized"
|
||||||
|
|
||||||
if reindex
|
if reindex
|
||||||
|
@ -79,6 +92,23 @@ class DNSManager::Storage
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Delegation token file: file that is used off-application to know when the delegation file should be re-generated.
|
||||||
|
# Each time this file is touched, the (root) zone files are generated.
|
||||||
|
#
|
||||||
|
# RATIONALE: generating the final (root) zone file may become quite slow over time.
|
||||||
|
# Instead of generating the file for each change (synchronously),
|
||||||
|
# it will be regenerated periodically by a script outside the application.
|
||||||
|
def delegation_token_file
|
||||||
|
"#{@delegationdir}/regen-token-file"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Updates the token file.
|
||||||
|
def update_delegation_token_file
|
||||||
|
File.open(delegation_token_file, "w") do |file|
|
||||||
|
file << "last change: #{Baguette::Log.now}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Generate Bind9 zone files.
|
# Generate Bind9 zone files.
|
||||||
# The file is written in a temporary file then moved, enabling safe manipulation of the file
|
# The file is written in a temporary file then moved, enabling safe manipulation of the file
|
||||||
# since its content always will be consistent even if not up-to-date.
|
# since its content always will be consistent even if not up-to-date.
|
||||||
|
@ -90,17 +120,11 @@ class DNSManager::Storage
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# Safe write.
|
filename = "#{@zonefiledir}/#{zone.domain}"
|
||||||
filename_final = "#{@zonefiledir}/#{zone.domain}"
|
Baguette::Log.info "writing zone file #{filename}"
|
||||||
filename_wip = "#{filename_final}.wip"
|
safe_write filename do |file|
|
||||||
Baguette::Log.info "writing zone file #{filename_final}"
|
|
||||||
|
|
||||||
File.open(filename_wip, "w") do |file|
|
|
||||||
zone.to_bind9 file
|
zone.to_bind9 file
|
||||||
end
|
end
|
||||||
|
|
||||||
# Rename WIP filename to final file name.
|
|
||||||
File.rename filename_wip, filename_final
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Request to generate a zone file.
|
# Request to generate a zone file.
|
||||||
|
@ -374,6 +398,12 @@ class DNSManager::Storage
|
||||||
Response::RRDeleted.new rrid
|
Response::RRDeleted.new rrid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Removes a delegation file, then update the token file.
|
||||||
|
def remove_delegation_file(domain : String) : Nil
|
||||||
|
Baguette::Log.info "Removing a delegation file."
|
||||||
|
File.delete "#{@delegationdir}/#{domain}"
|
||||||
|
end
|
||||||
|
|
||||||
# Removes a Bind9 zonefile.
|
# Removes a Bind9 zonefile.
|
||||||
def remove_bind9_zonefile(domain : String) : Nil
|
def remove_bind9_zonefile(domain : String) : Nil
|
||||||
Baguette::Log.info "Removing a Bind9 zone file."
|
Baguette::Log.info "Removing a Bind9 zone file."
|
||||||
|
@ -382,31 +412,8 @@ class DNSManager::Storage
|
||||||
|
|
||||||
# Deletes a domain.
|
# Deletes a domain.
|
||||||
def delete_domain(user_id : UserDataID, domain_name : String) : IPC::JSON
|
def delete_domain(user_id : UserDataID, domain_name : String) : IPC::JSON
|
||||||
zone_must_exist! domain_name
|
wipe_domain user_id, domain_name
|
||||||
user_should_own! user_id, domain_name
|
wipe_zone user_id, domain_name
|
||||||
|
|
||||||
domain = @domains_by_name.get domain_name
|
|
||||||
# The user isn't the only owner, just remove him from the list of owners.
|
|
||||||
if domain.owners.size > 1
|
|
||||||
domain_cloned = domain.clone
|
|
||||||
domain_cloned.owners.select! { |o| o != user_id }
|
|
||||||
@domains_by_name.update domain_cloned
|
|
||||||
|
|
||||||
# Not really a domain deletion, but that'll do the trick.
|
|
||||||
return Response::DomainDeleted.new domain_name
|
|
||||||
end
|
|
||||||
|
|
||||||
# Remove the related zone and their registered tokens.
|
|
||||||
zones_by_domain.delete domain_name
|
|
||||||
|
|
||||||
# The domain may not have tokens.
|
|
||||||
tokens_by_domain.delete? domain_name
|
|
||||||
|
|
||||||
# Remove this domain_name from the list of user's domains.
|
|
||||||
domains_by_name.delete domain_name
|
|
||||||
|
|
||||||
remove_bind9_zonefile domain_name
|
|
||||||
|
|
||||||
Response::DomainDeleted.new domain_name
|
Response::DomainDeleted.new domain_name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -449,24 +456,33 @@ class DNSManager::Storage
|
||||||
# Deletes the zone and its tokens to wipe the zone data then delegates the domain.
|
# Deletes the zone and its tokens to wipe the zone data then delegates the domain.
|
||||||
def delegate_domain(user_id : UserDataID, domain_name : String,
|
def delegate_domain(user_id : UserDataID, domain_name : String,
|
||||||
nameserver1 : String, nameserver2 : String) : IPC::JSON
|
nameserver1 : String, nameserver2 : String) : IPC::JSON
|
||||||
_ = zone_must_exist! domain_name
|
|
||||||
user_should_own! user_id, domain_name
|
|
||||||
|
|
||||||
# Name server domains verification.
|
# Verifies the provided name server domains.
|
||||||
return Response::InvalidDomainName.new unless Zone.is_domain_valid? nameserver1
|
return Response::InvalidDomainName.new unless Zone.is_domain_valid? nameserver1
|
||||||
return Response::InvalidDomainName.new unless Zone.is_domain_valid? nameserver2
|
return Response::InvalidDomainName.new unless Zone.is_domain_valid? nameserver2
|
||||||
|
|
||||||
# Remove the related zone and their registered tokens.
|
# Wipes the domain from dnsmanager (generated zone file, tokens).
|
||||||
zones_by_domain.delete domain_name
|
wipe_domain user_id, domain_name
|
||||||
|
|
||||||
# The domain may not have tokens.
|
remove_bind9_zonefile domain_name
|
||||||
tokens_by_domain.delete? domain_name
|
|
||||||
|
|
||||||
# TODO, FIXME: actually delegate the domain.
|
|
||||||
|
|
||||||
|
# Creates the new zone.
|
||||||
zone = Zone.new domain_name
|
zone = Zone.new domain_name
|
||||||
zone.delegation = Zone::Delegation.new nameserver1, nameserver2
|
zone.delegation = Zone::Delegation.new nameserver1, nameserver2
|
||||||
zones_by_domain.update_or_create zone.domain, zone
|
|
||||||
|
filename = "#{@delegationdir}/#{domain_name}"
|
||||||
|
Baguette::Log.info "New delegation file: #{filename}"
|
||||||
|
safe_write filename do |file|
|
||||||
|
zone.to_delegation file
|
||||||
|
rescue e : NoDelegation
|
||||||
|
Baguette::Log.error "domain #{domain_name}: trying to delegate but doesn't have delegation parameters"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Once the new delegation file has been written, the script generating the (root) zone file must
|
||||||
|
# be informed by touching a file (named "delegation token file" in the source code).
|
||||||
|
update_delegation_token_file
|
||||||
|
|
||||||
|
zones_by_domain.update_or_create zone
|
||||||
|
|
||||||
Response::DomainDelegated.new domain_name, nameserver1, nameserver2
|
Response::DomainDelegated.new domain_name, nameserver1, nameserver2
|
||||||
end
|
end
|
||||||
|
@ -483,6 +499,7 @@ class DNSManager::Storage
|
||||||
@tokens_by_domain.delete? domain_cloned.name
|
@tokens_by_domain.delete? domain_cloned.name
|
||||||
@zones_by_domain.delete domain_cloned.name
|
@zones_by_domain.delete domain_cloned.name
|
||||||
@domains_by_name.delete domain_cloned.name
|
@domains_by_name.delete domain_cloned.name
|
||||||
|
remove_bind9_zonefile domain_cloned.name
|
||||||
else
|
else
|
||||||
@domains_by_name.update domain_cloned
|
@domains_by_name.update domain_cloned
|
||||||
end
|
end
|
||||||
|
@ -544,6 +561,51 @@ class DNSManager::Storage
|
||||||
zone
|
zone
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# WARNING: this function removes a domain with all its related data (zone file, delegation file, indexes, etc.).
|
||||||
|
#
|
||||||
|
# RATIONALE: wipe_zone can be used to renew to remove zone-related content (the entry in the zone db, tokens
|
||||||
|
# and generated zone file) while preserving the entry in the domain db.
|
||||||
|
# wipe_zone is called when the domain is deleted or when it is delegated.
|
||||||
|
def wipe_zone(user_id : UserDataID, domain_name : String)
|
||||||
|
zone = zone_must_exist! domain_name
|
||||||
|
user_should_own! user_id, domain_name
|
||||||
|
|
||||||
|
Baguette::Log.info "Wiping zone content for #{domain_name}"
|
||||||
|
# Remove the related zone and their registered tokens.
|
||||||
|
zones_by_domain.delete domain_name
|
||||||
|
|
||||||
|
# The domain may not have tokens.
|
||||||
|
tokens_by_domain.delete? domain_name
|
||||||
|
|
||||||
|
# Remove the delegation file and regenerate the delegation files.
|
||||||
|
if delegation = zone.delegation
|
||||||
|
remove_delegation_file domain_name
|
||||||
|
end
|
||||||
|
|
||||||
|
# There is no need to keep a generated zone file.
|
||||||
|
remove_bind9_zonefile domain_name
|
||||||
|
end
|
||||||
|
|
||||||
|
# WARNING: this function removes a domain from the @domains db.
|
||||||
|
def wipe_domain(user_id : UserDataID, domain_name : String)
|
||||||
|
zone = zone_must_exist! domain_name
|
||||||
|
user_should_own! user_id, domain_name
|
||||||
|
|
||||||
|
Baguette::Log.info "Wiping domain entry for #{domain_name}"
|
||||||
|
domain = @domains_by_name.get domain_name
|
||||||
|
# The user isn't the only owner, just remove him from the list of owners.
|
||||||
|
if domain.owners.size > 1
|
||||||
|
domain_cloned = domain.clone
|
||||||
|
domain_cloned.owners.select! { |o| o != user_id }
|
||||||
|
@domains_by_name.update domain_cloned
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove this domain_name from the list of user's domains.
|
||||||
|
domains_by_name.delete domain_name
|
||||||
|
end
|
||||||
|
|
||||||
# Owning a domain means to be in the owners' list of the domain.
|
# Owning a domain means to be in the owners' list of the domain.
|
||||||
def user_should_own!(user_id : UserDataID, domain : String) : Nil
|
def user_should_own!(user_id : UserDataID, domain : String) : Nil
|
||||||
d = domains_by_name.get? domain
|
d = domains_by_name.get? domain
|
||||||
|
|
|
@ -55,6 +55,15 @@ class DNSManager::Storage::Zone
|
||||||
def_clone
|
def_clone
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_delegation(io : IO)
|
||||||
|
if delegation = @delegation
|
||||||
|
io << "#{@domain}. 1800 IN NS #{delegation.nameserver1}\n"
|
||||||
|
io << "#{@domain}. 1800 IN NS #{delegation.nameserver2}\n"
|
||||||
|
else
|
||||||
|
raise NoDelegation.new @domain
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
alias Error = String
|
alias Error = String
|
||||||
|
|
||||||
# Store a Resource Record: A, AAAA, TXT, PTR, CNAME…
|
# Store a Resource Record: A, AAAA, TXT, PTR, CNAME…
|
||||||
|
|
Loading…
Add table
Reference in a new issue