|         |      1 #! /usr/bin/perl -w | 
|         |      2  | 
|         |      3 #    Copyright (C) 2011 Matthias Förste | 
|         |      4 # | 
|         |      5 #    This program is free software: you can redistribute it and/or modify | 
|         |      6 #    it under the terms of the GNU General Public License as published by | 
|         |      7 #    the Free Software Foundation, either version 3 of the License, or | 
|         |      8 #    (at your option) any later version. | 
|         |      9 # | 
|         |     10 #    This program is distributed in the hope that it will be useful, | 
|         |     11 #    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|         |     12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|         |     13 #    GNU General Public License for more details. | 
|         |     14 # | 
|         |     15 #    You should have received a copy of the GNU General Public License | 
|         |     16 #    along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
|         |     17 # | 
|         |     18 #    Matthias Förste <foerste@schlittermann.de> | 
|         |     19  | 
|         |     20 use strict; | 
|         |     21 use warnings; | 
|         |     22  | 
|         |     23 use File::Basename qw(basename); | 
|         |     24 use Getopt::Long; | 
|         |     25 use Net::DNS; | 
|         |     26  | 
|         |     27 # for rrsig handling | 
|         |     28 require Net::DNS::SEC; | 
|         |     29 use Pod::Usage; | 
|         |     30  | 
|         |     31 my $ME      = basename $0; | 
|         |     32 my $VERSION = "0.1"; | 
|         |     33  | 
|         |     34 my $opt = { | 
|         |     35     'index-host' => '84.19.194.5', | 
|         |     36     'index-zone' => 'idx.net.schlittermann.de', | 
|         |     37     warning      => 12, | 
|         |     38     critical     => 72 | 
|         |     39 }; | 
|         |     40  | 
|         |     41 my $rv = { | 
|         |     42     OK       => 0, | 
|         |     43     WARNING  => 1, | 
|         |     44     CRITICAL => 2, | 
|         |     45     UNKNOWN  => 3 | 
|         |     46 }; | 
|         |     47  | 
|         |     48 Getopt::Long::Configure('no_ignore_case_always'); | 
|         |     49 GetOptions( | 
|         |     50     "w|warning=i"    => \$opt->{warning}, | 
|         |     51     "c|critical=i"   => \$opt->{critical}, | 
|         |     52     "H|index-host=s" => \$opt->{'index-host'}, | 
|         |     53     "Z|index-zone=s" => \$opt->{'index-zone'}, | 
|         |     54     "h|help" => sub { pod2usage( -verbose => 1, -exitval => $rv->{OK} ) }, | 
|         |     55     "m|man" => sub { pod2usage( -verbose => 2, -exitval => $rv->{OK} ) }, | 
|         |     56     "V|version" => sub { version( $ME, $VERSION ); exit $rv->{OK}; } | 
|         |     57 ) or pod2usage( -verbose => 1, -exitval => $rv->{CRITICAL} ); | 
|         |     58  | 
|         |     59 my $rc   = 'OK'; | 
|         |     60 my $res  = Net::DNS::Resolver->new; | 
|         |     61 my $ires = Net::DNS::Resolver->new( nameservers => [ $opt->{'index-host'} ] ); | 
|         |     62  | 
|         |     63 my ( $r, @a ); | 
|         |     64 unless ( defined( $r = $ires->query( $opt->{'index-zone'}, 'txt' ) ) | 
|         |     65     && ( @a = $r->answer ) ) | 
|         |     66 { | 
|         |     67  | 
|         |     68     print "No zones found"; | 
|         |     69     exit $rv->{UNKNOWN}; | 
|         |     70  | 
|         |     71 } | 
|         |     72  | 
|         |     73 my $now = time; | 
|         |     74 my $dates; | 
|         |     75 for (qw(critical warning)) { | 
|         |     76     my @d = gmtime $opt->{$_} * 3600; | 
|         |     77     $dates->{$_} = sprintf "%04d" . "%02d" x 5, $d[5] + 1900, $d[4] + 1, | 
|         |     78       reverse @d[ 0 .. 3 ]; | 
|         |     79 } | 
|         |     80  | 
|         |     81 my $counter; | 
|         |     82 @{$counter}{qw(critical warning ok total)} = (0) x 4; | 
|         |     83  | 
|         |     84 for my $txt (@a) { | 
|         |     85  | 
|         |     86     next unless $txt->rdatastr =~ /^"ZONE::(.*)::sec-on"$/; | 
|         |     87     my $z = $1; | 
|         |     88  | 
|         |     89     my ( $r, @a ); | 
|         |     90     unless ( defined( $r = $res->query( $z, 'rrsig' ) ) | 
|         |     91         && ( @a = $r->answer ) ) | 
|         |     92     { | 
|         |     93  | 
|         |     94         print "No RRSIGs found for zone '$z'"; | 
|         |     95         exit $rv->{UNKNOWN}; | 
|         |     96  | 
|         |     97     } | 
|         |     98  | 
|         |     99     for my $rrsig (@a) { | 
|         |    100  | 
|         |    101         $counter->{total}++; | 
|         |    102         my $e = $rrsig->sigexpiration; | 
|         |    103  | 
|         |    104         if ( $e < $dates->{critical} ) { | 
|         |    105  | 
|         |    106             $counter->{critical}++; | 
|         |    107             $rc = 'CRITICAL'; | 
|         |    108  | 
|         |    109         } | 
|         |    110         elsif ( $e < $dates->{warning} ) { | 
|         |    111  | 
|         |    112             $counter->{warning}++; | 
|         |    113             $rc = 'WARNING' unless $rv->{$rc} > $rv->{WARNING}; | 
|         |    114  | 
|         |    115         } | 
|         |    116         else { | 
|         |    117  | 
|         |    118             $counter->{ok}++; | 
|         |    119             next; | 
|         |    120         } | 
|         |    121  | 
|         |    122         warn "Signature for ", $rrsig->typecovered, | 
|         |    123           " Record(s) in zone '$z' expires at ", | 
|         |    124           ( join '.', reverse unpack 'A4A2A2', $e ), " ($e)\n"; | 
|         |    125  | 
|         |    126     } | 
|         |    127  | 
|         |    128 } | 
|         |    129  | 
|         |    130 print "DNSSEC $rc: ", $counter->{critical}, " critical, ", $counter->{warning}, | 
|         |    131   " warning, ", $counter->{ok}, " ok, ", $counter->{total}, " total.\n"; | 
|         |    132 exit $rv->{$rc}; | 
|         |    133  | 
|         |    134 sub version($$) { | 
|         |    135     my ( $progname, $version ) = @_; | 
|         |    136  | 
|         |    137     print <<_VERSION; | 
|         |    138 $progname version $version | 
|         |    139 Copyright (C) 2011 by Matthias Förste and Schlittermann internet & unix support. | 
|         |    140  | 
|         |    141 $ME comes with ABSOLUTELY NO WARRANTY. This is free software, | 
|         |    142 and you are welcome to redistribute it under certain conditions. | 
|         |    143 See the GNU General Public Licence for details. | 
|         |    144 _VERSION | 
|         |    145 } | 
|         |    146  | 
|         |    147 __END__ | 
|         |    148  | 
|         |    149 =head1 NAME | 
|         |    150  | 
|         |    151 check_dnssec - nagios plugin to warn about rrsig expiry | 
|         |    152  | 
|         |    153 =head1 SYNOPSIS | 
|         |    154  | 
|         |    155 check_dnssec [-H|--index-host string] | 
|         |    156           [-Z|--index-zone string] | 
|         |    157           [-w|--warning int] | 
|         |    158           [-c|--critical int] | 
|         |    159           [-h|--help] | 
|         |    160           [-m|--man] | 
|         |    161           [-V|--version] | 
|         |    162  | 
|         |    163 =head1 OPTIONS | 
|         |    164  | 
|         |    165 =over | 
|         |    166  | 
|         |    167 =item B<-H>|B<--index-host> I<string> | 
|         |    168  | 
|         |    169 Hostname or IP of a DNS Server knowing our index zone. | 
|         |    170  | 
|         |    171 =item B<-Z>|B<--index-zone> I<string> | 
|         |    172  | 
|         |    173 Name of a zone listing our DNSSEC enabled zone (and other records). | 
|         |    174  | 
|         |    175 =item B<-w>|B<--warning> I<int> | 
|         |    176  | 
|         |    177 Return with warning status if any RRSIG Record in any of the above zones | 
|         |    178 expires in less than that many hours. | 
|         |    179  | 
|         |    180 =item B<-c>|B<--critical> I<int> | 
|         |    181  | 
|         |    182 Return with critical status if any RRSIG Record in any of the above zones | 
|         |    183 expires in less than that many hours. | 
|         |    184  | 
|         |    185 =item B<-h>|B<--help> | 
|         |    186  | 
|         |    187 Print detailed help screen. | 
|         |    188  | 
|         |    189 =item B<-m>|B<--man> | 
|         |    190  | 
|         |    191 Print manual page. | 
|         |    192  | 
|         |    193 =item B<-V>|B<--version> | 
|         |    194  | 
|         |    195 Print version information. | 
|         |    196  | 
|         |    197 =back | 
|         |    198  | 
|         |    199 =head1 DESCRIPTION | 
|         |    200  | 
|         |    201 This plugin reads a list of DNSSEC enabled zones from our index zone and checks | 
|         |    202 for expiry of any of the RRSIG Records. | 
|         |    203  | 
|         |    204 =head1 AUTHOR | 
|         |    205  | 
|         |    206 Written by Matthias Förste L<foerste@schlittermann.de> | 
|         |    207  | 
|         |    208 =head1 COPYRIGHT | 
|         |    209  | 
|         |    210 Copyright (C) 2011 by Matthias Förste and Schlittermann internet & unix | 
|         |    211 support.  This is free software, and you are welcome to redistribute it under | 
|         |    212 certain conditions.  See the GNU General Public Licence for details. | 
|         |    213  | 
|         |    214 =cut |