Page MenuHomeGRNET

No OneTemporary

File Metadata

Created
Wed, Nov 19, 1:40 PM
diff --git a/JOBS b/JOBS
index 1cf6a8d..84bf522 100644
--- a/JOBS
+++ b/JOBS
@@ -1,48 +1,54 @@
# Jobs
## add_domain
Add domain to bind as slave (master: webdns). It uses the `/usr/sbin/rndc
addzone` interface.
## trigger_event
Trigger an event for the specified domain through the WebDNS API. The state
machine will take care of the rest once the event is received, it usually
changes state and pushes more jobs to the queue.
## opendnssec_add
Add a zone to opendnssec for signing. The zone is transfered from WebDNS
and a signed zone file is created to `/var/lib/opendnssec/signed` (should be
symlinked to '/var/cache/bind/webdns').
## bind_convert_to_dnssed
It checks that the signed zone file exists and triggers a 'rndc delzone'
followed by 'rnd addzone' that serves the signed zone as a master (file
"webdns/signed/%{zone}").
## wait_for_ready_to_push_ds
Wait for KSK to become ready so we can publish the DS records to the parent
authority.
## publish_ds
-Pushes the DS records to the parent depending on the parent authority.
+Pushes the DS records to the parent depending on the parent authority. Also
+drops DS when a DSSSEC domain is removed (on full-remove).
## wait_for_active
Wait for the KSK to become active. The KSK is marked active by the ds-monitor
script. ds-monitor checks if the DS records are visible using the local
recursor.
+## wait_until
+
+Wait until a specific timestamp is reached, used to pause other jobs in the
+queue, like domain removal.
+
## remove_domain
Remove a zone from bind using rndc delzone.
## opendnssec_remove
Remove a zone from ods uning 'ods-ksmutil zone delete'.
diff --git a/app/helpers/domains_helper.rb b/app/helpers/domains_helper.rb
index 992400f..0bcde66 100644
--- a/app/helpers/domains_helper.rb
+++ b/app/helpers/domains_helper.rb
@@ -1,38 +1,39 @@
module DomainsHelper
# Human names for domain states
def human_state(state)
human = case state.to_sym
when :initial then 'Initial'
when :pending_install then 'Becoming public'
when :pending_signing then 'Signing zone'
when :wait_for_ready then 'Waiting for KSK to become ready'
when :pending_ds then 'Publishing DS records'
when :pending_ds_rollover then 'Performing KSK rollover'
+ when :pending_ds_removal then 'Removing DS records'
when :pending_plain then 'Removing dnssec'
when :pending_remove then 'Preparing removal'
when :operational then 'Operational'
when :destroy then 'Ready to be destroyed'
else
state
end
prog = Domain.dnssec_progress(state)
return human if prog.nil?
"#{human} (#{prog})"
end
# Most of the time the parent zone will be easily computed
def guess_parent_zone(name)
name.split('.', 2).last || ''
end
def dnssec_policy_human(policy)
info = policy.info.map { |name, value|
[name, seconds_to_human(value)].join(': ')
}
"#{policy.name}: (#{info.join(' | ')})"
end
end
diff --git a/app/models/domain.rb b/app/models/domain.rb
index 65f4103..0edddca 100644
--- a/app/models/domain.rb
+++ b/app/models/domain.rb
@@ -1,304 +1,309 @@
class Domain < ActiveRecord::Base
class NotAChild < StandardError; end
self.inheritance_column = :nx
# List all supported domain types.
def self.domain_types
[
'NATIVE',
'MASTER',
'SLAVE',
]
end
# List domain types that can be created.
def self.allowed_domain_types
domain_types - WebDNS.settings[:prohibit_domain_types]
end
# List parent authorities
def self.dnssec_parent_authorities
WebDNS.settings[:dnssec_parent_authorities].keys.map(&:to_s)
end
# Fire event after transaction commmit
# Changing state inside a hook messes things up,
# this trick handles that
attr_accessor :fire_event
belongs_to :group
has_many :jobs
has_many :records
# BUG in bump_serial_trigger
has_one :soa, -> { unscope(where: :type).where(type: 'soa') }, class_name: SOA
belongs_to :dnssec_policy
validates :group_id, presence: true
validates :name, uniqueness: true, presence: true
validates :type, presence: true, inclusion: { in: domain_types }
validates :master, presence: true, ipv4: true, if: :slave?
validates :dnssec, inclusion: { in: [false] }, unless: :dnssec_elegible?
validates :dnssec_parent_authority, inclusion: { in: dnssec_parent_authorities }, if: :dnssec?
validates :dnssec_parent, hostname: true, if: :dnssec?
validates :dnssec_policy_id, presence: true, if: :dnssec?
after_create :generate_soa
after_create :generate_ns
after_create :install
before_save :check_convert
before_save :check_dnssec_parent_authority, if: :dnssec?
after_commit :after_commit_event
attr_writer :serial_strategy
def self.dnssec_progress(current_state)
progress = [
:pending_signing, # 1/3
:wait_for_ready, # 2/3
:pending_ds] # 3/3
idx = progress.index(current_state.to_sym)
return if idx.nil?
[idx+1, progress.size].join('/')
end
state_machine initial: :initial do
after_transition(any => :pending_install) { |domain, _t| Job.add_domain(domain) }
after_transition(any => :pending_remove) { |domain, _t| Job.shutdown_domain(domain) }
+ after_transition(any => :pending_ds_removal) { |domain, _t| Job.dnssec_drop_ds(domain) }
after_transition(any => :pending_signing) { |domain, _t| Job.dnssec_sign(domain) }
after_transition(any => :wait_for_ready) { |domain, _t| Job.wait_for_ready(domain) }
after_transition(any => :pending_ds) { |domain, t| Job.dnssec_push_ds(domain, *t.args) }
after_transition(any => :pending_ds_rollover) { |domain, t| Job.dnssec_rollover_ds(domain, *t.args) }
after_transition(any => :pending_plain) { |domain, _t| Job.convert_to_plain(domain) }
after_transition(any => :destroy) { |domain, _t| domain.destroy }
# User events
event :install do
transition initial: :pending_install
end
event :dnssec_sign do
transition operational: :pending_signing
end
event :signed do
transition pending_signing: :wait_for_ready
end
event :push_ds do
transition wait_for_ready: :pending_ds, operational: :pending_ds_rollover
end
event :plain_convert do
transition operational: :pending_plain
end
event :remove do
- transition operational: :pending_remove
+ transition [:operational, :pending_ds_removal] => :pending_remove
+ end
+
+ event :full_remove do
+ transition operational: :pending_ds_removal
end
# Machine events
event :installed do
transition pending_install: :operational
end
event :converted do
transition [:pending_ds, :pending_plain] => :operational
end
event :complete_rollover do
transition pending_ds_rollover: :operational
end
event :cleaned_up do
transition pending_remove: :destroy
end
event :ksk_rollover_detected do
transition operational: :ksk_rollover
end
end
# Returns true if this domain is elegigble for DNSSEC
def dnssec_elegible?
return false if slave?
true
end
# Returns the zone serial if a SOA record exists
def serial
return if !soa
soa.serial
end
# Get the zone's serial strategy.
#
# Returns one of the supported serial strategies.
def serial_strategy
@serial_strategy ||= WebDNS.settings[:serial_strategy]
end
# Returns true if this a reverse zone.
def reverse?
name.end_with?('.in-addr.arpa') || name.end_with?('.ip6.arpa')
end
# Returns true if this a ENUM zone.
def enum?
name.end_with?('.e164.arpa')
end
# Returns true if this is a slave zone.
def slave?
type == 'SLAVE'
end
# Compute subnet for reverse records
def subnet
return if not reverse?
if name.end_with?('.in-addr.arpa')
subnet_v4
elsif name.end_with?('.ip6.arpa')
subnet_v6
end
end
def self.replace_ds(parent, child, records)
parent = find_by_name!(parent)
fail NotAChild if not child.end_with?(parent.name)
existing = parent.records.where(name: child, type: 'DS')
recs = records.map { |rec| DS.new(domain: parent, name: child, content: rec) }
ActiveRecord::Base.transaction do
existing.destroy_all
recs.map(&:save!)
end
end
# Apply bulk to operations to the zones
#
# 1) Deletions
# 2) Changes
# 3) Additions
def bulk(opts)
deletes = opts[:deletes] || []
changes = opts[:changes] || {}
additions = opts[:additions] || {}
errors = Hash.new { |h, k| h[k] = {} }
ActiveRecord::Base.transaction do
# Deletes
to_delete = records.where(id: deletes).index_by(&:id)
deletes.each { |rec_id|
if rec = to_delete[Integer(rec_id)]
rec.destroy
next
end
errors[:deletes][rec_id] = 'Deleted record not found'
}
# Changes
to_change = records.where(id: changes.keys).index_by(&:id)
changes.each {|rec_id, changes|
binding
if rec = to_change[Integer(rec_id)]
errors[:changes][rec_id] = rec.errors.full_messages.join(', ') if !rec.update(changes)
next
end
errors[:changes][rec_id] = 'Changed record not found'
}
# Additions
additions.each { |inc, attrs|
rec = records.new(attrs)
errors[:additions][inc] = rec.errors.full_messages.join(', ') if !rec.save
}
raise ActiveRecord::Rollback if errors.any?
end
errors
end
private
def subnet_v4
# get ip octets (remove .in-addr.arpa)
octets = name.split('.')[0...-2].reverse
return if octets.any? { |_| false }
mask = 8 * octets.size
octets += [0, 0, 0, 0]
ip = IPAddr.new octets[0, 4].join('.')
[ip, mask].join('/')
end
def subnet_v6
nibbles = name.split('.')[0...-2].reverse
return if nibbles.any? { |_| false }
mask = 4 * nibbles.size
nibbles += [0] * 32
ip = IPAddr.new nibbles[0, 32].in_groups_of(4).map(&:join).join(':')
[ip, mask].join('/')
end
# Hooks
def generate_soa
soa_record = SOA.new(domain: self)
soa_record.save!
end
def generate_ns
return if slave?
return if WebDNS.settings[:default_ns].empty?
WebDNS.settings[:default_ns].each { |ns|
Record.find_or_create_by!(domain: self, type: 'NS', name: '', content: ns)
}
end
def check_convert
return if !dnssec_changed?
event = dnssec ? :dnssec_sign : :plain_convert
if state_events.include?(event)
self.fire_event = event # Schedule event for after commit
return true
end
errors.add(:dnssec, 'You cannot modify dnssec settings in this state!')
false
end
def check_dnssec_parent_authority
cfg = WebDNS.settings[:dnssec_parent_authorities][dnssec_parent_authority.to_sym]
return if !cfg[:valid]
return true if cfg[:valid].call(dnssec_parent)
errors.add(:dnssec_parent_authority, 'Parent zone is not accepted for the selected parent authority!')
false
end
def after_commit_event
return if !fire_event
fire_state_event(fire_event)
self.fire_event = nil
end
end
diff --git a/app/models/job.rb b/app/models/job.rb
index 723e18a..5aa81cd 100644
--- a/app/models/job.rb
+++ b/app/models/job.rb
@@ -1,131 +1,145 @@
class Job < ActiveRecord::Base
belongs_to :domain
scope :pending, -> { where(status: 0) }
scope :failed, -> { where(status: 2) }
scope :completed, -> { where(status: [1, 2]) }
def failed?
status == 2
end
def done?
status == 1
end
def pending?
status == 0
end
def arguments
JSON.parse(args)
end
def zone
return domain.name if domain
arguments['zone']
end
def run_event!
args = arguments
raise 'Not an event!' unless args['event']
Domain
.find_by_name(args['zone'])
.fire_state_event(args['event'])
self.status = 1
self.save!
end
class << self
def add_domain(domain)
ActiveRecord::Base.transaction do
jobs_for_domain(domain, :add_domain)
trigger_event(domain, :installed)
end
end
def shutdown_domain(domain)
ActiveRecord::Base.transaction do
job_for_domain(domain, :remove_domain)
job_for_domain(domain, :opendnssec_remove) if domain.dnssec?
trigger_event(domain, :cleaned_up)
end
end
def dnssec_sign(domain)
ActiveRecord::Base.transaction do
job_for_domain(domain, :opendnssec_add, policy: domain.dnssec_policy.name)
job_for_domain(domain, :bind_convert_to_dnssec)
trigger_event(domain, :signed)
end
end
def wait_for_ready(domain)
jobs_for_domain(domain,
:wait_for_ready_to_push_ds)
end
def dnssec_push_ds(domain, dss)
opts = Hash[:dnssec_parent, domain.dnssec_parent,
:dnssec_parent_authority, domain.dnssec_parent_authority,
:dss, dss]
keytag = dss.map { |ds| ds.split.first }.first # Both records should have the same keytag
ActiveRecord::Base.transaction do
job_for_domain(domain, :publish_ds, opts)
job_for_domain(domain, :wait_for_active, keytag: keytag)
trigger_event(domain, :converted)
end
end
def dnssec_rollover_ds(domain, dss)
opts = Hash[:dnssec_parent, domain.dnssec_parent,
:dnssec_parent_authority, domain.dnssec_parent_authority,
:dss, dss]
keytag = dss.map { |ds| ds.split.first }.first # Both records should have the same keytag
ActiveRecord::Base.transaction do
job_for_domain(domain, :publish_ds, opts)
job_for_domain(domain, :wait_for_active, keytag: keytag)
trigger_event(domain, :complete_rollover)
end
end
+ def dnssec_drop_ds(domain)
+ opts = Hash[:dnssec_parent, domain.dnssec_parent,
+ :dnssec_parent_authority, domain.dnssec_parent_authority,
+ :dss, []]
+
+ ActiveRecord::Base.transaction do
+ job_for_domain(domain, :publish_ds, opts)
+ # Wait for the change to propagate
+ job_for_domain(domain, :wait_until, until: Time.now.to_i + WebDNS.settings[:dnssec_ds_removal_sleep])
+
+ trigger_event(domain, :remove)
+ end
+ end
+
def convert_to_plain(domain)
ActiveRecord::Base.transaction do
jobs_for_domain(domain,
:remove_domain,
:add_domain,
:opendnssec_remove)
trigger_event(domain, :converted)
end
end
private
def trigger_event(domain, event)
job_for_domain(domain, :trigger_event, event: event)
end
def jobs_for_domain(domain, *job_names)
job_names.each { |job_name| job_for_domain(domain, job_name) }
end
def job_for_domain(domain, job_name, args = {})
args = { zone: domain.name }.merge!(args)
create!(domain: domain, job_type: job_name, args: args.to_json)
end
end
end
diff --git a/config/initializers/00_settings.rb b/config/initializers/00_settings.rb
index 6bfc783..57b321d 100644
--- a/config/initializers/00_settings.rb
+++ b/config/initializers/00_settings.rb
@@ -1,47 +1,48 @@
WebDNS.settings[:soa_defaults] = {
primary_ns: 'ns1.example.com',
contact: 'domainmaster@example.com',
serial: 1,
refresh: 10_800,
retry: 3600,
expire: 604_800,
nx: 3600
}
WebDNS.settings[:default_ns] = [
'ns1.example.com',
'ns2.example.com'
]
WebDNS.settings[:dnssec] = true
WebDNS.settings[:dnssec_parent_authorities] = {
webdns: {
valid: -> (parent) { Domain.find_by_name(parent) } # Check if parent is self-hosted
},
papaki: {
valid: -> (parent) { parent.split('.').size == 1 } # TLDs
}
}
+WebDNS.settings[:dnssec_ds_removal_sleep] = 14400 * 2
# Testing helper
WebDNS.settings[:dnssec_parent_authorities].merge!(
test_authority: {
valid: -> (parent) { true }
}
) if Rails.env.test?
WebDNS.settings[:serial_strategy] = Strategies::Date
WebDNS.settings[:prohibit_records_types] = []
WebDNS.settings[:prohibit_domain_types] = ['NATIVE']
WebDNS.settings[:contact_mail] = 'webdns@example.com'
WebDNS.settings[:mail_from] = 'webdns@example.com'
WebDNS.settings[:admin_group] = 'admin'
WebDNS.settings[:saml] = false
WebDNS.settings[:saml_required_entitlement] = 'webdns'
WebDNS.settings[:saml_login_text] = 'Login with SAML'
# Allow local overrides
local_settings = File.expand_path('../../local_settings.rb', __FILE__)
require_relative local_settings if File.exist?(local_settings)
diff --git a/dnsworker/lib/dnsworker/worker.rb b/dnsworker/lib/dnsworker/worker.rb
index 98d3aba..483029a 100755
--- a/dnsworker/lib/dnsworker/worker.rb
+++ b/dnsworker/lib/dnsworker/worker.rb
@@ -1,109 +1,113 @@
require 'json'
require 'net/http'
require 'uri'
require 'pp'
require 'rack/utils'
require 'dnsworker'
require 'dnsworker/base_worker'
require 'dnsworker/pushers/base'
require 'dnsworker/pushers/papaki'
require 'dnsworker/pushers/webdns'
class DNSWorker::Worker
include DNSWorker::BaseWorker
Pushers = Hash[
:papaki, DNSWorker::Pushers::Papaki,
:webdns, DNSWorker::Pushers::Webdns,
]
def initialize(cfg)
@cfg = cfg
super(cfg['mysql'])
end
def add_domain(params)
params[:master] = cfg['hidden_master']
cmd(cfg['bind_add'] % params)
end
def remove_domain(params)
cmd(cfg['bind_del'] % params)
end
def opendnssec_add(params)
cmd(cfg['ods_add'] % params)
end
def opendnssec_remove(params)
cmd(cfg['ods_del'] % params)
end
def bind_convert_to_dnssec(params)
fail Retry if !File.exist? File.join(cfg['zone_root'], 'signed', params[:zone])
# Remove zone and re-add it as a master zone
remove_domain(params)
cmd(cfg['bind_add_dnssec'] % params)
end
# The zone is signed, waiting for the ksk to become ready
def wait_for_ready_to_push_ds(params)
out, _err = cmd(cfg['ready_to_push_ds'] % params)
fail Retry unless out['ds-seen']
end
def publish_ds(params)
pub_cls = Pushers[params[:dnssec_parent_authority].to_sym]
fail JobFailed unless pub_cls
pub = pub_cls.new(cfg)
fail JobFailed unless pub.replace_ds(params[:dnssec_parent], params[:zone], params[:dss])
end
def wait_for_active(params)
keytag = params[:keytag]
out, _err = cmd(cfg['key_activated'] % params)
key_lines = out.each_line.select { |line| line.start_with?(params[:zone]) }
# Check if the key is activated
return if key_lines.any? {|kl|
# example
# <domain> KSK active 2016-12-12 18:41:33 (retire) 2048 8 b70042f966e5f01deb2e988607ad67ba SoftHSM 60076
kl.strip!
_domain, _type, status, _rest = kl.split(/\s+/, 4)
status == 'active' and _rest.end_with?(keytag)
}
fail Retry
end
+ def wait_until(params)
+ fail Retry if Time.now.to_i < params[:until]
+ end
+
def trigger_event(params)
query = Rack::Utils.build_query(domain: params[:zone], event: params[:event])
uri = URI(cfg.values_at('webdns_base', 'update_state').join % { query: query })
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
resp = http.request Net::HTTP::Put.new(uri.request_uri)
fail JobFailed if resp.code != '200'
ok = JSON.parse(resp.body)['ok']
fail JobFailed if !ok
end
end
private
def cmdline(jtype, jargs)
if jargs
send(jtype, jargs)
else
send(jtype)
end
end
end
diff --git a/test/models/domain_test.rb b/test/models/domain_test.rb
index 7b6433a..2e6255b 100644
--- a/test/models/domain_test.rb
+++ b/test/models/domain_test.rb
@@ -1,238 +1,305 @@
require 'test_helper'
class DomainTest < ActiveSupport::TestCase
def setup
@domain = build(:domain)
end
test 'automatic SOA creation' do
@domain.save!
@domain.reload
assert_not_nil @domain.soa
end
test 'increment serial on new record' do
@domain.save!
soa = @domain.soa
assert_serial_update soa do
www = A.new(name: 'www', domain: @domain, content: '1.2.3.4')
www.save!
end
end
test 'increment serial on record update' do
@domain.save!
www = A.new(name: 'www', domain: @domain, content: '1.2.3.4')
www.save!
soa = @domain.soa.reload
assert_serial_update soa do
www.content = '1.2.3.5'
www.save!
end
end
test 'automatic NS creation' do
@domain.save!
@domain.reload
assert_equal WebDNS.settings[:default_ns].sort,
@domain.records.where(type: 'NS').pluck(:content).sort
end
test 'increment serial on record destroy' do
@domain.save!
www = A.new(name: 'www', domain: @domain, content: '1.2.3.4')
www.save!
soa = @domain.soa.reload
assert_serial_update soa do
www.destroy!
end
end
class SlaveDomainTest < ActiveSupport::TestCase
def setup
@domain = build(:slave)
end
test 'saves' do
@domain.save
assert_empty @domain.errors
end
test 'automatic SOA creation' do
@domain.save!
@domain.reload
assert_not_nil @domain.soa
assert_equal 1, @domain.soa.serial
end
test 'validates master' do
@domain.master = 'not-an-ip'
@domain.save
assert_not_empty @domain.errors['master']
end
test 'no records are allowed for users' do
@domain.save!
rec = build(:a, domain_id: @domain.id)
assert_not rec.valid?
assert_not_empty rec.errors[:type]
end
end
class StatesDomainTest < ActiveSupport::TestCase
def setup
@domain = build(:domain)
@policy = create(:dnssec_policy)
end
test 'domain lifetime' do
assert_equal 'initial', @domain.state
# Create
assert_jobs do
@domain.save! # user triggered
assert_equal 'pending_install', @domain.state
end
@domain.installed # job triggered
assert_equal 'operational', @domain.state
# Convert to dnssec (sign)
assert_jobs do
@domain.dnssec = true
@domain.dnssec_policy = @policy
@domain.dnssec_parent = @domain.name.split('.', 2).last
@domain.dnssec_parent_authority = 'test_authority'
@domain.save!
# After commit is not triggered in tests,
# so we have to trigger it manually
@domain.send(:after_commit_event)
assert_equal 'pending_signing', @domain.state
end
assert_jobs do
assert @domain.signed # job triggered
assert_equal 'wait_for_ready', @domain.state
end
# Convert to dnssec (publish ds)
assert_jobs do
assert @domain.push_ds(['dss1', 'dss2']) # triggered by schedule-ds script
assert_equal 'pending_ds', @domain.state
end
assert @domain.converted # job triggered
assert_equal 'operational', @domain.state
# KSK rollover
assert_jobs do
assert @domain.push_ds(['dss3', 'dss4']) # triggered by schedule-ds script
assert_equal 'pending_ds_rollover', @domain.state
end
assert @domain.complete_rollover # job triggered
assert_equal 'operational', @domain.state
# Convert to plain
assert_jobs do
assert @domain.plain_convert # user triggered
assert_equal 'pending_plain', @domain.state
end
assert @domain.converted # job triggered
assert_equal 'operational', @domain.state
# Remove
assert_jobs do
assert @domain.remove # user triggered
assert_equal 'pending_remove', @domain.state
end
assert @domain.cleaned_up # job triggered
assert_equal 'destroy', @domain.state
end
+
+ test 'domain lifetime #full-destroy' do
+ assert_equal 'initial', @domain.state
+
+ # Create
+ assert_jobs do
+ @domain.save! # user triggered
+ assert_equal 'pending_install', @domain.state
+ end
+ @domain.installed # job triggered
+ assert_equal 'operational', @domain.state
+
+ # Convert to dnssec (sign)
+ assert_jobs do
+ @domain.dnssec = true
+ @domain.dnssec_policy = @policy
+ @domain.dnssec_parent = @domain.name.split('.', 2).last
+ @domain.dnssec_parent_authority = 'test_authority'
+ @domain.save!
+
+ # After commit is not triggered in tests,
+ # so we have to trigger it manually
+ @domain.send(:after_commit_event)
+
+ assert_equal 'pending_signing', @domain.state
+ end
+
+ assert_jobs do
+ assert @domain.signed # job triggered
+ assert_equal 'wait_for_ready', @domain.state
+ end
+
+ # Convert to dnssec (publish ds)
+ assert_jobs do
+ assert @domain.push_ds(['dss1', 'dss2']) # triggered by schedule-ds script
+ assert_equal 'pending_ds', @domain.state
+ end
+ assert @domain.converted # job triggered
+ assert_equal 'operational', @domain.state
+
+ # KSK rollover
+ assert_jobs do
+ assert @domain.push_ds(['dss3', 'dss4']) # triggered by schedule-ds script
+ assert_equal 'pending_ds_rollover', @domain.state
+ end
+ assert @domain.complete_rollover # job triggered
+ assert_equal 'operational', @domain.state
+
+ # Full Remove (Drops DS records)
+ assert_jobs do
+ assert @domain.full_remove # user triggered
+ assert_equal 'pending_ds_removal', @domain.state
+ end
+
+ assert_jobs do
+ assert @domain.remove # job triggered
+ assert_equal 'pending_remove', @domain.state
+ end
+ assert @domain.cleaned_up # job triggered
+ assert_equal 'destroy', @domain.state
+ end
end
class DsDomainTest < ActiveSupport::TestCase
def setup
@domain = create(:domain)
@ds = [
'31406 8 1 189968811e6eba862dd6c209f75623d8d9ed9142',
'31406 8 2 f78cf3344f72137235098ecbbd08947c2c9001c7f6a085a17f518b5d8f6b916d',
]
@child = "dnssec.#{@domain.name}"
@extra = DS.create(domain: @domain, name: @child, content: 'other')
end
test 'add ds records' do
Domain.replace_ds(@domain.name, @child, @ds)
@extra.save! # Should be deleted
assert_equal @ds.size, DS.where(name: "dnssec.#{@domain.name}").count
@ds.each { |ds|
assert_equal 1, DS.where(name: "dnssec.#{@domain.name}", content: ds).count
}
end
+ test 'remove ds records' do
+ Domain.replace_ds(@domain.name, @child, [])
+
+ assert_equal 0, DS.where(name: "dnssec.#{@domain.name}").count
+ end
+
test 'check if child is a valid subdomain' do
assert_raise Domain::NotAChild do
Domain.replace_ds(@domain.name, 'dnssec.example.net', @ds)
end
end
end
class BulkTest < ActiveSupport::TestCase
def setup
@domain = create(:domain)
@a = create(:a, domain: @domain)
@aaaa = create(:aaaa, domain: @domain)
@new = build(:mx, domain: @domain)
end
def valid_changes
@valid_changes ||= begin
{}.tap { |c|
c[:deletes] = [@a.id]
c[:changes] = { @aaaa.id => { content: '::42' }}
c[:additions] = { 1 => @new.as_bulky_json }
}
end
end
def invalid_changes
@invalid_changes ||= begin
{}.tap { |c|
c[:deletes] = [Record.maximum(:id) + 1]
c[:changes] = { @aaaa.id => { content: '1.2.3.4' }}
c[:additions] = { 1 => @new.as_bulky_json.update(prio: -1) }
}
end
end
test 'apply changes not' do
err = @domain.bulk invalid_changes
assert_not_empty err
assert_includes err[:deletes][Record.maximum(:id) + 1], 'record not found'
assert_includes err[:changes][@aaaa.id], 'not a valid IPv6'
assert_includes err[:additions][1], 'not a valid DNS priority'
end
test 'apply changes' do
err = @domain.bulk valid_changes
@domain.reload
@aaaa.reload
assert_empty err
assert_empty @domain.records.where(id: @a.id)
assert_equal '::42', @aaaa.content
assert_equal 1, @domain.records.where(type: :mx).count
end
end
end

Event Timeline