nixos/acme: More features and fixes

- Allow for key reuse when domains are the only thing that
  were changed.
- Fixed systemd service failure when preliminarySelfsigned
  was set to false
This commit is contained in:
Lucas Savva 2020-09-04 23:39:22 +01:00
parent f57824c915
commit 34b5c5c1a4
No known key found for this signature in database
GPG Key ID: F9CE6D3DCDC78F2D
2 changed files with 31 additions and 15 deletions

View File

@ -77,6 +77,7 @@ let
acmeServer = if data.server != null then data.server else cfg.server; acmeServer = if data.server != null then data.server else cfg.server;
useDns = data.dnsProvider != null; useDns = data.dnsProvider != null;
destPath = "/var/lib/acme/${cert}"; destPath = "/var/lib/acme/${cert}";
selfsignedDeps = optionals (cfg.preliminarySelfsigned) [ "acme-selfsigned-${cert}.service" ];
# Minica and lego have a "feature" which replaces * with _. We need # Minica and lego have a "feature" which replaces * with _. We need
# to make this substitution to reference the output files from both programs. # to make this substitution to reference the output files from both programs.
@ -92,19 +93,17 @@ let
); );
# Create hashes for cert data directories based on configuration # Create hashes for cert data directories based on configuration
# Flags are separated to avoid collisions
hashData = with builtins; '' hashData = with builtins; ''
${data.domain} ${data.keyType} ${concatStringsSep " " data.extraLegoFlags} -
${concatStringsSep " " ( ${concatStringsSep " " data.extraLegoRunFlags} -
extraDomains ${concatStringsSep " " data.extraLegoRenewFlags} -
++ data.extraLegoFlags
++ data.extraLegoRunFlags
++ data.extraLegoRenewFlags
)}
${toString acmeServer} ${toString data.dnsProvider} ${toString acmeServer} ${toString data.dnsProvider}
${toString data.ocspMustStaple} ${toString data.ocspMustStaple} ${data.keyType}
''; '';
mkHash = with builtins; val: substring 0 20 (hashString "sha256" val); mkHash = with builtins; val: substring 0 20 (hashString "sha256" val);
certDir = mkHash hashData; certDir = mkHash hashData;
domainHash = mkHash "${concatStringsSep " " extraDomains} ${data.domain}";
othersHash = mkHash "${toString acmeServer} ${data.keyType}"; othersHash = mkHash "${toString acmeServer} ${data.keyType}";
accountDir = "/var/lib/acme/.lego/accounts/" + othersHash; accountDir = "/var/lib/acme/.lego/accounts/" + othersHash;
@ -134,12 +133,12 @@ let
); );
renewOpts = escapeShellArgs ( renewOpts = escapeShellArgs (
commonOpts commonOpts
++ [ "renew" "--reuse-key" "--days" (toString cfg.validMinDays) ] ++ [ "renew" "--reuse-key" ]
++ data.extraLegoRenewFlags ++ data.extraLegoRenewFlags
); );
in { in {
inherit accountDir; inherit accountDir selfsignedDeps;
webroot = data.webroot; webroot = data.webroot;
group = data.group; group = data.group;
@ -208,8 +207,8 @@ let
renewService = { renewService = {
description = "Renew ACME certificate for ${cert}"; description = "Renew ACME certificate for ${cert}";
after = [ "network.target" "network-online.target" "acme-selfsigned-${cert}.service" "acme-fixperms.service" ]; after = [ "network.target" "network-online.target" "acme-fixperms.service" ] ++ selfsignedDeps;
wants = [ "network-online.target" "acme-selfsigned-${cert}.service" "acme-fixperms.service" ]; wants = [ "network-online.target" "acme-fixperms.service" ] ++ selfsignedDeps;
# https://github.com/NixOS/nixpkgs/pull/81371#issuecomment-605526099 # https://github.com/NixOS/nixpkgs/pull/81371#issuecomment-605526099
wantedBy = optionals (!config.boot.isContainer) [ "multi-user.target" ]; wantedBy = optionals (!config.boot.isContainer) [ "multi-user.target" ];
@ -247,15 +246,26 @@ let
script = '' script = ''
set -euo pipefail set -euo pipefail
echo '${domainHash}' > domainhash.txt
# Check if we can renew # Check if we can renew
if [ -e 'certificates/${keyName}.key' -a -e 'certificates/${keyName}.crt' ]; then if [ -e 'certificates/${keyName}.key' -a -e 'certificates/${keyName}.crt' ]; then
lego ${renewOpts}
# When domains are updated, there's no need to do a full
# Lego run, but it's likely renew won't work if days is too low.
if [ -e certificates/domainhash.txt ] && cmp -s domainhash.txt certificates/domainhash.txt; then
lego ${renewOpts} --days ${toString cfg.validMinDays}
else
# Any number > 90 works, but this one is over 9000 ;-)
lego ${renewOpts} --days 9001
fi
# Otherwise do a full run # Otherwise do a full run
else else
lego ${runOpts} lego ${runOpts}
fi fi
mv domainhash.txt certificates/
chmod 640 certificates/* chmod 640 certificates/*
chmod -R 700 accounts/* chmod -R 700 accounts/*
@ -650,8 +660,8 @@ in {
# Create some targets which can be depended on to be "active" after cert renewals # Create some targets which can be depended on to be "active" after cert renewals
systemd.targets = mapAttrs' (cert: conf: nameValuePair "acme-finished-${cert}" { systemd.targets = mapAttrs' (cert: conf: nameValuePair "acme-finished-${cert}" {
wantedBy = [ "default.target" ]; wantedBy = [ "default.target" ];
requires = [ "acme-${cert}.service" "acme-selfsigned-${cert}.service" ]; requires = [ "acme-${cert}.service" ] ++ conf.selfsignedDeps;
after = [ "acme-${cert}.service" "acme-selfsigned-${cert}.service" ]; after = [ "acme-${cert}.service" ] ++ conf.selfsignedDeps;
}) certConfigs; }) certConfigs;
}) })
]; ];

View File

@ -297,11 +297,17 @@ in import ./make-test-python.nix ({ lib, ... }: {
check_connection(client, "slow.example.com") check_connection(client, "slow.example.com")
with subtest("Can request certificate for vhost + aliases (nginx)"): with subtest("Can request certificate for vhost + aliases (nginx)"):
# Check the key hash before and after adding an alias. It should not change.
# The previous test reverts the ed384 change
webserver.wait_for_unit("acme-finished-a.example.test.target")
keyhash_old = webserver.succeed("md5sum /var/lib/acme/a.example.test/key.pem")
switch_to(webserver, "nginx-aliases") switch_to(webserver, "nginx-aliases")
webserver.wait_for_unit("acme-finished-a.example.test.target") webserver.wait_for_unit("acme-finished-a.example.test.target")
check_issuer(webserver, "a.example.test", "pebble") check_issuer(webserver, "a.example.test", "pebble")
check_connection(client, "a.example.test") check_connection(client, "a.example.test")
check_connection(client, "b.example.test") check_connection(client, "b.example.test")
keyhash_new = webserver.succeed("md5sum /var/lib/acme/a.example.test/key.pem")
assert keyhash_old == keyhash_new
with subtest("Can request certificates for vhost + aliases (apache-httpd)"): with subtest("Can request certificates for vhost + aliases (apache-httpd)"):
switch_to(webserver, "httpd-aliases") switch_to(webserver, "httpd-aliases")