|
1 #!/usr/bin/perl -w |
|
2 |
|
3 # Copyright (C) 2012 Christian Arnold |
|
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 # Christian Arnold <arnold@schlittermann.de> |
|
19 |
|
20 use strict; |
|
21 use File::Find; |
|
22 use File::Basename; |
|
23 use Fcntl; |
|
24 use DB_File; |
|
25 use Getopt::Long; |
|
26 use IPC::Open2; |
|
27 use Date::Manip; |
|
28 use POSIX ":sys_wait_h"; |
|
29 use Pod::Usage; |
|
30 |
|
31 delete @ENV{ grep /^LC_/ => keys %ENV }; |
|
32 $ENV{LANG} = "C"; |
|
33 $ENV{LC_ALL} = "C"; |
|
34 |
|
35 sub process_file(); |
|
36 sub print_help(); |
|
37 sub print_usage(); |
|
38 sub version($$); |
|
39 |
|
40 my %ERRORS = ( |
|
41 OK => 0, |
|
42 WARNING => 1, |
|
43 CRITICAL => 2, |
|
44 UNKNOWN => 3, |
|
45 DEPENDENT => 4 |
|
46 ); |
|
47 |
|
48 my $ME = basename $0; |
|
49 my $NAME = "CERT"; |
|
50 my $VERSION = "0.5"; |
|
51 my $hash_file = "/var/tmp/" . basename($0) . ".known.db"; |
|
52 my %known; |
|
53 my %certs = (); |
|
54 my $no_print = |
|
55 "no_header,no_version,no_serial,no_validity,no_subject,no_issuer,no_pubkey,no_sigdump,no_extensions"; |
|
56 my @cmd_x509 = ( |
|
57 "openssl", "x509", "-noout", "-text", |
|
58 "-certopt", "$no_print", "-subject", "-enddate" |
|
59 ); |
|
60 my @cmd_pkcs12 = |
|
61 ("openssl", "pkcs12", "-clcerts", "-nokeys", "-nomacver", "-passin", "pass:"); |
|
62 my @cmd_pipe = ( |
|
63 "openssl", "x509", "-noout", "-text", |
|
64 "-certopt", $no_print, "-subject", "-enddate" |
|
65 ); |
|
66 |
|
67 my %opt = ( |
|
68 "init" => 0, |
|
69 "binary" => "/usr/bin/openssl", |
|
70 "directory" => "/etc", |
|
71 "signature" => "md5WithRSAEncryption", |
|
72 "warning" => "1month", |
|
73 "critical" => "1week", |
|
74 "excluded" => "", |
|
75 "debug" => 0 |
|
76 ); |
|
77 |
|
78 my ($file, $w_time, $c_time); |
|
79 |
|
80 my (@critical, @warning); |
|
81 |
|
82 MAIN: { |
|
83 |
|
84 Getopt::Long::Configure('bundling'); |
|
85 GetOptions( |
|
86 "i|init" => \$opt{init}, |
|
87 "b|binary=s" => \$opt{binary}, |
|
88 "d|directory=s" => \$opt{directory}, |
|
89 "w|warning=s" => \$opt{warning}, |
|
90 "c|critical=s" => \$opt{critical}, |
|
91 "s|signature" => \$opt{signature}, |
|
92 "e|exclude=s" => \$opt{exclude}, |
|
93 "D|debug" => \$opt{debug}, |
|
94 "h|help" => sub { pod2usage(-verbose => 1, -exitval => $ERRORS{OK}) }, |
|
95 "m|man" => sub { pod2usage(-verbose => 2, -exitval => $ERRORS{OK}) }, |
|
96 "V|version" => sub { version($ME, $VERSION); exit $ERRORS{OK}; } |
|
97 ) or pod2usage(-verbose => 1, -exitval => $ERRORS{CRITICAL}); |
|
98 |
|
99 tie(%known, DB_File => $hash_file, O_RDWR | O_CREAT, 0600) |
|
100 or die "Couldn't tie hash to file $hash_file: $!; aborting"; |
|
101 |
|
102 # initiate file-data hash |
|
103 %known = () if $opt{init}; |
|
104 |
|
105 my @directorys = split(/,/, join(',', $opt{directory})) if $opt{directory}; |
|
106 find({ wanted => \&process_file }, @directorys); |
|
107 |
|
108 # calculate the time |
|
109 $w_time = DateCalc("today", "+ $opt{warning}"); |
|
110 $c_time = DateCalc("today", "+ $opt{critical}"); |
|
111 |
|
112 # check expire date |
|
113 foreach (sort keys %certs) { |
|
114 my $enddate; |
|
115 if (@{ $certs{$_} }[1] =~ /(\w+\s+\d+\s+\d+:\d+:\d+\s+\d+)/) { |
|
116 $enddate = $1; |
|
117 } |
|
118 $enddate = ParseDate($enddate); |
|
119 unless ($enddate) { |
|
120 print "CERT CRITICAL: Can't parse enddate\n"; |
|
121 exit $ERRORS{"CRITICAL"}; |
|
122 } |
|
123 |
|
124 &Date_Cmp($enddate, $w_time) > 0 and push(@{ $certs{$_} }, "OK"), next; |
|
125 &Date_Cmp($enddate, $c_time) > 0 |
|
126 and push(@{ $certs{$_} }, "WARNING"), next; |
|
127 push(@{ $certs{$_} }, "CRITICAL"); |
|
128 } |
|
129 |
|
130 # looking for stats |
|
131 foreach (sort keys %certs) { |
|
132 if (@{ $certs{$_} }[2]) { |
|
133 if (@{ $certs{$_} }[2] eq "$opt{signature}") { |
|
134 push(@warning, |
|
135 "file: $_, CN=@{$certs{$_}}[0] Signature Algorithm: @{$certs{$_}}[2]" |
|
136 ); |
|
137 } |
|
138 } |
|
139 |
|
140 if (@{ $certs{$_} }[3] eq "WARNING") { |
|
141 push(@warning, |
|
142 "file: $_, CN=@{$certs{$_}}[0] expires @{$certs{$_}}[1]"); |
|
143 } |
|
144 elsif (@{ $certs{$_} }[3] eq "CRITICAL") { |
|
145 push(@critical, |
|
146 "file: $_, CN=@{$certs{$_}}[0] expires @{$certs{$_}}[1]"); |
|
147 } |
|
148 } |
|
149 |
|
150 # return the state |
|
151 if (@critical) { |
|
152 print "CERT CRITICAL: " . join("\n", @critical) . "\n"; |
|
153 exit $ERRORS{"CRITICAL"}; |
|
154 } |
|
155 elsif (@warning) { |
|
156 print "CERT WARNING: @warning\n"; |
|
157 exit $ERRORS{"WARNING"}; |
|
158 } |
|
159 else { |
|
160 print "CERT OK: all certificates in limit\n"; |
|
161 exit $ERRORS{"OK"}; |
|
162 } |
|
163 |
|
164 untie %known; |
|
165 |
|
166 exit; |
|
167 } |
|
168 |
|
169 sub process_file() { |
|
170 return if not -f; |
|
171 |
|
172 my $id = join " ", (stat)[7, 9]; |
|
173 my $is_certificate = 0; |
|
174 my $in_cert = 0; |
|
175 my @cert = (); |
|
176 my ($rc, $temp, $signature, $subject, $enddate); |
|
177 |
|
178 # excluded files |
|
179 my @excludes = split(/,/, join(',', $opt{exclude})) if $opt{exclude}; |
|
180 foreach my $exclude_file (@excludes) { |
|
181 if ($exclude_file eq $File::Find::name) { |
|
182 $known{$File::Find::name} = $id; |
|
183 return; |
|
184 } |
|
185 } |
|
186 |
|
187 return |
|
188 if exists $known{$File::Find::name} |
|
189 and $known{$File::Find::name} eq $id; |
|
190 |
|
191 # checking for pkcs12 certificates |
|
192 my @cmd_pkcs12_current = @cmd_pkcs12; |
|
193 push @cmd_pkcs12_current, "-in", $File::Find::name, "2>/dev/null"; |
|
194 |
|
195 my $cid = open(FILE, "@cmd_pkcs12_current |") || die "Can't fork: $!"; |
|
196 |
|
197 while (<FILE>) { |
|
198 /^$cid:error:.*/ and last; |
|
199 $temp .= $_; |
|
200 } |
|
201 close(FILE); |
|
202 |
|
203 if ($temp) { |
|
204 local (*READ, *WRITE); |
|
205 my $cid = open2(\*READ, \*WRITE, @cmd_pipe) |
|
206 or die "Can't fork: $!\n"; |
|
207 print WRITE $temp; |
|
208 close(WRITE); |
|
209 while (<READ>) { |
|
210 /Signature\sAlgorithm:\s(.*)\s+$/ and $signature = $1; |
|
211 /^subject=\s+(.*)\s+$/ and $subject = $1; |
|
212 /^notAfter=(.*)\s+$/ and $enddate = $1; |
|
213 } |
|
214 close(READ); |
|
215 |
|
216 # waiting for child processes |
|
217 do { |
|
218 $cid = waitpid(-1, WNOHANG); |
|
219 } while $cid > 0; |
|
220 |
|
221 $is_certificate = 1; |
|
222 push(@{ $certs{$File::Find::name} }, ($subject, $enddate, $signature)); |
|
223 |
|
224 $known{$File::Find::name} = $id if not($is_certificate); |
|
225 return; |
|
226 } |
|
227 |
|
228 open(FILE, $File::Find::name) or die "can't open $_: $!"; |
|
229 |
|
230 while (<FILE>) { |
|
231 |
|
232 # cheking for x509 certificates |
|
233 if ($in_cert) { |
|
234 push @cert, $_; |
|
235 if (/^-----END CERTIFICATE-----$/) { |
|
236 $in_cert = 0; |
|
237 |
|
238 # open filehandles (for read and write) |
|
239 local (*READ, *WRITE); |
|
240 my $cid = open2(\*READ, \*WRITE, @cmd_x509) |
|
241 or die "Can' fork: $!\n"; |
|
242 print WRITE @cert; |
|
243 close(WRITE); |
|
244 @cert = (); |
|
245 |
|
246 while (<READ>) { |
|
247 /Signature\sAlgorithm:\s(.*)\s+$/ and $signature = $1; |
|
248 /^subject=\s+(.*)$/ and $subject = $1; |
|
249 /^notAfter=(.*)\s+$/ and $enddate = $1; |
|
250 } |
|
251 close(READ); |
|
252 |
|
253 # waiting for child processes |
|
254 do { |
|
255 $cid = waitpid(-1, WNOHANG); |
|
256 } while $cid > 0; |
|
257 |
|
258 if ($opt{debug}) { |
|
259 print "-----\n"; |
|
260 print "$File::Find::name\n"; |
|
261 print "Signature Algorithm: $signature\n" if ($signature); |
|
262 print "subject: $subject\n" if ($subject); |
|
263 print "enddate: $enddate\n" if ($enddate); |
|
264 } |
|
265 |
|
266 push( |
|
267 @{ $certs{$File::Find::name} }, |
|
268 ($subject, $enddate, $signature) |
|
269 ); |
|
270 $is_certificate = 1; |
|
271 next; |
|
272 } |
|
273 } |
|
274 |
|
275 if (/^-----BEGIN CERTIFICATE-----$/) { |
|
276 $in_cert = 1; |
|
277 push @cert, $_; |
|
278 next; |
|
279 } |
|
280 } |
|
281 |
|
282 $known{$File::Find::name} = $id if not($is_certificate); |
|
283 close(FILE); |
|
284 } |
|
285 |
|
286 sub version($$) { |
|
287 my ($progname, $version) = @_; |
|
288 |
|
289 print <<_VERSION; |
|
290 $progname version $version |
|
291 Copyright (C) 2012 by Christian Arnold and Schlittermann internet & unix support. |
|
292 |
|
293 $ME comes with ABSOLUTELY NO WARRANTY. This is free software, |
|
294 and you are welcome to redistribute it under certain conditions. |
|
295 See the GNU General Public Licence for details. |
|
296 _VERSION |
|
297 } |
|
298 |
|
299 __END__ |
|
300 |
|
301 =head1 NAME |
|
302 |
|
303 check_chert - nagios plugin to check the expire date for openssl certificates |
|
304 |
|
305 =head1 SYNOPSIS |
|
306 |
|
307 check_cert [B<-b>|B<--binary>] |
|
308 |
|
309 check_cert [B<-d>|B<--directory>] |
|
310 |
|
311 check_cert [B<-w>|B<--warning>] |
|
312 |
|
313 check_cert [B<-c>|B<--critical>] |
|
314 |
|
315 check_cert [B<-s>|B<--signature>] |
|
316 |
|
317 check_cert [B<-e>|B<--exclude>] |
|
318 |
|
319 check_cert [B<-D>|B<--debug>] |
|
320 |
|
321 check_cert [B<-h>|B<--help>] |
|
322 |
|
323 check_cert [B<-m>|B<--man>] |
|
324 |
|
325 check_cert [B<-V>|B<--version>] |
|
326 |
|
327 =head1 OPTIONS |
|
328 |
|
329 =over |
|
330 |
|
331 =item B<-b>|B<--binary> |
|
332 |
|
333 Path to openssl binary (default: /usr/bin/openssl). |
|
334 |
|
335 =item B<-w>|B<--warning> |
|
336 |
|
337 Certificat should not be more than this time older (default: 1month). |
|
338 |
|
339 =item B<-c>|B<--critical> |
|
340 |
|
341 Certificat should not be more than this time older (default: 1week). |
|
342 |
|
343 =item B<-d>|B<--directory> |
|
344 |
|
345 Absolute directory path in which will be recursively search for certificate files (default: /etc). |
|
346 |
|
347 =item B<-s>|B<--signature> |
|
348 |
|
349 Return WARNING status if <signature algorithm> is used (default: md5WithRSAEncryption). |
|
350 |
|
351 =item B<-e>|B<--exclude> |
|
352 |
|
353 Absolute path of excluded files, use comma-separated lists for multiple files. |
|
354 |
|
355 =item B<-D>|B<--debug> |
|
356 |
|
357 Enable debug mode. |
|
358 |
|
359 =item B<-h>|B<--help> |
|
360 |
|
361 Print detailed help screen. |
|
362 |
|
363 =item B<-m>|B<--man> |
|
364 |
|
365 Print manual page. |
|
366 |
|
367 =item B<-V>|B<--version> |
|
368 |
|
369 Print version information. |
|
370 |
|
371 =back |
|
372 |
|
373 =head1 DESCRIPTION |
|
374 |
|
375 his plugin checks the expire date for openssl certificates. |
|
376 |
|
377 =head1 VERSION |
|
378 |
|
379 This man page is current for version 0.5 of B<check_cert>. |
|
380 |
|
381 =head1 AUTHOR |
|
382 |
|
383 Written by Christian Arnold L<arnold@schlittermann.de> |
|
384 |
|
385 =head1 COPYRIGHT |
|
386 |
|
387 Copyright (C) 2012 by Christian Arnold and Schlittermann internet & unix support. |
|
388 This is free software, and you are welcome to redistribute it under certain conditions. |
|
389 See the GNU General Public Licence for details. |
|
390 |
|
391 =cut |