summary refs log tree commit diff
path: root/nixos/modules/security/acme.nix
diff options
context:
space:
mode:
authorBob van der Linden <bobvanderlinden@gmail.com>2016-06-01 12:39:46 +0200
committerDomen Kožar <domen@dev.si>2016-06-01 11:39:46 +0100
commit4e6697dcb6baba9a96de5d60451bcd025833a1e8 (patch)
tree4ffb100a5f7553646fb0e603179a4079dcf8d233 /nixos/modules/security/acme.nix
parent164ead312e6c1c7eb455cbcd251cb7b603eef298 (diff)
downloadnixpkgs-4e6697dcb6baba9a96de5d60451bcd025833a1e8.tar
nixpkgs-4e6697dcb6baba9a96de5d60451bcd025833a1e8.tar.gz
nixpkgs-4e6697dcb6baba9a96de5d60451bcd025833a1e8.tar.bz2
nixpkgs-4e6697dcb6baba9a96de5d60451bcd025833a1e8.tar.lz
nixpkgs-4e6697dcb6baba9a96de5d60451bcd025833a1e8.tar.xz
nixpkgs-4e6697dcb6baba9a96de5d60451bcd025833a1e8.tar.zst
nixpkgs-4e6697dcb6baba9a96de5d60451bcd025833a1e8.zip
acme: added option `security.acme.preliminarySelfsigned` (#15562)
Diffstat (limited to 'nixos/modules/security/acme.nix')
-rw-r--r--nixos/modules/security/acme.nix180
1 files changed, 134 insertions, 46 deletions
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index cb5410a5f15..ef6da788e61 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -114,6 +114,19 @@ in
         '';
       };
 
