at 23.05-pre 18 kB view raw
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" 4 version="5.0" 5 xml:id="module-security-acme"> 6 <title>SSL/TLS Certificates with ACME</title> 7 <para> 8 NixOS supports automatic domain validation &amp; 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 12 the hood. 13 </para> 14 <para> 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 18 based validation. 19 </para> 20 <section xml:id="module-security-acme-prerequisites"> 21 <title>Prerequisites</title> 22 23 <para> 24 To use the ACME module, you must accept the provider's terms of service 25 by setting <literal><xref linkend="opt-security.acme.acceptTerms" /></literal> 26 to <literal>true</literal>. The Let's Encrypt ToS can be found 27 <link xlink:href="https://letsencrypt.org/repository/">here</link>. 28 </para> 29 30 <para> 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 <literal><xref linkend="opt-security.acme.defaults.email" /></literal> 34 and/or on a per-cert basis with 35 <literal><xref linkend="opt-security.acme.certs._name_.email" /></literal>. 36 This address is only used for registration and renewal reminders, 37 and cannot be used to administer the certificates in any way. 38 </para> 39 40 <para> 41 Alternatively, you can use a different ACME server by changing the 42 <literal><xref linkend="opt-security.acme.defaults.server" /></literal> option 43 to a provider of your choosing, or just change the server for one cert with 44 <literal><xref linkend="opt-security.acme.certs._name_.server" /></literal>. 45 </para> 46 47 <para> 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. 53 </para> 54 </section> 55 <section xml:id="module-security-acme-nginx"> 56 <title>Using ACME certificates in Nginx</title> 57 58 <para> 59 NixOS supports fetching ACME certificates for you by setting 60 <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> 61 = true;</literal> in a virtualHost config. We first create self-signed 62 placeholder certificates in place of the real ACME certs. The placeholder 63 certs are overwritten when the ACME certs arrive. For 64 <literal>foo.example.com</literal> the config would look like this: 65 </para> 66 67<programlisting> 68<xref linkend="opt-security.acme.acceptTerms" /> = true; 69<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com"; 70services.nginx = { 71 <link linkend="opt-services.nginx.enable">enable</link> = true; 72 <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = { 73 "foo.example.com" = { 74 <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true; 75 <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true; 76 # All serverAliases will be added as <link linkend="opt-security.acme.certs._name_.extraDomainNames">extra domain names</link> on the certificate. 77 <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [ "bar.example.com" ]; 78 locations."/" = { 79 <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www"; 80 }; 81 }; 82 83 # We can also add a different vhost and reuse the same certificate 84 # but we have to append extraDomainNames manually beforehand: 85 # <link linkend="opt-security.acme.certs._name_.extraDomainNames">security.acme.certs."foo.example.com".extraDomainNames</link> = [ "baz.example.com" ]; 86 "baz.example.com" = { 87 <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true; 88 <link linkend="opt-services.nginx.virtualHosts._name_.useACMEHost">useACMEHost</link> = "foo.example.com"; 89 locations."/" = { 90 <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www"; 91 }; 92 }; 93 }; 94} 95</programlisting> 96 </section> 97 <section xml:id="module-security-acme-httpd"> 98 <title>Using ACME certificates in Apache/httpd</title> 99 100 <para> 101 Using ACME certificates with Apache virtual hosts is identical 102 to using them with Nginx. The attribute names are all the same, just replace 103 "nginx" with "httpd" where appropriate. 104 </para> 105 </section> 106 <section xml:id="module-security-acme-configuring"> 107 <title>Manual configuration of HTTP-01 validation</title> 108 109 <para> 110 First off you will need to set up a virtual host to serve the challenges. 111 This example uses a vhost called <literal>certs.example.com</literal>, with 112 the intent that you will generate certs for all your vhosts and redirect 113 everyone to HTTPS. 114 </para> 115 116<programlisting> 117<xref linkend="opt-security.acme.acceptTerms" /> = true; 118<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com"; 119 120# /var/lib/acme/.challenges must be writable by the ACME user 121# and readable by the Nginx user. The easiest way to achieve 122# this is to add the Nginx user to the ACME group. 123<link linkend="opt-users.users._name_.extraGroups">users.users.nginx.extraGroups</link> = [ "acme" ]; 124 125services.nginx = { 126 <link linkend="opt-services.nginx.enable">enable</link> = true; 127 <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = { 128 "acmechallenge.example.com" = { 129 # Catchall vhost, will redirect users to HTTPS for all vhosts 130 <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [ "*.example.com" ]; 131 locations."/.well-known/acme-challenge" = { 132 <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/lib/acme/.challenges"; 133 }; 134 locations."/" = { 135 <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.return">return</link> = "301 https://$host$request_uri"; 136 }; 137 }; 138 }; 139} 140# Alternative config for Apache 141<link linkend="opt-users.users._name_.extraGroups">users.users.wwwrun.extraGroups</link> = [ "acme" ]; 142services.httpd = { 143 <link linkend="opt-services.httpd.enable">enable = true;</link> 144 <link linkend="opt-services.httpd.virtualHosts">virtualHosts</link> = { 145 "acmechallenge.example.com" = { 146 # Catchall vhost, will redirect users to HTTPS for all vhosts 147 <link linkend="opt-services.httpd.virtualHosts._name_.serverAliases">serverAliases</link> = [ "*.example.com" ]; 148 # /var/lib/acme/.challenges must be writable by the ACME user and readable by the Apache user. 149 # By default, this is the case. 150 <link linkend="opt-services.httpd.virtualHosts._name_.documentRoot">documentRoot</link> = "/var/lib/acme/.challenges"; 151 <link linkend="opt-services.httpd.virtualHosts._name_.extraConfig">extraConfig</link> = '' 152 RewriteEngine On 153 RewriteCond %{HTTPS} off 154 RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge [NC] 155 RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301] 156 ''; 157 }; 158 }; 159} 160</programlisting> 161 162 <para> 163 Now you need to configure ACME to generate a certificate. 164 </para> 165 166<programlisting> 167<xref linkend="opt-security.acme.certs"/>."foo.example.com" = { 168 <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/lib/acme/.challenges"; 169 <link linkend="opt-security.acme.certs._name_.email">email</link> = "foo@example.com"; 170 # Ensure that the web server you use can read the generated certs 171 # Take a look at the <link linkend="opt-services.nginx.group">group</link> option for the web server you choose. 172 <link linkend="opt-security.acme.certs._name_.group">group</link> = "nginx"; 173 # Since we have a wildcard vhost to handle port 80, 174 # we can generate certs for anything! 175 # Just make sure your DNS resolves them. 176 <link linkend="opt-security.acme.certs._name_.extraDomainNames">extraDomainNames</link> = [ "mail.example.com" ]; 177}; 178</programlisting> 179 180 <para> 181 The private key <filename>key.pem</filename> and certificate 182 <filename>fullchain.pem</filename> will be put into 183 <filename>/var/lib/acme/foo.example.com</filename>. 184 </para> 185 186 <para> 187 Refer to <xref linkend="ch-options" /> for all available configuration 188 options for the <link linkend="opt-security.acme.certs">security.acme</link> 189 module. 190 </para> 191 </section> 192 <section xml:id="module-security-acme-config-dns"> 193 <title>Configuring ACME for DNS validation</title> 194 195 <para> 196 This is useful if you want to generate a wildcard certificate, since 197 ACME servers will only hand out wildcard certs over DNS validation. 198 There are a number of supported DNS providers and servers you can utilise, 199 see the <link xlink:href="https://go-acme.github.io/lego/dns/">lego docs</link> 200 for provider/server specific configuration values. For the sake of these 201 docs, we will provide a fully self-hosted example using bind. 202 </para> 203 204<programlisting> 205services.bind = { 206 <link linkend="opt-services.bind.enable">enable</link> = true; 207 <link linkend="opt-services.bind.extraConfig">extraConfig</link> = '' 208 include "/var/lib/secrets/dnskeys.conf"; 209 ''; 210 <link linkend="opt-services.bind.zones">zones</link> = [ 211 rec { 212 name = "example.com"; 213 file = "/var/db/bind/${name}"; 214 master = true; 215 extraConfig = "allow-update { key rfc2136key.example.com.; };"; 216 } 217 ]; 218} 219 220# Now we can configure ACME 221<xref linkend="opt-security.acme.acceptTerms" /> = true; 222<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com"; 223<xref linkend="opt-security.acme.certs" />."example.com" = { 224 <link linkend="opt-security.acme.certs._name_.domain">domain</link> = "*.example.com"; 225 <link linkend="opt-security.acme.certs._name_.dnsProvider">dnsProvider</link> = "rfc2136"; 226 <link linkend="opt-security.acme.certs._name_.credentialsFile">credentialsFile</link> = "/var/lib/secrets/certs.secret"; 227 # We don't need to wait for propagation since this is a local DNS server 228 <link linkend="opt-security.acme.certs._name_.dnsPropagationCheck">dnsPropagationCheck</link> = false; 229}; 230</programlisting> 231 232 <para> 233 The <filename>dnskeys.conf</filename> and <filename>certs.secret</filename> 234 must be kept secure and thus you should not keep their contents in your 235 Nix config. Instead, generate them one time with a systemd service: 236 </para> 237 238<programlisting> 239systemd.services.dns-rfc2136-conf = { 240 requiredBy = ["acme-example.com.service" "bind.service"]; 241 before = ["acme-example.com.service" "bind.service"]; 242 unitConfig = { 243 ConditionPathExists = "!/var/lib/secrets/dnskeys.conf"; 244 }; 245 serviceConfig = { 246 Type = "oneshot"; 247 UMask = 0077; 248 }; 249 path = [ pkgs.bind ]; 250 script = '' 251 mkdir -p /var/lib/secrets 252 chmod 755 /var/lib/secrets 253 tsig-keygen rfc2136key.example.com &gt; /var/lib/secrets/dnskeys.conf 254 chown named:root /var/lib/secrets/dnskeys.conf 255 chmod 400 /var/lib/secrets/dnskeys.conf 256 257 # extract secret value from the dnskeys.conf 258 while read x y; do if [ "$x" = "secret" ]; then secret="''${y:1:''${#y}-3}"; fi; done &lt; /var/lib/secrets/dnskeys.conf 259 260 cat &gt; /var/lib/secrets/certs.secret &lt;&lt; EOF 261 RFC2136_NAMESERVER='127.0.0.1:53' 262 RFC2136_TSIG_ALGORITHM='hmac-sha256.' 263 RFC2136_TSIG_KEY='rfc2136key.example.com' 264 RFC2136_TSIG_SECRET='$secret' 265 EOF 266 chmod 400 /var/lib/secrets/certs.secret 267 ''; 268}; 269</programlisting> 270 271 <para> 272 Now you're all set to generate certs! You should monitor the first invocation 273 by running <literal>systemctl start acme-example.com.service &amp; 274 journalctl -fu acme-example.com.service</literal> and watching its log output. 275 </para> 276 </section> 277 278 <section xml:id="module-security-acme-config-dns-with-vhosts"> 279 <title>Using DNS validation with web server virtual hosts</title> 280 281 <para> 282 It is possible to use DNS-01 validation with all certificates, 283 including those automatically configured via the Nginx/Apache 284 <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link></literal> 285 option. This configuration pattern is fully 286 supported and part of the module's test suite for Nginx + Apache. 287 </para> 288 289 <para> 290 You must follow the guide above on configuring DNS-01 validation 291 first, however instead of setting the options for one certificate 292 (e.g. <xref linkend="opt-security.acme.certs._name_.dnsProvider" />) 293 you will set them as defaults 294 (e.g. <xref linkend="opt-security.acme.defaults.dnsProvider" />). 295 </para> 296 297<programlisting> 298# Configure ACME appropriately 299<xref linkend="opt-security.acme.acceptTerms" /> = true; 300<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com"; 301<xref linkend="opt-security.acme.defaults" /> = { 302 <link linkend="opt-security.acme.defaults.dnsProvider">dnsProvider</link> = "rfc2136"; 303 <link linkend="opt-security.acme.defaults.credentialsFile">credentialsFile</link> = "/var/lib/secrets/certs.secret"; 304 # We don't need to wait for propagation since this is a local DNS server 305 <link linkend="opt-security.acme.defaults.dnsPropagationCheck">dnsPropagationCheck</link> = false; 306}; 307 308# For each virtual host you would like to use DNS-01 validation with, 309# set acmeRoot = null 310services.nginx = { 311 <link linkend="opt-services.nginx.enable">enable</link> = true; 312 <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = { 313 "foo.example.com" = { 314 <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true; 315 <link linkend="opt-services.nginx.virtualHosts._name_.acmeRoot">acmeRoot</link> = null; 316 }; 317 }; 318} 319</programlisting> 320 321 <para> 322 And that's it! Next time your configuration is rebuilt, or when 323 you add a new virtualHost, it will be DNS-01 validated. 324 </para> 325 </section> 326 327 <section xml:id="module-security-acme-root-owned"> 328 <title>Using ACME with services demanding root owned certificates</title> 329 330 <para> 331 Some services refuse to start if the configured certificate files 332 are not owned by root. PostgreSQL and OpenSMTPD are examples of these. 333 There is no way to change the user the ACME module uses (it will always be 334 <literal>acme</literal>), however you can use systemd's 335 <literal>LoadCredential</literal> feature to resolve this elegantly. 336 Below is an example configuration for OpenSMTPD, but this pattern 337 can be applied to any service. 338 </para> 339 340<programlisting> 341# Configure ACME however you like (DNS or HTTP validation), adding 342# the following configuration for the relevant certificate. 343# Note: You cannot use `systemctl reload` here as that would mean 344# the LoadCredential configuration below would be skipped and 345# the service would continue to use old certificates. 346security.acme.certs."mail.example.com".postRun = '' 347 systemctl restart opensmtpd 348''; 349 350# Now you must augment OpenSMTPD's systemd service to load 351# the certificate files. 352<link linkend="opt-systemd.services._name_.requires">systemd.services.opensmtpd.requires</link> = ["acme-finished-mail.example.com.target"]; 353<link linkend="opt-systemd.services._name_.serviceConfig">systemd.services.opensmtpd.serviceConfig.LoadCredential</link> = let 354 certDir = config.security.acme.certs."mail.example.com".directory; 355in [ 356 "cert.pem:${certDir}/cert.pem" 357 "key.pem:${certDir}/key.pem" 358]; 359 360# Finally, configure OpenSMTPD to use these certs. 361services.opensmtpd = let 362 credsDir = "/run/credentials/opensmtpd.service"; 363in { 364 enable = true; 365 setSendmail = false; 366 serverConfiguration = '' 367 pki mail.example.com cert "${credsDir}/cert.pem" 368 pki mail.example.com key "${credsDir}/key.pem" 369 listen on localhost tls pki mail.example.com 370 action act1 relay host smtp://127.0.0.1:10027 371 match for local action act1 372 ''; 373}; 374</programlisting> 375 </section> 376 377 <section xml:id="module-security-acme-regenerate"> 378 <title>Regenerating certificates</title> 379 380 <para> 381 Should you need to regenerate a particular certificate in a hurry, such 382 as when a vulnerability is found in Let's Encrypt, there is now a convenient 383 mechanism for doing so. Running 384 <literal>systemctl clean --what=state acme-example.com.service</literal> 385 will remove all certificate files and the account data for the given domain, 386 allowing you to then <literal>systemctl start acme-example.com.service</literal> 387 to generate fresh ones. 388 </para> 389 </section> 390 <section xml:id="module-security-acme-fix-jws"> 391 <title>Fixing JWS Verification error</title> 392 393 <para> 394 It is possible that your account credentials file may become corrupt and need 395 to be regenerated. In this scenario lego will produce the error <literal>JWS verification error</literal>. 396 The solution is to simply delete the associated accounts file and 397 re-run the affected service(s). 398 </para> 399 400<programlisting> 401# Find the accounts folder for the certificate 402systemctl cat acme-example.com.service | grep -Po 'accounts/[^:]*' 403export accountdir="$(!!)" 404# Move this folder to some place else 405mv /var/lib/acme/.lego/$accountdir{,.bak} 406# Recreate the folder using systemd-tmpfiles 407systemd-tmpfiles --create 408# Get a new account and reissue certificates 409# Note: Do this for all certs that share the same account email address 410systemctl start acme-example.com.service 411</programlisting> 412 413 </section> 414</chapter>