summaryrefslogtreecommitdiffstats
path: root/nixos
diff options
context:
space:
mode:
authorLucas Savva <lucas@m1cr0man.com>2021-12-04 18:09:43 +0000
committerLucas Savva <lucas@m1cr0man.com>2021-12-26 16:49:55 +0000
commit8d01b0862d3d52d72539cff65a405c09d864f82f (patch)
tree8064ed968c2b767e32fbb872851bf4ff8f4d37f6 /nixos
parent07c15833093b9db5dacb3829afda03d7c71cc077 (diff)
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.
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/security/acme.nix34
-rw-r--r--nixos/modules/security/acme.xml163
-rw-r--r--nixos/modules/services/networking/prosody.xml2
-rw-r--r--nixos/modules/services/web-apps/discourse.xml2
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.xml4
5 files changed, 174 insertions, 31 deletions
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index c39653d174e5..1b116482caeb 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 bf93800a0af4..f623cc509be6 100644
--- a/nixos/modules/security/acme.xml
+++ b/nixos/modules/security/acme.xml
@@ -7,8 +7,9 @@
<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 <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 &gt; /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 &gt; /var/lib/secrets/certs.secret &lt;&lt; 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 &gt; /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 &gt; /var/lib/secrets/certs.secret &lt;&lt; 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 471240cd1475..6358d744ff78 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 e91d3eac422d..ad9b65abf51e 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 97373bc6d9a8..ff44c724adf4 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>