diff options
author | Bob van der Linden <bobvanderlinden@gmail.com> | 2016-06-01 12:39:46 +0200 |
---|---|---|
committer | Domen Kožar <domen@dev.si> | 2016-06-01 11:39:46 +0100 |
commit | 4e6697dcb6baba9a96de5d60451bcd025833a1e8 (patch) | |
tree | 4ffb100a5f7553646fb0e603179a4079dcf8d233 /nixos | |
parent | 164ead312e6c1c7eb455cbcd251cb7b603eef298 (diff) |
acme: added option `security.acme.preliminarySelfsigned` (#15562)
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/modules/security/acme.nix | 180 | ||||
-rw-r--r-- | nixos/modules/security/acme.xml | 28 |
2 files changed, 162 insertions, 46 deletions
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix index cb5410a5f15d8..ef6da788e6194 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 ]; diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml index e32fa72c93939..15ed4c04a23dc 100644 --- a/nixos/modules/security/acme.xml +++ b/nixos/modules/security/acme.xml @@ -66,4 +66,32 @@ options for the <literal>security.acme</literal> module.</para> </section> +<section><title>Using ACME certificates in Nginx</title> +<para>In practice ACME is mostly used for retrieval and renewal of + certificates that will be used in a webserver like Nginx. A configuration for + Nginx that uses the certificates from ACME for + <literal>foo.example.com</literal> will look similar to: +</para> + +<programlisting> +services.nginx.httpConfig = '' + server { + server_name foo.example.com; + listen 443 ssl; + ssl_certificate ${config.security.acme.directory}/foo.example.com/fullchain.pem; + ssl_certificate_key ${config.security.acme.directory}/foo.example.com/key.pem; + root /var/www/foo.example.com/; + } +''; +</programlisting> + +<para>Now Nginx will try to use the certificates that will be retrieved by ACME. + ACME needs Nginx (or any other webserver) to function and Nginx needs + the certificates to actually start. For this reason the ACME module + automatically generates self-signed certificates that will be used by Nginx to + start. After that Nginx is used by ACME to retrieve the actual ACME + certificates. <literal>security.acme.preliminarySelfsigned</literal> can be + used to control whether to generate the self-signed certificates. +</para> +</section> </chapter> |