parent 64e3bc885a2cb8b46a1aaa4bf4f121ee7f5d44a6

author Karl Erik Õunapuu <karlerik@kreative.ee> 1591359032 +0300
committer Alex Sherman <yul.golem@gmail.com> 1617029320 +0500

CsyncJob: Don't respect IPv6 if nessecary
This commit is contained in:
Karl Erik Õunapuu 2020-06-05 15:10:32 +03:00 committed by Alex Sherman
parent e46fdd57af
commit 88e1bc3727
33 changed files with 1475 additions and 119 deletions

View file

@ -5,6 +5,7 @@ gem 'active_interaction', '~> 3.8'
gem 'apipie-rails', '~> 0.5.18'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'iso8601', '0.12.1' # for dates and times
gem 'mimemagic', '~> 0.3.7'
gem 'rails', '~> 6.0'
gem 'rest-client'
gem 'uglifier'
@ -39,6 +40,7 @@ gem 'devise', '~> 4.7'
# registry specfic
gem 'data_migrate', '~> 6.1'
gem 'dnsruby', '~> 1.61'
gem 'isikukood' # for EE-id validation
gem 'simpleidn', '0.1.1' # For punycode
gem 'money-rails'

View file

@ -54,13 +54,22 @@ GIT
GIT
remote: https://github.com/internetee/omniauth-tara.git
revision: cec845ec3794532144c4976104a07e206d759aa6
revision: 75c369dd86a5fe1a2ee4c2a6246252d79431071c
branch: extended-logging
specs:
omniauth-tara (0.3.0)
addressable (~> 2.5)
omniauth (~> 1.3)
openid_connect (~> 1.1)
GIT
remote: https://github.com/karlerikounapuu/dnsruby.git
revision: f54986268ba3109ecf7a7af0d41a78cddfce9847
branch: master
specs:
dnsruby (1.61.3)
simpleidn (~> 0.1)
GIT
remote: https://github.com/tarmotalu/digidoc_client.git
revision: 1645e83a5a548addce383f75703b0275c5310c32
@ -283,7 +292,9 @@ GEM
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2021.0225)
mimemagic (0.3.5)
mimemagic (0.3.10)
nokogiri (~> 1)
rake
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.4)
@ -519,6 +530,7 @@ DEPENDENCIES
devise (~> 4.7)
digidoc_client!
directo!
dnsruby!
domain_name
e_invoice!
epp!
@ -531,6 +543,7 @@ DEPENDENCIES
jquery-ui-rails (= 5.0.5)
kaminari
lhv!
mimemagic (~> 0.3.7)
minitest (~> 5.14)
money-rails
nokogiri (~> 1.10.0)

View file

