From b535ec32984f37a0d88c8822e352d95075cabe50 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Sat, 18 Feb 2017 12:20:18 +0200 Subject: Minor improvements. * acmeman: Improve SAN comparison. New option --stage, -s. --dry-run now does just what it's supposed to do Rearrange debug levels. --- acmeman | 87 +++++++++++++++++++++++++++++------------------------------------ 1 file changed, 39 insertions(+), 48 deletions(-) diff --git a/acmeman b/acmeman index a747a47..84c492b 100755 --- a/acmeman +++ b/acmeman @@ -31,7 +31,7 @@ use Getopt::Long qw(:config gnu_getopt no_ignore_case); use POSIX qw(strftime time floor); use Data::Dumper; -our $VERSION = '1.00'; +our $VERSION = '1.01'; =head1 NAME @@ -40,7 +40,7 @@ acmeman - manages ACME certificates =head1 SYNOPSIS B -[B<-Fadn>] +[B<-Fadns>] [B<-D> I] [B<-f> I] [B<-l> B|B|B] @@ -50,10 +50,11 @@ B [B<--dry-run>] [B<--force>] [B<--layout=>B|B|B] +[B<--stage>] [B<--time-delta=>I] [I...] -B B<--setup> | B<-s> +B B<--setup> | B<-S> [B<-Fdn>] [B<-f> I] [B<-l> B|B|B] @@ -254,14 +255,17 @@ B, B, and B (for Red Hat). =item B<-n>, B<--dry-run> -With B<--setup>, don't actually write anything, just print what would -have been done. Otherwise, use LetsEncrypt staging server, instead of -production. Implies B<--debug>. - -=item B<-s>, B<--setup> +Don't modify any files, just print what would have been done. +Implies B<--debug>. + +=item B<-S>, B<--setup> Set up the B infrastructure files. +=item B<-s>, B<--stage> + +Use LetsEncrypt staging server. + =back The following options are informational: @@ -293,7 +297,7 @@ my $progname = basename($0); my $progdescr = "manages ACME certificates"; my $debug; my $dry_run; -my $acme_host = 'acme-staging.api.letsencrypt.org'; +my $acme_host = 'prod'; my %acme_endpoint = (prod => 'acme-v01.api.letsencrypt.org', staging => 'acme-staging.api.letsencrypt.org'); my $letsencrypt_root_cert_url = @@ -358,7 +362,7 @@ sub make_filename { sub prep_dir { my $dir = dirname(shift); if (! -d $dir) { - debug(2, "creating directory $dir"); + debug(3, "creating directory $dir"); my @created = make_path("$dir", { error => \my $err } ); if (@$err) { for my $diag (@$err) { @@ -390,7 +394,7 @@ sub make_csr { $req->sign(); if (exists($filename_pattern{key})) { my $filename = make_filename('key', $cn); - debug(2, "writing $filename"); + debug(3, "writing $filename"); prep_dir($filename); my $u = umask(077); $req->write_pem_pk($filename); @@ -404,7 +408,7 @@ sub save_crt { my $domain = shift; my $filename = make_filename($type, $domain); - debug(2, "writing $filename"); + debug(3, "writing $filename"); prep_dir($filename); open(my $fd, '>', $filename); @@ -426,36 +430,26 @@ sub domain_cert_expires { if (exists($exts->{subjectAltName})) { my $msg = $check_alt_names ? 'will renew' : 'use -a to trigger renewal'; - # FIXME: Crypt::OpenSSL::X509 returns extensions as strings, # instead of as ASN.1 objects. Until it is fixed, the # following naive logic is implemented to split the string into # names: - my @names = split /\.\.+/, $exts->{subjectAltName}->value(); - # First value is irrelevant - shift @names; - # Prepare sorted arrays of alternative names and requested - # names (@vh). - @names = sort @names; - my @vh = sort ($domain, @_); - # Compare them - if ($#vh != $#names) { - debug(1, "$crt: number of subject names changed; $msg"); - return 1 if $check_alt_names; - } else { - for (my $i = 0; $i <= $#names; $i++) { - if ($names[$i] ne $vh[$i]) { - debug(1, "$crt: subject names differ; $msg"); - if ($check_alt_names) { - return 1; - } else { - last; - } - } + my $blob = $exts->{subjectAltName}->value(); + my @missing; + foreach my $vh (sort { length($b) <=> length($a) } + uniq($domain, @_)) { + unless ($blob =~ s/\Q$vh\E\b//) { + push @missing, $vh; } } + if (@missing) { + debug(1, "$crt: the following SANs are missing: " + . join(', ', @missing) + ."; $msg"); + return 1 if $check_alt_names; + } } - + my $expiry = $x509->notAfter(); my $strp = DateTime::Format::Strptime->new( @@ -475,14 +469,14 @@ sub domain_cert_expires { } else { $in = "today"; } - debug(1, "$crt expires on $expiry, $in"); + debug(2, "$crt expires on $expiry, $in"); if ($now + $time_delta < $ts) { return 0; } else { - debug(1, "will renew $crt (expires on $expiry, $in)"); + debug(2, "will renew $crt (expires on $expiry, $in)"); } } else { - debug(1, "will renew $crt"); + debug(2, "will renew $crt"); } } return 1; @@ -490,7 +484,7 @@ sub domain_cert_expires { sub register_domain_certificate { my $domain = shift; - + if ($debug) { my $crt = make_filename('cert', $domain); if (-f $crt) { @@ -499,9 +493,10 @@ sub register_domain_certificate { debug(1, "issuing $crt: CN=$domain, alternatives=@_"); } } + return if $dry_run; my $acme = Protocol::ACME->new( - host => $acme_host, + host => $acme_endpoint{$acme_host}, account_key => { buffer => $account_key->get_private_key_string(), format => 'PEM' }, @@ -803,7 +798,7 @@ EOT sub coalesce { my $ref = shift; - debug(1, "coalescing virtual hosts"); + debug(2, "coalescing virtual hosts"); my $i = 0; my @vhost; foreach my $ent (sort { $a->{names}[0] cmp $b->{names}[0] } @@ -860,6 +855,7 @@ GetOptions("h" => sub { }, "debug|d+" => \$debug, "dry-run|n" => \$dry_run, + "stage|s" => sub { $acme_host = 'staging' }, "force|F" => \$force, "time-delta|D=n" => \$time_delta, "layout|l=s" => sub { @@ -868,19 +864,14 @@ GetOptions("h" => sub { unless exists $apache_layout_tab{$arg}; $apache_layout = $apache_layout_tab{$arg}; }, - "setup|s" => \$setup, + "setup|S" => \$setup, "config-file|f=s" => sub { $apache_layout = { config => $_[1] } }, "alt-names|a" => \$check_alt_names ) or exit(EX_USAGE); -if ($dry_run) { - ++$debug; - $acme_host = $acme_endpoint{staging}; -} else { - $acme_host = $acme_endpoint{prod}; -} +++$debug if $dry_run; unless (defined($apache_layout)) { while (my ($name, $layout) = each %apache_layout_tab) { -- cgit v1.2.1