X-Git-Url: https://git.adam-barratt.org.uk/?a=blobdiff_plain;f=dsa-nagios-checks%2Fchecks%2Fdsa-check-dnssec-delegation;h=65b48b0cb19342d056ad6848b044b3da4f62f98e;hb=53474a40dd94c064058aae4f9936b9e52577162d;hp=82ffcd5e95e392ab1c220a889b7427490d85a99b;hpb=99b3f8cd5e377af0bb167a3b5bb2e56bdac0b68b;p=mirror%2Fdsa-nagios.git diff --git a/dsa-nagios-checks/checks/dsa-check-dnssec-delegation b/dsa-nagios-checks/checks/dsa-check-dnssec-delegation index 82ffcd5..65b48b0 100755 --- a/dsa-nagios-checks/checks/dsa-check-dnssec-delegation +++ b/dsa-nagios-checks/checks/dsa-check-dnssec-delegation @@ -1,6 +1,6 @@ #!/usr/bin/perl -# Copyright (c) 2010 Peter Palfrader +# Copyright (c) 2010, 2014, 2015, 2017 Peter Palfrader # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -26,6 +26,27 @@ use warnings; use English; use Net::DNS::Resolver; use Getopt::Long; +use File::Basename; + +# taken from Array::Utils +# http://cpansearch.perl.org/src/ZMIJ/Array-Utils-0.5/Utils.pm +# This module is Copyright (c) 2007 Sergei A. Fedorov. +# You may distribute under the terms of either the GNU General Public +# License or the Artistic License, as specified in the Perl README file. +# +sub intersect(\@\@) { + my %e = map { $_ => undef } @{$_[0]}; + return grep { exists( $e{$_} ) } @{$_[1]}; +} +sub array_diff(\@\@) { + my %e = map { $_ => undef } @{$_[1]}; + return @{[ ( grep { (exists $e{$_}) ? ( delete $e{$_} ) : ( 1 ) } @{ $_[0] } ), keys %e ] }; +} +sub array_minus(\@\@) { + my %e = map{ $_ => undef } @{$_[1]}; + return grep( ! exists( $e{$_} ), @{$_[0]} ); +} + $SIG{'__DIE__'} = sub { print @_; exit 4; }; @@ -36,8 +57,10 @@ my $params; sub get_tag_generic { my $zone = shift; my $type = shift; + my %options = @_; my @result; + my @zsks; print "Querying $type $zone\n" if $params->{'verbose'}; my $pkt = $RES->send($zone, $type); return () unless $pkt; @@ -46,28 +69,45 @@ sub get_tag_generic { next unless ($rr->type eq $type); next unless (lc($rr->name) eq lc($zone)); - # only handle KSKs, i.e. keys with the SEP flag set - next if ($type eq 'DNSKEY' && !($rr->is_sep)); + my $tag = $options{'pretty'} ? sprintf("%5d(%d)", $rr->keytag, $rr->algorithm) : $rr->keytag; - push @result, $rr->keytag; + if ($type eq 'DNSKEY' && ($rr->{'flags'} & (1<<(15-8)))) { + # key is revoked + next; + } + + # for now only handle KSKs, i.e. keys with the SEP flag set + if ($type eq 'DNSKEY' && !($rr->sep)) { + push @zsks, $tag; + next; + } + + push @result, $tag; }; + if ($type eq 'DNSKEY' && (scalar @result) == 0) { + # use remaining keys if no keys with the SEP bit are present + @result = @zsks; + } my %unique = (); - @result = sort {$a <=> $b} grep {!$unique{$_}++} @result; + @result = sort {$a cmp $b} grep {!$unique{$_}++} @result; return @result }; sub get_dnskeytags { my $zone = shift; - return get_tag_generic($zone, 'DNSKEY'); + my %options = @_; + return get_tag_generic($zone, 'DNSKEY', %options); }; sub get_dstags { my $zone = shift; - return get_tag_generic($zone, 'DS'); + my %options = @_; + return get_tag_generic($zone, 'DS', %options); }; sub get_dlvtags { my $zone = shift; + my %options = @_; $zone .= ".".$DLV; - return get_tag_generic($zone, 'DLV'); + return get_tag_generic($zone, 'DLV', %options); }; sub has_dnskey_parent { my $zone = shift; @@ -131,28 +171,35 @@ sub usage { sub what_to_check { my $zone = shift; - my $indir = shift; + my $zonefile = shift; my $do_dlv = 0; my $do_ds = 0; - open(F, "<", $indir."/".$zone) or die ("Cannot open zonefile for $zone: $!\n"); + open(F, "<", $zonefile) or die ("Cannot open zonefile $zonefile for $zone: $!\n"); while () { - if (/^;\s*dlv-submit\s*=\s*yes\s*$/) { $do_dlv = 1; } - if (/^;\s*ds-in-parent\s*=\s*yes\s*$/) { $do_ds = 1; } + if (/^[#;]\s*dlv-submit\s*=\s*yes\s*$/) { $do_dlv = 1; } + if (/^[#;]\s*ds-in-parent\s*=\s*yes\s*$/) { $do_ds = 1; } } close(F); - my @keys = (); - push @keys, 'dlv' if $do_dlv; - push @keys, 'ds' if $do_ds; - return @keys; + return { 'dlv' => $do_dlv, + 'ds' => $do_ds }; +} +sub diff_spec { + my $a = shift; + my $b = shift; + + my @elems = intersect(@$a, @$b); + push @elems, map { '-'.$_ } array_minus(@$a, @$b); + push @elems, map { '+'.$_ } array_minus(@$b, @$a); + return join(',', @elems); } Getopt::Long::config('bundling'); GetOptions ( '--help' => \$params->{'help'}, - '--dir=s' => \$params->{'dir'}, + '--dir=s@' => \$params->{'dir'}, '--dlv=s' => \$params->{'dlv'}, '--verbose' => \$params->{'verbose'}, ) or usage(\*STDERR, 1); @@ -162,42 +209,49 @@ my $mode = shift @ARGV; usage(\*STDOUT, 0) unless (defined $mode && $mode =~ /^(overview|check-dlv|check-ds|check-header)$/); die ("check-header needs --dir") if ($mode eq 'check-header' && !defined $params->{'dir'}); -my @zones; +my %zones; if (scalar @ARGV) { if (defined $params->{'dir'} && $mode ne 'check-header') { warn "--dir option ignored" } - @zones = @ARGV; + %zones = map { $_ => $_} @ARGV; } else { - my $dir = $params->{'dir'}; - usage(\*STDOUT, 0) unless (defined $dir); - - chdir $dir or die "chdir $dir failed? $!\n"; - opendir DIR, '.' or die ("Cannot opendir $dir\n"); - for my $file (readdir DIR) { - next if ( -l "$file" ); - next unless ( -f "$file" ); - next if $file =~ /^(dsset|keyset)-/; - - push @zones, $file; - } - closedir(DIR); + my $dirs = $params->{'dir'}; + usage(\*STDOUT, 0) unless (defined $dirs); + + for my $dir (@$dirs) { + chdir $dir or die "chdir $dir failed? $!\n"; + opendir DIR, '.' or die ("Cannot opendir $dir\n"); + for my $file (readdir DIR) { + next if ( -l "$file" ); + next unless ( -f "$file" ); + next if $file =~ /^(dsset|keyset)-/; + + my $zone = $file; + if ($file =~ /\.zone$/) { # it's one of our yaml things + $zone = basename($file, '.zone'); + }; + $zones{$zone} = "$dir/$file"; + } + closedir(DIR); + }; }; $DLV = $params->{'dlv'} if $params->{'dlv'}; -my %data; -for my $zone (@zones) { - $data{$zone} = { 'dnskey' => join(', ', get_dnskeytags($zone)), - 'ds' => join(', ', get_dstags($zone)), - 'dlv' => join(', ', get_dlvtags($zone)), - 'parent_dnssec' => get_parent_dnssec_status($zone) }; -} if ($mode eq 'overview') { - my $format = "%60s %-10s %-10s %-10s %-10s\n"; + my %data; + for my $zone (keys %zones) { + $data{$zone} = { 'dnskey' => join(', ', get_dnskeytags($zone, pretty=>1)), + 'ds' => join(', ', get_dstags($zone, pretty=>1)), + 'dlv' => join(', ', get_dlvtags($zone, pretty=>1)), + 'parent_dnssec' => get_parent_dnssec_status($zone) }; + } + + my $format = "%60s %-20s %-15s %-3s %-10s\n"; printf $format, "zone", "DNSKEY", "DS\@parent", "DLV", "dnssec\@parent"; - printf $format, "-"x 60, "-"x 10, "-"x 10, "-"x 10, "-"x 10; + printf $format, "-"x 60, "-"x 20, "-"x 15, "-"x 3, "-"x 10; for my $zone (sort {$a cmp $b} keys %data) { printf $format, $zone, $data{$zone}->{'dnskey'}, @@ -207,25 +261,33 @@ if ($mode eq 'overview') { } exit(0); } elsif ($mode eq 'check-dlv' || $mode eq 'check-ds' || $mode eq 'check-header') { - my $key; - $key = 'dlv' if $mode eq 'check-dlv'; - $key = 'ds' if $mode eq 'check-ds'; - $key = 'per-zone' if $mode eq 'check-header'; - die ("key undefined") unless $key; + my @to_check; + push @to_check, 'dlv' if $mode eq 'check-header' || $mode eq 'check-dlv'; + push @to_check, 'ds' if $mode eq 'check-header' || $mode eq 'check-ds'; my @warn; my @ok; - for my $zone (sort {$a cmp $b} keys %data) { - my @thiskeys = $key eq 'per-zone' ? what_to_check($zone, $params->{'dir'}) : ($key); - - for my $thiskey (@thiskeys) { - my $dnskey = $data{$zone}->{'dnskey'} || '-'; - my $target = $data{$zone}->{$thiskey} || '-'; + for my $zone (sort {$a cmp $b} keys %zones) { + my $require = { map { $_ => 1 } @to_check }; + if ($mode eq 'check-header') { + $require = what_to_check($zone, $zones{$zone}) + } - if ($dnskey ne $target) { - push @warn, "$zone ($dnskey != $target)"; + my @dnskey = get_dnskeytags($zone); + for my $thiskey (@to_check) { + my @target = $thiskey eq 'ds' ? get_dstags($zone) : get_dlvtags($zone); + + my $spec = diff_spec(\@target, \@dnskey); + # if the intersection between DS and KEY is empty, + # or if there are DS records for keys we do not have, that's an issue. + if (intersect(@dnskey, @target) == 0 || array_minus(@target, @dnskey)) { + if ($require->{$thiskey} || scalar @target > 0) { + push @warn, "$zone ($spec)"; + } } else { - push @ok, "$zone ($dnskey)"; + if ($require->{$thiskey}) { + push @ok, "$zone ($spec)"; + } }; } }