summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org.ua>2019-12-23 13:12:31 (GMT)
committer Sergey Poznyakoff <gray@gnu.org.ua>2019-12-23 13:12:31 (GMT)
commit438e1817a21dd79024cdf33ecc630c5bb0dc8cf0 (patch) (side-by-side diff)
tree5f4cb20e89764e5ad294a50af5f2e7d538ce3bda
parent4185f852471106b55ba430b81a1c5f09decdf16e (diff)
downloadacmeman-438e1817a21dd79024cdf33ecc630c5bb0dc8cf0.tar.gz
acmeman-438e1817a21dd79024cdf33ecc630c5bb0dc8cf0.tar.bz2
Define envvars prior to running postrenew. Other improvements.
* Changes: Update. * acmeman: Document changes. * lib/App/Acmeman.pm: Version 3.02.90. Pass information about renewed certificate to postrenew commands in environment variables. Remove created challenge files when no longer needed. * lib/App/Acmeman/Config.pm (mangle): Improve error message. Use "default" domain source by default. * lib/App/Acmeman/Source/Apache.pm: Minor changes. * lib/App/Acmeman/Source/Default.pm: New file. Source for the "default" domain source.
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Changes16
-rwxr-xr-xacmeman44
-rw-r--r--lib/App/Acmeman.pm39
-rw-r--r--lib/App/Acmeman/Config.pm11
-rw-r--r--lib/App/Acmeman/Source/Apache.pm20
-rw-r--r--lib/App/Acmeman/Source/Default.pm48
6 files changed, 159 insertions, 19 deletions
diff --git a/Changes b/Changes
index fe59d18..6714d51 100644
--- a/Changes
+++ b/Changes
@@ -1,3 +1,19 @@
+3.02.90 2019-12-23
+
+ - The following environment variables are set when running
+ postrenew commands defined in the [core] section:
+ ACMEMAN_CERTIFICATE_COUNT, ACMEMAN_CERTIFICATE_FILE,
+ ACMEMAN_DOMAIN_NAME, ACMEMAN_ALT_NAMES.
+ - The following environment variables are set when running
+ postrenew commands defined in [domain] sections:
+ ACMEMAN_CERTIFICATE_FILE, ACMEMAN_DOMAIN_NAME,
+ ACMEMAN_ALT_NAMES. See the manpage for a detailed discussion.
+ - Challenge files are removed before exiting.
+ - By default, a special domain source is used, which invokes
+ "apache" and, if unable to do so, outputs on standard error
+ a detailed description of the problem with a suggestion on
+ how to fix it.
+
3.02 2019-10-23
- Take into account ServerName if LetsEncryptReference is given.
diff --git a/acmeman b/acmeman
index 7ab46bb..92fdc55 100755
--- a/acmeman
+++ b/acmeman
@@ -384,6 +384,30 @@ server (or whatever server is using the certificates). If more than one
B<postrenew> statements are defined, they will be run in sequence, in the
same order as they appeared in the configuration file.
+I<COMMAND> inherits the environment from the B<acmeman> process, with the
+following additional variables:
+
+=over 8
+
+=item ACMEMAN_CERTIFICATE_COUNT
+
+Total count of renewed certificate files.
+
+=item ACMEMAN_CERTIFICATE_FILE
+
+Whitespace-delimited list of renewed certificate files
+
+=item ACMEMAN_DOMAIN_NAME
+
+Whitespace-delimited list of renewed domain names (CNs).
+
+=item ACMEMAN_ALT_NAMES
+
+Whitespace-delimited list of alternative DNS names from the renewed
+certificate files.
+
+=back
+
=item B<source=>I<NAME> [I<ARG>...]
Defines the source of domain names. The I<NAME> parameter identifies the
@@ -507,6 +531,26 @@ setting is used.
Run I<CMD> after successful update. If not given, the B<core.postrenew>
commands will be run.
+I<CMD> is run in the environment inherited from the calling B<acmeman>
+process with the following additional variables defined:
+
+=over 8
+
+=item ACMEMAN_CERTIFICATE_FILE
+
+Name of the certificate file that was renewed.
+
+=item ACMEMAN_DOMAIN_NAME
+
+Domain name (CN) from the renewed certificate.
+
+=item ACMEMAN_ALT_NAMES
+
+Whitespace-delimited list of the alternative DNS names extracted from the
+certificate.
+
+=back
+
=back
=head2 B<[files I<ID>]>
diff --git a/lib/App/Acmeman.pm b/lib/App/Acmeman.pm
index 76b8fb1..c6e9497 100644
--- a/lib/App/Acmeman.pm
+++ b/lib/App/Acmeman.pm
@@ -24,7 +24,7 @@ use Text::ParseWords;
use App::Acmeman::Log qw(:all :sysexits);
use feature 'state';
-our $VERSION = '3.02';
+our $VERSION = '3.02.90';
my $progdescr = "manages ACME certificates";
@@ -307,20 +307,31 @@ sub renew {
}
$self->coalesce;
- my $renewed = 0;
+ my @renewed;
foreach my $vhost ($self->selected_domains) {
if ($self->force_option || $self->domain_cert_expires($vhost)) {
if ($self->register_domain_certificate($vhost)) {
if (my $cmd = $vhost->postrenew) {
- $self->runcmd($cmd);
+ local $ENV{ACMEMAN_CERTIFICATE_FILE} =
+ $vhost->certificate_file;
+ local $ENV{ACMEMAN_DOMAIN_NAME} = $vhost;
+ local $ENV{ACMEMAN_ALT_NAMES} = join(' ', $vhost->alt);
+ $self->runcmd($cmd, $vhost);
} else {
- $renewed++;
+ push @renewed, $vhost;
}
}
}
}
- if ($renewed) {
+ if (@renewed) {
+ local $ENV{ACMEMAN_CERTIFICATE_COUNT} = @renewed;
+ local $ENV{ACMEMAN_CERTIFICATE_FILE} =
+ join(' ', map { $_->certificate_file } @renewed);
+ local $ENV{ACMEMAN_DOMAIN_NAME} =
+ join(' ', map { "$_" } @renewed);
+ local $ENV{ACMEMAN_ALT_NAMES} =
+ join(' ', map { ($_->alt) } @renewed);
if ($self->cf->is_set(qw(core postrenew))) {
foreach my $cmd ($self->cf->get(qw(core postrenew))) {
$self->runcmd($cmd);
@@ -396,6 +407,19 @@ sub debug_to_loglevel {
return $lev[$v > $#lev ? $#lev : $v];
}
+my @challenge_files;
+
+END {
+ if (@challenge_files) {
+ debug(3, "removing challenge files");
+ my $n = unlink @challenge_files;
+ unless ($n == @challenge_files) {
+ error("some challenge files were not removed",
+ prefix => 'warning');
+ }
+ }
+}
+
sub save_challenge {
my ($self,$challenge) = @_;
my $file = File::Spec->catfile($self->cf->get(qw(core rootdir)), $challenge->get_path);
@@ -403,6 +427,7 @@ sub save_challenge {
print $fh $self->acme->make_key_authorization($challenge);
close $fh;
debug(3, "wrote challenge file $file");
+ push @challenge_files, $file;
} else {
error("can't open $file for writing: $!");
die;
@@ -614,8 +639,8 @@ sub save_crt {
}
sub runcmd {
- my ($self,$cmd) = @_;
- debug(3, "running $cmd");
+ my ($self,$cmd,$domain) = @_;
+ debug(3, "running $cmd".($domain ? " for $domain" : ""));
unless ($self->dry_run_option) {
system($cmd);
if ($? == -1) {
diff --git a/lib/App/Acmeman/Config.pm b/lib/App/Acmeman/Config.pm
index da60700..7c05985 100644
--- a/lib/App/Acmeman/Config.pm
+++ b/lib/App/Acmeman/Config.pm
@@ -85,14 +85,15 @@ sub mangle {
}
}
- if (my @source = $self->get(qw(core source))) {
- $self->unset(qw(core source));
- foreach my $s (@source) {
+ if (my $source_node = $self->getnode(qw(core source))) {
+ $self->unset(qw(core source));
+ foreach my $s ($source_node->value) {
my ($name, @args) = quotewords('\s+', 0, $s);
my $pack = 'App::Acmeman::Source::' . ucfirst($name);
my $obj = eval "use $pack; new $pack(\@args);";
if ($@) {
- $self->error($@);
+ $self->error("error loading source module $name: $@",
+ locus => $source_node->locus);
++$err;
next;
}
@@ -122,7 +123,7 @@ __DATA__
rootdir = STRING :default=/var/www/acme
files = STRING
time-delta = NUMBER :default=86400
- source = STRING :default=apache :array
+ source = STRING :default=default :array
check-alt-names = BOOL :default=0
check-dns = BOOL :default=1
my-ip = STRING :array
diff --git a/lib/App/Acmeman/Source/Apache.pm b/lib/App/Acmeman/Source/Apache.pm
index 58ba39e..ecde285 100644
--- a/lib/App/Acmeman/Source/Apache.pm
+++ b/lib/App/Acmeman/Source/Apache.pm
@@ -168,15 +168,21 @@ sub setup {
my $www_root = $self->get(qw(core rootdir));
debug(2, "writing $filename");
unless ($args{dry_run}) {
- unless ($self->mkpath($self->layout->incdir())) {
- return 0;
+ my $challenge_dir = "$www_root/.well-known/acme-challenge";
+ my $acme_dir = "/etc/ssl/acme";
+
+ foreach my $dir ($self->layout->incdir(), $challenge_dir, $acme_dir) {
+ unless ($self->mkpath($dir)) {
+ return 0;
+ }
}
+
open(my $fd, '>', $filename)
or croak "can't open \"$filename\" for writing: $!";
print $fd <<EOT;
<Macro LetsEncryptChallenge>
- Alias /.well-known/acme-challenge $www_root/.well-known/acme-challenge
- <Directory $www_root/.well-known/acme-challenge>
+ Alias /.well-known/acme-challenge $challenge_dir
+ <Directory $challenge_dir>
Options None
Require all granted
</Directory>
@@ -196,9 +202,9 @@ sub setup {
SSLProtocol all -SSLv2 -SSLv3
SSLHonorCipherOrder on
SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:!DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
- SSLCertificateFile /etc/ssl/acme/\$domain/cert.pem
- SSLCertificateKeyFile /etc/ssl/acme/\$domain/privkey.pem
- SSLCACertificateFile /etc/ssl/acme/lets-encrypt-x3-cross-signed.pem
+ SSLCertificateFile $acme_dir/\$domain/cert.pem
+ SSLCertificateKeyFile $acme_dir/\$domain/privkey.pem
+ SSLCACertificateFile $acme_dir/lets-encrypt-x3-cross-signed.pem
</Macro>
<Macro LetsEncryptServer \$domain>
diff --git a/lib/App/Acmeman/Source/Default.pm b/lib/App/Acmeman/Source/Default.pm
new file mode 100644
index 0000000..9f9bd86
--- a/dev/null
+++ b/lib/App/Acmeman/Source/Default.pm
@@ -0,0 +1,48 @@
+package App::Acmeman::Source::Default;
+use strict;
+use warnings;
+use parent 'App::Acmeman::Source';
+
+sub new {
+ my $self;
+ shift; # Skip class name.
+ eval {
+ require App::Acmeman::Source::Apache;
+ $self = new App::Acmeman::Source::Apache(@_);
+ };
+ if ($@) {
+ (my $s = $@) =~ s{ at /.+$}{};
+ chomp($s);
+ die <<EOT;
+No valid domain source configured.
+
+You are seeing this error because acmeman was unable to load the default
+domain source module.
+
+The default domain source "apache" scans Apache configuration files and
+extracts names listed in ServerName and ServerAlias directives which have
+LetsEncrypt certificates configured.
+
+The source module couldn't be loaded because of the following error:
+
+"$s"
+
+If you are going to use the "apache" source, fix this error and retry.
+Otherwise, please create the /etc/acmeman.conf configuration file, and
+configure another domain source, for example:
+
+ [core]
+ source = file DOMAINFILE
+
+Please, see acmeman(1) (or run "acmeman --help") for a detailed discussion
+of available domain sources.
+
+EOT
+;
+ }
+ return $self;
+}
+
+1;
+
+

Return to:

Send suggestions and report system problems to the System administrator.