diff options
author | mail_redacted_for_web | 2019-04-17 19:07:19 +0200 |
---|---|---|
committer | mail_redacted_for_web | 2019-04-17 19:07:19 +0200 |
commit | 1e2387474a449452b78520b9ad96a8b4b5e99722 (patch) | |
tree | 836889471eec7d2aac177405068e2a8f1e2b1978 /nagios-plugins-contrib-24.20190301~bpo9+1/dsa/checks/dsa-check-dnssec-delegation | |
download | nagios-plugins-contrib-1e2387474a449452b78520b9ad96a8b4b5e99722.tar.bz2 |
initial commit of source fetch
Diffstat (limited to 'nagios-plugins-contrib-24.20190301~bpo9+1/dsa/checks/dsa-check-dnssec-delegation')
-rw-r--r-- | nagios-plugins-contrib-24.20190301~bpo9+1/dsa/checks/dsa-check-dnssec-delegation | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/nagios-plugins-contrib-24.20190301~bpo9+1/dsa/checks/dsa-check-dnssec-delegation b/nagios-plugins-contrib-24.20190301~bpo9+1/dsa/checks/dsa-check-dnssec-delegation new file mode 100644 index 0000000..65b48b0 --- /dev/null +++ b/nagios-plugins-contrib-24.20190301~bpo9+1/dsa/checks/dsa-check-dnssec-delegation @@ -0,0 +1,301 @@ +#!/usr/bin/perl + +# Copyright (c) 2010, 2014, 2015, 2017 Peter Palfrader <peter@palfrader.org> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +use strict; +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; }; + +my $RES = Net::DNS::Resolver->new; +my $DLV = 'dlv.isc.org'; +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; + return () unless $pkt->answer; + for my $rr ($pkt->answer) { + next unless ($rr->type eq $type); + next unless (lc($rr->name) eq lc($zone)); + + my $tag = $options{'pretty'} ? sprintf("%5d(%d)", $rr->keytag, $rr->algorithm) : $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 cmp $b} grep {!$unique{$_}++} @result; + return @result +}; + +sub get_dnskeytags { + my $zone = shift; + my %options = @_; + return get_tag_generic($zone, 'DNSKEY', %options); +}; +sub get_dstags { + my $zone = shift; + 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', %options); +}; +sub has_dnskey_parent { + my $zone = shift; + + my $potential_parent; + if ($zone =~ m/\./) { + $potential_parent = $zone; + $potential_parent =~ s/^[^.]+\.//; + } else { + $potential_parent = '.'; + } + + print "Querying DNSKEY $potential_parent\n" if $params->{'verbose'}; + my $pkt = $RES->send($potential_parent, 'DNSKEY'); + return undef unless $pkt; + return undef unless $pkt->header; + + unless ($pkt->answer) { + return undef unless $pkt->authority; + for my $rr ($pkt->authority) { + next unless ($rr->type eq 'SOA'); + + $potential_parent = $rr->name; + print "Querying DNSKEY $potential_parent\n" if $params->{'verbose'}; + $pkt = $RES->send($potential_parent, 'DNSKEY'); + return undef unless $pkt; + last; + }; + }; + + return (0, $potential_parent) unless $pkt->answer; + for my $rr ($pkt->answer) { + next unless ($rr->type eq 'DNSKEY'); + return (1, $potential_parent); + }; +} +sub get_parent_dnssec_status { + my $zone = shift; + my @result; + + while (1) { + my ($status, $parent) = has_dnskey_parent($zone); + last unless defined $status; + push @result, ($status ? "yes" : "no") . ("($parent)"); + $zone = $parent; + last if $zone eq "" || $zone eq '.'; + }; + + return join(', ', @result); +}; + +sub usage { + my $fd = shift; + my $exit = shift; + + print $fd "Usage: $PROGRAM_NAME [--dir <dir>] overview|check-dlv|check-ds|check-header zone [zone...]\n"; + print $fd " $PROGRAM_NAME --dir <dir> overview|check-dlv|check-ds|check-header\n"; + print $fd " $PROGRAM_NAME --help\n"; + exit $exit; +} + +sub what_to_check { + my $zone = shift; + my $zonefile = shift; + + my $do_dlv = 0; + my $do_ds = 0; + + open(F, "<", $zonefile) or die ("Cannot open zonefile $zonefile for $zone: $!\n"); + while (<F>) { + 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); + + 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'}, + '--dlv=s' => \$params->{'dlv'}, + '--verbose' => \$params->{'verbose'}, +) or usage(\*STDERR, 1); +usage(\*STDOUT, 0) if ($params->{'help'}); + +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; +if (scalar @ARGV) { + if (defined $params->{'dir'} && $mode ne 'check-header') { + warn "--dir option ignored" + } + %zones = map { $_ => $_} @ARGV; +} else { + 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'}; + + +if ($mode eq 'overview') { + 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 20, "-"x 15, "-"x 3, "-"x 10; + for my $zone (sort {$a cmp $b} keys %data) { + printf $format, $zone, + $data{$zone}->{'dnskey'}, + $data{$zone}->{'ds'}, + $data{$zone}->{'dlv'}, + $data{$zone}->{'parent_dnssec'}; + } + exit(0); +} elsif ($mode eq 'check-dlv' || $mode eq 'check-ds' || $mode eq 'check-header') { + 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 %zones) { + my $require = { map { $_ => 1 } @to_check }; + if ($mode eq 'check-header') { + $require = what_to_check($zone, $zones{$zone}) + } + + 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 { + if ($require->{$thiskey}) { + push @ok, "$zone ($spec)"; + } + }; + } + } + print "WARNING: ", join(", ", @warn), "\n" if (scalar @warn); + print "OK: ", join(", ", @ok), "\n" if (scalar @ok); + exit (1) if (scalar @warn); + exit (0); +} else { + die ("Invalid mode '$mode'\n"); +}; + |