#!/usr/bin/perl # -*- perl -*- =encoding utf8 =head1 NAME certbot_certs - Munin plugin to certbot (letsencrypt) certificate expiry =head1 APPLICABLE SYSTEMS Servers running certbot certificates. This script auto-discovers the current set of certificates =head1 CONFIGURATION This script requires root permissions to run, in your munin-node configuration: [certbot_certs] user root =head1 CAPABILITIES This plugin supports L =head1 LICENSE Copyright (C) 2018 J.T.Sage (jtsage@gmail.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see L. =head1 MAGIC MARKERS #%# family=manual #%# capabilities= =cut use warnings; use strict; use Munin::Plugin; use Date::Parse; use POSIX; if ( defined($ARGV[0]) && $ARGV[0] eq "autoconf" ) { if ( -e "/usr/bin/openssl" ) { print "no (Root User Required)\n"; } else { print "no (OpenSSL Not Found)\n"; } exit; } my %thecerts = get_data(); if ( defined($ARGV[0]) && $ARGV[0] eq "config" ) { # Do the config step for each set of graphs do_config(%thecerts); # If dirtyconfig is not supported, or turned off, exit here. Otherwise, continue to fetch section if ( !defined($ENV{'MUNIN_CAP_DIRTYCONFIG'}) || !$ENV{'MUNIN_CAP_DIRTYCONFIG'} ) { exit 0; } } # Do the fetch step for each set of graphs do_values(%thecerts); sub do_config { print "graph_title LetsEncrypt Certificate Expiry\n"; print "graph_args --upper-limit 90 -l 0\n"; print "graph_vlabel days\n"; print "graph_category security\n"; print "graph_info This graph shows the number of days until certificate expiry\n"; my ( %certs ) = @_; foreach my $key ( keys %certs ) { print $certs{$key}{"safename"} . ".label " . $certs{$key}{"name"} . "\n"; print $certs{$key}{"safename"} . ".info " . $certs{$key}{"info"} . "\n"; print $certs{$key}{"safename"} . ".warning 29:\n"; print $certs{$key}{"safename"} . ".critical 15:\n"; } } sub do_values { my ( %certs ) = @_; foreach my $key ( keys %certs ) { print $certs{$key}{"safename"} . ".value " . $certs{$key}{"expdays"} . "\n"; } } sub get_data { my $runtime = time(); my %cert_files; # Get Renewal Files opendir(DIR, "/etc/letsencrypt/renewal"); my @renew_files = grep(/\.conf$/,readdir(DIR)); closedir(DIR); # Each renewal file foreach my $file (@renew_files) { my @this_renew = `cat /etc/letsencrypt/renewal/$file`; my $cert_name = $file; $cert_name =~ s/\.conf//; my $safe_name = $cert_name; $safe_name =~ s/\./_/g; $cert_files{$cert_name}{"name"} = $cert_name; $cert_files{$cert_name}{"safename"} = $safe_name; # cert is listed foreach my $line ( @this_renew ) { if ( $line =~ /cert = (.+?)$/ ) { $cert_files{$cert_name}{"cert"} = $1; } } # use openssl for the info my @ssl_info = `openssl x509 -noout -in $cert_files{$cert_name}{"cert"} -text -certopt no_subject,no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux -enddate`; # parse for SAN and expiry foreach my $line ( @ssl_info ) { if ( $line =~ /notAfter=(.+?)$/ ) { $cert_files{$cert_name}{"expdate"} = $1; $cert_files{$cert_name}{"expdays"} = floor((str2time($1) - $runtime)/(60*60*24)); } if ( $line =~ /(DNS:.+?)$/ ) { my $san_line = $1; $san_line =~ s/DNS://g; $cert_files{$cert_name}{"info"} = $san_line; } } } return %cert_files; }