diff options
Diffstat (limited to 'nixos/modules/security/acme/default.md')
-rw-r--r-- | nixos/modules/security/acme/default.md | 366 |
1 files changed, 190 insertions, 176 deletions
diff --git a/nixos/modules/security/acme/default.md b/nixos/modules/security/acme/default.md index 38fbfbf0caec..a6ef2a3fdf18 100644 --- a/nixos/modules/security/acme/default.md +++ b/nixos/modules/security/acme/default.md @@ -46,33 +46,35 @@ certs are overwritten when the ACME certs arrive. For `foo.example.com` the config would look like this: ```nix -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"; +{ + 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"; + # 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} @@ -89,65 +91,69 @@ the intent that you will generate certs for all your vhosts and redirect everyone to HTTPS. ```nix -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"; +{ + 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] - ''; + # 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. ```nix -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" ]; -}; +{ + 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 @@ -168,31 +174,33 @@ for provider/server specific configuration values. For the sake of these docs, we will provide a fully self-hosted example using bind. ```nix -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"; - environmentFile = "/var/lib/secrets/certs.secret"; - # We don't need to wait for propagation since this is a local DNS server - dnsPropagationCheck = false; -}; +{ + 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"; + environmentFile = "/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` @@ -200,36 +208,38 @@ 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: ```nix -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; +{ + 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 + ''; }; - 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 @@ -251,27 +261,29 @@ you will set them as defaults (e.g. [](#opt-security.acme.defaults.dnsProvider)). ```nix -# Configure ACME appropriately -security.acme.acceptTerms = true; -security.acme.defaults.email = "admin+acme@example.com"; -security.acme.defaults = { - dnsProvider = "rfc2136"; - environmentFile = "/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; +{ + # Configure ACME appropriately + security.acme.acceptTerms = true; + security.acme.defaults.email = "admin+acme@example.com"; + security.acme.defaults = { + dnsProvider = "rfc2136"; + environmentFile = "/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 @@ -288,39 +300,41 @@ Below is an example configuration for OpenSMTPD, but this pattern can be applied to any service. ```nix -# 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 +{ + # 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} |