nixos/acme: convert manual chapter to MD
This commit is contained in:
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
```
|
||||
+235
-254
@@ -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 & 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 & 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.
|
||||
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>
|
||||
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" />.
|
||||
</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 = "admin+acme@example.com";
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts = {
|
||||
"foo.example.com" = {
|
||||
"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";
|
||||
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" = {
|
||||
# security.acme.certs."foo.example.com".extraDomainNames = [ "baz.example.com" ];
|
||||
"baz.example.com" = {
|
||||
forceSSL = true;
|
||||
useACMEHost = "foo.example.com";
|
||||
locations."/" = {
|
||||
root = "/var/www";
|
||||
useACMEHost = "foo.example.com";
|
||||
locations."/" = {
|
||||
root = "/var/www";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
</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 "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>
|
||||
security.acme.acceptTerms = true;
|
||||
security.acme.defaults.email = "admin+acme@example.com";
|
||||
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" ];
|
||||
users.users.nginx.extraGroups = [ "acme" ];
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts = {
|
||||
"acmechallenge.example.com" = {
|
||||
"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";
|
||||
serverAliases = [ "*.example.com" ];
|
||||
locations."/.well-known/acme-challenge" = {
|
||||
root = "/var/lib/acme/.challenges";
|
||||
};
|
||||
locations."/" = {
|
||||
return = "301 https://$host$request_uri";
|
||||
locations."/" = {
|
||||
return = "301 https://$host$request_uri";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
# Alternative config for Apache
|
||||
users.users.wwwrun.extraGroups = [ "acme" ];
|
||||
users.users.wwwrun.extraGroups = [ "acme" ];
|
||||
services.httpd = {
|
||||
enable = true;
|
||||
virtualHosts = {
|
||||
"acmechallenge.example.com" = {
|
||||
"acmechallenge.example.com" = {
|
||||
# Catchall vhost, will redirect users to HTTPS for all vhosts
|
||||
serverAliases = [ "*.example.com" ];
|
||||
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";
|
||||
documentRoot = "/var/lib/acme/.challenges";
|
||||
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."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";
|
||||
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" ];
|
||||
extraDomainNames = [ "mail.example.com" ];
|
||||
};
|
||||
</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 "/var/lib/secrets/dnskeys.conf";
|
||||
'';
|
||||
zones = [
|
||||
rec {
|
||||
name = "example.com";
|
||||
file = "/var/db/bind/${name}";
|
||||
name = "example.com";
|
||||
file = "/var/db/bind/${name}";
|
||||
master = true;
|
||||
extraConfig = "allow-update { key rfc2136key.example.com.; };";
|
||||
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";
|
||||
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;
|
||||
};
|
||||
</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 = ["acme-example.com.service" "bind.service"];
|
||||
before = ["acme-example.com.service" "bind.service"];
|
||||
unitConfig = {
|
||||
ConditionPathExists = "!/var/lib/secrets/dnskeys.conf";
|
||||
ConditionPathExists = "!/var/lib/secrets/dnskeys.conf";
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
Type = "oneshot";
|
||||
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 < /var/lib/secrets/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'
|
||||
@@ -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 &
|
||||
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 & 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 = "admin+acme@example.com";
|
||||
security.acme.defaults = {
|
||||
dnsProvider = "rfc2136";
|
||||
credentialsFile = "/var/lib/secrets/certs.secret";
|
||||
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;
|
||||
};
|
||||
@@ -309,97 +296,92 @@ security.acme.defaults = {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts = {
|
||||
"foo.example.com" = {
|
||||
"foo.example.com" = {
|
||||
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."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.requires = ["acme-finished-mail.example.com.target"];
|
||||
systemd.services.opensmtpd.serviceConfig.LoadCredential = let
|
||||
certDir = config.security.acme.certs."mail.example.com".directory;
|
||||
certDir = config.security.acme.certs."mail.example.com".directory;
|
||||
in [
|
||||
"cert.pem:${certDir}/cert.pem"
|
||||
"key.pem:${certDir}/key.pem"
|
||||
"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";
|
||||
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"
|
||||
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>
|
||||
|
||||
<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="$(!!)"
|
||||
# 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>
|
||||
|
||||
Reference in New Issue
Block a user