#!/usr/bin/env perl # # Copyright (c) 2021 Omar Polo # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # You can read the documentation for this script using # # $ perldoc renew-certs # use v5.10; use strict; use warnings; use Getopt::Std; use Time::Piece; my $auto = 0; my $conf = '/etc/gmid.conf'; my $days = 365; my $gmid = 'gmid'; my $restart = 0; my $threshold = 24 * 60 * 60; my %options = (); getopts("ac:d:g:r", \%options); foreach my $flag (keys %options) { if ($flag eq 'a') { $auto = 1; } elsif ($flag eq 'c') { $conf = $options{c}; } elsif ($flag eq 'd') { $days = int($options{d}) or exit 1; } elsif ($flag eq 'g') { $gmid = $options{g}; } elsif ($flag eq 'r') { $auto = 1; $restart = 1; } elsif ($flag eq 't') { $threshold = int($options{t}) or exit 1; } } my $now = localtime()->epoch + $threshold; my $found_one = 0; my $c = `$gmid -nn -c $conf @ARGV 2>/dev/null`; die "$gmid failed to parse $conf" if $? != 0; while ($c =~ /server \"(.*)\"/g) { my $server = $1; $c =~ /cert \"(.*)\"/gc; my $cert = $1; $c =~ /key \"(.*)\"/gc; my $key = $1; if (expired($cert)) { $found_one = 1; if ($auto) { renew($server, $cert, $key); } else { say $server; } } } if ($found_one && $restart) { my @cmd = ("pkill", "-HUP", $gmid); system(@cmd); } exit !$found_one; sub expired { my ($cert) = @_; my $exp = `openssl x509 -noout -enddate -in $cert`; die 'failed to execute openssl' if $? != 0; chomp $exp; my $d = Time::Piece->strptime($exp, "notAfter=%b %e %T %Y %Z"); return $d->epoch < $now; } sub renew { my ($hostname, $cert, $key) = @_; my @cmd = ( "openssl", "req", "-x509", "-newkey", "rsa:4096", "-out", $cert, "-keyout", $key, "-days", $days, "-nodes", "-subj", "/CN=".$hostname, ); system(@cmd) == 0 or die "system @cmd failed: $?"; } __END__ =head1 NAME B - automatically renew gmid certificates =head1 SYNOPSIS B [-ar] [-c I] [-d I] [-g I] [-t I] [-- I] =head1 DESCRIPTION B attempts to renew the certificates used by gmid if they are close to the expiration date and can optionally restart the server. It's meant to be used in a crontab(5) file. B needs at least B 1.8. The arguments are as follows: =over =item -a Automatically generate a new set of certificates. =item -c I Path to the gmid configuration. By default is F. =item -d I Number of I the newly generated certificates will be valid for; 365 by default. =item -g I Path to the gmid(1) executable. =item -r Restart B after re-generating the certificates by killing it with SIGHUP. Implies -a. =item -t I Tweak the expiring I. Certificates whose I field ends before I seconds will be considered outdated. By default is 86400, or 24 * 60 * 60, 24 hours. =item I Additional flags to be passed to gmid(1). =back =head1 EXIT STATUS The B utility exits on 0 when at least one certificate is about to expire and >0 otherwise, or if an error occurs. =head1 EXAMPLES Some examples of how to use B in a crontab(5) file follows: # automatically renew and restart gmid 0 0 * * * renew-certs -r # like the previous, but pass a custom flag to gmid 0 0 * * * renew-certs -r -- -Dcerts=/etc/ssl/ # automatically renew the certs but use a custom # command (rcctl in this case) to restart the server 0 0 * * * renew-certs -a && rcctl restart gmid # only check for expiration. `cmd' can read the names of the # servers with an expiring certificate from stdin, one per # line 0 0 * * * renew-certs | cmd =head1 SEE ALSO crontab(1) gmid(1) openssl(1) crontab(5) =cut