about summary refs log tree commit diff
diff options
context:
space:
mode:
authorpennae <github@quasiparticle.net>2023-01-02 22:50:30 +0100
committerpennae <github@quasiparticle.net>2023-01-10 10:31:54 +0100
commit53935b445fa62f6eefee11b5a8eaf42ce329ec6b (patch)
tree6647c13fb8b4bd55b56faeaf78d4181352c47ad1
parentf60e9eac857bbc020c6f4c263f558c6ded8965f8 (diff)
nixos/acme: convert manual chapter to MD
-rw-r--r--nixos/modules/security/acme/default.nix2
-rw-r--r--nixos/modules/security/acme/doc.md354
-rw-r--r--nixos/modules/security/acme/doc.xml489
3 files changed, 591 insertions, 254 deletions
diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix
index a380bb5484afc..3be78084b95c0 100644
--- a/nixos/modules/security/acme/default.nix
+++ b/nixos/modules/security/acme/default.nix
@@ -916,6 +916,8 @@ in {
 
   meta = {
     maintainers = lib.teams.acme.members;
+    # Don't edit the docbook xml directly, edit the md and generate it:
+    # `pandoc doc.md -t docbook --top-level-division=chapter --extract-media=media -f markdown-smart --lua-filter ../../../../doc/build-aux/pandoc-filters/myst-reader/roles.lua --lua-filter ../../../../doc/build-aux/pandoc-filters/docbook-writer/rst-roles.lua > doc.xml`
     doc = ./doc.xml;
   };
 }
