works for NS check
authorHeiko Schlittermann (JUMPER) <hs@schlittermann.de>
Tue, 30 Dec 2014 00:37:59 +0100
changeset 1 cff9b7e57f19
parent 0 676230d2d1ae
child 2 2d11bbd7be1e
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.
Build.PL
MANIFEST
plugins/check_dns-serial
t/10-minimal.t
--- 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;
--- 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
--- 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<address>
+
+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
--- /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();