From 754412b4ae61dc070f015978027fc11b92e22f63 Mon Sep 17 00:00:00 2001 From: C0D3 M4513R <28912031+C0D3-M4513R@users.noreply.github.com> Date: Mon, 14 Apr 2025 18:53:07 +0200 Subject: [PATCH] acme: Add csr option Signed-off-by: C0D3 M4513R <28912031+C0D3-M4513R@users.noreply.github.com> --- nixos/modules/security/acme/default.nix | 60 +++++++++++++++++++------ 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix index 286814eaba8e..58ab3623223d 100644 --- a/nixos/modules/security/acme/default.nix +++ b/nixos/modules/security/acme/default.nix @@ -236,13 +236,16 @@ let # Create hashes for cert data directories based on configuration # Flags are separated to avoid collisions - hashData = with builtins; '' - ${lib.concatStringsSep " " data.extraLegoFlags} - - ${lib.concatStringsSep " " data.extraLegoRunFlags} - - ${lib.concatStringsSep " " data.extraLegoRenewFlags} - - ${toString acmeServer} ${toString data.dnsProvider} - ${toString data.ocspMustStaple} ${data.keyType} - ''; + hashData = + with builtins; + '' + ${lib.concatStringsSep " " data.extraLegoFlags} - + ${lib.concatStringsSep " " data.extraLegoRunFlags} - + ${lib.concatStringsSep " " data.extraLegoRenewFlags} - + ${toString acmeServer} ${toString data.dnsProvider} + ${toString data.ocspMustStaple} ${data.keyType} + '' + + (lib.optionalString (data.csr != null) (" - " + data.csr)); certDir = mkHash hashData; # TODO remove domainHash usage entirely. Waiting on go-acme/lego#1532 domainHash = mkHash "${lib.concatStringsSep " " extraDomains} ${data.domain}"; @@ -286,18 +289,24 @@ let "--accept-tos" # Checking the option is covered by the assertions "--path" "." - "-d" - data.domain "--email" data.email - "--key-type" - data.keyType ] ++ protocolOpts ++ lib.optionals (acmeServer != null) [ "--server" acmeServer ] + ++ lib.optionals (data.csr != null) [ + "--csr" + data.csr + ] + ++ lib.optionals (data.csr == null) [ + "--key-type" + data.keyType + "-d" + data.domain + ] ++ lib.concatMap (name: [ "-d" name @@ -327,6 +336,8 @@ let webroots = lib.remove null ( lib.unique (builtins.map (certAttrs: certAttrs.webroot) (lib.attrValues config.security.acme.certs)) ); + + certificateKey = if data.csrKey != null then "${data.csrKey}" else "certificates/${keyName}.key"; in { inherit accountHash cert selfsignedDeps; @@ -529,7 +540,7 @@ let # Check if we can renew. # We can only renew if the list of domains has not changed. # We also need an account key. Avoids #190493 - if cmp -s domainhash.txt certificates/domainhash.txt && [ -e 'certificates/${keyName}.key' ] && [ -e 'certificates/${keyName}.crt' ] && [ -n "$(find accounts -name '${data.email}.key')" ]; then + if cmp -s domainhash.txt certificates/domainhash.txt && [ -e '${certificateKey}' ] && [ -e 'certificates/${keyName}.crt' ] && [ -n "$(find accounts -name '${data.email}.key')" ]; then # Even if a cert is not expired, it may be revoked by the CA. # Try to renew, and silently fail if the cert is not expired. @@ -564,7 +575,7 @@ let touch out/renewed echo Installing new certificate cp -vp 'certificates/${keyName}.crt' out/fullchain.pem - cp -vp 'certificates/${keyName}.key' out/key.pem + cp -vp '${certificateKey}' out/key.pem cp -vp 'certificates/${keyName}.issuer.crt' out/chain.pem ln -sf fullchain.pem out/cert.pem cat out/key.pem out/fullchain.pem > out/full.pem @@ -845,6 +856,18 @@ let description = "Domain to fetch certificate for (defaults to the entry name)."; }; + csr = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Path to a certificate signing request to apply when fetching the certificate."; + }; + + csrKey = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Path to the private key to the matching certificate signing request."; + }; + extraDomainNames = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; @@ -1113,6 +1136,17 @@ in used for variables suffixed by "_FILE". ''; } + + { + assertion = lib.all ( + certOpts: + (certOpts.csr == null && certOpts.csrKey == null) + || (certOpts.csr != null && certOpts.csrKey != null) + ) certs; + message = '' + When passing a certificate signing request both `security.acme.certs.${cert}.csr` and `security.acme.certs.${cert}.csrKey` need to be set. + ''; + } ]) cfg.certs ));