···
1
-
<chapter xmlns="http://docbook.org/ns/docbook"
2
-
xmlns:xlink="http://www.w3.org/1999/xlink"
3
-
xmlns:xi="http://www.w3.org/2001/XInclude"
5
-
xml:id="module-security-acme">
6
-
<title>SSL/TLS Certificates with ACME</title>
8
-
NixOS supports automatic domain validation & certificate retrieval and
9
-
renewal using the ACME protocol. Any provider can be used, but by default
10
-
NixOS uses Let's Encrypt. The alternative ACME client
11
-
<link xlink:href="https://go-acme.github.io/lego/">lego</link> is used under
15
-
Automatic cert validation and configuration for Apache and Nginx virtual
16
-
hosts is included in NixOS, however if you would like to generate a wildcard
17
-
cert or you are not using a web server you will have to configure DNS
20
-
<section xml:id="module-security-acme-prerequisites">
21
-
<title>Prerequisites</title>
1
+
<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-security-acme">
2
+
<title>SSL/TLS Certificates with ACME</title>
24
-
To use the ACME module, you must accept the provider's terms of service
25
-
by setting <xref linkend="opt-security.acme.acceptTerms" />
26
-
to <literal>true</literal>. The Let's Encrypt ToS can be found
27
-
<link xlink:href="https://letsencrypt.org/repository/">here</link>.
4
+
NixOS supports automatic domain validation & certificate
5
+
retrieval and renewal using the ACME protocol. Any provider can be
6
+
used, but by default NixOS uses Let's Encrypt. The alternative ACME
8
+
<link xlink:href="https://go-acme.github.io/lego/">lego</link> is
31
-
You must also set an email address to be used when creating accounts with
32
-
Let's Encrypt. You can set this for all certs with
33
-
<xref linkend="opt-security.acme.defaults.email" />
34
-
and/or on a per-cert basis with
35
-
<xref linkend="opt-security.acme.certs._name_.email" />.
36
-
This address is only used for registration and renewal reminders,
37
-
and cannot be used to administer the certificates in any way.
12
+
Automatic cert validation and configuration for Apache and Nginx
13
+
virtual hosts is included in NixOS, however if you would like to
14
+
generate a wildcard cert or you are not using a web server you will
15
+
have to configure DNS based validation.
41
-
Alternatively, you can use a different ACME server by changing the
42
-
<xref linkend="opt-security.acme.defaults.server" /> option
43
-
to a provider of your choosing, or just change the server for one cert with
44
-
<xref linkend="opt-security.acme.certs._name_.server" />.
48
-
You will need an HTTP server or DNS server for verification. For HTTP,
49
-
the server must have a webroot defined that can serve
50
-
<filename>.well-known/acme-challenge</filename>. This directory must be
51
-
writeable by the user that will run the ACME client. For DNS, you must
52
-
set up credentials with your provider/server for use with lego.
55
-
<section xml:id="module-security-acme-nginx">
56
-
<title>Using ACME certificates in Nginx</title>
59
-
NixOS supports fetching ACME certificates for you by setting
60
-
<literal>enableACME = true;</literal> in a virtualHost config. We first create self-signed
61
-
placeholder certificates in place of the real ACME certs. The placeholder
62
-
certs are overwritten when the ACME certs arrive. For
63
-
<literal>foo.example.com</literal> the config would look like this:
17
+
<section xml:id="module-security-acme-prerequisites">
18
+
<title>Prerequisites</title>
20
+
To use the ACME module, you must accept the provider's terms of
22
+
<xref linkend="opt-security.acme.acceptTerms"></xref> to
23
+
<literal>true</literal>. The Let's Encrypt ToS can be found
24
+
<link xlink:href="https://letsencrypt.org/repository/">here</link>.
27
+
You must also set an email address to be used when creating
28
+
accounts with Let's Encrypt. You can set this for all certs with
29
+
<xref linkend="opt-security.acme.defaults.email"></xref> and/or on
30
+
a per-cert basis with
31
+
<xref linkend="opt-security.acme.certs._name_.email"></xref>. This
32
+
address is only used for registration and renewal reminders, and
33
+
cannot be used to administer the certificates in any way.
36
+
Alternatively, you can use a different ACME server by changing the
37
+
<xref linkend="opt-security.acme.defaults.server"></xref> option
38
+
to a provider of your choosing, or just change the server for one
40
+
<xref linkend="opt-security.acme.certs._name_.server"></xref>.
43
+
You will need an HTTP server or DNS server for verification. For
44
+
HTTP, the server must have a webroot defined that can serve
45
+
<filename>.well-known/acme-challenge</filename>. This directory
46
+
must be writeable by the user that will run the ACME client. For
47
+
DNS, you must set up credentials with your provider/server for use
51
+
<section xml:id="module-security-acme-nginx">
52
+
<title>Using ACME certificates in Nginx</title>
54
+
NixOS supports fetching ACME certificates for you by setting
55
+
<literal>enableACME = true;</literal> in a virtualHost config. We
56
+
first create self-signed placeholder certificates in place of the
57
+
real ACME certs. The placeholder certs are overwritten when the
58
+
ACME certs arrive. For <literal>foo.example.com</literal> the
59
+
config would look like this:
security.acme.acceptTerms = true;
68
-
security.acme.defaults.email = "admin+acme@example.com";
63
+
security.acme.defaults.email = "admin+acme@example.com";
72
-
"foo.example.com" = {
67
+
"foo.example.com" = {
# All serverAliases will be added as extra domain names on the certificate.
76
-
serverAliases = [ "bar.example.com" ];
71
+
serverAliases = [ "bar.example.com" ];
72
+
locations."/" = {
73
+
root = "/var/www";
# We can also add a different vhost and reuse the same certificate
# but we have to append extraDomainNames manually beforehand:
84
-
# security.acme.certs."foo.example.com".extraDomainNames = [ "baz.example.com" ];
85
-
"baz.example.com" = {
79
+
# security.acme.certs."foo.example.com".extraDomainNames = [ "baz.example.com" ];
80
+
"baz.example.com" = {
87
-
useACMEHost = "foo.example.com";
82
+
useACMEHost = "foo.example.com";
83
+
locations."/" = {
84
+
root = "/var/www";
96
-
<section xml:id="module-security-acme-httpd">
97
-
<title>Using ACME certificates in Apache/httpd</title>
100
-
Using ACME certificates with Apache virtual hosts is identical
101
-
to using them with Nginx. The attribute names are all the same, just replace
102
-
"nginx" with "httpd" where appropriate.
105
-
<section xml:id="module-security-acme-configuring">
106
-
<title>Manual configuration of HTTP-01 validation</title>
109
-
First off you will need to set up a virtual host to serve the challenges.
110
-
This example uses a vhost called <literal>certs.example.com</literal>, with
111
-
the intent that you will generate certs for all your vhosts and redirect
91
+
<section xml:id="module-security-acme-httpd">
92
+
<title>Using ACME certificates in Apache/httpd</title>
94
+
Using ACME certificates with Apache virtual hosts is identical to
95
+
using them with Nginx. The attribute names are all the same, just
96
+
replace "nginx" with "httpd" where
100
+
<section xml:id="module-security-acme-configuring">
101
+
<title>Manual configuration of HTTP-01 validation</title>
103
+
First off you will need to set up a virtual host to serve the
104
+
challenges. This example uses a vhost called
105
+
<literal>certs.example.com</literal>, with the intent that you
106
+
will generate certs for all your vhosts and redirect everyone to
security.acme.acceptTerms = true;
117
-
security.acme.defaults.email = "admin+acme@example.com";
111
+
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.
122
-
users.users.nginx.extraGroups = [ "acme" ];
116
+
users.users.nginx.extraGroups = [ "acme" ];
127
-
"acmechallenge.example.com" = {
121
+
"acmechallenge.example.com" = {
# Catchall vhost, will redirect users to HTTPS for all vhosts
129
-
serverAliases = [ "*.example.com" ];
130
-
locations."/.well-known/acme-challenge" = {
131
-
root = "/var/lib/acme/.challenges";
123
+
serverAliases = [ "*.example.com" ];
124
+
locations."/.well-known/acme-challenge" = {
125
+
root = "/var/lib/acme/.challenges";
134
-
return = "301 https://$host$request_uri";
127
+
locations."/" = {
128
+
return = "301 https://$host$request_uri";
# Alternative config for Apache
140
-
users.users.wwwrun.extraGroups = [ "acme" ];
134
+
users.users.wwwrun.extraGroups = [ "acme" ];
144
-
"acmechallenge.example.com" = {
138
+
"acmechallenge.example.com" = {
# Catchall vhost, will redirect users to HTTPS for all vhosts
146
-
serverAliases = [ "*.example.com" ];
140
+
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.
149
-
documentRoot = "/var/lib/acme/.challenges";
143
+
documentRoot = "/var/lib/acme/.challenges";
···
162
-
Now you need to configure ACME to generate a certificate.
166
-
security.acme.certs."foo.example.com" = {
167
-
webroot = "/var/lib/acme/.challenges";
168
-
email = "foo@example.com";
155
+
Now you need to configure ACME to generate a certificate.
158
+
security.acme.certs."foo.example.com" = {
159
+
webroot = "/var/lib/acme/.challenges";
160
+
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.
163
+
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.
175
-
extraDomainNames = [ "mail.example.com" ];
167
+
extraDomainNames = [ "mail.example.com" ];
180
-
The private key <filename>key.pem</filename> and certificate
181
-
<filename>fullchain.pem</filename> will be put into
182
-
<filename>/var/lib/acme/foo.example.com</filename>.
186
-
Refer to <xref linkend="ch-options" /> for all available configuration
187
-
options for the <link linkend="opt-security.acme.certs">security.acme</link>
191
-
<section xml:id="module-security-acme-config-dns">
192
-
<title>Configuring ACME for DNS validation</title>
195
-
This is useful if you want to generate a wildcard certificate, since
196
-
ACME servers will only hand out wildcard certs over DNS validation.
197
-
There are a number of supported DNS providers and servers you can utilise,
198
-
see the <link xlink:href="https://go-acme.github.io/lego/dns/">lego docs</link>
199
-
for provider/server specific configuration values. For the sake of these
200
-
docs, we will provide a fully self-hosted example using bind.
171
+
The private key <filename>key.pem</filename> and certificate
172
+
<filename>fullchain.pem</filename> will be put into
173
+
<filename>/var/lib/acme/foo.example.com</filename>.
176
+
Refer to <xref linkend="ch-options"></xref> for all available
177
+
configuration options for the
178
+
<link linkend="opt-security.acme.certs">security.acme</link>
182
+
<section xml:id="module-security-acme-config-dns">
183
+
<title>Configuring ACME for DNS validation</title>
185
+
This is useful if you want to generate a wildcard certificate,
186
+
since ACME servers will only hand out wildcard certs over DNS
187
+
validation. There are a number of supported DNS providers and
188
+
servers you can utilise, see the
189
+
<link xlink:href="https://go-acme.github.io/lego/dns/">lego
190
+
docs</link> for provider/server specific configuration values. For
191
+
the sake of these docs, we will provide a fully self-hosted
192
+
example using bind.
207
-
include "/var/lib/secrets/dnskeys.conf";
198
+
include "/var/lib/secrets/dnskeys.conf";
211
-
name = "example.com";
212
-
file = "/var/db/bind/${name}";
202
+
name = "example.com";
203
+
file = "/var/db/bind/${name}";
214
-
extraConfig = "allow-update { key rfc2136key.example.com.; };";
205
+
extraConfig = "allow-update { key rfc2136key.example.com.; };";
# Now we can configure ACME
security.acme.acceptTerms = true;
221
-
security.acme.defaults.email = "admin+acme@example.com";
222
-
security.acme.certs."example.com" = {
223
-
domain = "*.example.com";
224
-
dnsProvider = "rfc2136";
225
-
credentialsFile = "/var/lib/secrets/certs.secret";
212
+
security.acme.defaults.email = "admin+acme@example.com";
213
+
security.acme.certs."example.com" = {
214
+
domain = "*.example.com";
215
+
dnsProvider = "rfc2136";
216
+
credentialsFile = "/var/lib/secrets/certs.secret";
# We don't need to wait for propagation since this is a local DNS server
dnsPropagationCheck = false;
232
-
The <filename>dnskeys.conf</filename> and <filename>certs.secret</filename>
233
-
must be kept secure and thus you should not keep their contents in your
234
-
Nix config. Instead, generate them one time with a systemd service:
222
+
The <filename>dnskeys.conf</filename> and
223
+
<filename>certs.secret</filename> must be kept secure and thus you
224
+
should not keep their contents in your Nix config. Instead,
225
+
generate them one time with a systemd service:
systemd.services.dns-rfc2136-conf = {
239
-
requiredBy = ["acme-example.com.service" "bind.service"];
240
-
before = ["acme-example.com.service" "bind.service"];
229
+
requiredBy = ["acme-example.com.service" "bind.service"];
230
+
before = ["acme-example.com.service" "bind.service"];
242
-
ConditionPathExists = "!/var/lib/secrets/dnskeys.conf";
232
+
ConditionPathExists = "!/var/lib/secrets/dnskeys.conf";
235
+
Type = "oneshot";
···
chmod 400 /var/lib/secrets/dnskeys.conf
# extract secret value from the dnskeys.conf
257
-
while read x y; do if [ "$x" = "secret" ]; then secret="''${y:1:''${#y}-3}"; fi; done < /var/lib/secrets/dnskeys.conf
247
+
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'
···
271
-
Now you're all set to generate certs! You should monitor the first invocation
272
-
by running <literal>systemctl start acme-example.com.service &
273
-
journalctl -fu acme-example.com.service</literal> and watching its log output.
277
-
<section xml:id="module-security-acme-config-dns-with-vhosts">
278
-
<title>Using DNS validation with web server virtual hosts</title>
281
-
It is possible to use DNS-01 validation with all certificates,
282
-
including those automatically configured via the Nginx/Apache
283
-
<link linkend="opt-services.nginx.virtualHosts._name_.enableACME"><literal>enableACME</literal></link>
284
-
option. This configuration pattern is fully
285
-
supported and part of the module's test suite for Nginx + Apache.
289
-
You must follow the guide above on configuring DNS-01 validation
290
-
first, however instead of setting the options for one certificate
291
-
(e.g. <xref linkend="opt-security.acme.certs._name_.dnsProvider" />)
292
-
you will set them as defaults
293
-
(e.g. <xref linkend="opt-security.acme.defaults.dnsProvider" />).
260
+
Now you're all set to generate certs! You should monitor the first
261
+
invocation by running
262
+
<literal>systemctl start acme-example.com.service & journalctl -fu acme-example.com.service</literal>
263
+
and watching its log output.
266
+
<section xml:id="module-security-acme-config-dns-with-vhosts">
267
+
<title>Using DNS validation with web server virtual hosts</title>
269
+
It is possible to use DNS-01 validation with all certificates,
270
+
including those automatically configured via the Nginx/Apache
271
+
<link linkend="opt-services.nginx.virtualHosts._name_.enableACME"><literal>enableACME</literal></link>
272
+
option. This configuration pattern is fully supported and part of
273
+
the module's test suite for Nginx + Apache.
276
+
You must follow the guide above on configuring DNS-01 validation
277
+
first, however instead of setting the options for one certificate
279
+
<xref linkend="opt-security.acme.certs._name_.dnsProvider"></xref>)
280
+
you will set them as defaults (e.g.
281
+
<xref linkend="opt-security.acme.defaults.dnsProvider"></xref>).
# Configure ACME appropriately
security.acme.acceptTerms = true;
299
-
security.acme.defaults.email = "admin+acme@example.com";
286
+
security.acme.defaults.email = "admin+acme@example.com";
security.acme.defaults = {
301
-
dnsProvider = "rfc2136";
302
-
credentialsFile = "/var/lib/secrets/certs.secret";
288
+
dnsProvider = "rfc2136";
289
+
credentialsFile = "/var/lib/secrets/certs.secret";
# We don't need to wait for propagation since this is a local DNS server
dnsPropagationCheck = false;
···
312
-
"foo.example.com" = {
299
+
"foo.example.com" = {
321
-
And that's it! Next time your configuration is rebuilt, or when
322
-
you add a new virtualHost, it will be DNS-01 validated.
326
-
<section xml:id="module-security-acme-root-owned">
327
-
<title>Using ACME with services demanding root owned certificates</title>
330
-
Some services refuse to start if the configured certificate files
331
-
are not owned by root. PostgreSQL and OpenSMTPD are examples of these.
332
-
There is no way to change the user the ACME module uses (it will always be
333
-
<literal>acme</literal>), however you can use systemd's
334
-
<literal>LoadCredential</literal> feature to resolve this elegantly.
335
-
Below is an example configuration for OpenSMTPD, but this pattern
336
-
can be applied to any service.
307
+
And that's it! Next time your configuration is rebuilt, or when
308
+
you add a new virtualHost, it will be DNS-01 validated.
311
+
<section xml:id="module-security-acme-root-owned">
312
+
<title>Using ACME with services demanding root owned
313
+
certificates</title>
315
+
Some services refuse to start if the configured certificate files
316
+
are not owned by root. PostgreSQL and OpenSMTPD are examples of
317
+
these. There is no way to change the user the ACME module uses (it
318
+
will always be <literal>acme</literal>), however you can use
319
+
systemd's <literal>LoadCredential</literal> feature to resolve
320
+
this elegantly. Below is an example configuration for OpenSMTPD,
321
+
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.
345
-
security.acme.certs."mail.example.com".postRun = ''
329
+
security.acme.certs."mail.example.com".postRun = ''
systemctl restart opensmtpd
# Now you must augment OpenSMTPD's systemd service to load
351
-
systemd.services.opensmtpd.requires = ["acme-finished-mail.example.com.target"];
335
+
systemd.services.opensmtpd.requires = ["acme-finished-mail.example.com.target"];
systemd.services.opensmtpd.serviceConfig.LoadCredential = let
353
-
certDir = config.security.acme.certs."mail.example.com".directory;
337
+
certDir = config.security.acme.certs."mail.example.com".directory;
355
-
"cert.pem:${certDir}/cert.pem"
356
-
"key.pem:${certDir}/key.pem"
339
+
"cert.pem:${certDir}/cert.pem"
340
+
"key.pem:${certDir}/key.pem"
# Finally, configure OpenSMTPD to use these certs.
361
-
credsDir = "/run/credentials/opensmtpd.service";
345
+
credsDir = "/run/credentials/opensmtpd.service";
366
-
pki mail.example.com cert "${credsDir}/cert.pem"
367
-
pki mail.example.com key "${credsDir}/key.pem"
350
+
pki mail.example.com cert "${credsDir}/cert.pem"
351
+
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
376
-
<section xml:id="module-security-acme-regenerate">
377
-
<title>Regenerating certificates</title>
380
-
Should you need to regenerate a particular certificate in a hurry, such
381
-
as when a vulnerability is found in Let's Encrypt, there is now a convenient
382
-
mechanism for doing so. Running
383
-
<literal>systemctl clean --what=state acme-example.com.service</literal>
384
-
will remove all certificate files and the account data for the given domain,
385
-
allowing you to then <literal>systemctl start acme-example.com.service</literal>
386
-
to generate fresh ones.
389
-
<section xml:id="module-security-acme-fix-jws">
390
-
<title>Fixing JWS Verification error</title>
393
-
It is possible that your account credentials file may become corrupt and need
394
-
to be regenerated. In this scenario lego will produce the error <literal>JWS verification error</literal>.
395
-
The solution is to simply delete the associated accounts file and
396
-
re-run the affected service(s).
359
+
<section xml:id="module-security-acme-regenerate">
360
+
<title>Regenerating certificates</title>
362
+
Should you need to regenerate a particular certificate in a hurry,
363
+
such as when a vulnerability is found in Let's Encrypt, there is
364
+
now a convenient mechanism for doing so. Running
365
+
<literal>systemctl clean --what=state acme-example.com.service</literal>
366
+
will remove all certificate files and the account data for the
367
+
given domain, allowing you to then
368
+
<literal>systemctl start acme-example.com.service</literal> to
369
+
generate fresh ones.
372
+
<section xml:id="module-security-acme-fix-jws">
373
+
<title>Fixing JWS Verification error</title>
375
+
It is possible that your account credentials file may become
376
+
corrupt and need to be regenerated. In this scenario lego will
377
+
produce the error <literal>JWS verification error</literal>. The
378
+
solution is to simply delete the associated accounts file and
379
+
re-run the affected service(s).
# Find the accounts folder for the certificate
systemctl cat acme-example.com.service | grep -Po 'accounts/[^:]*'
402
-
export accountdir="$(!!)"
384
+
export accountdir="$(!!)"
# Move this folder to some place else
mv /var/lib/acme/.lego/$accountdir{,.bak}
# Recreate the folder using systemd-tmpfiles
···
# Note: Do this for all certs that share the same account email address
systemctl start acme-example.com.service