diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2017-02-07 16:53:02 +0100 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2017-02-07 16:57:47 +0100 |
commit | b147b39e5cfbba272e0f172dc84c2f7570318fcc (patch) | |
tree | 3323decc80a0a7f48815c186328a4dae9994bc06 /acmeman | |
parent | e9b6f0f44f231d53758033dd319abae100736ff6 (diff) | |
download | acmeman-b147b39e5cfbba272e0f172dc84c2f7570318fcc.tar.gz acmeman-b147b39e5cfbba272e0f172dc84c2f7570318fcc.tar.bz2 |
Fix adding alternative names to the certificates
* .gitignore: Update.
* MANIFEST: inc/ExtUtils/AutoInstall.pm
* Makefile.PL: Add Data::Dumper
* acmeman: Optional arguments select what domains to
renew.
Improve configuration parser.
(register_domain_certificate): Autorize each alternative.
(make_csr): Fix adding alternative names.
Diffstat (limited to 'acmeman')
-rwxr-xr-x | acmeman | 99 |
1 files changed, 61 insertions, 38 deletions
@@ -1,5 +1,6 @@ #! /usr/bin/perl use strict; +use feature 'state'; use Protocol::ACME; use Protocol::ACME::Challenge::LocalFile; use Crypt::Format; @@ -14,6 +15,7 @@ use Pod::Usage; use Pod::Man; use Getopt::Long qw(:config gnu_getopt no_ignore_case); use POSIX qw(strftime time floor); +use Data::Dumper; our $VERSION = '0.90'; @@ -34,6 +36,7 @@ B<acmeman> [B<--force>] [B<--layout=>B<slackware>|B<debian>|B<rh>] [B<--time-delta=>I<N>] +[I<DOMAIN>...] B<acmeman> B<--setup> | B<-s> [B<-Fdn>] @@ -210,9 +213,17 @@ my %acme_endpoint = (prod => 'acme-v01.api.letsencrypt.org', staging => 'acme-staging.api.letsencrypt.org'); my $letsencrypt_root_cert_url = 'https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem'; -my $www_root; my $time_delta = 86400; my $force = 0; +my %select; # Hash of selected domain names + +# Variables read from the Apache configuration files +my $server_root; +my $www_root; # Root directory for ACME challenges +my @virthost; # List of virtual hosts using ACME certificates +my $filename_arg; # Name of the formal argument to the LetsEncrypServer +my %filename_pattern; # File name pattern. + # Valid keys are: cert, key and chain. my $account_key; my $challenge; @@ -249,9 +260,6 @@ sub abend { exit $code; } -my $filename_arg; -my %filename_pattern; - sub make_filename { my ($type, $arg) = @_; abend(EX_SOFTWARE, "no $type in \%filename_pattern") @@ -290,9 +298,8 @@ sub make_csr { my $cn = shift; my $req = Crypt::OpenSSL::PKCS10->new(2048); $req->set_subject("/CN=$cn"); - foreach my $name (@_) { - $req->add_ext(Crypt::OpenSSL::PKCS10::NID_subject_alt_name, $name); - } + $req->add_ext(Crypt::OpenSSL::PKCS10::NID_subject_alt_name, + join(',', map { "DNS:$_" } @_)); $req->add_ext_final(); $req->sign(); if (exists($filename_pattern{key})) { @@ -378,11 +385,12 @@ sub register_domain_certificate { $acme->register(); $acme->accept_tos(); - $acme->authz($domain); - $acme->handle_challenge($challenge); - $acme->check_challenge(); - $acme->cleanup_challenge($challenge); - + foreach my $name ($domain, @_) { + $acme->authz($name); + $acme->handle_challenge($challenge); + $acme->check_challenge(); + $acme->cleanup_challenge($challenge); + } my $csr = make_csr($domain, @_); my $cert = $acme->sign({ format => 'PEM', buffer => $csr }); my $chain = $acme->chain(); @@ -395,14 +403,15 @@ sub register_domain_certificate { }; if ($@) { if (UNIVERSAL::isa($@, 'Protocol::ACME::Exception')) { - error("$domain: can't update certificate: $@->{status}"); + error("$domain: can't renew certificate: $@->{status}"); if (exists($@->{error})) { error("$domain: $@->{error}{status} $@->{error}{detail}"); } else { error("$domain: $@->{detail} $@->{type}"); } } else { - abend(EX_SOFTWARE, $@); + error("$domain: failed to renew certificate"); + print STDERR Dumper([$@]) if $debug; } } } @@ -426,23 +435,24 @@ sub uniq { } sub examine_http_config { - my ($file, $vhosts, $state, $optional) = @_; + my ($file) = @_; + + use constant { + STATE_INITIAL => 0, # Initial state + STATE_VIRTUAL_HOST => 1, # In VirtualHost block + STATE_USE_CHALLENGE => 2, # Ditto, but challenge macro was used + STATE_MACRO_CHALLENGE => 3, # In LetsEncryptChallenge macro + STATE_MACRO_SERVER => 4 + }; + + state $state = STATE_INITIAL; + debug(2, "reading apache configuration file \"$file\""); if (open(my $fd, '<', $file)) { my $server_name; my @server_aliases; my $line; - use constant { - STATE_INITIAL => 0, # Initial state - STATE_VIRTUAL_HOST => 1, # In VirtualHost block - STATE_USE_CHALLENGE => 2, # Ditto, but challenge macro was used - STATE_MACRO_CHALLENGE => 3, # In LetsEncryptChallenge macro - STATE_MACRO_SERVER => 4 - }; - - $state = STATE_INITIAL unless defined $state; - while (<$fd>) { ++$line; chomp; @@ -450,7 +460,7 @@ sub examine_http_config { next if /^(#.*)?$/; if (/^include(optional)?\s+(.+?)\s*$/i) { debug(2, "$file:$line: state $state: Include$1 $2"); - http_include(dequote($2), $vhosts, $state, $1 ne ''); + http_include(dequote($2), $1 ne ''); next; } @@ -459,6 +469,8 @@ sub examine_http_config { $state = STATE_VIRTUAL_HOST; $server_name = undef; @server_aliases = (); + } elsif (/^ServerRoot\s+(.+)/i) { + $server_root = dequote($1); } elsif (/^<(?:(?i)Macro)\s+LetsEncryptChallenge/) { $state = STATE_MACRO_CHALLENGE; } elsif (/^<(?:(?i)Macro)\s+LetsEncryptServer\s+(.+?)\s*>/) { @@ -471,22 +483,24 @@ sub examine_http_config { if (m{</VirtualHost}i) { unshift @server_aliases, $server_name if defined $server_name; - if ($state == STATE_USE_CHALLENGE && @server_aliases) { + if ($state == STATE_USE_CHALLENGE + && @server_aliases + && (!%select || exists($select{$server_name}))) { my @names = uniq(@server_aliases); debug(1, "$file:$line: will handle @names"); - push @{$vhosts}, \@names; + push @virthost, \@names; } $state = STATE_INITIAL; } elsif (/^(?:(?i)Use)\s+LetsEncryptChallenge/) { $state = STATE_USE_CHALLENGE; # } elsif (/^Use\s+LetsEncryptSSL\s+(\S+)/i) { # debug(1, "$file: $1"); - # push @{$vhosts}, [ $1 ]; + # push @virthost, [ $1 ]; } elsif (/^(?:(?i)ServerName)\s+(\S+)/) { $server_name = dequote($1); } elsif (/^(?:(?i)ServerAlias)\s+(.+)\s*$/) { push @server_aliases, - map { /^\*\./ ? () : dequoe($_) } split /\s+/, $1; + map { /^\*\./ ? () : dequote($_) } split /\s+/, $1; } } elsif ($state == STATE_MACRO_CHALLENGE) { if (m{^</macro}i) { @@ -506,8 +520,6 @@ sub examine_http_config { } } close $fd; - } elsif ($optional) { - debug(1, "optional include file \"$file\" doesn't exist"); } else { error("can't open file \"$file\": $!"); return 0; @@ -516,11 +528,16 @@ sub examine_http_config { } sub http_include { - my ($pattern, $vhosts, $state, $optional) = @_; + my ($pattern, $optional) = @_; + $pattern = "$server_root/$pattern" unless $pattern =~ m{^/}; $pattern =~ s{/*$}{}; $pattern .= '/*' if -d $pattern; foreach my $file (glob $pattern) { - examine_http_config($file, $vhosts, $state, $optional); + if ($optional && ! -e $file) { + debug(1, "optional include file \"$file\" doesn't exist"); + next; + } + examine_http_config($file); } } # ## @@ -636,7 +653,7 @@ my %apache_layout_tab = ( my $name = basename($filename); if ($dir eq '/etc/apache2/conf-available') { chdir('/etc/apache2/conf-enabled'); - symlink "../$name", $name; + symlink "../conf-available/$name", $name; } } }, @@ -694,9 +711,15 @@ unless (defined($apache_layout)) { abend(EX_OSFILE, "no suitable apache configuration file found"); } -my @virthost; -examine_http_config($apache_layout->{config}, \@virthost) or exit(EX_OSFILE); -initial_setup if $setup; +$server_root = dirname($apache_layout->{config}); + +if ($setup) { + examine_http_config($apache_layout->{config}) or exit(EX_OSFILE); + initial_setup +} else { + @select{@ARGV} = (1) x @ARGV; + examine_http_config($apache_layout->{config}) or exit(EX_OSFILE); +} debug(1, "nothing to do") unless @virthost; # Check challenge root directory |