WIP: Tokens. Can create tokens for RRs and their indexes + partitions.

This commit is contained in:
Philippe Pittoli 2024-03-13 01:16:09 +01:00
parent 0c817e1f96
commit 088980109d
4 changed files with 141 additions and 2 deletions

35
src/requests/token.cr Normal file
View File

@ -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

View File

@ -12,6 +12,10 @@ class DNSManager::Storage
getter zones : DODB::CachedDataBase(Zone) getter zones : DODB::CachedDataBase(Zone)
getter zones_by_domain : DODB::Index(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 root : String
getter zonefiledir : String getter zonefiledir : String
@ -20,6 +24,9 @@ class DNSManager::Storage
@user_data_by_uid = @user_data.new_index "uid", &.uid.to_s @user_data_by_uid = @user_data.new_index "uid", &.uid.to_s
@zones = DODB::CachedDataBase(Zone).new "#{@root}/zones" @zones = DODB::CachedDataBase(Zone).new "#{@root}/zones"
@zones_by_domain = @zones.new_index "domain", &.domain @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" @zonefiledir = "#{@root}/bind9-zones"
# TODO: create the directory # TODO: create the directory
@ -32,6 +39,8 @@ class DNSManager::Storage
@user_data.reindex_everything! @user_data.reindex_everything!
Baguette::Log.debug "Reindexing zones..." Baguette::Log.debug "Reindexing zones..."
@zones.reindex_everything! @zones.reindex_everything!
Baguette::Log.debug "Reindexing tokens..."
@tokens.reindex_everything!
Baguette::Log.debug "Reindexed!" Baguette::Log.debug "Reindexed!"
end end
end end
@ -331,8 +340,9 @@ class DNSManager::Storage
# Update on-disk user data. # Update on-disk user data.
update_user_data user_data update_user_data user_data
# Remove the related zone. # Remove the related zone and their registered tokens.
zones_by_domain.delete domain zones_by_domain.delete domain
tokens_by_domain.delete domain
Response::DomainDeleted.new domain Response::DomainDeleted.new domain
rescue e rescue e
@ -378,6 +388,60 @@ class DNSManager::Storage
Baguette::Log.error "trying to list all user #{user_id} domains: #{e}" Baguette::Log.error "trying to list all user #{user_id} domains: #{e}"
Response::Error.new "error while listing domains" Response::Error.new "error while listing domains"
end 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 end
require "./storage/*" require "./storage/*"

13
src/storage/token.cr Normal file
View File

@ -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

View File

@ -55,8 +55,12 @@ class DNSManager::Storage::Zone
# Yes. It already happened. Many, MANY times. I WANT MY FUCKING TIME BACK. # Yes. It already happened. Many, MANY times. I WANT MY FUCKING TIME BACK.
property readonly : Bool = false 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. # 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::/, "" @rrtype = self.class.name.upcase.gsub /DNSMANAGER::STORAGE::ZONE::/, ""
end end
@ -619,6 +623,18 @@ class DNSManager::Storage::Zone
@current_rrid += 1 @current_rrid += 1
end 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) def to_s(io : IO)
io << "DOMAIN #{@domain}.\n" io << "DOMAIN #{@domain}.\n"
@resources.each do |rr| @resources.each do |rr|
@ -754,6 +770,17 @@ class DNSManager::Storage::Zone
end end
end 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 end
def qualifier_to_char(qualifier : DNSManager::Storage::Zone::SPF::Qualifier) : Char def qualifier_to_char(qualifier : DNSManager::Storage::Zone::SPF::Qualifier) : Char