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.
This commit is contained in:
parent
07c1583309
commit
8d01b0862d
|
@ -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
|
||||
|
|
|
@ -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
|
||||
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
|
||||
# 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
|
||||
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>
|
||||
|
||||
|
|
|
@ -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" = {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue