@@ -1,430 +0,0 @@
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, ... }:
4−with lib;
5−let
6−
7− cfg = config.security.acme;
8−
9− certOpts = { name, ... }: {
10− options = {
11− webroot = mkOption {
12− type = types.nullOr types.str;
13− default = null;
14− example = "/var/lib/acme/acme-challenges";
15− description = ''
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).
21− '';
22− };
23−
24− server = mkOption {
25− type = types.nullOr types.str;
26− default = null;
27− description = ''
28− ACME Directory Resource URI. Defaults to let's encrypt
29− production endpoint,
30− https://acme-v02.api.letsencrypt.org/directory, if unset.
31− '';
32− };
33−
34− domain = mkOption {
35− type = types.str;
36− default = name;
37− description = "Domain to fetch certificate for (defaults to the entry name)";
38− };
39−
40− email = mkOption {
41− type = types.nullOr types.str;
42− default = cfg.email;
43− description = "Contact email address for the CA to be able to reach you.";
44− };
45−
46− user = mkOption {
47− type = types.str;
48− default = "root";
49− description = "User running the ACME client.";
50− };
51−
52− group = mkOption {
53− type = types.str;
54− default = "root";
55− description = "Group running the ACME client.";
56− };
57−
58− allowKeysForGroup = mkOption {
59− type = types.bool;
60− default = false;
61− description = ''
62− Give read permissions to the specified group
63− (<option>security.acme.cert.<name>.group</option>) to read SSL private certificates.
64− '';
65− };
66−
67− postRun = mkOption {
68− type = types.lines;
69− default = "";
70− example = "systemctl reload nginx.service";
71− description = ''
72− Commands to run after new certificates go live. Typically
73− the web server and other servers using certificates need to
74− be reloaded.
75− Executed in the same directory with the new certificate.
76− '';
77− };
78−
79− directory = mkOption {
80− type = types.str;
81− readOnly = true;
82− default = "/var/lib/acme/${name}";
83− description = "Directory where certificate and other state is stored.";
84− };
85−
86− extraDomains = mkOption {
87− type = types.attrsOf (types.nullOr types.str);
88− default = {};
89− example = literalExample ''
90− {
91− "example.org" = null;
92− "mydomain.org" = null;
93− }
94− '';
95− description = ''
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+
98− '';
99− };
100−
101− keyType = mkOption {
102− type = types.str;
103− default = "ec256";
104− description = ''
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.
108− '';
109− };
110−
111− dnsProvider = mkOption {
112− type = types.nullOr types.str;
113− default = null;
114− example = "route53";
115− description = ''
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/.
118− '';
119− };
120−
121− credentialsFile = mkOption {
122− type = types.path;
123− description = ''
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.
128− '';
129− example = "/var/src/secrets/example.org-route53-api-token";
130− };
131−
132− dnsPropagationCheck = mkOption {
133− type = types.bool;
134− default = true;
135− description = ''
136− Toggles lego DNS propagation check, which is used alongside DNS-01
137− challenge to ensure the DNS entries required are available.
138− '';
139− };
140− };
141− };
142−
143−in
144−
145−{
146−
147− ###### interface
148− imports = [
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".
153− ''
154− )
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)))
159− ];
160− options = {
161− security.acme = {
162−
163− validMinDays = mkOption {
164− type = types.int;
165− default = 30;
166− description = "Minimum remaining validity before renewal in days.";
167− };
168−
169− email = mkOption {
170− type = types.nullOr types.str;
171− default = null;
172− description = "Contact email address for the CA to be able to reach you.";
173− };
174−
175− renewInterval = mkOption {
176− type = types.str;
177− default = "daily";
178− description = ''
179− Systemd calendar expression when to check for renewal. See
180− <citerefentry><refentrytitle>systemd.time</refentrytitle>
181− <manvolnum>7</manvolnum></citerefentry>.
182− '';
183− };
184−
185− server = mkOption {
186− type = types.nullOr types.str;
187− default = null;
188− description = ''
189− ACME Directory Resource URI. Defaults to let's encrypt
190− production endpoint,
191− <literal>https://acme-v02.api.letsencrypt.org/directory</literal>, if unset.
192− '';
193− };
194−
195− preliminarySelfsigned = mkOption {
196− type = types.bool;
197− default = true;
198− description = ''
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.
204− '';
205− };
206−
207− acceptTerms = mkOption {
208− type = types.bool;
209− default = false;
210− description = ''
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/
213− '';
214− };
215−
216− certs = mkOption {
217− default = { };
218− type = with types; attrsOf (submodule certOpts);
219− description = ''
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.
225− '';
226− example = literalExample ''
227− {
228− "example.com" = {
229− webroot = "/var/www/challenges/";
230− email = "foo@example.com";
231− extraDomains = { "www.example.com" = null; "foo.example.com" = null; };
232− };
233− "bar.example.com" = {
234− webroot = "/var/www/challenges/";
235− email = "bar@example.com";
236− };
237− }
238− '';
239− };
240− };
241− };
242−
243− ###### implementation
244− config = mkMerge [
245− (mkIf (cfg.certs != { }) {
246−
247− assertions = let
248− certs = (mapAttrsToList (k: v: v) cfg.certs);
249− in [
250− {
251− assertion = all (certOpts: certOpts.dnsProvider == null || certOpts.webroot == null) certs;
252− message = ''
253− Options `security.acme.certs.<name>.dnsProvider` and
254− `security.acme.certs.<name>.webroot` are mutually exclusive.
255− '';
256− }
257− {
258− assertion = cfg.email != null || all (certOpts: certOpts.email != null) certs;
259− message = ''
260− You must define `security.acme.certs.<name>.email` or
261− `security.acme.email` to register with the CA.
262− '';
263− }
264− {
265− assertion = cfg.acceptTerms;
266− message = ''
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/
270− '';
271− }
272− ];
273−
274− systemd.services = let
275− services = concatLists servicesLists;
276− servicesLists = mapAttrsToList certToServices cfg.certs;
277− certToServices = cert: data:
278− let
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) ]);
292− acmeService = {
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" ];
297− serviceConfig = {
298− Type = "oneshot";
299− User = data.user;
300− Group = data.group;
301− PrivateTmp = true;
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}
311− '';
312− ExecStartPost =
313− let
314− keyName = builtins.replaceStrings ["*"] ["_"] data.domain;
315− script = pkgs.writeScript "acme-post-start" ''
316− #!${pkgs.runtimeShell} -e
317− cd ${apath}
318− # Test that existing cert is older than new cert
319− KEY=${spath}/certificates/${keyName}.key
320− KEY_CHANGED=no
321− if [ -e $KEY -a $KEY -nt key.pem ]; then
322− KEY_CHANGED=yes
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
328− fi
329− chmod ${fileMode} *.pem
330− chown '${data.user}:${data.group}' *.pem
331− if [ "$KEY_CHANGED" = "yes" ]; then
332− : # noop in case postRun is empty
333− ${data.postRun}
334− fi
335− '';
336− in
337− "+${script}";
338− };
339−
340− };
341− selfsignedService = {
342− description = "Create preliminary self-signed certificate for ${cert}";
343− path = [ pkgs.openssl ];
344− script =
345− ''
346− workdir="$(mktemp -d)"
347− # Create CA
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
353− # Create key
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
370− '';
371− serviceConfig = {
372− Type = "oneshot";
373− PrivateTmp = true;
374− StateDirectory = lpath;
375− User = data.user;
376− Group = data.group;
377− };
378− unitConfig = {
379− # Do not create self-signed key when key already exists
380− ConditionPathExists = "!${apath}/key.pem";
381− };
382− };
383− in (
384− [ { name = "acme-${cert}"; value = acmeService; } ]
385− ++ optional cfg.preliminarySelfsigned { name = "acme-selfsigned-${cert}"; value = selfsignedService; }
386− );
387− servicesAttr = listToAttrs services;
388− in
389− servicesAttr;
390−
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));
393−
394− systemd.timers = let
395− # Allow systemd to pick a convenient time within the day
396− # to run the check.
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
405− ("acme-${cert}")
406− ({
407− description = "Renew ACME Certificate for ${cert}";
408− wantedBy = [ "timers.target" ];
409− timerConfig = {
410− OnCalendar = cfg.renewInterval;
411− Unit = "acme-${cert}.service";
412− Persistent = "yes";
413− inherit AccuracySec;
414− # Skew randomly within the day, per https://letsencrypt.org/docs/integration-guide/.
415− RandomizedDelaySec = "24h";
416− };
417− })
418− );
419−
420− systemd.targets.acme-selfsigned-certificates = mkIf cfg.preliminarySelfsigned {};
421− systemd.targets.acme-certificates = {};
422− })
423−
424− ];
425−
426− meta = {
427− maintainers = with lib.maintainers; [ abbradar fpletz globin m1cr0man ];
428− # doc = ./acme.xml; # MODIFIED
429− };
430−}