check_cert.pl
changeset 4 fe368de5cf81
parent 1 acac1ec01fd8
child 5 0aa961b42ea7
equal deleted inserted replaced
1:acac1ec01fd8 4:fe368de5cf81
       
     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