# HG changeset patch # User Heiko Schlittermann (JUMPER) # Date 1419896279 -3600 # Node ID cff9b7e57f19c13b0ba9d6ffee0c809d8ffe2f94 # Parent 676230d2d1ae601b3a5f26c214d8b161de9bb7be works for NS check Now we can compare the officially known name servers (from googles public NS service) with the name servers a reference system knows. diff -r 676230d2d1ae -r cff9b7e57f19 Build.PL --- a/Build.PL Sun Dec 28 16:44:41 2014 +0100 +++ b/Build.PL Tue Dec 30 00:37:59 2014 +0100 @@ -15,6 +15,9 @@ perl => '5.14.2', 'Net::DNS' => '0.74', }, + test_requires => { + 'Test::Exception' => '0.32', + }, ); if (not defined $builder->install_path('nagios')) { @@ -29,5 +32,5 @@ $builder->bindoc_dirs([@{ $builder->bindoc_dirs }, 'blib/nagios/plugins/ius']); $builder->add_build_element('checks'); -# finally +# finally $builder->create_build_script; diff -r 676230d2d1ae -r cff9b7e57f19 MANIFEST --- a/MANIFEST Sun Dec 28 16:44:41 2014 +0100 +++ b/MANIFEST Tue Dec 30 00:37:59 2014 +0100 @@ -2,3 +2,4 @@ Build.PL MANIFEST This list of files plugins/check_dns-serial +t/10-minimal.t diff -r 676230d2d1ae -r cff9b7e57f19 plugins/check_dns-serial --- a/plugins/check_dns-serial Sun Dec 28 16:44:41 2014 +0100 +++ b/plugins/check_dns-serial Tue Dec 30 00:37:59 2014 +0100 @@ -2,19 +2,136 @@ use 5.014; use strict; use warnings; -use GetOpt:::Long; +use Getopt::Long qw(GetOptionsFromArray); use Net::DNS; +use Pod::Usage; + + +my %resolver; +sub uniq { my %h; @h{@_} = (); return keys %h; } sub get_domains { - return qw(schlittermann.de); + my @sources = @_; + my @domains = (); + + foreach my $src (@sources) { + + if ($src =~ m{^(?:(/.*)|file://(/.*))}) { + open(my $f, '<', $1) or die "$0: Can't open $1 for reading: $!\n"; + push @domains, map { /^\s*(\S+)\s*/ } <$f>; + next; + } + + push @domains, $src; + } + + return @domains; } -# query the serial from +# return a list of "official" nameservers +sub get_ns { + my ($nameserver) = map { /^\@(.*)/ } $_[0] =~ /^\@/ ? shift : '@8.8.8.8'; + my ($domain) = @_; + my @ns; + + my $r = $resolver{$nameserver} //= Net::DNS::Resolver->new(nameservers => [$nameserver]); + my $q = $r->query($domain, 'NS') or die $r->errorstring, "\n"; + push @ns, map { $_->nsdname } grep { $_->type eq 'NS' } $q->answer; + + return sort @ns; +} + +sub get_serial { + my ($nameserver) = map { /^\@(.*)/ } $_[0] =~ /^\@/ ? shift : '@8.8.8.8'; + my ($domain) = shift; + + my $r = $resolver{$nameserver} //= + Net::DNS::Resolver->new(nameservers => [$nameserver]); + my $q = $r->query($domain, 'SOA') or die $r->errorstring, "\n"; + return (map { $_->serial } grep { $_->type eq 'SOA' } $q->answer)[0]; +} + # - the nameservers known from the ns records -# - from the primary master if this is not one of the +# - from the primary master if this is not one of the # NS for the zone # - from a list of additional (hidden) servers # # OK - if the serial numbers are in sync # WARNING - if there is some difference # CRITICAL - if the serial cannot be found at one of the sources + +sub ns_ok { + my ($reference, $domain) = @_; + + my @our = sort eval { get_ns($reference, $domain) }; + my @their = sort +get_ns($domain); + + { + local $" = "\0"; + return 1 if "@our" eq "@their"; + } + + local $" = ', '; + die "NS differ (our @our) vs (their @their)\n"; +}; + +sub main { + my @argv = @_; + my $opt_reference = '212.80.235.130'; + my $opt_progress = -t; + + GetOptionsFromArray(\@argv, + 'reference=s' => \$opt_reference) + and @argv or pod2usage; + my @domains = get_domains(@argv); + + my (@OK, %CRITICAL); + foreach my $domain (@domains) { + print STDERR "$domain " if $opt_progress; + eval { ns_ok('@212.80.235.130', $domain) }; + if ($@) { $CRITICAL{$domain} = $@ } + else { push @OK, $domain } + say STDERR $@ ? 'not ok' : 'ok' if $opt_progress; + } + +# use DDP; +# p @OK; +# p %CRITICAL; + + if (my $n = keys %CRITICAL) { + print "CRITICAL: $n of ".@domains." domains\n", map { "$_: $CRITICAL{$_}" } sort keys %CRITICAL; + return 2; + } + + say 'OK: ' . @OK . ' domains checked'; + return 0; + +} + +exit main @ARGV unless caller; + +__END__ + +=head1 NAME + + check_dns-serial - check the dns serial number from multiple sources + +=head1 SYNOPSIS + + check_dns-serial [options] DOMAINS + +=head1 OPTIONS + +=over + +=item B<--reference>=I
+ +The address of the reference server for our own domains (default: 212.80.235.130) + +=item B<--progress> + +Tell about the progress. (default: on if input is connected to a terminal) + +=back + +=cut diff -r 676230d2d1ae -r cff9b7e57f19 t/10-minimal.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/t/10-minimal.t Tue Dec 30 00:37:59 2014 +0100 @@ -0,0 +1,54 @@ +use 5.014; +use strict; +use warnings; +use Test::More; +use File::Temp; +use Test::Exception; + +my $tmp = File::Temp->new; +$tmp->print(<<__); + a + b + c +__ +$tmp->flush; + +sub dig_serial { (split " ", `dig +short SOA @_`)[2] } +sub dig_ns { sort map { /(\S+?)\.?$/ } `dig +short NS @_` } + +# we require it, it's not a normal module +require_ok 'blib/nagios/plugins/ius/check_dns-serial' + or BAIL_OUT q{can't require the module}; + +is_deeply [sort +uniq(qw(a b a c))], [qw(a b c)] => 'uniq helper'; + +# get_domains should read a list of names, either from a file +# or from the arguments, or from a combination of both +is_deeply [get_domains(qw(a b c))], [qw(a b c)] => 'domains from list'; +is_deeply [get_domains("$tmp")], [qw(a b c)] => 'domains from file'; +is_deeply [get_domains('a', "$tmp", 'z')], + [qw(a a b c z)] => 'domains from args and file'; + +for (qw(heise.de schlittermann.de google.com debian.org example.org)) { + + subtest $_ => sub { + + # get_ns should return the NS from public dns servers + is_deeply [get_ns($_)], [dig_ns($_)] => "ns \@default"; + is_deeply [get_ns('@8.8.4.4', $_)], [dig_ns('@8.8.4.4', $_)] => "ns \@8.8.4.4"; + is get_serial('@8.8.8.8', $_), dig_serial('@8.8.8.8', $_) => 'serial'; + }; + +} + +# ns for some domain we're not the master for, should be refused +throws_ok { get_ns('@212.80.235.130', 'heise.de') } qr/^REFUSED/ => 'throws on refused query'; +throws_ok { get_ns('safasdfasdfrandomadsfefvddeas') } qr/^NXDOMAIN/ => 'throws on nx domain'; + +ok ns_ok('@212.80.235.130', 'schlittermann.de') => 'ns for schlittermann.de'; +throws_ok { ns_ok('@212.80.235.130', 'heise.de') } qr/differ/ => 'ns for heise.de'; + + +# serial + +done_testing();