+      preliminarySelfsigned = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether a preliminary self-signed certificate should be generated before
+          doing ACME requests. This can be useful when certificates are required in
+          a webserver, but ACME needs the webserver to make its requests.
+
+          With preliminary self-signed certificate the webserver can be started and
+          can later reload the correct ACME certificates.
+        '';
+      };
+
       certs = mkOption {
         default = { };
         type = types.loaOf types.optionSet;
@@ -140,54 +153,126 @@ in
   config = mkMerge [
     (mkIf (cfg.certs != { }) {
 
-      systemd.services = flip mapAttrs' cfg.certs (cert: data:
-        let
-          cpath = "${cfg.directory}/${cert}";
-          rights = if data.allowKeysForGroup then "750" else "700";
-          cmdline = [ "-v" "-d" cert "--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);
+      systemd.services = let
+          services = concatLists servicesLists;
+          servicesLists = mapAttrsToList certToServices cfg.certs;
+          certToServices = cert: data:
+              let
+                cpath = "${cfg.directory}/${cert}";
+                rights = if data.allowKeysForGroup then "750" else "700";
+                cmdline = [ "-v" "-d" cert "--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);
+                acmeService = {
+                  description = "Renew ACME Certificate for ${cert}";
+                  after = [ "network.target" ];
+                  serviceConfig = {
+                    Type = "oneshot";
+                    SuccessExitStatus = [ "0" "1" ];
+                    PermissionsStartOnly = true;
+                    User = data.user;
+                    Group = data.group;
+                    PrivateTmp = true;
+                  };
+                  path = [ pkgs.simp_le ];
+                  preStart = ''
+                    mkdir -p '${cfg.directory}'
+                    if [ ! -d '${cpath}' ]; then
+                      mkdir '${cpath}'
+                    fi
+                    chmod ${rights} '${cpath}'
+                    chown -R '${data.user}:${data.group}' '${cpath}'
+                  '';
+                  script = ''
+                    cd '${cpath}'
+                    set +e
+                    simp_le ${concatMapStringsSep " " (arg: escapeShellArg (toString arg)) cmdline}
+                    EXITCODE=$?
+                    set -e
+                    echo "$EXITCODE" > /tmp/lastExitCode
+                    exit "$EXITCODE"
+                  '';
+                  postStop = ''
+                    if [ -e /tmp/lastExitCode ] && [ "$(cat /tmp/lastExitCode)" = "0" ]; then
+                      echo "Executing postRun hook..."
+                      ${data.postRun}
+                    fi
+                  '';
 
-        in nameValuePair
-        ("acme-${cert}")
-        ({
-          description = "Renew ACME Certificate for ${cert}";
-          after = [ "network.target" ];
-          serviceConfig = {
-            Type = "oneshot";
-            SuccessExitStatus = [ "0" "1" ];
-            PermissionsStartOnly = true;
-            User = data.user;
-            Group = data.group;
-            PrivateTmp = true;
+                  before = [ "acme-certificates.target" ];
+                  wantedBy = [ "acme-certificates.target" ];
+                };
+                selfsignedService = {
+                  description = "Create preliminary self-signed certificate for ${cert}";
+                  preStart = ''
+                      if [ ! -d '${cpath}' ]
+                      then
+                        mkdir -p '${cpath}'
+                        chmod ${rights} '${cpath}'
+                        chown '${data.user}:${data.group}' '${cpath}'
+                      fi
+                  '';
+                  script = 
+                    ''
+                      # Create self-signed key
+                      workdir="/run/acme-selfsigned-${cert}"
+                      ${pkgs.openssl.bin}/bin/openssl genrsa -des3 -passout pass:x -out $workdir/server.pass.key 2048
+                      ${pkgs.openssl.bin}/bin/openssl rsa -passin pass:x -in $workdir/server.pass.key -out $workdir/server.key
+                      ${pkgs.openssl.bin}/bin/openssl req -new -key $workdir/server.key -out $workdir/server.csr \
+                        -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
+                      ${pkgs.openssl.bin}/bin/openssl x509 -req -days 1 -in $workdir/server.csr -signkey $workdir/server.key -out $workdir/server.crt
+
+                      # Move key to destination
+                      mv $workdir/server.key ${cpath}/key.pem
+                      mv $workdir/server.crt ${cpath}/fullchain.pem
+
+                      # Clean up working directory
+                      rm $workdir/server.csr
+                      rm $workdir/server.pass.key
+
+                      # Give key acme permissions
+                      chmod ${rights} '${cpath}/key.pem'
+                      chown '${data.user}:${data.group}' '${cpath}/key.pem'
+                      chmod ${rights} '${cpath}/fullchain.pem'
+                      chown '${data.user}:${data.group}' '${cpath}/fullchain.pem'
+                    '';
+                  serviceConfig = {
+                    Type = "oneshot";
+                    RuntimeDirectory = "acme-selfsigned-${cert}";
+                    PermissionsStartOnly = true;
+                    User = data.user;
+                    Group = data.group;
+                  };
+                  unitConfig = {
+                    # Do not create self-signed key when key already exists
+                    ConditionPathExists = "!${cpath}/key.pem";
+                  };
+                  before = [
+                    "acme-selfsigned-certificates.target"
+                  ];
+                  wantedBy = [
+                    "acme-selfsigned-certificates.target"
+                  ];
+                };
+              in (
+                [ { name = "acme-${cert}"; value = acmeService; } ]
+                ++
+                (if cfg.preliminarySelfsigned
+                  then [ { name = "acme-selfsigned-${cert}"; value = selfsignedService; } ]
+                  else []
+                )
+              );
+          servicesAttr = listToAttrs services;
+          nginxAttr = {
+            nginx = {
+              after = [ "acme-selfsigned-certificates.target" ];
+              wants = [ "acme-selfsigned-certificates.target" "acme-certificates.target" ];
+            };
           };
-          path = [ pkgs.simp_le ];
-          preStart = ''
-            mkdir -p '${cfg.directory}'
-            if [ ! -d '${cpath}' ]; then
-              mkdir '${cpath}'
-            fi
-            chmod ${rights} '${cpath}'
-            chown -R '${data.user}:${data.group}' '${cpath}'
-          '';
-          script = ''
-            cd '${cpath}'
-            set +e
-            simp_le ${concatMapStringsSep " " (arg: escapeShellArg (toString arg)) cmdline}
-            EXITCODE=$?
-            set -e
-            echo "$EXITCODE" > /tmp/lastExitCode
-            exit "$EXITCODE"
-          '';
-          postStop = ''
-            if [ -e /tmp/lastExitCode ] && [ "$(cat /tmp/lastExitCode)" = "0" ]; then
-              echo "Executing postRun hook..."
-              ${data.postRun}
-            fi
-          '';
-        })
-      );
+        in
+          servicesAttr //
+          (if config.services.nginx.enable then nginxAttr else {});
 
       systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair
         ("acme-${cert}")
@@ -200,6 +285,9 @@ in
           };
         })
       );
+
+      systemd.targets."acme-selfsigned-certificates" = mkIf cfg.preliminarySelfsigned {};
+      systemd.targets."acme-certificates" = {};
     })
 
     { meta.maintainers = with lib.maintainers; [ abbradar fpletz globin ];