···114114 '';
115115 };
116116117117+ preliminarySelfsigned = mkOption {
118118+ type = types.bool;
119119+ default = true;
120120+ description = ''
121121+ Whether a preliminary self-signed certificate should be generated before
122122+ doing ACME requests. This can be useful when certificates are required in
123123+ a webserver, but ACME needs the webserver to make its requests.
124124+125125+ With preliminary self-signed certificate the webserver can be started and
126126+ can later reload the correct ACME certificates.
127127+ '';
128128+ };
129129+117130 certs = mkOption {
118131 default = { };
119132 type = types.loaOf types.optionSet;
···140153 config = mkMerge [
141154 (mkIf (cfg.certs != { }) {
142155143143- systemd.services = flip mapAttrs' cfg.certs (cert: data:
144144- let
145145- cpath = "${cfg.directory}/${cert}";
146146- rights = if data.allowKeysForGroup then "750" else "700";
147147- cmdline = [ "-v" "-d" cert "--default_root" data.webroot "--valid_min" cfg.validMin ]
148148- ++ optionals (data.email != null) [ "--email" data.email ]
149149- ++ concatMap (p: [ "-f" p ]) data.plugins
150150- ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains);
156156+ systemd.services = let
157157+ services = concatLists servicesLists;
158158+ servicesLists = mapAttrsToList certToServices cfg.certs;
159159+ certToServices = cert: data:
160160+ let
161161+ cpath = "${cfg.directory}/${cert}";
162162+ rights = if data.allowKeysForGroup then "750" else "700";
163163+ cmdline = [ "-v" "-d" cert "--default_root" data.webroot "--valid_min" cfg.validMin ]
164164+ ++ optionals (data.email != null) [ "--email" data.email ]
165165+ ++ concatMap (p: [ "-f" p ]) data.plugins
166166+ ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains);
167167+ acmeService = {
168168+ description = "Renew ACME Certificate for ${cert}";
169169+ after = [ "network.target" ];
170170+ serviceConfig = {
171171+ Type = "oneshot";
172172+ SuccessExitStatus = [ "0" "1" ];
173173+ PermissionsStartOnly = true;
174174+ User = data.user;
175175+ Group = data.group;
176176+ PrivateTmp = true;
177177+ };
178178+ path = [ pkgs.simp_le ];
179179+ preStart = ''
180180+ mkdir -p '${cfg.directory}'
181181+ if [ ! -d '${cpath}' ]; then
182182+ mkdir '${cpath}'
183183+ fi
184184+ chmod ${rights} '${cpath}'
185185+ chown -R '${data.user}:${data.group}' '${cpath}'
186186+ '';
187187+ script = ''
188188+ cd '${cpath}'
189189+ set +e
190190+ simp_le ${concatMapStringsSep " " (arg: escapeShellArg (toString arg)) cmdline}
191191+ EXITCODE=$?
192192+ set -e
193193+ echo "$EXITCODE" > /tmp/lastExitCode
194194+ exit "$EXITCODE"
195195+ '';
196196+ postStop = ''
197197+ if [ -e /tmp/lastExitCode ] && [ "$(cat /tmp/lastExitCode)" = "0" ]; then
198198+ echo "Executing postRun hook..."
199199+ ${data.postRun}
200200+ fi
201201+ '';
151202152152- in nameValuePair
153153- ("acme-${cert}")
154154- ({
155155- description = "Renew ACME Certificate for ${cert}";
156156- after = [ "network.target" ];
157157- serviceConfig = {
158158- Type = "oneshot";
159159- SuccessExitStatus = [ "0" "1" ];
160160- PermissionsStartOnly = true;
161161- User = data.user;
162162- Group = data.group;
163163- PrivateTmp = true;
203203+ before = [ "acme-certificates.target" ];
204204+ wantedBy = [ "acme-certificates.target" ];
205205+ };
206206+ selfsignedService = {
207207+ description = "Create preliminary self-signed certificate for ${cert}";
208208+ preStart = ''
209209+ if [ ! -d '${cpath}' ]
210210+ then
211211+ mkdir -p '${cpath}'
212212+ chmod ${rights} '${cpath}'
213213+ chown '${data.user}:${data.group}' '${cpath}'
214214+ fi
215215+ '';
216216+ script =
217217+ ''
218218+ # Create self-signed key
219219+ workdir="/run/acme-selfsigned-${cert}"
220220+ ${pkgs.openssl.bin}/bin/openssl genrsa -des3 -passout pass:x -out $workdir/server.pass.key 2048
221221+ ${pkgs.openssl.bin}/bin/openssl rsa -passin pass:x -in $workdir/server.pass.key -out $workdir/server.key
222222+ ${pkgs.openssl.bin}/bin/openssl req -new -key $workdir/server.key -out $workdir/server.csr \
223223+ -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
224224+ ${pkgs.openssl.bin}/bin/openssl x509 -req -days 1 -in $workdir/server.csr -signkey $workdir/server.key -out $workdir/server.crt
225225+226226+ # Move key to destination
227227+ mv $workdir/server.key ${cpath}/key.pem
228228+ mv $workdir/server.crt ${cpath}/fullchain.pem
229229+230230+ # Clean up working directory
231231+ rm $workdir/server.csr
232232+ rm $workdir/server.pass.key
233233+234234+ # Give key acme permissions
235235+ chmod ${rights} '${cpath}/key.pem'
236236+ chown '${data.user}:${data.group}' '${cpath}/key.pem'
237237+ chmod ${rights} '${cpath}/fullchain.pem'
238238+ chown '${data.user}:${data.group}' '${cpath}/fullchain.pem'
239239+ '';
240240+ serviceConfig = {
241241+ Type = "oneshot";
242242+ RuntimeDirectory = "acme-selfsigned-${cert}";
243243+ PermissionsStartOnly = true;
244244+ User = data.user;
245245+ Group = data.group;
246246+ };
247247+ unitConfig = {
248248+ # Do not create self-signed key when key already exists
249249+ ConditionPathExists = "!${cpath}/key.pem";
250250+ };
251251+ before = [
252252+ "acme-selfsigned-certificates.target"
253253+ ];
254254+ wantedBy = [
255255+ "acme-selfsigned-certificates.target"
256256+ ];
257257+ };
258258+ in (
259259+ [ { name = "acme-${cert}"; value = acmeService; } ]
260260+ ++
261261+ (if cfg.preliminarySelfsigned
262262+ then [ { name = "acme-selfsigned-${cert}"; value = selfsignedService; } ]
263263+ else []
264264+ )
265265+ );
266266+ servicesAttr = listToAttrs services;
267267+ nginxAttr = {
268268+ nginx = {
269269+ after = [ "acme-selfsigned-certificates.target" ];
270270+ wants = [ "acme-selfsigned-certificates.target" "acme-certificates.target" ];
271271+ };
164272 };
165165- path = [ pkgs.simp_le ];
166166- preStart = ''
167167- mkdir -p '${cfg.directory}'
168168- if [ ! -d '${cpath}' ]; then
169169- mkdir '${cpath}'
170170- fi
171171- chmod ${rights} '${cpath}'
172172- chown -R '${data.user}:${data.group}' '${cpath}'
173173- '';
174174- script = ''
175175- cd '${cpath}'
176176- set +e
177177- simp_le ${concatMapStringsSep " " (arg: escapeShellArg (toString arg)) cmdline}
178178- EXITCODE=$?
179179- set -e
180180- echo "$EXITCODE" > /tmp/lastExitCode
181181- exit "$EXITCODE"
182182- '';
183183- postStop = ''
184184- if [ -e /tmp/lastExitCode ] && [ "$(cat /tmp/lastExitCode)" = "0" ]; then
185185- echo "Executing postRun hook..."
186186- ${data.postRun}
187187- fi
188188- '';
189189- })
190190- );
273273+ in
274274+ servicesAttr //
275275+ (if config.services.nginx.enable then nginxAttr else {});
191276192277 systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair
193278 ("acme-${cert}")
···200285 };
201286 })
202287 );
288288+289289+ systemd.targets."acme-selfsigned-certificates" = mkIf cfg.preliminarySelfsigned {};
290290+ systemd.targets."acme-certificates" = {};
203291 })
204292205293 { meta.maintainers = with lib.maintainers; [ abbradar fpletz globin ];
+28
nixos/modules/security/acme.xml
···66666767</section>
68686969+<section><title>Using ACME certificates in Nginx</title>
7070+<para>In practice ACME is mostly used for retrieval and renewal of
7171+ certificates that will be used in a webserver like Nginx. A configuration for
7272+ Nginx that uses the certificates from ACME for
7373+ <literal>foo.example.com</literal> will look similar to:
7474+</para>
7575+7676+<programlisting>
7777+services.nginx.httpConfig = ''
7878+ server {
7979+ server_name foo.example.com;
8080+ listen 443 ssl;
8181+ ssl_certificate ${config.security.acme.directory}/foo.example.com/fullchain.pem;
8282+ ssl_certificate_key ${config.security.acme.directory}/foo.example.com/key.pem;
8383+ root /var/www/foo.example.com/;
8484+ }
8585+'';
8686+</programlisting>
8787+8888+<para>Now Nginx will try to use the certificates that will be retrieved by ACME.
8989+ ACME needs Nginx (or any other webserver) to function and Nginx needs
9090+ the certificates to actually start. For this reason the ACME module
9191+ automatically generates self-signed certificates that will be used by Nginx to
9292+ start. After that Nginx is used by ACME to retrieve the actual ACME
9393+ certificates. <literal>security.acme.preliminarySelfsigned</literal> can be
9494+ used to control whether to generate the self-signed certificates.
9595+</para>
9696+</section>
6997</chapter>