1349863e82f46ea1446d75fe6ada844b2819bbab
[mirror/dsa-nagios.git] / dsa-nagios-checks / checks / dsa-check-zone-rrsig-expiration
1 #!/usr/bin/perl
2
3 # downloaded from http://dns.measurement-factory.com/tools/nagios-plugins/check_zone_rrsig_expiration.html
4 # on 2010-02-07 by Peter Palfrader
5
6 # $Id: check_zone_rrsig_expiration,v 1.7 2008/11/25 01:36:36 wessels Exp $
7 #
8 # check_zone_rrsig_expiration
9 #
10 # nagios plugin to check expiration times of RRSIG records.  Reminds
11 # you if its time to re-sign your zone.
12
13 # Copyright (c) 2008, The Measurement Factory, Inc. All rights reserved.
14
15 # Redistribution and use in source and binary forms, with or without
16 # modification, are permitted provided that the following conditions
17 # are met:
18
19 # Redistributions of source code must retain the above copyright
20 # notice, this list of conditions and the following disclaimer.
21 # Redistributions in binary form must reproduce the above copyright
22 # notice, this list of conditions and the following disclaimer in the
23 # documentation and/or other materials provided with the distribution.
24 # Neither the name of The Measurement Factory nor the names of its
25 # contributors may be used to endorse or promote products derived
26 # from this software without specific prior written permission.
27
28 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
31 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
32 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
33 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
34 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
35 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
36 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
38 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39 # POSSIBILITY OF SUCH DAMAGE.
40
41 # Copyright (c) 2010 Peter Palfrader <peter@palfrader.org>
42
43
44 # usage
45 #
46 # define command {
47 #   command_name    check-zone-rrsig
48 #   command_line    /usr/local/libexec/nagios-local/check_zone_rrsig -Z $HOSTADDRESS$
49 # }
50
51 # define service {
52 #   name                dns-rrsig-service
53 #   check_command       check-zone-rrsig
54 #   ...
55 # }
56
57 # define host {
58 #   use dns-zone
59 #   host_name zone.example.com
60 #   alias ZONE example.com
61 # }
62
63 # define service {
64 #   use dns-rrsig-service
65 #   host_name zone.example.com
66 # }
67
68 use warnings;
69 use strict;
70
71 use Getopt::Std;
72 use Net::DNS::Resolver;
73 use Time::HiRes qw ( gettimeofday tv_interval);
74 use Time::Local;
75 use List::Util qw ( shuffle );
76
77 my %opts = (t=>30);
78 getopts('hZ:dt:', \%opts);
79 usage() unless $opts{Z};
80 usage() if $opts{h};
81 my $zone = $opts{Z};
82 $zone =~ s/^zone\.//;
83
84 my $data;
85 my $start;
86 my $stop;
87 my $CRIT_DAYS = 3;
88 my $WARN_DAYS = 7;
89
90 my @refs = qw (
91 a.root-servers.net
92 b.root-servers.net
93 c.root-servers.net
94 d.root-servers.net
95 e.root-servers.net
96 f.root-servers.net
97 g.root-servers.net
98 h.root-servers.net
99 i.root-servers.net
100 j.root-servers.net
101 k.root-servers.net
102 l.root-servers.net
103 m.root-servers.net
104 );
105
106 $start = [gettimeofday()];
107 do_recursion();
108 do_queries();
109 $stop = [gettimeofday()];
110 do_analyze();
111
112 sub do_recursion {
113         my $done = 0;
114         my $res = Net::DNS::Resolver->new;
115         do {
116                 print STDERR "\nRECURSE\n" if $opts{d};
117                 my $pkt;
118                 foreach my $ns (shuffle @refs) {
119                         print STDERR "sending query for $zone RRSIG to $ns\n" if $opts{d};
120                         $res->nameserver($ns);
121                         $res->udp_timeout($opts{t});
122                         $res->udppacketsize(4096);
123                         $pkt = $res->send($zone, 'RRSIG');
124                         last if $pkt;
125                 }
126                 critical("No response to seed query") unless $pkt;
127                 critical($pkt->header->rcode . " from " . $pkt->answerfrom)
128                         unless ($pkt->header->rcode eq 'NOERROR');
129                 @refs = ();
130                 foreach my $rr ($pkt->authority) {
131                         print STDERR $rr->string, "\n" if $opts{d};
132                         push (@refs, $rr->nsdname);
133                         next unless lc($rr->name) eq lc($zone);
134                         add_nslist_to_data($pkt);
135                         $done = 1;
136                 }
137         } while (! $done);
138 }
139
140
141 sub do_queries {
142         my $n;
143         do {
144                 $n = 0;
145                 foreach my $ns (keys %$data) {
146                         next if $data->{$ns}->{done};
147                         print STDERR "\nQUERY $ns\n" if $opts{d};
148
149                         my $pkt = send_query($zone, 'RRSIG', $ns);
150                         add_nslist_to_data($pkt);
151                         $data->{$ns}->{queries}->{RRSIG} = $pkt;
152
153                         print STDERR "done with $ns\n" if $opts{d};
154                         $data->{$ns}->{done} = 1;
155                         $n++;
156                 }
157         } while ($n);
158 }
159
160 sub do_analyze {
161         my $nscount = 0;
162         my $NOW = time;
163         my %MAX_EXP_BY_TYPE;
164         foreach my $ns (keys %$data) {
165                 print STDERR "\nANALYZE $ns\n" if $opts{d};
166                 my $pkt = $data->{$ns}->{queries}->{RRSIG};
167                 critical("No response from $ns") unless $pkt;
168                 print STDERR $pkt->string if $opts{d};
169                 critical($pkt->header->rcode . " from $ns")
170                         unless ($pkt->header->rcode eq 'NOERROR');
171                 critical("$ns is lame") unless $pkt->header->ancount;
172                 foreach my $rr ($pkt->answer) {
173                         next unless $rr->type eq 'RRSIG';
174                         my $exp = sigrr_exp_epoch($rr);
175                         my $T = $rr->typecovered;
176                         if (!defined($MAX_EXP_BY_TYPE{$T}->{exp}) || $exp > $MAX_EXP_BY_TYPE{$T}->{exp}) {
177                                 $MAX_EXP_BY_TYPE{$T}->{exp} = $exp;
178                                 $MAX_EXP_BY_TYPE{$T}->{ns} = $ns;
179                         }
180                 }
181                 $nscount++;
182         }
183         warning("No nameservers found.  Is '$zone' a zone?") if ($nscount < 1);
184         warning("No RRSIGs found") unless %MAX_EXP_BY_TYPE;
185         my $min_exp = undef;
186         my $min_ns = undef;
187         my $min_type = undef;
188         foreach my $T (keys %MAX_EXP_BY_TYPE) {
189                 printf STDERR ("%s RRSIG expires in %.1f days\n", $T, ($MAX_EXP_BY_TYPE{$T}->{exp}-$NOW)/86400) if $opts{d};
190                 if (!defined($min_exp) || $MAX_EXP_BY_TYPE{$T}->{exp} < $min_exp) {
191                         $min_exp = $MAX_EXP_BY_TYPE{$T}->{exp};
192                         $min_ns = $MAX_EXP_BY_TYPE{$T}->{ns};
193                         $min_type = $T;
194                 }
195         }
196         critical("$min_ns has expired RRSIGs") if ($min_exp < $NOW);
197         if ($min_exp - $NOW < ($CRIT_DAYS*86400)) {
198                 my $ND = sprintf "%3.1f days", ($min_exp-$NOW)/86400;
199                 critical("$min_type RRSIG expires in $ND at $min_ns")
200         }
201         if ($min_exp - $NOW < ($WARN_DAYS*86400)) {
202                 my $ND = sprintf "%3.1f days", ($min_exp-$NOW)/86400;
203                 warning("$min_type RRSIG expires in $ND at $min_ns")
204         }
205         success("No RRSIGs expiring in the next $WARN_DAYS days");
206 }
207
208 sub sigrr_exp_epoch {
209         my $rr = shift;
210         die unless $rr->type eq 'RRSIG';
211         my $exp = $rr->sigexpiration;
212         die "bad exp time '$exp'"
213                 unless $exp =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/;
214         my $exp_epoch = timegm($6,$5,$4,$3,$2-1,$1);
215         return $exp_epoch;
216 }
217
218 sub add_nslist_to_data {
219         my $pkt = shift;
220         foreach my $ns (get_nslist($pkt)) {
221                 next if defined $data->{$ns}->{done};
222                 print STDERR "adding NS $ns\n" if $opts{d};
223                 $data->{$ns}->{done} |= 0;
224         }
225 }
226
227 sub success {
228         output('OK', shift);
229         exit(0);
230 }
231
232 sub warning {
233         output('WARNING', shift);
234         exit(1);
235 }
236
237 sub critical {
238         output('CRITICAL', shift);
239         exit(2);
240 }
241
242 sub output {
243         my $state = shift;
244         my $msg = shift;
245         $stop = [gettimeofday()] unless $stop;
246         my $latency = tv_interval($start, $stop);
247         printf "ZONE %s: %s; (%.2fs) |time=%.6fs;;;0.000000\n",
248                 $state,
249                 $msg,
250                 $latency,
251                 $latency;
252 }
253
254 sub usage {
255         print STDERR "usage: $0 [-d] [-t=<timeout>] -Z zone\n";
256         exit 3;
257 }
258
259 sub send_query {
260         my $qname = shift;
261         my $qtype = shift;
262         my $server = shift;
263         my $res = Net::DNS::Resolver->new;
264         $res->nameserver($server) if $server;
265         $res->udp_timeout($opts{t});
266         $res->retry(2);
267         $res->udppacketsize(4096);
268         my $pkt = $res->send($qname, $qtype);
269         unless ($pkt) {
270                 $res->usevc(1);
271                 $res->tcp_timeout($opts{t});
272                 $pkt = $res->send($qname, $qtype);
273         }
274         return $pkt;
275 }
276
277 sub get_nslist {
278         my $pkt = shift;
279         return () unless $pkt;
280         return () unless $pkt->authority;
281         my @nslist;
282         foreach my $rr ($pkt->authority) {
283                 next unless ($rr->type eq 'NS');
284                 next unless ($rr->name eq $zone);
285                 push(@nslist, lc($rr->nsdname));
286         }
287         return @nslist;
288 }