diff options
author | Lucas Savva <lucas@m1cr0man.com> | 2021-12-04 18:09:43 +0000 |
---|---|---|
committer | Lucas Savva <lucas@m1cr0man.com> | 2021-12-26 16:49:55 +0000 |
commit | 8d01b0862d3d52d72539cff65a405c09d864f82f (patch) | |
tree | 8064ed968c2b767e32fbb872851bf4ff8f4d37f6 | |
parent | 07c15833093b9db5dacb3829afda03d7c71cc077 (diff) | |
download | nixpkgs-8d01b0862d3d52d72539cff65a405c09d864f82f.tar nixpkgs-8d01b0862d3d52d72539cff65a405c09d864f82f.tar.gz nixpkgs-8d01b0862d3d52d72539cff65a405c09d864f82f.tar.bz2 nixpkgs-8d01b0862d3d52d72539cff65a405c09d864f82f.tar.lz nixpkgs-8d01b0862d3d52d72539cff65a405c09d864f82f.tar.xz nixpkgs-8d01b0862d3d52d72539cff65a405c09d864f82f.tar.zst nixpkgs-8d01b0862d3d52d72539cff65a405c09d864f82f.zip |
nixos/acme: Update documentation
- Added defaultText for all inheritable options. - Add docs on using new defaults option to configure DNS validation for all domains. - Update DNS docs to show using a service to configure rfc2136 instead of manual steps.
-rw-r--r-- | nixos/modules/security/acme.nix | 34 | ||||
-rw-r--r-- | nixos/modules/security/acme.xml | 163 | ||||
-rw-r--r-- | nixos/modules/services/networking/prosody.xml | 2 | ||||
-rw-r--r-- | nixos/modules/services/web-apps/discourse.xml | 2 | ||||
-rw-r--r-- | nixos/modules/services/web-apps/jitsi-meet.xml | 4 |
5 files changed, 174 insertions, 31 deletions
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix index c39653d174e..1b116482cae 100644 --- a/nixos/modules/security/acme.nix +++ b/nixos/modules/security/acme.nix @@ -425,6 +425,8 @@ let certConfigs = mapAttrs certToConfig cfg.certs; + mkDefaultText = val: "Inherit from security.acme.defaults, otherwise ${val}" ; + # These options can be specified within # security.acme or security.acme.certs.<name> inheritableOpts = @@ -432,12 +434,14 @@ let validMinDays = mkOption { type = types.int; default = if inheritDefaults then defaults.validMinDays else 30; + defaultText = mkDefaultText "30"; description = "Minimum remaining validity before renewal in days."; }; renewInterval = mkOption { type = types.str; default = if inheritDefaults then defaults.renewInterval else "daily"; + defaultText = mkDefaultText "'daily'"; description = '' Systemd calendar expression when to check for renewal. See <citerefentry><refentrytitle>systemd.time</refentrytitle> @@ -452,6 +456,7 @@ let webroot = mkOption { type = types.nullOr types.str; default = if inheritDefaults then defaults.webroot else null; + defaultText = mkDefaultText "null"; example = "/var/lib/acme/acme-challenge"; description = '' Where the webroot of the HTTP vhost is located. @@ -465,6 +470,7 @@ let server = mkOption { type = types.nullOr types.str; default = if inheritDefaults then defaults.server else null; + defaultText = mkDefaultText "null"; description = '' ACME Directory Resource URI. Defaults to Let's Encrypt's production endpoint, @@ -475,6 +481,7 @@ let email = mkOption { type = types.str; default = if inheritDefaults then defaults.email else null; + defaultText = mkDefaultText "null"; description = '' Email address for account creation and correspondence from the CA. It is recommended to use the same email for all certs to avoid account @@ -485,12 +492,14 @@ let group = mkOption { type = types.str; default = if inheritDefaults then defaults.group else "acme"; + defaultText = mkDefaultText "'acme'"; description = "Group running the ACME client."; }; reloadServices = mkOption { type = types.listOf types.str; default = if inheritDefaults then defaults.reloadServices else []; + defaultText = mkDefaultText "[]"; description = '' The list of systemd services to call <code>systemctl try-reload-or-restart</code> on. @@ -500,6 +509,7 @@ let postRun = mkOption { type = types.lines; default = if inheritDefaults then defaults.postRun else ""; + defaultText = mkDefaultText "''"; example = "cp full.pem backup.pem"; description = '' Commands to run after new certificates go live. Note that @@ -512,6 +522,7 @@ let keyType = mkOption { type = types.str; default = if inheritDefaults then defaults.keyType else "ec256"; + defaultText = mkDefaultText "'ec256'"; description = '' Key type to use for private keys. For an up to date list of supported values check the --key-type option @@ -522,6 +533,7 @@ let dnsProvider = mkOption { type = types.nullOr types.str; default = if inheritDefaults then defaults.dnsProvider else null; + defaultText = mkDefaultText "null"; example = "route53"; description = '' DNS Challenge provider. For a list of supported providers, see the "code" @@ -532,6 +544,7 @@ let dnsResolver = mkOption { type = types.nullOr types.str; default = if inheritDefaults then defaults.dnsResolver else null; + defaultText = mkDefaultText "null"; example = "1.1.1.1:53"; description = '' Set the resolver to use for performing recursive DNS queries. Supported: @@ -543,6 +556,7 @@ let credentialsFile = mkOption { type = types.path; default = if inheritDefaults then defaults.credentialsFile else null; + defaultText = mkDefaultText "null"; description = '' Path to an EnvironmentFile for the cert's service containing any required and optional environment variables for your selected dnsProvider. @@ -555,6 +569,7 @@ let dnsPropagationCheck = mkOption { type = types.bool; default = if inheritDefaults then defaults.dnsPropagationCheck else true; + defaultText = mkDefaultText "true"; description = '' Toggles lego DNS propagation check, which is used alongside DNS-01 challenge to ensure the DNS entries required are available. @@ -564,6 +579,7 @@ let ocspMustStaple = mkOption { type = types.bool; default = if inheritDefaults then defaults.ocspMustStaple else false; + defaultText = mkDefaultText "false"; description = '' Turns on the OCSP Must-Staple TLS extension. Make sure you know what you're doing! See: @@ -577,6 +593,7 @@ let extraLegoFlags = mkOption { type = types.listOf types.str; default = if inheritDefaults then defaults.extraLegoFlags else []; + defaultText = mkDefaultText "[]"; description = '' Additional global flags to pass to all lego commands. ''; @@ -585,6 +602,7 @@ let extraLegoRenewFlags = mkOption { type = types.listOf types.str; default = if inheritDefaults then defaults.extraLegoRenewFlags else []; + defaultText = mkDefaultText "[]"; description = '' Additional flags to pass to lego renew. ''; @@ -593,14 +611,24 @@ let extraLegoRunFlags = mkOption { type = types.listOf types.str; default = if inheritDefaults then defaults.extraLegoRunFlags else []; + defaultText = mkDefaultText "[]"; description = '' Additional flags to pass to lego run. ''; }; }; - certOpts = { name, ... }: { - options = (inheritableOpts { inherit (cfg) defaults; inheritDefaults = cfg.certs."${name}".inheritDefaults; }) // { + certOpts = { name, config, ... }: { + options = (inheritableOpts { + inherit (cfg) defaults; + # During doc generation, name = "<name>" and doesn't really + # exist as a cert. As such, handle undfined certs. + inheritDefaults = (lib.attrByPath + [name] + { inheritDefaults = false; } + cfg.certs + ).inheritDefaults; + }) // { # user option has been removed user = mkOption { visible = false; @@ -696,7 +724,7 @@ in { }; defaults = mkOption { - type = types.submodule ({ ... }: { options = inheritableOpts {}; }); + type = types.submodule { options = inheritableOpts {}; }; description = '' Default values inheritable by all configured certs. You can use this to define options shared by all your certs. These defaults diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml index bf93800a0af..f623cc509be 100644 --- a/nixos/modules/security/acme.xml +++ b/nixos/modules/security/acme.xml @@ -7,8 +7,9 @@ <para> 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 <literal>lego</literal> - is used under the hood. + 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 @@ -29,7 +30,7 @@ <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 - <literal><xref linkend="opt-security.acme.email" /></literal> + <literal><xref linkend="opt-security.acme.defaults.email" /></literal> and/or on a per-cert basis with <literal><xref linkend="opt-security.acme.certs._name_.email" /></literal>. This address is only used for registration and renewal reminders, @@ -38,7 +39,7 @@ <para> Alternatively, you can use a different ACME server by changing the - <literal><xref linkend="opt-security.acme.server" /></literal> option + <literal><xref linkend="opt-security.acme.defaults.server" /></literal> option to a provider of your choosing, or just change the server for one cert with <literal><xref linkend="opt-security.acme.certs._name_.server" /></literal>. </para> @@ -60,12 +61,12 @@ = 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. + <literal>foo.example.com</literal> the config would look like this: </para> <programlisting> <xref linkend="opt-security.acme.acceptTerms" /> = true; -<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com"; +<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com"; services.nginx = { <link linkend="opt-services.nginx.enable">enable</link> = true; <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = { @@ -114,7 +115,7 @@ services.nginx = { <programlisting> <xref linkend="opt-security.acme.acceptTerms" /> = true; -<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com"; +<xref linkend="opt-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 @@ -218,7 +219,7 @@ services.bind = { # Now we can configure ACME <xref linkend="opt-security.acme.acceptTerms" /> = true; -<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com"; +<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com"; <xref linkend="opt-security.acme.certs" />."example.com" = { <link linkend="opt-security.acme.certs._name_.domain">domain</link> = "*.example.com"; <link linkend="opt-security.acme.certs._name_.dnsProvider">dnsProvider</link> = "rfc2136"; @@ -231,25 +232,39 @@ services.bind = { <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 these commands: + Nix config. Instead, generate them one time with a systemd service: </para> <programlisting> -mkdir -p /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 - -# Copy the secret value from the dnskeys.conf, and put it in -# RFC2136_TSIG_SECRET below - -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='your secret key' -EOF -chmod 400 /var/lib/secrets/certs.secret +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 + 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 + + # Copy the secret value from the dnskeys.conf, and put it in + # RFC2136_TSIG_SECRET below + + 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='your secret key' + EOF + chmod 400 /var/lib/secrets/certs.secret + ''; +}; </programlisting> <para> @@ -258,6 +273,106 @@ chmod 400 /var/lib/secrets/certs.secret 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 + <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link></literal> + 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> +# Configure ACME appropriately +<xref linkend="opt-security.acme.acceptTerms" /> = true; +<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com"; +<xref linkend="opt-security.acme.defaults" /> = { + <link linkend="opt-security.acme.defaults.dnsProvider">dnsProvider</link> = "rfc2136"; + <link linkend="opt-security.acme.defaults.credentialsFile">credentialsFile</link> = "/var/lib/secrets/certs.secret"; + # We don't need to wait for propagation since this is a local DNS server + <link linkend="opt-security.acme.defaults.dnsPropagationCheck">dnsPropagationCheck</link> = false; +}; + +# For each virtual host you would like to use DNS-01 validation with, +# set acmeRoot = null +services.nginx = { + <link linkend="opt-services.nginx.enable">enable</link> = true; + <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = { + "foo.example.com" = { + <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true; + <link linkend="opt-services.nginx.virtualHosts._name_.acmeRoot">acmeRoot</link> = 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> +# 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. +<link linkend="opt-systemd.services._name_.requires">systemd.services.opensmtpd.requires</link> = ["acme-finished-mail.example.com.target"]; +<link linkend="opt-systemd.services._name_.serviceConfig">systemd.services.opensmtpd.serviceConfig.LoadCredential</link> = 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 + ''; +}; +</programlisting> + </section> + <section xml:id="module-security-acme-regenerate"> <title>Regenerating certificates</title> diff --git a/nixos/modules/services/networking/prosody.xml b/nixos/modules/services/networking/prosody.xml index 471240cd147..6358d744ff7 100644 --- a/nixos/modules/services/networking/prosody.xml +++ b/nixos/modules/services/networking/prosody.xml @@ -72,7 +72,7 @@ services.prosody = { a TLS certificate for the three endponits: <programlisting> security.acme = { - <link linkend="opt-security.acme.email">email</link> = "root@example.org"; + <link linkend="opt-security.acme.defaults.email">email</link> = "root@example.org"; <link linkend="opt-security.acme.acceptTerms">acceptTerms</link> = true; <link linkend="opt-security.acme.certs">certs</link> = { "example.org" = { diff --git a/nixos/modules/services/web-apps/discourse.xml b/nixos/modules/services/web-apps/discourse.xml index e91d3eac422..ad9b65abf51 100644 --- a/nixos/modules/services/web-apps/discourse.xml +++ b/nixos/modules/services/web-apps/discourse.xml @@ -25,7 +25,7 @@ services.discourse = { }; <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file"; }; -<link linkend="opt-security.acme.email">security.acme.email</link> = "me@example.com"; +<link linkend="opt-security.acme.defaults.email">security.acme.email</link> = "me@example.com"; <link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true; </programlisting> </para> diff --git a/nixos/modules/services/web-apps/jitsi-meet.xml b/nixos/modules/services/web-apps/jitsi-meet.xml index 97373bc6d9a..ff44c724adf 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.xml +++ b/nixos/modules/services/web-apps/jitsi-meet.xml @@ -20,7 +20,7 @@ }; <link linkend="opt-services.jitsi-videobridge.openFirewall">services.jitsi-videobridge.openFirewall</link> = true; <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ]; - <link linkend="opt-security.acme.email">security.acme.email</link> = "me@example.com"; + <link linkend="opt-security.acme.defaults.email">security.acme.email</link> = "me@example.com"; <link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true; }</programlisting> </para> @@ -46,7 +46,7 @@ }; <link linkend="opt-services.jitsi-videobridge.openFirewall">services.jitsi-videobridge.openFirewall</link> = true; <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ]; - <link linkend="opt-security.acme.email">security.acme.email</link> = "me@example.com"; + <link linkend="opt-security.acme.defaults.email">security.acme.email</link> = "me@example.com"; <link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true; }</programlisting> </para> |