From 088980109dc6c52f3505520d896432167bd4cc70 Mon Sep 17 00:00:00 2001 From: Philippe Pittoli Date: Wed, 13 Mar 2024 01:16:09 +0100 Subject: [PATCH] WIP: Tokens. Can create tokens for RRs and their indexes + partitions. --- src/requests/token.cr | 35 +++++++++++++++++++++++ src/storage.cr | 66 ++++++++++++++++++++++++++++++++++++++++++- src/storage/token.cr | 13 +++++++++ src/storage/zone.cr | 29 ++++++++++++++++++- 4 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 src/requests/token.cr create mode 100644 src/storage/token.cr diff --git a/src/requests/token.cr b/src/requests/token.cr new file mode 100644 index 0000000..f6301e3 --- /dev/null +++ b/src/requests/token.cr @@ -0,0 +1,35 @@ +require "ipc/json" +require "grok" + +class DNSManager::Request + IPC::JSON.message NewToken, 18 do + property domain : String + property rrid : UInt32 + + def initialize(@domain, @rrid) + end + + def handle(dnsmanagerd : DNSManager::Service, event : IPC::Event) : IPC::JSON + user = dnsmanagerd.get_logged_user event + return Response::ErrorUserNotLogged.new unless user + dnsmanagerd.storage.new_token user.uid, @domain, @rrid + end + end + DNSManager.requests << NewToken + + IPC::JSON.message UseToken, 19 do + property token : String + property address : String + + def initialize(@token, @address) + end + + def handle(dnsmanagerd : DNSManager::Service, event : IPC::Event) : IPC::JSON + user = dnsmanagerd.get_logged_user event + return Response::ErrorUserNotLogged.new unless user + return Response::InsufficientRights.new unless user.admin + dnsmanagerd.storage.use_token user.uid, @token, @address + end + end + DNSManager.requests << UseToken +end diff --git a/src/storage.cr b/src/storage.cr index 82e2f87..3515f85 100644 --- a/src/storage.cr +++ b/src/storage.cr @@ -12,6 +12,10 @@ class DNSManager::Storage getter zones : DODB::CachedDataBase(Zone) getter zones_by_domain : DODB::Index(Zone) + getter tokens : DODB::CachedDataBase(Token) + getter tokens_by_uuid : DODB::Index(Token) + getter tokens_by_domain : DODB::Partition(Token) + getter root : String getter zonefiledir : String @@ -20,6 +24,9 @@ class DNSManager::Storage @user_data_by_uid = @user_data.new_index "uid", &.uid.to_s @zones = DODB::CachedDataBase(Zone).new "#{@root}/zones" @zones_by_domain = @zones.new_index "domain", &.domain + @tokens = DODB::CachedDataBase(Token).new "#{@root}/tokens" + @tokens_by_uuid = @tokens.new_index "uuid", &.uuid + @tokens_by_domain = @tokens.new_partition "domain", &.domain @zonefiledir = "#{@root}/bind9-zones" # TODO: create the directory @@ -32,6 +39,8 @@ class DNSManager::Storage @user_data.reindex_everything! Baguette::Log.debug "Reindexing zones..." @zones.reindex_everything! + Baguette::Log.debug "Reindexing tokens..." + @tokens.reindex_everything! Baguette::Log.debug "Reindexed!" end end @@ -331,8 +340,9 @@ class DNSManager::Storage # Update on-disk user data. update_user_data user_data - # Remove the related zone. + # Remove the related zone and their registered tokens. zones_by_domain.delete domain + tokens_by_domain.delete domain Response::DomainDeleted.new domain rescue e @@ -378,6 +388,60 @@ class DNSManager::Storage Baguette::Log.error "trying to list all user #{user_id} domains: #{e}" Response::Error.new "error while listing domains" end + + def new_token(user_id : Int32, domain : String, rrid : UInt32) : IPC::JSON + puts "new token for domain #{domain} rrid #{rrid}" + + # 0. verifications: must exist: user, zone, RR. Zone must be owned by user. + + # User must exist. + user_data = user_data_by_uid.get? user_id.to_s + unless user_data + Baguette::Log.warning "unknown user #{user_id} tries get a new token for a RR in domain #{domain}" + return Response::UnknownUser.new + end + + # Zone must exist. + zone = zones_by_domain.get? domain + return Response::DomainNotFound.new unless zone + + # User should own it. + unless user_data.domains.includes?(domain) || user_data.admin + Baguette::Log.warning "user #{user_id} doesn't own domain #{domain}" + return Response::NoOwnership.new + end + + # RR must exist. + rr = zone.get_rr rrid + return Response::RRNotFound.new unless rr + + old_token = rr.token + + # 1. create token + token = Token.new domain, rrid + + # 2. add token to the RR. + rr.token = token.uuid + zone.update_rr rr + + # 3. update the zone (no need to call `update_zone` to change the zone serial). + zones_by_domain.update_or_create zone.domain, zone + + # 4. if there is an old token, remove it. + if old_token_uuid = old_token + tokens_by_uuid.delete old_token_uuid + end + + # 5. add token to the db. + @tokens << token + + Response::RRUpdated.new domain, rr + end + + def use_token(user_id : Int32, token : String, address : String) : IPC::JSON + puts "use token #{token} address #{address}" + Response::Error.new "unimplemented" + end end require "./storage/*" diff --git a/src/storage/token.cr b/src/storage/token.cr new file mode 100644 index 0000000..22d7329 --- /dev/null +++ b/src/storage/token.cr @@ -0,0 +1,13 @@ +require "uuid" + +class DNSManager::Storage::Token + include JSON::Serializable + + property uuid : String + property domain : String + property rrid : UInt32 + + def initialize(@domain, @rrid) + @uuid = UUID.random.to_s + end +end diff --git a/src/storage/zone.cr b/src/storage/zone.cr index e9acdd9..7a7e228 100644 --- a/src/storage/zone.cr +++ b/src/storage/zone.cr @@ -55,8 +55,12 @@ class DNSManager::Storage::Zone # Yes. It already happened. Many, MANY times. I WANT MY FUCKING TIME BACK. property readonly : Bool = false + # RR entries can have an update token. + # This way, users can automatically update their IP address using a simple HTTP request (ex: with `wget`). + property token : String? = nil + # zone class is omited, it always will be IN in our case. - def initialize(@name, @ttl, @target) + def initialize(@name, @ttl, @target, @token = nil) @rrtype = self.class.name.upcase.gsub /DNSMANAGER::STORAGE::ZONE::/, "" end @@ -619,6 +623,18 @@ class DNSManager::Storage::Zone @current_rrid += 1 end + def get_rr(rrid : UInt32) : ResourceRecord? + @resources.each do |rr| + return rr if rr.rrid = rrid + end + end + + def update_rr(rr : ResourceRecord) + puts "updating rr #{rr.rrid}" + @resources.select! { |x| x.rrid == rr.rrid } + @resources << rr + end + def to_s(io : IO) io << "DOMAIN #{@domain}.\n" @resources.each do |rr| @@ -754,6 +770,17 @@ class DNSManager::Storage::Zone end end end + + def new_token(rrid : UInt32, token : String) + old_token : String? = nil + @resources.each do |rr| + if rr.rrid == rrid + old_token = rr.token + rr.token = token + end + end + old_token + end end def qualifier_to_char(qualifier : DNSManager::Storage::Zone::SPF::Qualifier) : Char