@ -2,18 +2,78 @@
class CsyncJob < Que::Job
def run(generate: false)
@logger = Logger.new(STDOUT)
generate ? generate_scanner_input : scanner_results
@store = {}
@input_store = { secure: {}, insecure: {} }
@results = {}
@logger = Rails.env.test? ? Rails.logger : Logger.new(STDOUT)
generate ? generate_scanner_input : process_scanner_results
@logger.info 'CsyncJob: Finished.'
end
def qualified_for_monitoring?(domain, data)
result_types = data[:ns].map { |ns| ns[:type] }.uniq
ns_ok = redundant_data_for?(nameserver: true, input: result_types)
key_ok = redundant_data_for?(nameserver: false, input: data)
return true if ns_ok && key_ok
@logger.info "CsyncJob: #{domain}: Reseting state. Reason: " +
unqualification_reason(ns_ok, key_ok, result_types)
CsyncRecord.where(domain: Domain.where(name: domain)).delete_all
false
end
def redundant_data_for?(nameserver: false, input:)
if nameserver
input.size == 1 && (input & %w[secure insecure]).any?
else
input[:ns].map { |ns| ns[:cdnskey] }.uniq.size == 1
end
end
def unqualification_reason(nss, key, result_types)
return 'no CDNSKEY / nameservers reported different CDNSKEYs' unless key
if result_types.include? 'untrustworthy'
return 'current DNSSEC config invalid (required for rollover/delete)'
end
"Nameserver(s) not reachable / invalid data (#{result_types.join(', ')})" unless nss
end
def process_scanner_results
scanner_results
@results.keys.each do |domain|
next unless qualified_for_monitoring?(domain, @results[domain])
CsyncRecord.by_domain_name(domain)&.record_new_scan(@results[domain][:ns].first)
end
end
def scanner_results
scanner_line_results.each do |fetch|
domain_name = fetch[:domain]
@results[domain_name] = { ns: [] } unless @results[domain_name]
@results[domain_name][:ns] << fetch.except(:domain)
end
end
def scanner_line_results
records = []
File.open(ENV['cdns_scanner_output_file'], 'r').each_line do |line|
# Input type, NS host, NS IP, Domain name, Key type, Protocol, Algorithm, Public key
data = line.strip.split(' ')
if data[0] == 'secure'
type, domain, key_bit, proto, alg, pub, ns, ns_ip = data
else
type, ns, ns_ip, domain, key_bit, proto, alg, pub = data
end
cdnskey = key_bit && proto && alg && pub ? "#{key_bit} #{proto} #{alg} #{pub}" : nil
record = { domain: domain, type: type, ns: ns, ns_ip: ns_ip, key_bit: key_bit, proto: proto,
record = { domain: domain, type: type, ns: ns, ns_ip: ns_ip, flags: key_bit, proto: proto,
alg: alg, pub: pub, cdnskey: cdnskey }
records << record
end
@ -23,38 +83,39 @@ class CsyncJob < Que::Job
# From this point we're working on generating input for cdnskey-scanner
def gather_pollable_domains
@logger.info 'CsyncJob Generate: Gathering current domain(s) data'
@store = { secure: {}, insecure: {} }
Nameserver.select(:hostname, :domain_id).all.each do |ns|
@store[:secure][ns.hostname] = [] unless @store[:secure].key? ns.hostname
@store[:insecure][ns.hostname] = [] unless @store[:insecure].key? ns.hostname
Domain.where(id: ns.domain_id).all.each do |domain|
state = domain.dnskeys.any? ? :secure : :insecure
@store[state][ns.hostname].push domain.name
%i[secure insecure].each do |i|
@input_store[i][ns.hostname] = [] unless @input_store[i].key? ns.hostname
end
append_domains_to_list(ns)
end
end
def append_domains_to_list(nameserver)
Domain.where(id: nameserver.domain_id).all.each do |domain|
@input_store[domain.dnskeys.any? ? :secure : :insecure][nameserver.hostname].push domain.name
end
end
def generate_scanner_input
@logger.info 'CsyncJob Generate: Gathering current domain(s) data'
gather_pollable_domains
@logger.info 'CsyncJob Generate: Writing input for cdnskey-scanner to ' \
"#{ENV['cdns_scanner_input_file']}"
out_file = File.new(ENV['cdns_scanner_input_file'], 'w+')
out_file.puts '[secure]'
create_input_lines(out_file, secure: true)
out_file.puts '[insecure]'
create_input_lines(out_file, secure: false)
out_file.close
@logger.info 'CsyncJob Generate: Finished writing output.'
%i[secure insecure].each do |state|
out_file.puts "[#{state}]"
create_input_lines(out_file, state)
end
def create_input_lines(out_file, secure: false)
state = secure ? :secure : :insecure
@store[state].keys.each do |nameserver|
domains = @store[state][nameserver].join(' ')
out_file.close
@logger.info 'CsyncJob Generate: Finished writing output to ' + ENV['cdns_scanner_input_file']
end
def create_input_lines(out_file, state)
@input_store[state].keys.each do |nameserver|
domains = @input_store[state][nameserver].join(' ')
next unless domains.length.positive?
out_file.puts "#{nameserver} #{domains}"

View file

@ -0,0 +1,23 @@
class CsyncMailer < ApplicationMailer
def dnssec_updated(domain:)
@domain = domain
emails = contact_emails(domain)
subject = default_i18n_subject(domain_name: domain.name)
mail(to: emails, subject: subject)
end
def dnssec_deleted(domain:)
@domain = domain
emails = contact_emails(domain)
subject = default_i18n_subject(domain_name: domain.name)
mail(to: emails, subject: subject)
end
private
def contact_emails(domain)
(domain.contacts.map(&:email) << domain.registrant.email).uniq
end
end

View file

@ -0,0 +1,81 @@
module Actions
class ContactCreate
attr_reader :contact, :legal_document, :ident
def initialize(contact, legal_document, ident)
@contact = contact
@legal_document = legal_document
@ident = ident
end
def call
maybe_remove_address
maybe_attach_legal_doc
validate_ident
commit
end
def maybe_remove_address
return if Contact.address_processing?
contact.city = nil
contact.zip = nil
contact.street = nil
contact.state = nil
contact.country_code = nil
end
def validate_ident
validate_ident_integrity
validate_ident_birthday
identifier = ::Contact::Ident.new(code: ident[:ident], type: ident[:ident_type],
country_code: ident[:ident_country_code])
identifier.validate
contact.identifier = identifier
end
def validate_ident_integrity
return if ident.blank?
if ident[:ident_type].blank?
contact.add_epp_error('2003', nil, 'ident_type',
I18n.t('errors.messages.required_ident_attribute_missing'))
@error = true
elsif !%w[priv org birthday].include?(ident[:ident_type])
contact.add_epp_error('2003', nil, 'ident_type', 'Invalid ident type')
@error = true
end
end
def validate_ident_birthday
return if ident.blank?
return unless ident[:ident_type] != 'birthday' && ident[:ident_country_code].blank?
contact.add_epp_error('2003', nil, 'ident_country_code',
I18n.t('errors.messages.required_ident_attribute_missing'))
@error = true
end
def maybe_attach_legal_doc
return unless legal_document
doc = LegalDocument.create(
documentable_type: Contact,
document_type: legal_document[:type], body: legal_document[:body]
)
contact.legal_documents = [doc]
contact.legal_document_id = doc.id
end
def commit
contact.id = nil # new record
return false if @error
contact.generate_code
contact.save
end
end
end

View file

@ -0,0 +1,46 @@
module Actions
class ContactDelete
attr_reader :contact
attr_reader :new_attributes
attr_reader :legal_document
attr_reader :ident
attr_reader :user
def initialize(contact, legal_document = nil)
@legal_document = legal_document
@contact = contact
end
def call
maybe_attach_legal_doc
if contact.linked?
contact.errors.add(:domains, :exist)
return
end
if contact.delete_prohibited?
contact.errors.add(:statuses, :delete_prohibited)
return
end
commit
end
def maybe_attach_legal_doc
return unless legal_document
document = contact.legal_documents.create(
document_type: legal_document[:type],
body: legal_document[:body]
)
contact.legal_document_id = document.id
contact.save
end
def commit
contact.destroy
end
end
end

View file

@ -0,0 +1,109 @@
module Actions
class ContactUpdate
attr_reader :contact
attr_reader :new_attributes
attr_reader :legal_document
attr_reader :ident
attr_reader :user
def initialize(contact, new_attributes, legal_document, ident, user)
@contact = contact
@new_attributes = new_attributes
@legal_document = legal_document
@ident = ident
@user = user
end
def call
maybe_remove_address
maybe_update_statuses
maybe_update_ident if ident.present?
maybe_attach_legal_doc
commit
end
def maybe_remove_address
return if Contact.address_processing?
new_attributes.delete(:city)
new_attributes.delete(:zip)
new_attributes.delete(:street)
new_attributes.delete(:state)
new_attributes.delete(:country_code)
end
def maybe_update_statuses
return unless Setting.client_status_editing_enabled
new_statuses =
contact.statuses - new_attributes[:statuses_to_remove] + new_attributes[:statuses_to_add]
new_attributes[:statuses] = new_statuses
end
def maybe_attach_legal_doc
return unless legal_document
document = contact.legal_documents.create(
document_type: legal_document[:type],
body: legal_document[:body]
)
contact.legal_document_id = document.id
end
def maybe_update_ident
unless ident.is_a?(Hash)
contact.add_epp_error('2308', nil, nil, I18n.t('epp.contacts.errors.valid_ident'))
@error = true
return
end
if contact.identifier.valid?
submitted_ident = ::Contact::Ident.new(code: ident[:ident],
type: ident[:ident_type],
country_code: ident[:ident_country_code])
if submitted_ident != contact.identifier
contact.add_epp_error('2308', nil, nil, I18n.t('epp.contacts.errors.valid_ident'))
@error = true
end
else
ident_update_attempt = ident[:ident] != contact.ident
if ident_update_attempt
contact.add_epp_error('2308', nil, nil, I18n.t('epp.contacts.errors.ident_update'))
@error = true
end
identifier = ::Contact::Ident.new(code: ident[:ident],
type: ident[:ident_type],
country_code: ident[:ident_country_code])
identifier.validate
contact.identifier = identifier
contact.ident_updated_at ||= Time.zone.now
end
end
def commit
return false if @error
contact.upid = user.registrar&.id
contact.up_date = Time.zone.now
contact.attributes = new_attributes
email_changed = contact.will_save_change_to_email?
old_email = contact.email_was
updated = contact.save
if updated && email_changed && contact.registrant?
ContactMailer.email_changed(contact: contact, old_email: old_email).deliver_now
end
updated
end
end
end

View file

@ -0,0 +1,74 @@
module Actions
class DomainTransfer
attr_reader :domain
attr_reader :transfer_code
attr_reader :legal_document
attr_reader :ident
attr_reader :user
def initialize(domain, transfer_code, user)
@domain = domain
@transfer_code = transfer_code
@user = user
end
def call
return unless domain_exists?
return unless valid_transfer_code?
run_validations
# return domain.pending_transfer if domain.pending_transfer
# attach_legal_document(::Deserializers::Xml::LegalDocument.new(frame).call)
return if domain.errors[:epp_errors].any?
commit
end
def domain_exists?
return true if domain.persisted?
domain.add_epp_error('2303', nil, nil, 'Object does not exist')
false
end
def run_validations
validate_registrar
validate_eligilibty
validate_not_discarded
end
def valid_transfer_code?
return true if transfer_code == domain.transfer_code
domain.add_epp_error('2202', nil, nil, 'Invalid authorization information')
false
end
def validate_registrar
return unless user == domain.registrar
domain.add_epp_error('2002', nil, nil,
I18n.t(:domain_already_belongs_to_the_querying_registrar))
end
def validate_eligilibty
return unless domain.non_transferable?
domain.add_epp_error('2304', nil, nil, 'Object status prohibits operation')
end
def validate_not_discarded
return unless domain.discarded?
domain.add_epp_error('2106', nil, nil, 'Object is not eligible for transfer')
end
def commit
bare_domain = Domain.find(domain.id)
::DomainTransfer.request(bare_domain, user)
end
end
end

View file

@ -0,0 +1,45 @@
module CsyncRecord::Diggable
extend ActiveSupport::Concern
def valid_security_level?(post: false)
res = post ? valid_post_action? : valid_pre_action?
log_dnssec_entry(valid: res, post: post)
res
rescue Dnsruby::NXDomain
log.info("CsyncRecord: #{domain.name}: Could not resolve (NXDomain)")
false
end
def valid_pre_action?
case domain.dnssec_security_level
when Dnsruby::Message::SecurityLevel.SECURE
return true if %w[rollover deactivate].include?(action)
when Dnsruby::Message::SecurityLevel.INSECURE, Dnsruby::Message::SecurityLevel.BOGUS
return true if action == 'initialized'
end
false
end
def valid_post_action?
secure_msg = Dnsruby::Message::SecurityLevel.SECURE
security_level = domain.dnssec_security_level(stubber: dnskey)
return true if action == 'deactivate' && security_level != secure_msg
return true if %w[rollover initialized].include?(action) && security_level == secure_msg
false
end
def dnssec_validates?
return false unless dnskey.valid?
return true if valid_security_level? && valid_security_level?(post: true)
false
end
def log_dnssec_entry(valid:, post:)
log.info("#{domain.name}: #{post ? 'Post' : 'Pre'} DNSSEC validation " \
"#{valid ? 'PASSED' : 'FAILED'} for action '#{action}'")
end
end

132
app/models/csync_record.rb Normal file
View file

@ -0,0 +1,132 @@
# frozen_string_literal: true
class CsyncRecord < ApplicationRecord
include CsyncRecord::Diggable
belongs_to :domain, optional: false
validates :domain, uniqueness: true
validates :cdnskey, :action, :last_scan, presence: true
validate :validate_unique_pub_key
validate :validate_csync_action
validate :validate_cdnskey_format
after_save :process_new_dnskey, if: proc { pushable? && !disable_requested? }
after_save :remove_dnskeys, if: proc { pushable? && disable_requested? }
SCAN_CYCLES = 3
def record_new_scan(result)
assign_scanner_data!(result)
prefix = "CsyncRecord: #{domain.name}:"
if save
log.info "#{prefix} Cycle done."
else
log.info "#{prefix}: not processing. Reason: #{errors.full_messages.join(' .')}"
CsyncRecord.where(domain: domain).delete_all
end
end
def assign_scanner_data!(result)
state = result[:type]
self.last_scan = Time.zone.now
self.times_scanned = (persisted? && cdnskey != result[:cdnskey] ? 1 : times_scanned + 1)
self.cdnskey = result[:cdnskey]
self.action = initializes_dnssec?(state) ? 'initialized' : determine_csync_intention(state)
end
def dnskey
key = Dnskey.new_from_csync(domain: domain, cdnskey: cdnskey)
log.info "DNSKEY not valid. #{key.errors.full_messages.join('. ')}." unless key.valid?
key
end
def destroy_all_but_last_one
domain.dnskeys.order(id: :desc).offset(1).destroy_all
end
def process_new_dnskey
return unless dnssec_validates?
if dnskey.save
destroy_all_but_last_one
finalize_and_notify
else
log.info "Failed to save DNSKEY. Errors: #{dnskey.errors.full_messages.join('. ')}"
end
end
def finalize_and_notify
CsyncMailer.dnssec_updated(domain: domain).deliver_now
notify_registrar_about_csync
CsyncRecord.where(domain: domain).destroy_all
log.info "CsyncRecord: #{domain.name}: DNSKEYs updated."
end
def pushable?
return true if domain.dnskeys.any? || times_scanned >= SCAN_CYCLES
false
end
def disable_requested?
['0 3 0 AA==', '0 3 0 0'].include? cdnskey
end
def remove_dnskeys
log.info "CsyncJob: Removing DNSKEYs for domain '#{domain.name}'"
domain.dnskeys.destroy_all
CsyncMailer.dnssec_deleted(domain: domain).deliver_now
notify_registrar_about_csync
destroy
end
def notify_registrar_about_csync
domain.update_whois_record
domain.registrar.notifications.create!(text: I18n.t('notifications.texts.csync',
domain: domain.name, action: action))
end
def validate_unique_pub_key
return false unless domain
return true if disable_requested?
return true unless domain.dnskeys.where(public_key: dnskey.public_key).any?
errors.add(:public_key, 'already tied to this domain')
end
def self.by_domain_name(domain_name)
domain = Domain.find_by(name: domain_name)
log.info "CsyncRecord: '#{domain_name}' not in zone. Not initializing record." unless domain
CsyncRecord.find_or_initialize_by(domain: domain) if domain
end
def determine_csync_intention(scan_state)
return unless domain.dnskeys.any? && scan_state == 'secure'
disable_requested? ? 'deactivate' : 'rollover'
end
def initializes_dnssec?(scan_state)
true if domain.dnskeys.empty? && !disable_requested? && scan_state == 'insecure'
end
def log
@log ||= Rails.env.test? ? logger : Logger.new(STDOUT)
@log
end
def validate_csync_action
return true if %w[initialized rollover].include? action
return true if action == 'deactivate' && disable_requested?
errors.add(:action, :invalid)
end
def validate_cdnskey_format
return true if disable_requested?
return true if dnskey.valid?
errors.add(:cdnskey, :invalid)
end
end

View file

@ -28,7 +28,7 @@ class Dnskey < ApplicationRecord
PROTOCOLS = %w(3)
FLAGS = %w(0 256 257) # 256 = ZSK, 257 = KSK
DS_DIGEST_TYPE = [1,2]
RESOLVERS = ENV['dnssec_resolver_ips'].to_s.strip.split(', ').freeze
self.ignored_columns = %w[legacy_domain_id]
def epp_code_map
@ -122,6 +122,26 @@ class Dnskey < ApplicationRecord
errors.add(:public_key, :invalid)
end
def self.new_from_csync(cdnskey:, domain:)
cdnskey ||= '' # avoid strip() issues for gibberish key
flags, proto, alg, pub = cdnskey.strip.split(' ')
Dnskey.new(domain: domain, flags: flags, protocol: proto, alg: alg, public_key: pub)
end
def ds_rr
# Break the DNSSEC trust chain as we are not able to fake RRSIG's
Dnsruby::Dnssec.clear_trust_anchors
Dnsruby::Dnssec.clear_trusted_keys
# Basically let's configure domain as root anchor. We can still verify
# RRSIG's / DNSKEY targeted by DS of this domain
generate_digest
generate_ds_key_tag
Dnsruby::RR.create("#{domain.name}. 3600 IN DS #{ds_key_tag} #{ds_alg} " \
"#{ds_digest_type} #{ds_digest}")
end
class << self
def int_to_hex(s)
s = s.to_s(16)

View file

@ -58,6 +58,7 @@ class Domain < ApplicationRecord
has_many :legal_documents, as: :documentable
has_many :registrant_verifications, dependent: :destroy
has_one :csync_record, dependent: :destroy
after_initialize do
self.pending_json = {} if pending_json.blank?
@ -167,6 +168,35 @@ class Domain < ApplicationRecord
validate :validate_nameserver_ips
validate :statuses_uniqueness
def security_level_resolver
resolver = Dnsruby::Resolver.new(nameserver: Dnskey::RESOLVERS)
resolver.do_validation = true
resolver.do_caching = false
resolver.dnssec = true
resolver
end
def dnssec_security_level(stubber: nil)
Dnsruby::Dnssec.reset
resolver = security_level_resolver
Dnsruby::Recursor.clear_caches(resolver)
if Rails.env.staging?
clear_dnssec_trusted_anchors_and_keys
elsif stubber
Dnsruby::Dnssec.add_trust_anchor(stubber.ds_rr)
end
recursor = Dnsruby::Recursor.new(resolver)
recursor.dnssec = true
recursor.query(name, 'A', 'IN').security_level
end
def clear_dnssec_trusted_anchors_and_keys
Dnsruby::Dnssec.clear_trust_anchors
Dnsruby::Dnssec.clear_trusted_keys
Dnsruby::Dnssec.add_trust_anchor(Dnsruby::RR.create(ENV['trusted_dnskey']))
end
def statuses_uniqueness
return if statuses.uniq == statuses
errors.add(:statuses, :taken)

View file

@ -44,6 +44,8 @@ class Epp::Contact < Contact
def check_availability(codes, reg:)
codes = [codes] if codes.is_a?(String)
codes = codes.map { |c| c.include?(':') ? c : "#{reg}:#{c}" }
res = []
codes.map { |c| c.include?(':') ? c : "#{reg}:#{c}" }.map { |c| c.strip.upcase }.each do |x|
c = find_by_epp_code(x)

View file

@ -58,8 +58,8 @@ class Nameserver < ApplicationRecord
end
def hostname=(hostname)
self[:hostname] = SimpleIDN.to_unicode(hostname)
self[:hostname_puny] = SimpleIDN.to_ascii(hostname)
self[:hostname] = SimpleIDN.to_unicode(hostname).gsub(/\.+$/, '')
self[:hostname_puny] = SimpleIDN.to_ascii(hostname).gsub(/\.+$/, '')
end
class << self

View file

@ -1,18 +0,0 @@
.panel.panel-default
.panel-heading.clearfix
= t(:dnskeys)
.table-responsive
%table.table.table-hover.table-bordered.table-condensed
%thead
%tr
%th{class: 'col-xs-1'}= t(:flag)
%th{class: 'col-xs-1'}= t(:protocol)
%th{class: 'col-xs-1'}= t(:algorithm)
%th{class: 'col-xs-9'}= t(:public_key)
%tbody
- @domain.dnskeys.each do |x|
%tr
%td= x.flags
%td= x.protocol
%td= x.alg
%td= x.public_key

View file

@ -1,14 +0,0 @@
.panel.panel-default
.panel-heading.clearfix
= t(:legal_documents)
.table-responsive
%table.table.table-hover.table-bordered.table-condensed
%thead
%tr
%th{class: 'col-xs-8'}= t(:created_at)
%th{class: 'col-xs-4'}= t(:type)
%tbody
- legal_documents.each do |x|
%tr
%td= link_to(x.created_at, [:admin, x])
%td= x.document_type

View file

@ -1,16 +0,0 @@
.panel.panel-default
.panel-heading.clearfix
= t(:nameservers)
.table-responsive
%table.table.table-hover.table-bordered.table-condensed
%thead
%tr
%th{class: 'col-xs-4'}= t(:hostname)
%th{class: 'col-xs-4'}= t(:ipv4)
%th{class: 'col-xs-4'}= t(:ipv6)
%tbody
- @domain.nameservers.each do |x|
%tr
%td= x
%td= x.ipv4
%td= x.ipv6

View file

@ -1,18 +0,0 @@
#domain_statuses.panel.panel-default
.panel-heading.clearfix
= t(:statuses)
.table-responsive
%table.table.table-hover.table-bordered.table-condensed
%thead
%tr
%th{class: 'col-xs-6'}= t(:status)
%th{class: 'col-xs-6'}= t(:notes)
%tbody
- @domain.statuses.each do |status|
%tr
%td
- if @domain.pending_json.present? && [DomainStatus::PENDING_UPDATE, DomainStatus::PENDING_DELETE].include?(status)
= link_to status, admin_domain_domain_versions_path(@domain.id)
- else
= status
%td= @domain.status_notes[status]

View file

@ -0,0 +1,14 @@
Tere,
<br><br>
Oleme eemaldanud Teie domeeni <%= @domain.name %> DNSSEC kirjed registrist.
Domeeni <%= @domain.name %> DNSSEC on nüüd välja lülitatud.
<%= render 'mailers/shared/signatures/signature.et.html' %>
<hr>
<br><br>
Hi,
<br><br>
We have removed DNSSEC data in our registry for domain <%= @domain.name %>.
<br>
DNSSEC has been turned off for <%= @domain.name %> as of now.
<%= render 'mailers/shared/signatures/signature.en.html' %>

View file

@ -0,0 +1,14 @@
Tere,
<br><br>
Oleme uuendatud Teie domeeni <%= @domain.name %> DNSSEC andmeid registris.
<br>
Lisasime CDNSKEY väärtuses kajastatud võtme oma tsoonifaili ning on nüüd aktiveeritud.
<%= render 'mailers/shared/signatures/signature.et.html' %>
<hr>
<br><br>
Hi,
<br><br>
We have updated DNSSEC data in our registry for domain <%= @domain.name %>.
<br>
DNS key specified in CDNSKEY has been added to our zone file and is now taking effect.
<%= render 'mailers/shared/signatures/signature.en.html' %>

View file

@ -41,6 +41,7 @@ ca_key_password: 'your-root-key-password'
directo_invoice_url: 'https://domain/ddddd.asp'
cdns_scanner_input_file: '/opt/cdns/input.txt'
cdns_scanner_output_file: '/opt/cdns/output.txt'
dnssec_resolver_ips: 8.8.8.8, 8.8.4.4
#
# EPP
#
@ -192,6 +193,10 @@ test:
action_mailer_force_delete_from: 'legal@registry.test'
lhv_p12_keystore: 'test/fixtures/files/keystore.p12'
lhv_keystore_password: 'testtest'
lhv_keystore_alias: 'testtest'
cdns_scanner_input_file: 'tmp/cdns_input.txt'
cdns_scanner_output_file: 'test/fixtures/files/cdns_output.txt'
dnssec_resolver_ips: 8.8.8.8, 8.8.4.4
legal_documents_dir: 'test/fixtures/files'
# Airbrake // Errbit:

View file

@ -16,6 +16,7 @@ en:
disputed_domains: Disputed domains
bulk_actions: Bulk actions
bounced_email_addresses: Bounced emails
mass_actions: Mass actions
epp_log: EPP log
repp_log: REPP log
que: Que

View file

@ -6,3 +6,4 @@ en:
reset_btn: Reset
show:
title: REPP log

View file

@ -0,0 +1,10 @@
en:
csync_mailer:
dnssec_updated:
subject: >-
Teie domeeni %{domain_name} DNSSEC andmed on uuendatud
/ DNSSEC data for %{domain_name} has been updated
dnssec_deleted:
subject: >-
Teie domeeni %{domain_name} DNSSEC andmed on eemaldatud
/ DNSSEC data for %{domain_name} has been removed

View file

@ -6,5 +6,6 @@ en:
It was associated with registrant %{old_registrant_code}
and contacts %{old_contacts_codes}.
contact_update: Contact %{contact} has been updated by registrant
csync: CSYNC DNSSEC %{action} for domain %{domain}
registrar_locked: Domain %{domain_name} has been locked by registrant
registrar_unlocked: Domain %{domain_name} has been unlocked by registrant

View file

@ -0,0 +1,12 @@
class CreateCsyncRecords < ActiveRecord::Migration[6.0]
def change
create_table :csync_records do |t|
t.belongs_to :domain, foreign_key: true, null: false, index: { unique: true }
t.string :cdnskey, null: false
t.string :action, null: false
t.integer :times_scanned, null: false, default: 0
t.datetime :last_scan, null: false
t.timestamps
end
end
end

View file

@ -2251,6 +2251,15 @@ CREATE TABLE public.registrant_verifications (
action character varying NOT NULL,
domain_id integer NOT NULL,
action_type character varying NOT NULL,
<<<<<<< HEAD
=======
creator_id integer,
updater_id integer
session character varying,
children json,
ident_updated_at timestamp without time zone,
uuid character varying
>>>>>>> parent 64e3bc885a2cb8b46a1aaa4bf4f121ee7f5d44a6
creator_str character varying,
updator_str character varying
);
@ -3017,21 +3026,52 @@ ALTER TABLE ONLY public.que_jobs ALTER COLUMN job_id SET DEFAULT nextval('public
ALTER TABLE ONLY public.registrant_verifications ALTER COLUMN id SET DEFAULT nextval('public.registrant_verifications_id_seq'::regclass);
--
-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.users_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.accounts ALTER COLUMN id SET DEFAULT nextval('public.accounts_id_seq'::regclass);
ALTER TABLE ONLY public.log_invoices
ADD CONSTRAINT log_invoices_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.registrars ALTER COLUMN id SET DEFAULT nextval('public.registrars_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.disputes ALTER COLUMN id SET DEFAULT nextval('public.disputes_id_seq'::regclass);
ALTER TABLE ONLY public.log_nameservers
ADD CONSTRAINT log_nameservers_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.reserved_domains ALTER COLUMN id SET DEFAULT nextval('public.reserved_domains_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.setting_entries ALTER COLUMN id SET DEFAULT nextval('public.setting_entries_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
@ -3050,6 +3090,285 @@ ALTER TABLE ONLY public.settings ALTER COLUMN id SET DEFAULT nextval('public.set
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.domains ALTER COLUMN id SET DEFAULT nextval('public.domains_id_seq'::regclass);
--
-- Name: versions id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.email_address_verifications ALTER COLUMN id SET DEFAULT nextval('public.email_address_verifications_id_seq'::regclass);
--
-- Name: white_ips id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.email_addresses_validations ALTER COLUMN id SET DEFAULT nextval('public.email_addresses_validations_id_seq'::regclass);
--
-- Name: whois_records id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.email_addresses_verifications ALTER COLUMN id SET DEFAULT nextval('public.email_addresses_verifications_id_seq'::regclass);
--
-- Name: zones id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.epp_sessions ALTER COLUMN id SET DEFAULT nextval('public.epp_sessions_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.invoice_items ALTER COLUMN id SET DEFAULT nextval('public.invoice_items_id_seq'::regclass);
--
-- Name: invoices id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.invoices ALTER COLUMN id SET DEFAULT nextval('public.invoices_id_seq'::regclass);
--
-- Name: legal_documents id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.legal_documents ALTER COLUMN id SET DEFAULT nextval('public.legal_documents_id_seq'::regclass);
--
-- Name: log_account_activities id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_account_activities ALTER COLUMN id SET DEFAULT nextval('public.log_account_activities_id_seq'::regclass);
--
-- Name: log_accounts id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_accounts ALTER COLUMN id SET DEFAULT nextval('public.log_accounts_id_seq'::regclass);
--
-- Name: log_actions id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_actions ALTER COLUMN id SET DEFAULT nextval('public.log_actions_id_seq'::regclass);
--
-- Name: log_bank_statements id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_bank_statements ALTER COLUMN id SET DEFAULT nextval('public.log_bank_statements_id_seq'::regclass);
--
-- Name: log_bank_transactions id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_bank_transactions ALTER COLUMN id SET DEFAULT nextval('public.log_bank_transactions_id_seq'::regclass);
--
-- Name: log_blocked_domains id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_blocked_domains ALTER COLUMN id SET DEFAULT nextval('public.log_blocked_domains_id_seq'::regclass);
--
-- Name: log_certificates id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_certificates ALTER COLUMN id SET DEFAULT nextval('public.log_certificates_id_seq'::regclass);
--
-- Name: log_contacts id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_contacts ALTER COLUMN id SET DEFAULT nextval('public.log_contacts_id_seq'::regclass);
--
-- Name: log_dnskeys id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_dnskeys ALTER COLUMN id SET DEFAULT nextval('public.log_dnskeys_id_seq'::regclass);
--
-- Name: log_domain_contacts id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_domain_contacts ALTER COLUMN id SET DEFAULT nextval('public.log_domain_contacts_id_seq'::regclass);
--
-- Name: log_domains id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_domains ALTER COLUMN id SET DEFAULT nextval('public.log_domains_id_seq'::regclass);
--
-- Name: log_invoice_items id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_invoice_items ALTER COLUMN id SET DEFAULT nextval('public.log_invoice_items_id_seq'::regclass);
--
-- Name: log_invoices id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_invoices ALTER COLUMN id SET DEFAULT nextval('public.log_invoices_id_seq'::regclass);
--
-- Name: log_nameservers id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_nameservers ALTER COLUMN id SET DEFAULT nextval('public.log_nameservers_id_seq'::regclass);
--
-- Name: log_notifications id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_notifications ALTER COLUMN id SET DEFAULT nextval('public.log_notifications_id_seq'::regclass);
--
-- Name: log_payment_orders id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_payment_orders ALTER COLUMN id SET DEFAULT nextval('public.log_payment_orders_id_seq'::regclass);
--
-- Name: log_registrant_verifications id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_registrant_verifications ALTER COLUMN id SET DEFAULT nextval('public.log_registrant_verifications_id_seq'::regclass);
--
-- Name: log_registrars id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_registrars ALTER COLUMN id SET DEFAULT nextval('public.log_registrars_id_seq'::regclass);
--
-- Name: log_reserved_domains id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_reserved_domains ALTER COLUMN id SET DEFAULT nextval('public.log_reserved_domains_id_seq'::regclass);
--
-- Name: log_settings id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_settings ALTER COLUMN id SET DEFAULT nextval('public.log_settings_id_seq'::regclass);
--
-- Name: log_users id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_users ALTER COLUMN id SET DEFAULT nextval('public.log_users_id_seq'::regclass);
--
-- Name: log_white_ips id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_white_ips ALTER COLUMN id SET DEFAULT nextval('public.log_white_ips_id_seq'::regclass);
--
-- Name: nameservers id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.nameservers ALTER COLUMN id SET DEFAULT nextval('public.nameservers_id_seq'::regclass);
--
-- Name: notifications id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.notifications ALTER COLUMN id SET DEFAULT nextval('public.notifications_id_seq'::regclass);
--
-- Name: payment_orders id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.payment_orders ALTER COLUMN id SET DEFAULT nextval('public.payment_orders_id_seq'::regclass);
--
-- Name: prices id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.prices ALTER COLUMN id SET DEFAULT nextval('public.prices_id_seq'::regclass);
--
-- Name: que_jobs job_id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.que_jobs ALTER COLUMN job_id SET DEFAULT nextval('public.que_jobs_job_id_seq'::regclass);
--
-- Name: registrant_verifications id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.registrant_verifications ALTER COLUMN id SET DEFAULT nextval('public.registrant_verifications_id_seq'::regclass);
--
-- Name: registrars id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.registrars ALTER COLUMN id SET DEFAULT nextval('public.registrars_id_seq'::regclass);
--
-- Name: reserved_domains id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.reserved_domains ALTER COLUMN id SET DEFAULT nextval('public.reserved_domains_id_seq'::regclass);
--
-- Name: settings id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.settings ALTER COLUMN id SET DEFAULT nextval('public.settings_id_seq'::regclass);
--
-- Name: users id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
ALTER TABLE ONLY public.log_prices
ADD CONSTRAINT log_prices_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
@ -4318,6 +4637,19 @@ CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING b
-- Name: contacts_registrar_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--
CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version);
--
-- Name: contacts process_contact_audit; Type: TRIGGER; Schema: public; Owner: -
--
CREATE TRIGGER process_contact_audit AFTER INSERT OR DELETE OR UPDATE ON public.contacts FOR EACH ROW EXECUTE PROCEDURE public.process_contact_audit();
--
-- Name: contacts contacts_registrar_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.contacts
ADD CONSTRAINT contacts_registrar_id_fk FOREIGN KEY (registrar_id) REFERENCES public.registrars(id);
@ -4951,6 +5283,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200605100827'),
('20200610090110'),
('20200630081231'),
('20200605125332'),
('20200714115338'),
('20200807110611'),
('20200811074839'),
@ -4961,6 +5294,6 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200910085157'),
('20200910102028'),
('20200916125326'),
('20210215101019');
('20200917104213'),
('20210215101019'),
('20200921084356');

View file

@ -9,3 +9,12 @@ one:
minimum_ttl: 1
email: admin@registry.test
master_nameserver: ns.test
subzone:
origin: 'sub.zone'
ttl: 1
refresh: 1
retry: 1
expire: 1
minimum_ttl: 1
email: admin@registry.test
master_nameserver: ns.test

10
test/fixtures/dnskeys.yml vendored Normal file
View file

@ -0,0 +1,10 @@
one:
domain:
flags: 257
protocol: 3
alg: 13
public_key: mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==
ds_key_tag: 2371
ds_alg: 13
ds_digest_type: 2
ds_digest: 39456058862EA09DD96992ED2BDAFAEDE8C7E949589E3DA903A46F4F9CD373EA

4
test/fixtures/files/cdns_output.txt vendored Normal file
View file

@ -0,0 +1,4 @@
insecure-empty ns1.bestnames.test 127.0.0.1 airport.test
insecure-empty ns2.bestnames.test 127.0.0.1 airport.test
insecure ns1.bestnames.test 127.0.0.1 shop.test 257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==
insecure ns2.bestnames.test 127.0.0.1 shop.test 257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==

View file

@ -0,0 +1,73 @@
require 'test_helper'
class CsyncJobTest < ActiveSupport::TestCase
include ActionMailer::TestHelper
setup do
@dnskey = dnskeys(:one)
@domain = domains(:shop)
end
def test_generates_input_file_for_cdnskey_scanner
@dnskey.update(domain: domains(:shop))
expected_contents = "[secure]\nns1.bestnames.test shop.test\nns2.bestnames.test shop.test\n" \
"[insecure]\nns1.bestnames.test airport.test metro.test\nns2.bestnames.test airport.test\n"
CsyncJob.run(generate: true)
assert_equal expected_contents, IO.read(ENV['cdns_scanner_input_file'])
end
def test_creates_csync_record_when_new_cdnskey_discovered
assert_nil @domain.csync_record
CsyncJob.run
@domain.reload
assert @domain.csync_record
csync_record = @domain.csync_record
assert_equal 1, csync_record.times_scanned
assert_equal '257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==', csync_record.cdnskey
assert_not @domain.dnskeys.any?
end
def test_creates_dnskey_after_required_cycles
assert_equal 0, @domain.dnskeys.count
assert_nil @domain.csync_record
CsyncJob.run # Creates initial CsyncRecord for domain
@domain.reload
assert @domain.csync_record.present?
@domain.csync_record.update(times_scanned: 2) # 3rd time trigger DNSKEY push
assert_equal 0, @domain.dnskeys.count
assert_equal 2, @domain.csync_record.times_scanned
CsyncRecord.stub :by_domain_name, @domain.csync_record do
@domain.csync_record.stub :dnssec_validates?, true do
CsyncJob.run
end
end
@domain.reload
assert_equal 1, @domain.dnskeys.count
assert_equal 'mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==', @domain.dnskeys.last.public_key
assert_nil @domain.csync_record
end
def test_sends_mail_to_contacts_if_dnskey_updated
assert_emails 1 do
CsyncJob.run
@domain.reload
CsyncRecord.stub :by_domain_name, @domain.csync_record do
@domain.csync_record.stub :dnssec_validates?, true do
2.times do
CsyncJob.run
end
end
end
end
end
end

View file

@ -291,37 +291,45 @@ class ContactTest < ActiveSupport::TestCase
assert_equal 'US', contact.country_code
end
def test_normalizes_ident_country_code
contact = Contact.new(ident_country_code: 'us')
contact.validate
assert_equal 'US', contact.ident_country_code
def test_linked_scope_returns_contact_that_acts_as_admin_contact
domains(:shop).admin_contacts = [@contact]
assert Contact.linked.include?(@contact), 'Contact should be included'
end
def test_generates_code
contact = Contact.new(registrar: registrars(:bestnames))
assert_nil contact.code
contact.generate_code
assert_not_empty contact.code
def test_linked_scope_returns_contact_that_acts_as_tech_contact
domains(:shop).tech_contacts = [@contact]
assert Contact.linked.include?(@contact), 'Contact should be included'
end
def test_prohibits_code_change
assert_no_changes -> { @contact.code } do
@contact.code = 'new'
@contact.save!
@contact.reload
end
def test_linked_scope_skips_unlinked_contact
contact = unlinked_contact
assert_not Contact.linked.include?(contact), 'Contact should be excluded'
end
def test_removes_duplicate_statuses
contact = Contact.new(statuses: %w[ok ok])
assert_equal %w[ok], contact.statuses
def test_unlinked_scope_returns_unlinked_contact
contact = unlinked_contact
assert Contact.unlinked.include?(contact), 'Contact should be included'
end
def test_default_status
contact = Contact.new
assert_equal %w[ok], contact.statuses
def test_unlinked_scope_skips_contact_that_is_linked_as_registrant
contact = unlinked_contact
domains(:shop).update_columns(registrant_id: contact.becomes(Registrant).id)
assert Contact.unlinked.exclude?(contact), 'Contact should be excluded'
end
def test_unlinked_scope_skips_contact_that_is_linked_as_admin_contact
contact = unlinked_contact
domains(:shop).admin_contacts = [contact]
assert Contact.unlinked.exclude?(contact), 'Contact should be excluded'
end
def test_unlinked_scope_skips_contact_that_is_linked_as_tech_contact
contact = unlinked_contact
domains(:shop).tech_contacts = [contact]
assert Contact.unlinked.exclude?(contact), 'Contact should be excluded'
end
def test_whois_gets_updated_after_contact_save

View file

@ -0,0 +1,289 @@
require 'test_helper'
class CsyncRecordTest < ActiveSupport::TestCase
setup do
@domain = domains(:shop)
@csync_record = CsyncRecord.new
@csync_record.cdnskey = '257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ=='
@csync_record.action = 'initialized'
@csync_record.domain = domains(:shop)
@csync_record.last_scan = Time.now
end
def test_domain_must_be_present
assert @csync_record.valid?
@csync_record.domain = nil
assert_not @csync_record.valid?
end
def test_action_must_be_present_and_valid
@csync_record.action = nil
assert_not @csync_record.valid?
@csync_record.action = 'definitely invalid action'
assert_not @csync_record.valid?
@csync_record.action = 'initialized'
assert @csync_record.valid?
@csync_record.action = 'rollover'
assert @csync_record.valid?
@csync_record.action = 'deactivate'
assert_not @csync_record.valid?
@csync_record.action = 'deactivate'
@csync_record.cdnskey = '0 3 0 AA=='
assert @csync_record.valid?
end
def test_cdnskey_must_be_unique_for_domain
dnskey = @csync_record.dnskey
dnskey.save!
assert_not @csync_record.valid?
assert_includes @csync_record.errors.full_messages, 'Public key already tied to this domain'
@csync_record.cdnskey = nil
assert_not @csync_record.valid?
assert_includes @csync_record.errors.full_messages, 'Cdnskey is missing'
end
def test_cdnskey_must_be_parsable
@csync_record.cdnskey = 'gibberish'
assert_not @csync_record.valid?
@csync_record.cdnskey = nil
assert_not @csync_record.valid?
@csync_record.cdnskey = ''
assert_not @csync_record.valid?
@csync_record.cdnskey = '257 3 13 KlHFYV42UtxC7LpsolDpoUZ9DNPDRYQypalBRIqlubBg/zg78aqciLk+NaWUbrkN7AUaM7h7tx91sLN+ORVPxA=='
assert @csync_record.valid?
end
def test_initializes_valid_record_with_scanner_input
scanner_result = {
type: 'insecure', ns: 'ns1.bestnames.test', ns_ip: '127.0.0.1', flags: '257', proto: '3', alg: '13',
pub: 'mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==',
cdnskey: '257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ=='
}
csync_record = CsyncRecord.by_domain_name(@domain.name)
csync_record.assign_scanner_data!(scanner_result)
assert csync_record.valid?
assert_equal scanner_result[:cdnskey], csync_record.cdnskey
assert_equal 'initialized', csync_record.action
assert_equal 1, csync_record.times_scanned
end
def test_creates_rollover_record_with_scanner_input
# Rollover always requires valid dnssec conf beforehand
dnskey = @csync_record.dnskey
dnskey.save!
# Type 'secure' reflects from cdnskey-scanner that custom domain security level is SECURE (dnssec valid)
scanner_result = {
type: 'secure', ns: 'ns1.bestnames.test', ns_ip: '127.0.0.1', flags: '256', proto: '3', alg: '13',
pub: 'PiVTNvqOTrCSoXf5obNEPrDe0yhrKPmjyv+MWfoscBHF49rRIH1/yDdAXY3SyUD86qq/AiXDzsTQIqOjvak7gw==',
cdnskey: '256 3 13 PiVTNvqOTrCSoXf5obNEPrDe0yhrKPmjyv+MWfoscBHF49rRIH1/yDdAXY3SyUD86qq/AiXDzsTQIqOjvak7gw=='
}
csync_record = CsyncRecord.by_domain_name(@domain.name)
csync_record.assign_scanner_data!(scanner_result)
assert_equal 'rollover', csync_record.action
assert csync_record.valid?
scanner_result[:type] = 'insecure'
csync_record.assign_scanner_data!(scanner_result)
assert_not csync_record.valid?
assert_includes csync_record.errors.full_messages, 'Action is invalid'
end
def test_creates_deactivate_record_with_scanner_input
# Deactivate always requires valid dnssec conf beforehand
dnskey = @csync_record.dnskey
dnskey.save!
# Type 'secure' reflects from cdnskey-scanner that custom domain security level is SECURE (dnssec valid)
scanner_result = {
type: 'secure', ns: 'ns1.bestnames.test', ns_ip: '127.0.0.1', flags: '0', proto: '3', alg: '0',
pub: 'AA==',
cdnskey: '0 3 0 AA=='
}
csync_record = CsyncRecord.by_domain_name(@domain.name)
csync_record.assign_scanner_data!(scanner_result)
assert_equal 'deactivate', csync_record.action
assert csync_record.valid?
scanner_result[:type] = 'insecure'
csync_record.assign_scanner_data!(scanner_result)
assert_not csync_record.valid?
assert_includes csync_record.errors.full_messages, 'Action is invalid'
end
def test_initializes_dnssec_for_domain
scanner_result = {
type: 'insecure', ns: 'ns1.bestnames.test', ns_ip: '127.0.0.1', flags: '257', proto: '3', alg: '13',
pub: 'mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==',
cdnskey: '257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ=='
}
2.times { CsyncRecord.by_domain_name(@domain.name).record_new_scan(scanner_result) }
@domain.reload
CsyncRecord.stub :by_domain_name, @domain.csync_record do
@domain.csync_record.stub :dnssec_validates?, true do
CsyncRecord.by_domain_name(@domain.name).record_new_scan(scanner_result)
end
end
assert_equal 1, @domain.dnskeys.count
assert_equal scanner_result[:pub], @domain.dnskeys.last.public_key
mail = ActionMailer::Base.deliveries.last
assert_equal (@domain.contacts.map(&:email) << @domain.registrant.email).uniq, mail.to
assert_equal mail.subject, "Teie domeeni #{@domain.name} DNSSEC andmed on uuendatud / DNSSEC data for #{@domain.name} has been updated"
end
def test_rollovers_dnssec_for_domain
dnskey = @csync_record.dnskey
dnskey.save!
scanner_result = {
type: 'secure', ns: 'ns1.bestnames.test', ns_ip: '127.0.0.1', flags: '256', proto: '3', alg: '13',
pub: 'PiVTNvqOTrCSoXf5obNEPrDe0yhrKPmjyv+MWfoscBHF49rRIH1/yDdAXY3SyUD86qq/AiXDzsTQIqOjvak7gw==',
cdnskey: '256 3 13 PiVTNvqOTrCSoXf5obNEPrDe0yhrKPmjyv+MWfoscBHF49rRIH1/yDdAXY3SyUD86qq/AiXDzsTQIqOjvak7gw=='
}
stub_any_instance(CsyncRecord, :dnssec_validates?, true) do
CsyncRecord.by_domain_name(@domain.name).record_new_scan(scanner_result)
end
assert_equal 2, @domain.dnskeys.count
assert_equal scanner_result[:pub], @domain.dnskeys.last.public_key
mail = ActionMailer::Base.deliveries.last
assert_equal (@domain.contacts.map(&:email) << @domain.registrant.email).uniq, mail.to
assert_equal mail.subject, "Teie domeeni #{@domain.name} DNSSEC andmed on uuendatud / DNSSEC data for #{@domain.name} has been updated"
end
def test_deactivates_dnssec_for_domain
dnskey = @csync_record.dnskey
dnskey.save!
scanner_result = {
type: 'secure', ns: 'ns1.bestnames.test', ns_ip: '127.0.0.1', flags: '0', proto: '3', alg: '0',
pub: 'AA==',
cdnskey: '0 3 0 AA=='
}
stub_any_instance(CsyncRecord, :dnssec_validates?, true) do
CsyncRecord.by_domain_name(@domain.name).record_new_scan(scanner_result)
end
assert @domain.dnskeys.empty?
mail = ActionMailer::Base.deliveries.last
assert_equal (@domain.contacts.map(&:email) << @domain.registrant.email).uniq, mail.to
assert_equal mail.subject, "Teie domeeni #{@domain.name} DNSSEC andmed on eemaldatud / DNSSEC data for #{@domain.name} has been removed"
end
def test_validates_security_level_for_initialize_action
@csync_record.action = 'initialized'
@domain.stub :dnssec_security_level, Dnsruby::Message::SecurityLevel.INSECURE do
assert @csync_record.valid_pre_action?
assert_not @csync_record.valid_post_action?
end
@domain.stub :dnssec_security_level, Dnsruby::Message::SecurityLevel.BOGUS do
assert @csync_record.valid_pre_action?
assert_not @csync_record.valid_post_action?
end
@domain.stub :dnssec_security_level, Dnsruby::Message::SecurityLevel.SECURE do
assert @csync_record.valid_post_action?
assert_not @csync_record.valid_pre_action?
end
end
def test_validates_security_level_for_rollover_action
@csync_record.action = 'rollover'
@domain.stub :dnssec_security_level, Dnsruby::Message::SecurityLevel.INSECURE do
assert_not @csync_record.valid_pre_action?
assert_not @csync_record.valid_post_action?
end
@domain.stub :dnssec_security_level, Dnsruby::Message::SecurityLevel.BOGUS do
assert_not @csync_record.valid_pre_action?
assert_not @csync_record.valid_post_action?
end
@domain.stub :dnssec_security_level, Dnsruby::Message::SecurityLevel.SECURE do
assert @csync_record.valid_pre_action?
assert @csync_record.valid_post_action?
end
end
def test_validates_security_level_for_deactivate_action
@csync_record.action = 'deactivate'
@domain.stub :dnssec_security_level, Dnsruby::Message::SecurityLevel.INSECURE do
assert @csync_record.valid_post_action?
assert_not @csync_record.valid_pre_action?
end
@domain.stub :dnssec_security_level, Dnsruby::Message::SecurityLevel.BOGUS do
assert @csync_record.valid_post_action?
assert_not @csync_record.valid_pre_action?
end
@domain.stub :dnssec_security_level, Dnsruby::Message::SecurityLevel.SECURE do
assert @csync_record.valid_pre_action?
assert_not @csync_record.valid_post_action?
end
end
def test_returns_correct_result_if_pre_and_post_actions_succeed
stub_any_instance(CsyncRecord, :valid_post_action?, true) do
stub_any_instance(CsyncRecord, :valid_pre_action?, true) do
assert @csync_record.dnssec_validates?
end
end
stub_any_instance(CsyncRecord, :valid_post_action?, false) do
stub_any_instance(CsyncRecord, :valid_pre_action?, false) do
assert_not @csync_record.dnssec_validates?
end
end
end
def test_dnssec_validation_fails_if_domain_unreachable
@domain.name = 'definitely.not.valid.domain.123'
assert_not @csync_record.dnssec_validates?
end
def stub_any_instance(klass, method, value)
klass.class_eval do
alias_method :"new_#{method}", method
define_method(method) do
if value.respond_to?(:call)
value.call
else
value
end
end
end
yield
ensure
klass.class_eval do
undef_method method
alias_method method, :"new_#{method}"
undef_method :"new_#{method}"
end
end
end