diff options
| -rwxr-xr-x | renew.pl | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/renew.pl b/renew.pl new file mode 100755 index 0000000..2cdb8e8 --- /dev/null +++ b/renew.pl @@ -0,0 +1,120 @@ +#!/usr/bin/perl +package SPIN::LE; + +use strict; +use warnings; +use autodie qw(:all); +use Date::Parse; +use LWP::UserAgent; + +{ + our @restart_services_cmd = qw(sudo service httpd graceful); + our @openssl_bin = qw(/usr/bin/openssl); + our @acme_tiny_bin = qw(/usr/bin/python acme-tiny/acme_tiny.py); + our $account_key_path = 'data/account.key'; + our $challenges_dir = 'data/challenges/'; + our $days_left_limit = 14; + our $csr_dir = 'data'; + + require 'config.pm' if -e 'config.pm'; + + sub get_intermediate_cert_url { + my $crt_fn = shift; + open my $openssl, '-|', @openssl_bin, 'x509', '-in', $crt_fn, '-text', '-noout'; + while(my $line = <$openssl>) { + return $1 if $line =~ m|^\s*CA Issuers - URI:(.*?)\s*$|; + } + die("Cert URL not found"); + } + + sub get_intermediate_cert { + my $crt_fn = shift; + my $pem_fn = shift; + my $der_fn = "$pem_fn.tmp"; + my $url = get_intermediate_cert_url($crt_fn); + my $ua = LWP::UserAgent->new; + my $response = $ua->mirror($url, $der_fn); + die("Failed to download $url\n") if !($response->is_success); + system(@openssl_bin, 'x509', '-inform', 'der', '-in', $der_fn, '-out', $pem_fn); + unlink($der_fn); + } + + sub get_cert { + my ($csr_fn, $crt_fn, $int_crt_fn) = @_; + open my $acme_tiny, '-|', @acme_tiny_bin, + '--account-key', $account_key_path, '--csr', $csr_fn, + '--acme-dir', $challenges_dir; + my $cert_start = 0; + my $cert_end = 0; + my $lines = ''; + while (my $line = <$acme_tiny>) { + print $line; + $cert_start = 1 if $line =~ /^-----BEGIN CERTIFICATE-----$/; + $cert_end = 1 if $line =~ /^-----END CERTIFICATE-----$/; + $lines .= $line; + } + if ($cert_start && $cert_end) { + my $tmp_fn = "$crt_fn.tmp"; + my $int_tmp_fn = "$int_crt_fn.tmp"; + open my $crt, '>', $tmp_fn; + print $crt $lines or die("Write failed: $!\n"); + close $crt; + get_intermediate_cert($tmp_fn, $int_tmp_fn); + rename $tmp_fn,$crt_fn; + rename $int_tmp_fn,$int_crt_fn; + } else { + die("Output doesn't look like a certificate\n"); + } + } + + sub renew { + my %fmap = @_; + my $certs_updated = 0; + for my $csr_fn (keys %fmap) { + my $crt_fn = $fmap{$csr_fn}->[0]; + my $int_crt_fn = $fmap{$csr_fn}->[1]; + if (!-e $crt_fn) { + print "Getting new certificate $crt_fn\n"; + get_cert($csr_fn, $crt_fn, $int_crt_fn); + next; + } + open my $openssl, '-|', @openssl_bin, 'x509', '-enddate', '-noout', '-in', $crt_fn; + my $ret = <$openssl>; + if ($ret =~ /^notAfter=(.*)$/) { + my $date_str = $1; + my $time = str2time($date_str); + if (defined $time) { + if ($time - time < $days_left_limit*24*60*60) { + my $days = (($time - time)/60/60/24)|0; + print "Certificate $crt_fn "; + if ($days >= 1) { print "will expire in $days days" } + elsif ($days == 0) { print "will expire today" } + elsif ($days == -1) { print "expired yesterday" } + else { printf "expired %d days ago", -$days }; + print "\nRenewing...\n"; + get_cert($csr_fn, $crt_fn, $int_crt_fn); + $certs_updated++; + } + } else { + warn "Failed parsing expiration date for $crt_fn: openssl returned $ret\n"; + } + } else { + warn "Failed getting expiration date for $crt_fn: openssl returned $ret\n"; + } + } + return $certs_updated; + } + + sub run { + opendir(my $dh, $csr_dir); + my %fmap = + map { my $base = $_; $base =~ s/\.csr$//; $_ => [ "$base.crt", "$base-int.crt" ] } + map { "$csr_dir/$_" } + grep { /\.csr$/ } + readdir $dh; + + system(@restart_services_cmd) if renew(%fmap); + } +} + +__PACKAGE__->run() unless caller(); |
