From e70d293b6b5f236d69d559ccccdafba19c6d29c3 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sun, 19 Nov 2017 16:41:28 +0100 Subject: [PATCH 1/2] nixos/acme: Allow for time window between cert issue and activation --- nixos/modules/security/acme.nix | 65 +++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix index fb011019f7f5..df0b4986eb82 100644 --- a/nixos/modules/security/acme.nix +++ b/nixos/modules/security/acme.nix @@ -57,9 +57,11 @@ let default = ""; example = "systemctl reload nginx.service"; description = '' - Commands to run after certificates are re-issued. Typically + Commands to run after new certificates go live. Typically the web server and other servers using certificates need to be reloaded. + + Executed in the same directory with the new certificate. ''; }; @@ -77,6 +79,27 @@ let ''; }; + activationDelay = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Systemd time span expression to delay copying new certificates to main + state directory. See systemd.time + 7. + ''; + }; + + preDelay = mkOption { + type = types.lines; + default = ""; + description = '' + Commands to run after certificates are re-issued but before they are + activated. Typically the new certificate is published to DNS. + + Executed in the same directory with the new certificate. + ''; + }; + extraDomains = mkOption { type = types.attrsOf (types.nullOr types.str); default = {}; @@ -186,14 +209,14 @@ in certToServices = cert: data: let domain = if data.domain != null then data.domain else cert; - cpath = "${cfg.directory}/${cert}"; + cpath = lpath + optionalString (data.activationDelay != null) ".staging"; + lpath = "${cfg.directory}/${cert}"; rights = if data.allowKeysForGroup then "750" else "700"; cmdline = [ "-v" "-d" domain "--default_root" data.webroot "--valid_min" cfg.validMin ] ++ optionals (data.email != null) [ "--email" data.email ] ++ concatMap (p: [ "-f" p ]) data.plugins ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains) - ++ (if cfg.production then [] - else ["--server" "https://acme-staging.api.letsencrypt.org/directory"]); + ++ optionals (!cfg.production) ["--server" "https://acme-staging.api.letsencrypt.org/directory"]; acmeService = { description = "Renew ACME Certificate for ${cert}"; after = [ "network.target" "network-online.target" ]; @@ -206,7 +229,7 @@ in Group = data.group; PrivateTmp = true; }; - path = [ pkgs.simp_le ]; + path = with pkgs; [ simp_le systemd ]; preStart = '' mkdir -p '${cfg.directory}' chown 'root:root' '${cfg.directory}' @@ -229,15 +252,36 @@ in exit "$EXITCODE" ''; postStop = '' + cd '${cpath}' + if [ -e /tmp/lastExitCode ] && [ "$(cat /tmp/lastExitCode)" = "0" ]; then - echo "Executing postRun hook..." - ${data.postRun} + ${if data.activationDelay != null then '' + + ${data.preDelay} + + if [ -d '${lpath}' ]; then + systemd-run --no-block --on-active='${data.activationDelay}' --unit acme-setlive-${cert}.service + else + systemctl --wait start acme-setlive-${cert}.service + fi + '' else data.postRun} fi ''; before = [ "acme-certificates.target" ]; wantedBy = [ "acme-certificates.target" ]; }; + delayService = { + description = "Set certificate for ${cert} live"; + path = with pkgs; [ rsync ]; + serviceConfig = { + Type = "oneshot"; + }; + script = '' + rsync -a --delete-after '${cpath}/' '${lpath}' + ''; + postStop = data.postRun; + }; selfsignedService = { description = "Create preliminary self-signed certificate for ${cert}"; preStart = '' @@ -297,11 +341,8 @@ in }; in ( [ { name = "acme-${cert}"; value = acmeService; } ] - ++ - (if cfg.preliminarySelfsigned - then [ { name = "acme-selfsigned-${cert}"; value = selfsignedService; } ] - else [] - ) + ++ optional cfg.preliminarySelfsigned { name = "acme-selfsigned-${cert}"; value = selfsignedService; } + ++ optional (data.activationDelay != null) { name = "acme-setlive-${cert}"; value = delayService; } ); servicesAttr = listToAttrs services; injectServiceDep = { From 79eebad05559adb9a4e7c3de73e3bcdad508729a Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sat, 21 Apr 2018 22:34:39 +0200 Subject: [PATCH 2/2] Fix incorrect merge --- nixos/modules/security/acme.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix index eb705007d028..e430c2ddb903 100644 --- a/nixos/modules/security/acme.nix +++ b/nixos/modules/security/acme.nix @@ -212,7 +212,6 @@ in domain = if data.domain != null then data.domain else cert; cpath = lpath + optionalString (data.activationDelay != null) ".staging"; lpath = "${cfg.directory}/${cert}"; - cpath = "${cfg.directory}/${cert}"; rights = if data.allowKeysForGroup then "750" else "700"; cmdline = [ "-v" "-d" data.domain "--default_root" data.webroot "--valid_min" cfg.validMin ] ++ optionals (data.email != null) [ "--email" data.email ]