aboutsummaryrefslogtreecommitdiff
path: root/acmeman
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2017-02-07 16:53:02 +0100
committerSergey Poznyakoff <gray@gnu.org.ua>2017-02-07 16:57:47 +0100
commitb147b39e5cfbba272e0f172dc84c2f7570318fcc (patch)
tree3323decc80a0a7f48815c186328a4dae9994bc06 /acmeman
parente9b6f0f44f231d53758033dd319abae100736ff6 (diff)
downloadacmeman-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-xacmeman99
1 files changed, 61 insertions, 38 deletions
diff --git a/acmeman b/acmeman
index 5efffeb..6c7498d 100755
--- a/acmeman
+++ b/acmeman
@@ -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

Return to:

Send suggestions and report system problems to the System administrator.