diff --git a/nixos/modules/security/acme/doc.md b/nixos/modules/security/acme/doc.md
new file mode 100644
index 0000000000000..8ff97b55f6856
--- /dev/null
+++ b/nixos/modules/security/acme/doc.md
@@ -0,0 +1,354 @@
+# SSL/TLS Certificates with ACME {#module-security-acme}
+
+NixOS supports automatic domain validation & certificate retrieval and
+renewal using the ACME protocol. Any provider can be used, but by default
+NixOS uses Let's Encrypt. The alternative ACME client
+[lego](https://go-acme.github.io/lego/) is used under
+the hood.
+
+Automatic cert validation and configuration for Apache and Nginx virtual
+hosts is included in NixOS, however if you would like to generate a wildcard
+cert or you are not using a web server you will have to configure DNS
+based validation.
+
+## Prerequisites {#module-security-acme-prerequisites}
+
+To use the ACME module, you must accept the provider's terms of service
+by setting [](#opt-security.acme.acceptTerms)
+to `true`. The Let's Encrypt ToS can be found
+[here](https://letsencrypt.org/repository/).
+
+You must also set an email address to be used when creating accounts with
+Let's Encrypt. You can set this for all certs with
+[](#opt-security.acme.defaults.email)
+and/or on a per-cert basis with
+[](#opt-security.acme.certs._name_.email).
+This address is only used for registration and renewal reminders,
+and cannot be used to administer the certificates in any way.
+
+Alternatively, you can use a different ACME server by changing the
+[](#opt-security.acme.defaults.server) option
+to a provider of your choosing, or just change the server for one cert with
+[](#opt-security.acme.certs._name_.server).
+
+You will need an HTTP server or DNS server for verification. For HTTP,
+the server must have a webroot defined that can serve
+{file}`.well-known/acme-challenge`. This directory must be
+writeable by the user that will run the ACME client. For DNS, you must
+set up credentials with your provider/server for use with lego.
+
+## Using ACME certificates in Nginx {#module-security-acme-nginx}
+
+NixOS supports fetching ACME certificates for you by setting
+`enableACME = true;` in a virtualHost config. We first create self-signed
+placeholder certificates in place of the real ACME certs. The placeholder
+certs are overwritten when the ACME certs arrive. For
+`foo.example.com` the config would look like this:
+
+```
+security.acme.acceptTerms = true;
+security.acme.defaults.email = "admin+acme@example.com";
+services.nginx = {
+  enable = true;
+  virtualHosts = {
+    "foo.example.com" = {
+      forceSSL = true;
+      enableACME = true;
+      # All serverAliases will be added as extra domain names on the certificate.
+      serverAliases = [ "bar.example.com" ];
+      locations."/" = {
+        root = "/var/www";
+      };
+    };
+
+    # We can also add a different vhost and reuse the same certificate
+    # but we have to append extraDomainNames manually beforehand:
+    # security.acme.certs."foo.example.com".extraDomainNames = [ "baz.example.com" ];
+    "baz.example.com" = {
+      forceSSL = true;
+      useACMEHost = "foo.example.com";
+      locations."/" = {
+        root = "/var/www";
+      };
+    };
+  };
+}
+```
+
+## Using ACME certificates in Apache/httpd {#module-security-acme-httpd}
+
+Using ACME certificates with Apache virtual hosts is identical
+to using them with Nginx. The attribute names are all the same, just replace
+"nginx" with "httpd" where appropriate.
+
+## Manual configuration of HTTP-01 validation {#module-security-acme-configuring}
+
+First off you will need to set up a virtual host to serve the challenges.
+This example uses a vhost called `certs.example.com`, with
+the intent that you will generate certs for all your vhosts and redirect
+everyone to HTTPS.
+
+```
+security.acme.acceptTerms = true;
+security.acme.defaults.email = "admin+acme@example.com";
+
+# /var/lib/acme/.challenges must be writable by the ACME user
+# and readable by the Nginx user. The easiest way to achieve
+# this is to add the Nginx user to the ACME group.
+users.users.nginx.extraGroups = [ "acme" ];
+
+services.nginx = {
+  enable = true;
+  virtualHosts = {
+    "acmechallenge.example.com" = {
+      # Catchall vhost, will redirect users to HTTPS for all vhosts
+      serverAliases = [ "*.example.com" ];
+      locations."/.well-known/acme-challenge" = {
+        root = "/var/lib/acme/.challenges";
+      };
+      locations."/" = {
+        return = "301 https://$host$request_uri";
+      };
+    };
+  };
+}
+# Alternative config for Apache
+users.users.wwwrun.extraGroups = [ "acme" ];
+services.httpd = {
+  enable = true;
+  virtualHosts = {
+    "acmechallenge.example.com" = {
+      # Catchall vhost, will redirect users to HTTPS for all vhosts
+      serverAliases = [ "*.example.com" ];
+      # /var/lib/acme/.challenges must be writable by the ACME user and readable by the Apache user.
+      # By default, this is the case.
+      documentRoot = "/var/lib/acme/.challenges";
+      extraConfig = ''
+        RewriteEngine On
+        RewriteCond %{HTTPS} off
+        RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge [NC]
+        RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301]
+      '';
+    };
+  };
+}
+```
+
+Now you need to configure ACME to generate a certificate.
+
+```
+security.acme.certs."foo.example.com" = {
+  webroot = "/var/lib/acme/.challenges";
+  email = "foo@example.com";
+  # Ensure that the web server you use can read the generated certs
+  # Take a look at the group option for the web server you choose.
+  group = "nginx";
+  # Since we have a wildcard vhost to handle port 80,
+  # we can generate certs for anything!
+  # Just make sure your DNS resolves them.
+  extraDomainNames = [ "mail.example.com" ];
+};
+```
+
+The private key {file}`key.pem` and certificate
+{file}`fullchain.pem` will be put into
+{file}`/var/lib/acme/foo.example.com`.
+
+Refer to [](#ch-options) for all available configuration
+options for the [security.acme](#opt-security.acme.certs)
+module.
+
+## Configuring ACME for DNS validation {#module-security-acme-config-dns}
+
+This is useful if you want to generate a wildcard certificate, since
+ACME servers will only hand out wildcard certs over DNS validation.
+There are a number of supported DNS providers and servers you can utilise,
+see the [lego docs](https://go-acme.github.io/lego/dns/)
+for provider/server specific configuration values. For the sake of these
+docs, we will provide a fully self-hosted example using bind.
+
+```
+services.bind = {
+  enable = true;
+  extraConfig = ''
+    include "/var/lib/secrets/dnskeys.conf";
+  '';
+  zones = [
+    rec {
+      name = "example.com";
+      file = "/var/db/bind/${name}";
+      master = true;
+      extraConfig = "allow-update { key rfc2136key.example.com.; };";
+    }
+  ];
+}
+
+# Now we can configure ACME
+security.acme.acceptTerms = true;
+security.acme.defaults.email = "admin+acme@example.com";
+security.acme.certs."example.com" = {
+  domain = "*.example.com";
+  dnsProvider = "rfc2136";
+  credentialsFile = "/var/lib/secrets/certs.secret";
+  # We don't need to wait for propagation since this is a local DNS server
+  dnsPropagationCheck = false;
+};
+```
+
+The {file}`dnskeys.conf` and {file}`certs.secret`
+must be kept secure and thus you should not keep their contents in your
+Nix config. Instead, generate them one time with a systemd service:
+
+```
+systemd.services.dns-rfc2136-conf = {
+  requiredBy = ["acme-example.com.service" "bind.service"];
+  before = ["acme-example.com.service" "bind.service"];
+  unitConfig = {
+    ConditionPathExists = "!/var/lib/secrets/dnskeys.conf";
+  };
+  serviceConfig = {
+    Type = "oneshot";
+    UMask = 0077;
+  };
+  path = [ pkgs.bind ];
+  script = ''
+    mkdir -p /var/lib/secrets
+    chmod 755 /var/lib/secrets
+    tsig-keygen rfc2136key.example.com > /var/lib/secrets/dnskeys.conf
+    chown named:root /var/lib/secrets/dnskeys.conf
+    chmod 400 /var/lib/secrets/dnskeys.conf
+
+    # extract secret value from the dnskeys.conf
+    while read x y; do if [ "$x" = "secret" ]; then secret="''${y:1:''${#y}-3}"; fi; done < /var/lib/secrets/dnskeys.conf
+
+    cat > /var/lib/secrets/certs.secret << EOF
+    RFC2136_NAMESERVER='127.0.0.1:53'
+    RFC2136_TSIG_ALGORITHM='hmac-sha256.'
+    RFC2136_TSIG_KEY='rfc2136key.example.com'
+    RFC2136_TSIG_SECRET='$secret'
+    EOF
+    chmod 400 /var/lib/secrets/certs.secret
+  '';
+};
+```
+
+Now you're all set to generate certs! You should monitor the first invocation
+by running `systemctl start acme-example.com.service &
+journalctl -fu acme-example.com.service` and watching its log output.
+
+## Using DNS validation with web server virtual hosts {#module-security-acme-config-dns-with-vhosts}
+
+It is possible to use DNS-01 validation with all certificates,
+including those automatically configured via the Nginx/Apache
+[`enableACME`](#opt-services.nginx.virtualHosts._name_.enableACME)
+option. This configuration pattern is fully
+supported and part of the module's test suite for Nginx + Apache.
+
+You must follow the guide above on configuring DNS-01 validation
+first, however instead of setting the options for one certificate
+(e.g. [](#opt-security.acme.certs._name_.dnsProvider))
+you will set them as defaults
+(e.g. [](#opt-security.acme.defaults.dnsProvider)).
+
+```
+# Configure ACME appropriately
+security.acme.acceptTerms = true;
+security.acme.defaults.email = "admin+acme@example.com";
+security.acme.defaults = {
+  dnsProvider = "rfc2136";
+  credentialsFile = "/var/lib/secrets/certs.secret";
+  # We don't need to wait for propagation since this is a local DNS server
+  dnsPropagationCheck = false;
+};
+
+# For each virtual host you would like to use DNS-01 validation with,
+# set acmeRoot = null
+services.nginx = {
+  enable = true;
+  virtualHosts = {
+    "foo.example.com" = {
+      enableACME = true;
+      acmeRoot = null;
+    };
+  };
+}
+```
+
+And that's it! Next time your configuration is rebuilt, or when
+you add a new virtualHost, it will be DNS-01 validated.
+
+## Using ACME with services demanding root owned certificates {#module-security-acme-root-owned}
+
+Some services refuse to start if the configured certificate files
+are not owned by root. PostgreSQL and OpenSMTPD are examples of these.
+There is no way to change the user the ACME module uses (it will always be
+`acme`), however you can use systemd's
+`LoadCredential` feature to resolve this elegantly.
+Below is an example configuration for OpenSMTPD, but this pattern
+can be applied to any service.
+
+```
+# Configure ACME however you like (DNS or HTTP validation), adding
+# the following configuration for the relevant certificate.
+# Note: You cannot use `systemctl reload` here as that would mean
+# the LoadCredential configuration below would be skipped and
+# the service would continue to use old certificates.
+security.acme.certs."mail.example.com".postRun = ''
+  systemctl restart opensmtpd
+'';
+
+# Now you must augment OpenSMTPD's systemd service to load
+# the certificate files.
+systemd.services.opensmtpd.requires = ["acme-finished-mail.example.com.target"];
+systemd.services.opensmtpd.serviceConfig.LoadCredential = let
+  certDir = config.security.acme.certs."mail.example.com".directory;
+in [
+  "cert.pem:${certDir}/cert.pem"
+  "key.pem:${certDir}/key.pem"
+];
+
+# Finally, configure OpenSMTPD to use these certs.
+services.opensmtpd = let
+  credsDir = "/run/credentials/opensmtpd.service";
+in {
+  enable = true;
+  setSendmail = false;
+  serverConfiguration = ''
+    pki mail.example.com cert "${credsDir}/cert.pem"
+    pki mail.example.com key "${credsDir}/key.pem"
+    listen on localhost tls pki mail.example.com
+    action act1 relay host smtp://127.0.0.1:10027
+    match for local action act1
+  '';
+};
+```
+
+## Regenerating certificates {#module-security-acme-regenerate}
+
+Should you need to regenerate a particular certificate in a hurry, such
+as when a vulnerability is found in Let's Encrypt, there is now a convenient
+mechanism for doing so. Running
+`systemctl clean --what=state acme-example.com.service`
+will remove all certificate files and the account data for the given domain,
+allowing you to then `systemctl start acme-example.com.service`
+to generate fresh ones.
+
+## Fixing JWS Verification error {#module-security-acme-fix-jws}
+
+It is possible that your account credentials file may become corrupt and need
+to be regenerated. In this scenario lego will produce the error `JWS verification error`.
+The solution is to simply delete the associated accounts file and
+re-run the affected service(s).
+
+```
+# Find the accounts folder for the certificate
+systemctl cat acme-example.com.service | grep -Po 'accounts/[^:]*'
+export accountdir="$(!!)"
+# Move this folder to some place else
+mv /var/lib/acme/.lego/$accountdir{,.bak}
+# Recreate the folder using systemd-tmpfiles
+systemd-tmpfiles --create
+# Get a new account and reissue certificates
+# Note: Do this for all certs that share the same account email address
+systemctl start acme-example.com.service
+```
diff --git a/nixos/modules/security/acme/doc.xml b/nixos/modules/security/acme/doc.xml
index 0905fefd62321..c21f802caf4b7 100644
--- a/nixos/modules/security/acme/doc.xml
+++ b/nixos/modules/security/acme/doc.xml
@@ -1,152 +1,146 @@
-<chapter xmlns="http://docbook.org/ns/docbook"
-         xmlns:xlink="http://www.w3.org/1999/xlink"
-         xmlns:xi="http://www.w3.org/2001/XInclude"
-         version="5.0"
-         xml:id="module-security-acme">
- <title>SSL/TLS Certificates with ACME</title>
- <para>
-  NixOS supports automatic domain validation &amp; certificate retrieval and
-  renewal using the ACME protocol. Any provider can be used, but by default
-  NixOS uses Let's Encrypt. The alternative ACME client
-  <link xlink:href="https://go-acme.github.io/lego/">lego</link> is used under
-  the hood.
- </para>
- <para>
-  Automatic cert validation and configuration for Apache and Nginx virtual
-  hosts is included in NixOS, however if you would like to generate a wildcard
-  cert or you are not using a web server you will have to configure DNS
-  based validation.
- </para>
- <section xml:id="module-security-acme-prerequisites">
-  <title>Prerequisites</title>
-
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-security-acme">
+  <title>SSL/TLS Certificates with ACME</title>
   <para>
-   To use the ACME module, you must accept the provider's terms of service
-   by setting <xref linkend="opt-security.acme.acceptTerms" />
-   to <literal>true</literal>. The Let's Encrypt ToS can be found
-   <link xlink:href="https://letsencrypt.org/repository/">here</link>.
+    NixOS supports automatic domain validation &amp; certificate
+    retrieval and renewal using the ACME protocol. Any provider can be
+    used, but by default NixOS uses Let's Encrypt. The alternative ACME
+    client
+    <link xlink:href="https://go-acme.github.io/lego/">lego</link> is
+    used under the hood.
   </para>
-
-  <para>
-   You must also set an email address to be used when creating accounts with
-   Let's Encrypt. You can set this for all certs with
-   <xref linkend="opt-security.acme.defaults.email" />
-   and/or on a per-cert basis with
-   <xref linkend="opt-security.acme.certs._name_.email" />.
-   This address is only used for registration and renewal reminders,
-   and cannot be used to administer the certificates in any way.
-  </para>
-
   <para>
-   Alternatively, you can use a different ACME server by changing the
-   <xref linkend="opt-security.acme.defaults.server" /> option
-   to a provider of your choosing, or just change the server for one cert with
-   <xref linkend="opt-security.acme.certs._name_.server" />.
+    Automatic cert validation and configuration for Apache and Nginx
+    virtual hosts is included in NixOS, however if you would like to
+    generate a wildcard cert or you are not using a web server you will
+    have to configure DNS based validation.
   </para>
-
-  <para>
-   You will need an HTTP server or DNS server for verification. For HTTP,
-   the server must have a webroot defined that can serve
-   <filename>.well-known/acme-challenge</filename>. This directory must be
-   writeable by the user that will run the ACME client. For DNS, you must
-   set up credentials with your provider/server for use with lego.
-  </para>
- </section>
- <section xml:id="module-security-acme-nginx">
-  <title>Using ACME certificates in Nginx</title>
-
-  <para>
-   NixOS supports fetching ACME certificates for you by setting
-   <literal>enableACME = true;</literal> in a virtualHost config. We first create self-signed
-   placeholder certificates in place of the real ACME certs. The placeholder
-   certs are overwritten when the ACME certs arrive. For
-   <literal>foo.example.com</literal> the config would look like this:
-  </para>
-
-<programlisting>
+  <section xml:id="module-security-acme-prerequisites">
+    <title>Prerequisites</title>
+    <para>
+      To use the ACME module, you must accept the provider's terms of
+      service by setting
+      <xref linkend="opt-security.acme.acceptTerms"></xref> to
+      <literal>true</literal>. The Let's Encrypt ToS can be found
+      <link xlink:href="https://letsencrypt.org/repository/">here</link>.
+    </para>
+    <para>
+      You must also set an email address to be used when creating
+      accounts with Let's Encrypt. You can set this for all certs with
+      <xref linkend="opt-security.acme.defaults.email"></xref> and/or on
+      a per-cert basis with
+      <xref linkend="opt-security.acme.certs._name_.email"></xref>. This
+      address is only used for registration and renewal reminders, and
+      cannot be used to administer the certificates in any way.
+    </para>
+    <para>
+      Alternatively, you can use a different ACME server by changing the
+      <xref linkend="opt-security.acme.defaults.server"></xref> option
+      to a provider of your choosing, or just change the server for one
+      cert with
+      <xref linkend="opt-security.acme.certs._name_.server"></xref>.
+    </para>
+    <para>
+      You will need an HTTP server or DNS server for verification. For
+      HTTP, the server must have a webroot defined that can serve
+      <filename>.well-known/acme-challenge</filename>. This directory
+      must be writeable by the user that will run the ACME client. For
+      DNS, you must set up credentials with your provider/server for use
+      with lego.
+    </para>
+  </section>
+  <section xml:id="module-security-acme-nginx">
+    <title>Using ACME certificates in Nginx</title>
+    <para>
+      NixOS supports fetching ACME certificates for you by setting
+      <literal>enableACME = true;</literal> in a virtualHost config. We
+      first create self-signed placeholder certificates in place of the
+      real ACME certs. The placeholder certs are overwritten when the
+      ACME certs arrive. For <literal>foo.example.com</literal> the
+      config would look like this:
+    </para>
+    <programlisting>
 security.acme.acceptTerms = true;
-security.acme.defaults.email = "admin+acme@example.com";
+security.acme.defaults.email = &quot;admin+acme@example.com&quot;;
 services.nginx = {
   enable = true;
   virtualHosts = {
-    "foo.example.com" = {
+    &quot;foo.example.com&quot; = {
       forceSSL = true;
       enableACME = true;
       # All serverAliases will be added as extra domain names on the certificate.
-      serverAliases = [ "bar.example.com" ];
-      locations."/" = {
-        root = "/var/www";
+      serverAliases = [ &quot;bar.example.com&quot; ];
+      locations.&quot;/&quot; = {
+        root = &quot;/var/www&quot;;
       };
     };
 
     # We can also add a different vhost and reuse the same certificate
     # but we have to append extraDomainNames manually beforehand:
-    # security.acme.certs."foo.example.com".extraDomainNames = [ "baz.example.com" ];
-    "baz.example.com" = {
+    # security.acme.certs.&quot;foo.example.com&quot;.extraDomainNames = [ &quot;baz.example.com&quot; ];
+    &quot;baz.example.com&quot; = {
       forceSSL = true;
-      useACMEHost = "foo.example.com";
-      locations."/" = {
-        root = "/var/www";
+      useACMEHost = &quot;foo.example.com&quot;;
+      locations.&quot;/&quot; = {
+        root = &quot;/var/www&quot;;
       };
     };
   };
 }
 </programlisting>
- </section>
- <section xml:id="module-security-acme-httpd">
-  <title>Using ACME certificates in Apache/httpd</title>
-
-  <para>
-   Using ACME certificates with Apache virtual hosts is identical
-   to using them with Nginx. The attribute names are all the same, just replace
-   "nginx" with "httpd" where appropriate.
-  </para>
- </section>
- <section xml:id="module-security-acme-configuring">
-  <title>Manual configuration of HTTP-01 validation</title>
-
-  <para>
-   First off you will need to set up a virtual host to serve the challenges.
-   This example uses a vhost called <literal>certs.example.com</literal>, with
-   the intent that you will generate certs for all your vhosts and redirect
-   everyone to HTTPS.
-  </para>
-
-<programlisting>
+  </section>
+  <section xml:id="module-security-acme-httpd">
+    <title>Using ACME certificates in Apache/httpd</title>
+    <para>
+      Using ACME certificates with Apache virtual hosts is identical to
+      using them with Nginx. The attribute names are all the same, just
+      replace &quot;nginx&quot; with &quot;httpd&quot; where
+      appropriate.
+    </para>
+  </section>
+  <section xml:id="module-security-acme-configuring">
+    <title>Manual configuration of HTTP-01 validation</title>
+    <para>
+      First off you will need to set up a virtual host to serve the
+      challenges. This example uses a vhost called
+      <literal>certs.example.com</literal>, with the intent that you
+      will generate certs for all your vhosts and redirect everyone to
+      HTTPS.
+    </para>
+    <programlisting>
 security.acme.acceptTerms = true;
-security.acme.defaults.email = "admin+acme@example.com";
+security.acme.defaults.email = &quot;admin+acme@example.com&quot;;
 
 # /var/lib/acme/.challenges must be writable by the ACME user
 # and readable by the Nginx user. The easiest way to achieve
 # this is to add the Nginx user to the ACME group.
-users.users.nginx.extraGroups = [ "acme" ];
+users.users.nginx.extraGroups = [ &quot;acme&quot; ];
 
 services.nginx = {
   enable = true;
   virtualHosts = {
-    "acmechallenge.example.com" = {
+    &quot;acmechallenge.example.com&quot; = {
       # Catchall vhost, will redirect users to HTTPS for all vhosts
-      serverAliases = [ "*.example.com" ];
-      locations."/.well-known/acme-challenge" = {
-        root = "/var/lib/acme/.challenges";
+      serverAliases = [ &quot;*.example.com&quot; ];
+      locations.&quot;/.well-known/acme-challenge&quot; = {
+        root = &quot;/var/lib/acme/.challenges&quot;;
       };
-      locations."/" = {
-        return = "301 https://$host$request_uri";
+      locations.&quot;/&quot; = {
+        return = &quot;301 https://$host$request_uri&quot;;
       };
     };
   };
 }
 # Alternative config for Apache
-users.users.wwwrun.extraGroups = [ "acme" ];
+users.users.wwwrun.extraGroups = [ &quot;acme&quot; ];
 services.httpd = {
   enable = true;
   virtualHosts = {
-    "acmechallenge.example.com" = {
+    &quot;acmechallenge.example.com&quot; = {
       # Catchall vhost, will redirect users to HTTPS for all vhosts
-      serverAliases = [ "*.example.com" ];
+      serverAliases = [ &quot;*.example.com&quot; ];
       # /var/lib/acme/.challenges must be writable by the ACME user and readable by the Apache user.
       # By default, this is the case.
-      documentRoot = "/var/lib/acme/.challenges";
+      documentRoot = &quot;/var/lib/acme/.challenges&quot;;
       extraConfig = ''
         RewriteEngine On
         RewriteCond %{HTTPS} off
@@ -157,92 +151,88 @@ services.httpd = {
   };
 }
 </programlisting>
-
-  <para>
-   Now you need to configure ACME to generate a certificate.
-  </para>
-
-<programlisting>
-security.acme.certs."foo.example.com" = {
-  webroot = "/var/lib/acme/.challenges";
-  email = "foo@example.com";
+    <para>
+      Now you need to configure ACME to generate a certificate.
+    </para>
+    <programlisting>
+security.acme.certs.&quot;foo.example.com&quot; = {
+  webroot = &quot;/var/lib/acme/.challenges&quot;;
+  email = &quot;foo@example.com&quot;;
   # Ensure that the web server you use can read the generated certs
   # Take a look at the group option for the web server you choose.
-  group = "nginx";
+  group = &quot;nginx&quot;;
   # Since we have a wildcard vhost to handle port 80,
   # we can generate certs for anything!
   # Just make sure your DNS resolves them.
-  extraDomainNames = [ "mail.example.com" ];
+  extraDomainNames = [ &quot;mail.example.com&quot; ];
 };
 </programlisting>
-
-  <para>
-   The private key <filename>key.pem</filename> and certificate
-   <filename>fullchain.pem</filename> will be put into
-   <filename>/var/lib/acme/foo.example.com</filename>.
-  </para>
-
-  <para>
-   Refer to <xref linkend="ch-options" /> for all available configuration
-   options for the <link linkend="opt-security.acme.certs">security.acme</link>
-   module.
-  </para>
- </section>
- <section xml:id="module-security-acme-config-dns">
-  <title>Configuring ACME for DNS validation</title>
-
-  <para>
-   This is useful if you want to generate a wildcard certificate, since
-   ACME servers will only hand out wildcard certs over DNS validation.
-   There are a number of supported DNS providers and servers you can utilise,
-   see the <link xlink:href="https://go-acme.github.io/lego/dns/">lego docs</link>
-   for provider/server specific configuration values. For the sake of these
-   docs, we will provide a fully self-hosted example using bind.
-  </para>
-
-<programlisting>
+    <para>
+      The private key <filename>key.pem</filename> and certificate
+      <filename>fullchain.pem</filename> will be put into
+      <filename>/var/lib/acme/foo.example.com</filename>.
+    </para>
+    <para>
+      Refer to <xref linkend="ch-options"></xref> for all available
+      configuration options for the
+      <link linkend="opt-security.acme.certs">security.acme</link>
+      module.
+    </para>
+  </section>
+  <section xml:id="module-security-acme-config-dns">
+    <title>Configuring ACME for DNS validation</title>
+    <para>
+      This is useful if you want to generate a wildcard certificate,
+      since ACME servers will only hand out wildcard certs over DNS
+      validation. There are a number of supported DNS providers and
+      servers you can utilise, see the
+      <link xlink:href="https://go-acme.github.io/lego/dns/">lego
+      docs</link> for provider/server specific configuration values. For
+      the sake of these docs, we will provide a fully self-hosted
+      example using bind.
+    </para>
+    <programlisting>
 services.bind = {
   enable = true;
   extraConfig = ''
-    include "/var/lib/secrets/dnskeys.conf";
+    include &quot;/var/lib/secrets/dnskeys.conf&quot;;
   '';
   zones = [
     rec {
-      name = "example.com";
-      file = "/var/db/bind/${name}";
+      name = &quot;example.com&quot;;
+      file = &quot;/var/db/bind/${name}&quot;;
       master = true;
-      extraConfig = "allow-update { key rfc2136key.example.com.; };";
+      extraConfig = &quot;allow-update { key rfc2136key.example.com.; };&quot;;
     }
   ];
 }
 
 # Now we can configure ACME
 security.acme.acceptTerms = true;
-security.acme.defaults.email = "admin+acme@example.com";
-security.acme.certs."example.com" = {
-  domain = "*.example.com";
-  dnsProvider = "rfc2136";
-  credentialsFile = "/var/lib/secrets/certs.secret";
+security.acme.defaults.email = &quot;admin+acme@example.com&quot;;
+security.acme.certs.&quot;example.com&quot; = {
+  domain = &quot;*.example.com&quot;;
+  dnsProvider = &quot;rfc2136&quot;;
+  credentialsFile = &quot;/var/lib/secrets/certs.secret&quot;;
   # We don't need to wait for propagation since this is a local DNS server
   dnsPropagationCheck = false;
 };
 </programlisting>
-
-  <para>
-   The <filename>dnskeys.conf</filename> and <filename>certs.secret</filename>
-   must be kept secure and thus you should not keep their contents in your
-   Nix config. Instead, generate them one time with a systemd service:
-  </para>
-
-<programlisting>
+    <para>
+      The <filename>dnskeys.conf</filename> and
+      <filename>certs.secret</filename> must be kept secure and thus you
+      should not keep their contents in your Nix config. Instead,
+      generate them one time with a systemd service:
+    </para>
+    <programlisting>
 systemd.services.dns-rfc2136-conf = {
-  requiredBy = ["acme-example.com.service" "bind.service"];
-  before = ["acme-example.com.service" "bind.service"];
+  requiredBy = [&quot;acme-example.com.service&quot; &quot;bind.service&quot;];
+  before = [&quot;acme-example.com.service&quot; &quot;bind.service&quot;];
   unitConfig = {
-    ConditionPathExists = "!/var/lib/secrets/dnskeys.conf";
+    ConditionPathExists = &quot;!/var/lib/secrets/dnskeys.conf&quot;;
   };
   serviceConfig = {
-    Type = "oneshot";
+    Type = &quot;oneshot&quot;;
     UMask = 0077;
   };
   path = [ pkgs.bind ];
@@ -254,7 +244,7 @@ systemd.services.dns-rfc2136-conf = {
     chmod 400 /var/lib/secrets/dnskeys.conf
 
     # extract secret value from the dnskeys.conf
-    while read x y; do if [ "$x" = "secret" ]; then secret="''${y:1:''${#y}-3}"; fi; done &lt; /var/lib/secrets/dnskeys.conf
+    while read x y; do if [ &quot;$x&quot; = &quot;secret&quot; ]; then secret=&quot;''${y:1:''${#y}-3}&quot;; fi; done &lt; /var/lib/secrets/dnskeys.conf
 
     cat &gt; /var/lib/secrets/certs.secret &lt;&lt; EOF
     RFC2136_NAMESERVER='127.0.0.1:53'
@@ -266,40 +256,37 @@ systemd.services.dns-rfc2136-conf = {
   '';
 };
 </programlisting>
-
-  <para>
-   Now you're all set to generate certs! You should monitor the first invocation
-   by running <literal>systemctl start acme-example.com.service &amp;
-   journalctl -fu acme-example.com.service</literal> and watching its log output.
-  </para>
- </section>
-
- <section xml:id="module-security-acme-config-dns-with-vhosts">
-  <title>Using DNS validation with web server virtual hosts</title>
-
-  <para>
-   It is possible to use DNS-01 validation with all certificates,
-   including those automatically configured via the Nginx/Apache
-   <link linkend="opt-services.nginx.virtualHosts._name_.enableACME"><literal>enableACME</literal></link>
-   option. This configuration pattern is fully
-   supported and part of the module's test suite for Nginx + Apache.
-  </para>
-
-  <para>
-   You must follow the guide above on configuring DNS-01 validation
-   first, however instead of setting the options for one certificate
-   (e.g. <xref linkend="opt-security.acme.certs._name_.dnsProvider" />)
-   you will set them as defaults
-   (e.g. <xref linkend="opt-security.acme.defaults.dnsProvider" />).
-  </para>
-
-<programlisting>
+    <para>
+      Now you're all set to generate certs! You should monitor the first
+      invocation by running
+      <literal>systemctl start acme-example.com.service &amp; journalctl -fu acme-example.com.service</literal>
+      and watching its log output.
+    </para>
+  </section>
+  <section xml:id="module-security-acme-config-dns-with-vhosts">
+    <title>Using DNS validation with web server virtual hosts</title>
+    <para>
+      It is possible to use DNS-01 validation with all certificates,
+      including those automatically configured via the Nginx/Apache
+      <link linkend="opt-services.nginx.virtualHosts._name_.enableACME"><literal>enableACME</literal></link>
+      option. This configuration pattern is fully supported and part of
+      the module's test suite for Nginx + Apache.
+    </para>
+    <para>
+      You must follow the guide above on configuring DNS-01 validation
+      first, however instead of setting the options for one certificate
+      (e.g.
+      <xref linkend="opt-security.acme.certs._name_.dnsProvider"></xref>)
+      you will set them as defaults (e.g.
+      <xref linkend="opt-security.acme.defaults.dnsProvider"></xref>).
+    </para>
+    <programlisting>
 # Configure ACME appropriately
 security.acme.acceptTerms = true;
-security.acme.defaults.email = "admin+acme@example.com";
+security.acme.defaults.email = &quot;admin+acme@example.com&quot;;
 security.acme.defaults = {
-  dnsProvider = "rfc2136";
-  credentialsFile = "/var/lib/secrets/certs.secret";
+  dnsProvider = &quot;rfc2136&quot;;
+  credentialsFile = &quot;/var/lib/secrets/certs.secret&quot;;
   # We don't need to wait for propagation since this is a local DNS server
   dnsPropagationCheck = false;
 };
@@ -309,97 +296,92 @@ security.acme.defaults = {
 services.nginx = {
   enable = true;
   virtualHosts = {
-    "foo.example.com" = {
+    &quot;foo.example.com&quot; = {
       enableACME = true;
       acmeRoot = null;
     };
   };
 }
 </programlisting>
-
-  <para>
-   And that's it! Next time your configuration is rebuilt, or when
-   you add a new virtualHost, it will be DNS-01 validated.
-  </para>
- </section>
-
- <section xml:id="module-security-acme-root-owned">
-  <title>Using ACME with services demanding root owned certificates</title>
-
-  <para>
-   Some services refuse to start if the configured certificate files
-   are not owned by root. PostgreSQL and OpenSMTPD are examples of these.
-   There is no way to change the user the ACME module uses (it will always be
-   <literal>acme</literal>), however you can use systemd's
-   <literal>LoadCredential</literal> feature to resolve this elegantly.
-   Below is an example configuration for OpenSMTPD, but this pattern
-   can be applied to any service.
-  </para>
-
-<programlisting>
+    <para>
+      And that's it! Next time your configuration is rebuilt, or when
+      you add a new virtualHost, it will be DNS-01 validated.
+    </para>
+  </section>
+  <section xml:id="module-security-acme-root-owned">
+    <title>Using ACME with services demanding root owned
+    certificates</title>
+    <para>
+      Some services refuse to start if the configured certificate files
+      are not owned by root. PostgreSQL and OpenSMTPD are examples of
+      these. There is no way to change the user the ACME module uses (it
+      will always be <literal>acme</literal>), however you can use
+      systemd's <literal>LoadCredential</literal> feature to resolve
+      this elegantly. Below is an example configuration for OpenSMTPD,
+      but this pattern can be applied to any service.
+    </para>
+    <programlisting>
 # Configure ACME however you like (DNS or HTTP validation), adding
 # the following configuration for the relevant certificate.
 # Note: You cannot use `systemctl reload` here as that would mean
 # the LoadCredential configuration below would be skipped and
 # the service would continue to use old certificates.
-security.acme.certs."mail.example.com".postRun = ''
+security.acme.certs.&quot;mail.example.com&quot;.postRun = ''
   systemctl restart opensmtpd
 '';
 
 # Now you must augment OpenSMTPD's systemd service to load
 # the certificate files.
-systemd.services.opensmtpd.requires = ["acme-finished-mail.example.com.target"];
+systemd.services.opensmtpd.requires = [&quot;acme-finished-mail.example.com.target&quot;];
 systemd.services.opensmtpd.serviceConfig.LoadCredential = let
-  certDir = config.security.acme.certs."mail.example.com".directory;
+  certDir = config.security.acme.certs.&quot;mail.example.com&quot;.directory;
 in [
-  "cert.pem:${certDir}/cert.pem"
-  "key.pem:${certDir}/key.pem"
+  &quot;cert.pem:${certDir}/cert.pem&quot;
+  &quot;key.pem:${certDir}/key.pem&quot;
 ];
 
 # Finally, configure OpenSMTPD to use these certs.
 services.opensmtpd = let
-  credsDir = "/run/credentials/opensmtpd.service";
+  credsDir = &quot;/run/credentials/opensmtpd.service&quot;;
 in {
   enable = true;
   setSendmail = false;
   serverConfiguration = ''
-    pki mail.example.com cert "${credsDir}/cert.pem"
-    pki mail.example.com key "${credsDir}/key.pem"
+    pki mail.example.com cert &quot;${credsDir}/cert.pem&quot;
+    pki mail.example.com key &quot;${credsDir}/key.pem&quot;
     listen on localhost tls pki mail.example.com
     action act1 relay host smtp://127.0.0.1:10027
     match for local action act1
   '';
 };
 </programlisting>
- </section>
-
- <section xml:id="module-security-acme-regenerate">
-  <title>Regenerating certificates</title>
-
-  <para>
-   Should you need to regenerate a particular certificate in a hurry, such
-   as when a vulnerability is found in Let's Encrypt, there is now a convenient
-   mechanism for doing so. Running
-   <literal>systemctl clean --what=state acme-example.com.service</literal>
-   will remove all certificate files and the account data for the given domain,
-   allowing you to then <literal>systemctl start acme-example.com.service</literal>
-   to generate fresh ones.
-  </para>
- </section>
- <section xml:id="module-security-acme-fix-jws">
-  <title>Fixing JWS Verification error</title>
-
-  <para>
-   It is possible that your account credentials file may become corrupt and need
-   to be regenerated. In this scenario lego will produce the error <literal>JWS verification error</literal>.
-   The solution is to simply delete the associated accounts file and
-   re-run the affected service(s).
-  </para>
-
-<programlisting>
+  </section>
+  <section xml:id="module-security-acme-regenerate">
+    <title>Regenerating certificates</title>
+    <para>
+      Should you need to regenerate a particular certificate in a hurry,
+      such as when a vulnerability is found in Let's Encrypt, there is
+      now a convenient mechanism for doing so. Running
+      <literal>systemctl clean --what=state acme-example.com.service</literal>
+      will remove all certificate files and the account data for the
+      given domain, allowing you to then
+      <literal>systemctl start acme-example.com.service</literal> to
+      generate fresh ones.
+    </para>
+  </section>
+  <section xml:id="module-security-acme-fix-jws">
+    <title>Fixing JWS Verification error</title>
+    <para>
+      It is possible that your account credentials file may become
+      corrupt and need to be regenerated. In this scenario lego will
+      produce the error <literal>JWS verification error</literal>. The
+      solution is to simply delete the associated accounts file and
+      re-run the affected service(s).
+    </para>
+    <programlisting>
 # Find the accounts folder for the certificate
 systemctl cat acme-example.com.service | grep -Po 'accounts/[^:]*'
-export accountdir="$(!!)"
+export accountdir=&quot;$(!!)&quot;
 # Move this folder to some place else
 mv /var/lib/acme/.lego/$accountdir{,.bak}
 # Recreate the folder using systemd-tmpfiles
@@ -408,6 +390,5 @@ systemd-tmpfiles --create
 # Note: Do this for all certs that share the same account email address
 systemctl start acme-example.com.service
 </programlisting>
-
- </section>
+  </section>
 </chapter>