1# Copied from https://github.com/NixOS/nixpkgs-channels/blob/70717a337f7ae4e486ba71a500367cad697e5f09/nixos/modules/security/acme.nix # MODIFIED
2# Lines marked '# MODIFIED' have been added or modified from the original. # MODIFIED
3{ config, lib, pkgs, ... }:
7 cfg = config.security.acme;
9 certOpts = { name, ... }: {
12 type = types.nullOr types.str;
14 example = "/var/lib/acme/acme-challenges";
16 Where the webroot of the HTTP vhost is located.
17 <filename>.well-known/acme-challenge/</filename> directory
18 will be created below the webroot if it doesn't exist.
19 <literal>http://example.org/.well-known/acme-challenge/</literal> must also
20 be available (notice unencrypted HTTP).
25 type = types.nullOr types.str;
28 ACME Directory Resource URI. Defaults to let's encrypt
30 https://acme-v02.api.letsencrypt.org/directory, if unset.
37 description = "Domain to fetch certificate for (defaults to the entry name)";
41 type = types.nullOr types.str;
43 description = "Contact email address for the CA to be able to reach you.";
49 description = "User running the ACME client.";
55 description = "Group running the ACME client.";
58 allowKeysForGroup = mkOption {
62 Give read permissions to the specified group
63 (<option>security.acme.cert.<name>.group</option>) to read SSL private certificates.
70 example = "systemctl reload nginx.service";
72 Commands to run after new certificates go live. Typically
73 the web server and other servers using certificates need to
75 Executed in the same directory with the new certificate.
79 directory = mkOption {
82 default = "/var/lib/acme/${name}";
83 description = "Directory where certificate and other state is stored.";
86 extraDomains = mkOption {
87 type = types.attrsOf (types.nullOr types.str);
89 example = literalExample ''
92 "mydomain.org" = null;
96 A list of extra domain names, which are included in the one certificate to be issued.
97 Setting a distinct server root is deprecated and not functional in 20.03+
105 Key type to use for private keys.
106 For an up to date list of supported values check the --key-type option
107 at https://go-acme.github.io/lego/usage/cli/#usage.
111 dnsProvider = mkOption {
112 type = types.nullOr types.str;
116 DNS Challenge provider. For a list of supported providers, see the "code"
117 field of the DNS providers listed at https://go-acme.github.io/lego/dns/.
121 credentialsFile = mkOption {
124 Path to an EnvironmentFile for the cert's service containing any required and
125 optional environment variables for your selected dnsProvider.
126 To find out what values you need to set, consult the documentation at
127 https://go-acme.github.io/lego/dns/ for the corresponding dnsProvider.
129 example = "/var/src/secrets/example.org-route53-api-token";
132 dnsPropagationCheck = mkOption {
136 Toggles lego DNS propagation check, which is used alongside DNS-01
137 challenge to ensure the DNS entries required are available.
149 (mkRemovedOptionModule [ "security" "acme" "production" ] ''
150 Use security.acme.server to define your staging ACME server URL instead.
151 To use the let's encrypt staging server, use security.acme.server =
152 "https://acme-staging-v02.api.letsencrypt.org/directory".
155 # (mkRemovedOptionModule [ "security" "acme" "directory"] "ACME Directory is now hardcoded to /var/lib/acme and its permisisons are managed by systemd. See https://github.com/NixOS/nixpkgs/issues/53852 for more info.") # MODIFIED
156 # (mkRemovedOptionModule [ "security" "acme" "preDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") # MODIFIED
157 # (mkRemovedOptionModule [ "security" "acme" "activationDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal") # MODIFIED
158 (mkChangedOptionModule [ "security" "acme" "validMin"] [ "security" "acme" "validMinDays"] (config: config.security.acme.validMin / (24 * 3600)))
163 validMinDays = mkOption {
166 description = "Minimum remaining validity before renewal in days.";
170 type = types.nullOr types.str;
172 description = "Contact email address for the CA to be able to reach you.";
175 renewInterval = mkOption {
179 Systemd calendar expression when to check for renewal. See
180 <citerefentry><refentrytitle>systemd.time</refentrytitle>
181 <manvolnum>7</manvolnum></citerefentry>.
186 type = types.nullOr types.str;
189 ACME Directory Resource URI. Defaults to let's encrypt
191 <literal>https://acme-v02.api.letsencrypt.org/directory</literal>, if unset.
195 preliminarySelfsigned = mkOption {
199 Whether a preliminary self-signed certificate should be generated before
200 doing ACME requests. This can be useful when certificates are required in
201 a webserver, but ACME needs the webserver to make its requests.
202 With preliminary self-signed certificate the webserver can be started and
203 can later reload the correct ACME certificates.
207 acceptTerms = mkOption {
211 Accept the CA's terms of service. The default provier is Let's Encrypt,
212 you can find their ToS at https://letsencrypt.org/repository/
218 type = with types; attrsOf (submodule certOpts);
220 Attribute set of certificates to get signed and renewed. Creates
221 <literal>acme-''${cert}.{service,timer}</literal> systemd units for
222 each certificate defined here. Other services can add dependencies
223 to those units if they rely on the certificates being present,
224 or trigger restarts of the service if certificates get renewed.
226 example = literalExample ''
229 webroot = "/var/www/challenges/";
230 email = "foo@example.com";
231 extraDomains = { "www.example.com" = null; "foo.example.com" = null; };
233 "bar.example.com" = {
234 webroot = "/var/www/challenges/";
235 email = "bar@example.com";
243 ###### implementation
245 (mkIf (cfg.certs != { }) {
248 certs = (mapAttrsToList (k: v: v) cfg.certs);
251 assertion = all (certOpts: certOpts.dnsProvider == null || certOpts.webroot == null) certs;
253 Options `security.acme.certs.<name>.dnsProvider` and
254 `security.acme.certs.<name>.webroot` are mutually exclusive.
258 assertion = cfg.email != null || all (certOpts: certOpts.email != null) certs;
260 You must define `security.acme.certs.<name>.email` or
261 `security.acme.email` to register with the CA.
265 assertion = cfg.acceptTerms;
267 You must accept the CA's terms of service before using
268 the ACME module by setting `security.acme.acceptTerms`
269 to `true`. For Let's Encrypt's ToS see https://letsencrypt.org/repository/
274 systemd.services = let
275 services = concatLists servicesLists;
276 servicesLists = mapAttrsToList certToServices cfg.certs;
277 certToServices = cert: data:
279 # StateDirectory must be relative, and will be created under /var/lib by systemd
280 lpath = "acme/${cert}";
281 apath = "/var/lib/${lpath}";
282 spath = "/var/lib/acme/.lego/${cert}";
283 fileMode = if data.allowKeysForGroup then "640" else "600";
284 globalOpts = [ "-d" data.domain "--email" data.email "--path" "." "--key-type" data.keyType ]
285 ++ optionals (cfg.acceptTerms) [ "--accept-tos" ]
286 ++ optionals (data.dnsProvider != null && !data.dnsPropagationCheck) [ "--dns.disable-cp" ]
287 ++ concatLists (mapAttrsToList (name: root: [ "-d" name ]) data.extraDomains)
288 ++ (if data.dnsProvider != null then [ "--dns" data.dnsProvider ] else [ "--http" "--http.webroot" data.webroot ])
289 ++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)];
290 runOpts = escapeShellArgs (globalOpts ++ [ "run" ]);
291 renewOpts = escapeShellArgs (globalOpts ++ [ "renew" "--days" (toString cfg.validMinDays) ]);
293 description = "Renew ACME Certificate for ${cert}";
294 after = [ "network.target" "network-online.target" ];
295 wants = [ "network-online.target" ];
296 wantedBy = mkIf (!config.boot.isContainer) [ "multi-user.target" ];
302 StateDirectory = "acme/.lego/${cert} acme/.lego/accounts ${lpath}";
303 StateDirectoryMode = if data.allowKeysForGroup then "750" else "700";
304 WorkingDirectory = spath;
305 # Only try loading the credentialsFile if the dns challenge is enabled
306 EnvironmentFile = if data.dnsProvider != null then data.credentialsFile else null;
307 ExecStart = pkgs.writeScript "acme-start" ''
308 #!${pkgs.runtimeShell} -e
309 test -L ${spath}/accounts -o -d ${spath}/accounts || ln -s ../accounts ${spath}/accounts
310 ${pkgs.lego}/bin/lego ${renewOpts} || ${pkgs.lego}/bin/lego ${runOpts}
314 keyName = builtins.replaceStrings ["*"] ["_"] data.domain;
315 script = pkgs.writeScript "acme-post-start" ''
316 #!${pkgs.runtimeShell} -e
318 # Test that existing cert is older than new cert
319 KEY=${spath}/certificates/${keyName}.key
321 if [ -e $KEY -a $KEY -nt key.pem ]; then
323 cp -p ${spath}/certificates/${keyName}.key key.pem
324 cp -p ${spath}/certificates/${keyName}.crt fullchain.pem
325 cp -p ${spath}/certificates/${keyName}.issuer.crt chain.pem
326 ln -sf fullchain.pem cert.pem
327 cat key.pem fullchain.pem > full.pem
329 chmod ${fileMode} *.pem
330 chown '${data.user}:${data.group}' *.pem
331 if [ "$KEY_CHANGED" = "yes" ]; then
332 : # noop in case postRun is empty
341 selfsignedService = {
342 description = "Create preliminary self-signed certificate for ${cert}";
343 path = [ pkgs.openssl ];
346 workdir="$(mktemp -d)"
348 openssl genrsa -des3 -passout pass:xxxx -out $workdir/ca.pass.key 2048
349 openssl rsa -passin pass:xxxx -in $workdir/ca.pass.key -out $workdir/ca.key
350 openssl req -new -key $workdir/ca.key -out $workdir/ca.csr \
351 -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=Security Department/CN=example.com"
352 openssl x509 -req -days 1 -in $workdir/ca.csr -signkey $workdir/ca.key -out $workdir/ca.crt
354 openssl genrsa -des3 -passout pass:xxxx -out $workdir/server.pass.key 2048
355 openssl rsa -passin pass:xxxx -in $workdir/server.pass.key -out $workdir/server.key
356 openssl req -new -key $workdir/server.key -out $workdir/server.csr \
357 -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
358 openssl x509 -req -days 1 -in $workdir/server.csr -CA $workdir/ca.crt \
359 -CAkey $workdir/ca.key -CAserial $workdir/ca.srl -CAcreateserial \
360 -out $workdir/server.crt
361 # Copy key to destination
362 cp $workdir/server.key ${apath}/key.pem
363 # Create fullchain.pem (same format as "simp_le ... -f fullchain.pem" creates)
364 cat $workdir/{server.crt,ca.crt} > "${apath}/fullchain.pem"
365 # Create full.pem for e.g. lighttpd
366 cat $workdir/{server.key,server.crt,ca.crt} > "${apath}/full.pem"
367 # Give key acme permissions
368 chown '${data.user}:${data.group}' "${apath}/"{key,fullchain,full}.pem
369 chmod ${fileMode} "${apath}/"{key,fullchain,full}.pem
374 StateDirectory = lpath;
379 # Do not create self-signed key when key already exists
380 ConditionPathExists = "!${apath}/key.pem";
384 [ { name = "acme-${cert}"; value = acmeService; } ]
385 ++ optional cfg.preliminarySelfsigned { name = "acme-selfsigned-${cert}"; value = selfsignedService; }
387 servicesAttr = listToAttrs services;
391 systemd.tmpfiles.rules =
392 map (data: "d ${data.webroot}/.well-known/acme-challenge - ${data.user} ${data.group}") (filter (data: data.webroot != null) (attrValues cfg.certs));
395 # Allow systemd to pick a convenient time within the day
397 # This allows the coalescing of multiple timer jobs.
398 # We divide by the number of certificates so that if you
399 # have many certificates, the renewals are distributed over
400 # the course of the day to avoid rate limits.
401 numCerts = length (attrNames cfg.certs);
402 _24hSecs = 60 * 60 * 24;
403 AccuracySec = "${toString (_24hSecs / numCerts)}s";
404 in flip mapAttrs' cfg.certs (cert: data: nameValuePair
407 description = "Renew ACME Certificate for ${cert}";
408 wantedBy = [ "timers.target" ];
410 OnCalendar = cfg.renewInterval;
411 Unit = "acme-${cert}.service";
414 # Skew randomly within the day, per https://letsencrypt.org/docs/integration-guide/.
415 RandomizedDelaySec = "24h";
420 systemd.targets.acme-selfsigned-certificates = mkIf cfg.preliminarySelfsigned {};
421 systemd.targets.acme-certificates = {};
427 maintainers = with lib.maintainers; [ abbradar fpletz globin m1cr0man ];
428 # doc = ./acme.xml; # MODIFIED
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list