app/jobs/validate_dnssec_job.rb
class ValidateDnssecJob < ApplicationJob
discard_on StandardError
def perform(domain_name: nil)
unless domain_name.nil?
domain = Domain.find_by(name: domain_name)
return logger.info "This domain not contain any dnskeys" if domain.dnskeys.empty?
return logger.info "No domain found" if domain.nil?
return logger.info "No related nameservers for this domain" if domain.nameservers.empty?
iterate_nameservers(domain)
else
domain_list = Domain.all.reject { |d| d.dnskeys.empty? }
domain_list.each do |d|
if d.nameservers.empty?
logger.info "#{d.name} has no nameserver"
next
end
iterate_nameservers(d)
end
end
rescue StandardError => e
logger.error e.message
raise e
end
private
def iterate_nameservers(domain)
domain.nameservers.each do |n|
next unless n.validated?
validate(nameserver: n, domain: domain)
notify_contacts(domain)
logger.info "----------------------------"
end
end
def notify_contacts(domain)
flag = domain.dnskeys.any? { |k| k.validation_datetime.present? }
return if flag
text = "DNSKEY record of a domain #{domain.name} is invalid. Please fix or contact the registrant."
logger.info text
# ContactNotification.notify_registrar(domain: domain, text: text)
# ContactNotification.notify_tech_contact(domain: domain, reason: 'dnssec')
end
def validate(nameserver:, domain:, type: 'DNSKEY', klass: 'IN')
resolver = prepare_validator(nameserver.hostname)
answer = resolver.query(domain.name, type, klass)
return logger.info "no any data for #{domain.name} | hostname - #{nameserver.hostname}" if answer.nil?
logger.info "-----------"
logger.info "data for domain name - #{domain.name} | hostname - #{nameserver.hostname}"
logger.info "-----------"
response_container = parse_response(answer)
compare_dnssec_data(response_container: response_container, domain: domain, nameserver: nameserver)
rescue Exception => e
logger.error "#{e.message} - domain name: #{domain.name} - hostname: #{nameserver.hostname}"
nameserver.update(failed_validation_reason: "#{e.message} - domain name: #{domain.name} - hostname: #{nameserver.hostname}")
nil
end
def compare_dnssec_data(response_container:, domain:, nameserver:)
domain.dnskeys.each do |key|
next unless key.flags.to_s == '257'
next if key.validation_datetime.present?
flag = make_magic(response_container: response_container, dnskey: key)
text = "#{key.flags} - #{key.protocol} - #{key.alg} - #{key.public_key}"
if flag
key.validation_datetime = Time.zone.now
key.failed_validation_reason = nil
key.save
nameserver.failed_validation_reason = nil
nameserver.save
logger.info text + " ------->> succesfully!"
else
key.update!(failed_validation_reason: text + " not found in zone! Domain name - #{domain.name}. Hostname - #{nameserver.hostname}")
logger.info text + " ------->> not found in zone! Domain name - #{domain.name}. Hostname - #{nameserver.hostname}"
end
end
end
def make_magic(response_container:, dnskey:)
response_container.any? do |r|
r[:flags].to_s == dnskey.flags.to_s &&
r[:protocol].to_s == dnskey.protocol.to_s &&
r[:alg].to_s == dnskey.alg.to_s &&
r[:public_key] == dnskey.public_key
end
end
def parse_response(answer)
response_container = []
answer.each_answer do |a|
a_string = a.to_s
a_string = a_string.gsub /\t/, ' '
a_string = a_string.split(' ')
next unless a_string[4] == '257'
protocol = a.protocol
alg = a.algorithm.code
response_container << {
flags: a_string[4],
protocol: protocol,
alg: alg,
public_key: a_string[8]
}
end
response_container
end
def prepare_validator(nameserver)
inner_resolver = Dnsruby::Resolver.new
timeouts = ENV['nameserver_validation_timeout'] || 4
inner_resolver.do_validation = true
inner_resolver.dnssec = true
inner_resolver.nameserver = nameserver
inner_resolver.packet_timeout = timeouts.to_i
inner_resolver.query_timeout = timeouts.to_i
# resolver = Dnsruby::Recursor.new(inner_resolver)
# resolver.dnssec = true
# resolver
inner_resolver
end
def logger
@logger ||= Rails.logger
end
end