Unverified Commit e65f8ef1 authored by Martin Weinelt's avatar Martin Weinelt
Browse files

nixos/acme: drop email requirement

Let's Encrypt does not require email addreses any longer, so we should
allow users not to provide any.

Unsetting the email adress will change the account hash and Lego will
start using a dummy email address instead. The address is hardcoded in
the Lego source code in the userIDPlaceholder constant.

We then verify in tests that changing between no email address and the
placeholder address does not create a new account nor rotate the previous
certificate.

This is supported since Lego 4.30.1.

https://github.com/go-acme/lego/commit/bc163db9edd23bbfc3521086c0b570f468b9a87b
parent a5021dd6
Loading
Loading
Loading
Loading
+21 −10
Original line number Diff line number Diff line
@@ -15,9 +15,21 @@ let
  numCerts = lib.length (builtins.attrNames cfg.certs);
  _24hSecs = 60 * 60 * 24;

  # The placerholder email address used by lego in case none gets passed
  placeholderEmail = "noemail@example.com";

  # Used to make unique paths for each cert/account config set
  mkHash = with builtins; val: lib.substring 0 20 (hashString "sha256" val);
  mkAccountHash = acmeServer: data: mkHash "${toString acmeServer} ${data.keyType} ${data.email}";
  mkAccountHash =
    acmeServer: data:
    mkHash (
      lib.concatStringsSep " " [
        (toString acmeServer)
        data.keyType
        (if (data.email != null) then data.email else placeholderEmail)
      ]

    );
  accountDirRoot = "/var/lib/acme/.lego/accounts/";

  # Lockdir is acme-setup.service's RuntimeDirectory.
@@ -263,6 +275,8 @@ let
        "--accept-tos" # Checking the option is covered by the assertions
        "--path"
        "."
      ]
      ++ lib.optionals (data.email != null) [
        "--email"
        data.email
      ]
@@ -544,7 +558,12 @@ let
          # Check if a new order is needed
          # 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 '${certificateKey}' ] && [ -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 '${
              if (data.email != null) then data.email else placeholderEmail
            }.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.
            # Avoids #85794 and resolves #129838
@@ -1062,14 +1081,6 @@ in
          certs = lib.attrValues cfg.certs;
        in
        [
          {
            assertion = cfg.defaults.email != null || lib.all (certOpts: certOpts.email != null) certs;
            message = ''
              You must define `security.acme.certs.<name>.email` or
              `security.acme.defaults.email` to register with the CA. Note that using
              many different addresses for certs may trigger account rate limits.
            '';
          }
          {
            assertion = cfg.acceptTerms;
            message = ''
+21 −1
Original line number Diff line number Diff line
@@ -51,7 +51,14 @@ in
          };

          accountchange.configuration = {
            security.acme.certs."${config.networking.fqdn}".email = "admin@example.test";
            # Providing an email address is optional
            security.acme.certs."${config.networking.fqdn}".email = null;
          };

          emailplaceholder.configuration = {
            # but Lego will default to this email address, which should not
            # result in any change when configured
            security.acme.certs."${config.networking.fqdn}".email = "noemail@example.com";
          };

          keytype.configuration = {
@@ -154,6 +161,7 @@ in
      certName = nodes.builtin.networking.fqdn;
      caDomain = nodes.acme.test-support.acme.caDomain;
    in
    # python
    ''
      ${(import ./utils.nix).pythonUtils}

@@ -212,11 +220,23 @@ in
          hash_after = builtin.succeed(f"sha256sum /var/lib/acme/{cert}/cert.pem")
          # Has to do a full run to register account, which creates new certs.
          assert hash != hash_after, "Certificate was not renewed"
          hash = hash_after

          builtin.succeed("systemctl stop renew-triggered.target")
          switch_to(builtin, "emailplaceholder")
          builtin.wait_for_unit("renew-triggered.target")

          # Check that there are still two account directories
          builtin.succeed("test $(ls -1 /var/lib/acme/.lego/accounts | tee /dev/stderr | wc -l) -eq 2")
          hash_after = builtin.succeed(f"sha256sum /var/lib/acme/{cert}/cert.pem")
          assert hash == hash_after, "Implicit to explicit email placeholder renewed the certificate"

          # Remove the new account directory
          builtin.succeed(
              "cd /var/lib/acme/.lego/accounts"
              " && ls -1 --sort=time | tee /dev/stderr | head -1 | xargs rm -rf"
          )

          # old_hash will be used in the preservation tests later
          old_hash = hash_after