summary refs log tree commit diff
path: root/nixos/modules/security/acme.nix
diff options
context:
space:
mode:
authorGregor Kleen <pngwjpgh@users.noreply.github.com>2017-11-19 16:41:28 +0100
committerGregor Kleen <pngwjpgh@users.noreply.github.com>2017-11-19 17:25:00 +0100
commite70d293b6b5f236d69d559ccccdafba19c6d29c3 (patch)
treeb85bf1d04c12446bfa3af28e6601597d5c1b9471 /nixos/modules/security/acme.nix
parent7c240776758cb1893f2cff7dfc8badc80cdc9cc2 (diff)
downloadnixpkgs-e70d293b6b5f236d69d559ccccdafba19c6d29c3.tar
nixpkgs-e70d293b6b5f236d69d559ccccdafba19c6d29c3.tar.gz
nixpkgs-e70d293b6b5f236d69d559ccccdafba19c6d29c3.tar.bz2
nixpkgs-e70d293b6b5f236d69d559ccccdafba19c6d29c3.tar.lz
nixpkgs-e70d293b6b5f236d69d559ccccdafba19c6d29c3.tar.xz
nixpkgs-e70d293b6b5f236d69d559ccccdafba19c6d29c3.tar.zst
nixpkgs-e70d293b6b5f236d69d559ccccdafba19c6d29c3.zip
nixos/acme: Allow for time window between cert issue and activation
Diffstat (limited to 'nixos/modules/security/acme.nix')
-rw-r--r--nixos/modules/security/acme.nix65
1 files changed, 53 insertions, 12 deletions
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index fb011019f7f..df0b4986eb8 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 <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>.
+        '';
+      };
+
+      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 = {