Unverified Commit f462e256 authored by Martin Weinelt's avatar Martin Weinelt Committed by GitHub
Browse files

acme: Add csr option (#376334)

parents b475c480 123d1344
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -638,6 +638,8 @@
  They are still expected to be working until future version 5.0.0, but will generate warnings in logs.
  Read the [release notes](https://www.authelia.com/blog/4.39-release-notes/) for human readable summaries of the changes.

- `security.acme` now supports renewal using CSRs (Certificate Signing Request) through the options `security.acme.*.csr` and `security.acme.*.csrKey`.

- `programs.fzf.keybindings` now supports the fish shell.

- `gerbera` now has wavpack support.
+47 −13
Original line number Diff line number Diff line
@@ -236,13 +236,16 @@ let

      # Create hashes for cert data directories based on configuration
      # Flags are separated to avoid collisions
      hashData = with builtins; ''
      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
        ));

+44 −0
Original line number Diff line number Diff line
@@ -99,6 +99,45 @@ in
              "builtin-3.${domain}".listenHTTP = ":80";
            };
          };

          csr.configuration =
            let
              conf = pkgs.writeText "openssl.csr.conf" ''
                [req]
                default_bits = 2048
                prompt = no
                default_md = sha256
                req_extensions = req_ext
                distinguished_name = dn

                [ dn ]
                CN = ${config.networking.fqdn}

                [ req_ext ]
                subjectAltName = @alt_names

                [ alt_names ]
                DNS.1 = ${config.networking.fqdn}
              '';
              csrData =
                pkgs.runCommandNoCC "csr-and-key"
                  {
                    buildInputs = [ pkgs.openssl ];
                  }
                  ''
                    mkdir -p $out
                    openssl req -new -newkey rsa:2048 -nodes \
                      -keyout $out/key.pem \
                      -out $out/request.csr \
                      -config ${conf}
                  '';
            in
            {
              security.acme.certs."${config.networking.fqdn}" = {
                csr = "${csrData}/request.csr";
                csrKey = "${csrData}/key.pem";
              };
            };
        };
      };
  };
@@ -211,5 +250,10 @@ in

      with subtest("Validate permissions (self-signed)"):
          check_permissions(builtin, cert, "acme")

      with subtest("Can renew using a CSR"):
          builtin.succeed(f"systemctl clean acme-{cert}.service --what=state")
          switch_to(builtin, "csr")
          check_issuer(builtin, cert, "pebble")
    '';
}