mirror of
https://github.com/internetee/registry.git
synced 2025-06-07 13:15:40 +02:00
Merge pull request #2233 from internetee/2202-2-nameserver-record-validation
added nameserver validator
This commit is contained in:
commit
d4fa99de73
10 changed files with 334 additions and 6 deletions
|
@ -82,7 +82,8 @@ module Actions
|
|||
dns_servers = ENV['dnssec_resolver_ips'].to_s.split(',').map(&:strip)
|
||||
|
||||
Resolv::DNS.open({ nameserver: dns_servers }) do |dns|
|
||||
dns.timeouts = ENV['a_and_aaaa_validation_timeout'].to_i || 1
|
||||
timeouts = ENV['a_and_aaaa_validation_timeout'] || '1'
|
||||
dns.timeouts = timeouts.to_i
|
||||
ress = nil
|
||||
|
||||
case value
|
||||
|
|
124
app/jobs/nameserver_record_validation_job.rb
Normal file
124
app/jobs/nameserver_record_validation_job.rb
Normal file
|
@ -0,0 +1,124 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'resolv'
|
||||
|
||||
class NameserverRecordValidationJob < ApplicationJob
|
||||
include Dnsruby
|
||||
|
||||
def perform(domain_name: nil)
|
||||
if domain_name.nil?
|
||||
domains = Domain.all.select { |domain| domain.created_at < Time.zone.now - NameserverValidator::VALIDATION_DOMAIN_PERIOD }
|
||||
.select { |domain| domain.nameservers.exists? }
|
||||
|
||||
domains.each do |domain|
|
||||
domain.nameservers.each do |nameserver|
|
||||
next if nameserver.nameserver_failed_validation? || nameserver.validated?
|
||||
|
||||
result = NameserverValidator.run(domain_name: domain.name, nameserver: nameserver)
|
||||
|
||||
if result[:result]
|
||||
add_nameserver_to_succesfully(nameserver)
|
||||
|
||||
true
|
||||
else
|
||||
parse_result(result, nameserver)
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
domain = Domain.find_by(name: domain_name)
|
||||
|
||||
return logger.info 'Domain not found' if domain.nil?
|
||||
|
||||
if domain.created_at > Time.zone.now - NameserverValidator::VALIDATION_DOMAIN_PERIOD
|
||||
return logger.info "It should take #{NameserverValidator::VALIDATION_DOMAIN_PERIOD} hours after the domain was created"
|
||||
end
|
||||
|
||||
return logger.info 'Domain not has nameservers' if domain.nameservers.empty?
|
||||
|
||||
domain.nameservers.each do |nameserver|
|
||||
next if nameserver.nameserver_failed_validation?
|
||||
|
||||
result = NameserverValidator.run(domain_name: domain.name, nameserver: nameserver)
|
||||
|
||||
if result[:result]
|
||||
add_nameserver_to_succesfully(nameserver)
|
||||
|
||||
true
|
||||
else
|
||||
parse_result(result, nameserver)
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_nameserver_to_succesfully(nameserver)
|
||||
nameserver.validation_counter = nil
|
||||
nameserver.failed_validation_reason = nil
|
||||
nameserver.validation_datetime = Time.zone.now
|
||||
|
||||
nameserver.save
|
||||
end
|
||||
|
||||
def add_nameserver_to_failed(nameserver:, reason:)
|
||||
if nameserver.validation_counter.nil?
|
||||
nameserver.validation_counter = 1
|
||||
else
|
||||
nameserver.validation_counter = nameserver.validation_counter + 1
|
||||
end
|
||||
|
||||
nameserver.failed_validation_reason = reason
|
||||
nameserver.save
|
||||
end
|
||||
|
||||
def parse_result(result, nameserver)
|
||||
domain = Domain.find(nameserver.domain_id)
|
||||
|
||||
text = ""
|
||||
case result[:reason]
|
||||
when 'answer'
|
||||
text = "No any answer comes from **#{nameserver.hostname}**. Nameserver not exist"
|
||||
when 'serial'
|
||||
text = "Serial number for nameserver hostname **#{nameserver.hostname}** doesn't present. SOA validation failed."
|
||||
when 'not found'
|
||||
text = "Seems nameserver hostname **#{nameserver.hostname}** doesn't exist"
|
||||
when 'exception'
|
||||
text = "Something went wrong, exception reason: **#{result[:error_info]}**"
|
||||
when 'domain'
|
||||
text = "#{domain} zone is not in nameserver**#{nameserver.hostname}**"
|
||||
when 'glup record'
|
||||
text = "Hostname #{nameserver.hostname} didn't resovle by glue record to #{domain}"
|
||||
end
|
||||
|
||||
logger.info text
|
||||
failed_log(text: text, nameserver: nameserver)
|
||||
add_nameserver_to_failed(nameserver: nameserver, reason: text)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def failed_log(text:, nameserver:)
|
||||
inform_to_tech_contact(text)
|
||||
inform_to_registrar(text: text, nameserver: nameserver)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def inform_to_tech_contact(text)
|
||||
"NEED TO DO!"
|
||||
text
|
||||
end
|
||||
|
||||
def inform_to_registrar(text:, nameserver:)
|
||||
# nameserver.domain.registrar.notifications.create!(text: text)
|
||||
"NEED TO DO!"
|
||||
end
|
||||
|
||||
def logger
|
||||
@logger ||= Rails.logger
|
||||
end
|
||||
end
|
|
@ -34,6 +34,8 @@ class Nameserver < ApplicationRecord
|
|||
|
||||
delegate :name, to: :domain, prefix: true
|
||||
|
||||
scope :non_validated, -> { where(validation_datetime: nil) }
|
||||
|
||||
self.ignored_columns = %w[legacy_domain_id]
|
||||
|
||||
def epp_code_map
|
||||
|
@ -53,6 +55,18 @@ class Nameserver < ApplicationRecord
|
|||
}
|
||||
end
|
||||
|
||||
def nameserver_failed_validation?
|
||||
return false if validation_counter.nil?
|
||||
|
||||
validation_counter >= NameserverValidator::VALID_NAMESERVER_COUNT_THRESHOLD
|
||||
end
|
||||
|
||||
def validated?
|
||||
return false if validation_datetime.nil?
|
||||
|
||||
validation_datetime + NameserverValidator::VALIDATION_NAMESERVER_PERIOD > Time.zone.now
|
||||
end
|
||||
|
||||
def to_s
|
||||
hostname
|
||||
end
|
||||
|
|
77
app/services/nameserver_validator.rb
Normal file
77
app/services/nameserver_validator.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
module NameserverValidator
|
||||
include Dnsruby
|
||||
|
||||
extend self
|
||||
|
||||
VALIDATION_NAMESERVER_PERIOD = 1.year.freeze
|
||||
VALIDATION_DOMAIN_PERIOD = 8.hours.freeze
|
||||
VALID_NAMESERVER_COUNT_THRESHOLD = 3
|
||||
|
||||
def run(domain_name:, nameserver:)
|
||||
result_response = validate(domain_name: domain_name, hostname: nameserver.hostname)
|
||||
|
||||
unless result_response[:result] && result_response[:reason] == :exception
|
||||
if result_response[:error_info].to_s.include? "Nameserver invalid!"
|
||||
if nameserver.ipv4.present?
|
||||
result_response = validate(domain_name: domain_name, hostname: nameserver.ipv4)
|
||||
# elsif nameserver.ipv6.present?
|
||||
# result_response = validate(domain_name: domain_name, hostname: nameserver.ipv6)
|
||||
end
|
||||
|
||||
return { result: false, reason: 'glup record' } if result.answer.empty? if result_response[:result]
|
||||
|
||||
return result_response
|
||||
end
|
||||
end
|
||||
|
||||
result_response
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate(domain_name:, hostname:)
|
||||
resolver = setup_resolver(hostname)
|
||||
result = resolver.query(domain_name, 'SOA', 'IN')
|
||||
|
||||
return { result: false, reason: 'answer' } if result.answer.empty?
|
||||
|
||||
decision = result.answer.all? do |a|
|
||||
a.serial.present?
|
||||
end
|
||||
|
||||
return { result: false, reason: 'serial' } unless decision
|
||||
|
||||
logger.info "Serial number - #{result.answer[0].serial.to_s} of #{hostname} - domain name: #{domain_name}"
|
||||
|
||||
{ result: true, reason: '' }
|
||||
rescue Dnsruby::Refused => e
|
||||
logger.error e.message
|
||||
logger.error "failed #{hostname} validation of #{domain_name} domain name. Domain not found"
|
||||
return { result: false, reason: 'domain', error_info: e }
|
||||
rescue Dnsruby::NXDomain => e
|
||||
logger.error e.message
|
||||
logger.error "failed #{hostname} validation of #{domain_name} domain name. Domain not found"
|
||||
return { result: false, reason: 'domain', error_info: e }
|
||||
rescue StandardError => e
|
||||
logger.error e.message
|
||||
logger.error "failed #{hostname} validation of #{domain_name} domain name"
|
||||
return { result: false, reason: 'exception', error_info: e }
|
||||
end
|
||||
|
||||
def setup_resolver(hostname)
|
||||
resolver = Dnsruby::Resolver.new
|
||||
timeouts = ENV['nameserver_validation_timeout'] || 4
|
||||
resolver.query_timeout = timeouts.to_i
|
||||
resolver.retry_times = 3
|
||||
resolver.recurse = 0 # Send out non-recursive queries
|
||||
# disable caching otherwise SOA is cached from first nameserver queried
|
||||
resolver.do_caching = false
|
||||
resolver.nameserver = hostname
|
||||
|
||||
resolver
|
||||
end
|
||||
|
||||
def logger
|
||||
@logger ||= Rails.logger
|
||||
end
|
||||
end
|
|
@ -233,4 +233,5 @@ registry_demo_registrar_results_url: 'http://registry.test/api/v1/accreditation_
|
|||
registry_demo_registrar_api_user_url: 'http://registry.test/api/v1/accreditation_center/show_api_user'
|
||||
registry_demo_accredited_users_url: 'http://registry.test/api/v1/accreditation_center/list_accreditated_api_users'
|
||||
a_and_aaaa_validation_timeout: '1'
|
||||
nameserver_validation_timeout: '1'
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
class AddValidationFieldsToNameserver < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :nameservers, :validation_datetime, :datetime
|
||||
add_column :nameservers, :validation_counter, :integer
|
||||
add_column :nameservers, :failed_validation_reason, :string
|
||||
end
|
||||
end
|
|
@ -824,7 +824,8 @@ CREATE TABLE public.dnskeys (
|
|||
creator_str character varying,
|
||||
updator_str character varying,
|
||||
legacy_domain_id integer,
|
||||
updated_at timestamp without time zone
|
||||
updated_at timestamp without time zone,
|
||||
validation_datetime timestamp without time zone
|
||||
);
|
||||
|
||||
|
||||
|
@ -2177,7 +2178,10 @@ CREATE TABLE public.nameservers (
|
|||
creator_str character varying,
|
||||
updator_str character varying,
|
||||
legacy_domain_id integer,
|
||||
hostname_puny character varying
|
||||
hostname_puny character varying,
|
||||
validation_datetime timestamp without time zone,
|
||||
validation_counter integer,
|
||||
failed_validation_reason character varying
|
||||
);
|
||||
|
||||
|
||||
|
@ -5389,6 +5393,8 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||
('20211124084308'),
|
||||
('20211125181033'),
|
||||
('20211125184334'),
|
||||
('20211126085139');
|
||||
('20211126085139'),
|
||||
('20211231113934'),
|
||||
('20220106123143');
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ class EppDomainUpdateBaseTest < EppTestCase
|
|||
@domain = domains(:shop)
|
||||
@contact = contacts(:john)
|
||||
@original_registrant_change_verification =
|
||||
Setting.request_confirmation_on_registrant_change_enabled
|
||||
Setting.request_confirmation_on_registrant_change_enabled
|
||||
ActionMailer::Base.deliveries.clear
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ class EppDomainUpdateRemDnsTest < EppTestCase
|
|||
@dnskey = dnskeys(:one)
|
||||
@dnskey.update(domain: @domain)
|
||||
@original_registrant_change_verification =
|
||||
Setting.request_confirmation_on_registrant_change_enabled
|
||||
Setting.request_confirmation_on_registrant_change_enabled
|
||||
ActionMailer::Base.deliveries.clear
|
||||
end
|
||||
|
||||
|
|
98
test/jobs/nameserver_record_validation_job_test.rb
Normal file
98
test/jobs/nameserver_record_validation_job_test.rb
Normal file
|
@ -0,0 +1,98 @@
|
|||
require 'test_helper'
|
||||
|
||||
class NameserverRecordValidationJobTest < ActiveSupport::TestCase
|
||||
include ActionMailer::TestHelper
|
||||
|
||||
setup do
|
||||
@nameserver = nameservers(:shop_ns1)
|
||||
@domain = domains(:shop)
|
||||
@domain.update(created_at: Time.zone.now - 10.hours)
|
||||
@domain.reload
|
||||
end
|
||||
|
||||
def test_nameserver_should_validate_succesfully_and_set_validation_datetime
|
||||
mock_dns_response = OpenStruct.new
|
||||
answer = OpenStruct.new
|
||||
answer.serial = '12343'
|
||||
mock_dns_response.answer = [ answer ]
|
||||
|
||||
Spy.on_instance_method(NameserverValidator, :setup_resolver).and_return(Dnsruby::Resolver.new)
|
||||
Spy.on_instance_method(Dnsruby::Resolver, :query).and_return(mock_dns_response)
|
||||
|
||||
assert_nil @nameserver.validation_datetime
|
||||
assert_nil @nameserver.validation_counter
|
||||
assert_nil @nameserver.failed_validation_reason
|
||||
|
||||
NameserverRecordValidationJob.perform_now(domain_name: @domain.name)
|
||||
@nameserver.reload
|
||||
|
||||
assert_not_nil @nameserver.validation_datetime
|
||||
assert_nil @nameserver.validation_counter
|
||||
assert_nil @nameserver.failed_validation_reason
|
||||
end
|
||||
|
||||
def test_should_return_failed_validation_with_answer_reason
|
||||
mock_dns_response = OpenStruct.new
|
||||
mock_dns_response.answer = [ ]
|
||||
|
||||
Spy.on_instance_method(NameserverValidator, :setup_resolver).and_return(Dnsruby::Resolver.new)
|
||||
Spy.on_instance_method(Dnsruby::Resolver, :query).and_return(mock_dns_response)
|
||||
|
||||
assert_nil @nameserver.validation_datetime
|
||||
assert_nil @nameserver.validation_counter
|
||||
assert_nil @nameserver.failed_validation_reason
|
||||
|
||||
NameserverRecordValidationJob.perform_now(domain_name: @domain.name)
|
||||
@nameserver.reload
|
||||
|
||||
assert_nil @nameserver.validation_datetime
|
||||
assert @nameserver.validation_counter, 1
|
||||
assert @nameserver.failed_validation_reason.include? "No any answer comes from **#{@nameserver.hostname}**"
|
||||
end
|
||||
|
||||
def test_should_return_failed_validation_with_serial_reason
|
||||
mock_dns_response = OpenStruct.new
|
||||
answer = OpenStruct.new
|
||||
answer.some_field = '12343'
|
||||
mock_dns_response.answer = [ answer ]
|
||||
|
||||
Spy.on_instance_method(NameserverValidator, :setup_resolver).and_return(Dnsruby::Resolver.new)
|
||||
Spy.on_instance_method(Dnsruby::Resolver, :query).and_return(mock_dns_response)
|
||||
|
||||
assert_nil @nameserver.validation_datetime
|
||||
assert_nil @nameserver.validation_counter
|
||||
assert_nil @nameserver.failed_validation_reason
|
||||
|
||||
NameserverRecordValidationJob.perform_now(domain_name: @domain.name)
|
||||
@nameserver.reload
|
||||
|
||||
assert_nil @nameserver.validation_datetime
|
||||
assert @nameserver.validation_counter, 1
|
||||
assert @nameserver.failed_validation_reason.include? "Serial number for nameserver hostname **#{@nameserver.hostname}** doesn't present. SOA validation failed."
|
||||
end
|
||||
|
||||
def test_after_third_invalid_times_nameserver_should_be_invalid
|
||||
mock_dns_response = OpenStruct.new
|
||||
answer = OpenStruct.new
|
||||
answer.some_field = '12343'
|
||||
mock_dns_response.answer = [ answer ]
|
||||
|
||||
Spy.on_instance_method(NameserverValidator, :setup_resolver).and_return(Dnsruby::Resolver.new)
|
||||
Spy.on_instance_method(Dnsruby::Resolver, :query).and_return(mock_dns_response)
|
||||
|
||||
assert_nil @nameserver.validation_datetime
|
||||
assert_nil @nameserver.validation_counter
|
||||
assert_nil @nameserver.failed_validation_reason
|
||||
|
||||
3.times do
|
||||
NameserverRecordValidationJob.perform_now(domain_name: @domain.name)
|
||||
end
|
||||
|
||||
@nameserver.reload
|
||||
assert_nil @nameserver.validation_datetime
|
||||
assert @nameserver.validation_counter, 1
|
||||
assert @nameserver.failed_validation_reason.include? "Serial number for nameserver hostname **#{@nameserver.hostname}** doesn't present. SOA validation failed."
|
||||
|
||||
assert @nameserver.nameserver_failed_validation?
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue