@@ -0,0 +1,397 @@
2+### https://github.com/NixOS/nixpkgs/blob/1d36ad6d16dbf1d3937f899a087a4360332eb141/nixos/modules/security/acme.nix
4+### to support new plugins in simp_le for ACMEv2 support.
5+### Changed lines are marked with a comment: ### CHANGED
7+{ config, lib, pkgs, ... }:
13+ cfg = config.security.acme;
15+ certOpts = { name, ... }: {
19+ example = "/var/lib/acme/acme-challenges";
21+ Where the webroot of the HTTP vhost is located.
22+ <filename>.well-known/acme-challenge/</filename> directory
23+ will be created below the webroot if it doesn't exist.
24+ <literal>http://example.org/.well-known/acme-challenge/</literal> must also
25+ be available (notice unencrypted HTTP).
32+ description = "Domain to fetch certificate for (defaults to the entry name)";
36+ type = types.nullOr types.str;
38+ description = "Contact email address for the CA to be able to reach you.";
44+ description = "User running the ACME client.";
50+ description = "Group running the ACME client.";
53+ allowKeysForGroup = mkOption {
57+ Give read permissions to the specified group
58+ (<option>security.acme.cert.<name>.group</option>) to read SSL private certificates.
65+ example = "systemctl reload nginx.service";
67+ Commands to run after new certificates go live. Typically
68+ the web server and other servers using certificates need to
71+ Executed in the same directory with the new certificate.
76+ type = types.listOf (types.enum [
77+ "cert.der" "cert.pem" "chain.pem" "external.sh"
78+ "fullchain.pem" "full.pem" "key.der" "key.pem" "account_key.json" "account_reg.json" ### CHANGED
80+ default = [ "fullchain.pem" "full.pem" "key.pem" "account_key.json" "account_reg.json" ]; ### CHANGED
82+ Plugins to enable. With default settings simp_le will
83+ store public certificate bundle in <filename>fullchain.pem</filename>,
84+ private key in <filename>key.pem</filename> and those two previous
85+ files combined in <filename>full.pem</filename> in its state directory.
89+ activationDelay = mkOption {
90+ type = types.nullOr types.str;
93+ Systemd time span expression to delay copying new certificates to main
94+ state directory. See <citerefentry><refentrytitle>systemd.time</refentrytitle>
95+ <manvolnum>7</manvolnum></citerefentry>.
99+ preDelay = mkOption {
103+ Commands to run after certificates are re-issued but before they are
104+ activated. Typically the new certificate is published to DNS.
106+ Executed in the same directory with the new certificate.
110+ extraDomains = mkOption {
111+ type = types.attrsOf (types.nullOr types.str);
113+ example = literalExample ''
115+ "example.org" = "/srv/http/nginx";
116+ "mydomain.org" = null;
120+ A list of extra domain names, which are included in the one certificate to be issued, with their
121+ own server roots if needed.
135+ directory = mkOption {
136+ default = "/var/lib/acme";
139+ Directory where certs and other state will be stored by default.
143+ validMin = mkOption {
145+ default = 30 * 24 * 3600;
146+ description = "Minimum remaining validity before renewal in seconds.";
149+ renewInterval = mkOption {
153+ Systemd calendar expression when to check for renewal. See
154+ <citerefentry><refentrytitle>systemd.time</refentrytitle>
155+ <manvolnum>7</manvolnum></citerefentry>.
159+ preliminarySelfsigned = mkOption {
163+ Whether a preliminary self-signed certificate should be generated before
164+ doing ACME requests. This can be useful when certificates are required in
165+ a webserver, but ACME needs the webserver to make its requests.
167+ With preliminary self-signed certificate the webserver can be started and
168+ can later reload the correct ACME certificates.
172+ production = mkOption {
176+ If set to true, use Let's Encrypt's production environment
177+ instead of the staging environment. The main benefit of the
178+ staging environment is to get much higher rate limits.
181+ <literal>https://letsencrypt.org/docs/staging-environment</literal>
188+ type = with types; attrsOf (submodule certOpts);
190+ Attribute set of certificates to get signed and renewed.
192+ example = literalExample ''
195+ webroot = "/var/www/challenges/";
196+ email = "foo@example.com";
197+ extraDomains = { "www.example.com" = null; "foo.example.com" = "/var/www/foo/"; };
199+ "bar.example.com" = {
200+ webroot = "/var/www/challenges/";
201+ email = "bar@example.com";
209+ ###### implementation
211+ (mkIf (cfg.certs != { }) {
213+ systemd.services = let
214+ services = concatLists servicesLists;
215+ servicesLists = mapAttrsToList certToServices cfg.certs;
216+ certToServices = cert: data:
218+ cpath = lpath + optionalString (data.activationDelay != null) ".staging";
219+ lpath = "${cfg.directory}/${cert}";
220+ rights = if data.allowKeysForGroup then "750" else "700";
221+ cmdline = [ "-v" "-d" data.domain "--default_root" data.webroot "--valid_min" cfg.validMin ]
222+ ++ optionals (data.email != null) [ "--email" data.email ]
223+ ++ concatMap (p: [ "-f" p ]) data.plugins
224+ ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains)
225+ ++ optionals (!cfg.production) ["--server" "https://acme-staging.api.letsencrypt.org/directory"];
227+ description = "Renew ACME Certificate for ${cert}";
228+ after = [ "network.target" "network-online.target" ];
229+ wants = [ "network-online.target" ];
232+ SuccessExitStatus = [ "0" "1" ];
233+ PermissionsStartOnly = true;
238+ path = with pkgs; [ simp_le systemd ];
240+ mkdir -p '${cfg.directory}'
241+ chown 'root:root' '${cfg.directory}'
242+ chmod 755 '${cfg.directory}'
243+ if [ ! -d '${cpath}' ]; then
246+ chmod ${rights} '${cpath}'
247+ chown -R '${data.user}:${data.group}' '${cpath}'
248+ mkdir -p '${data.webroot}/.well-known/acme-challenge'
249+ chown -R '${data.user}:${data.group}' '${data.webroot}/.well-known/acme-challenge'
254+ simp_le ${escapeShellArgs cmdline}
257+ echo "$EXITCODE" > /tmp/lastExitCode
263+ if [ -e /tmp/lastExitCode ] && [ "$(cat /tmp/lastExitCode)" = "0" ]; then
264+ ${if data.activationDelay != null then ''
268+ if [ -d '${lpath}' ]; then
269+ systemd-run --no-block --on-active='${data.activationDelay}' --unit acme-setlive-${cert}.service
271+ systemctl --wait start acme-setlive-${cert}.service
273+ '' else data.postRun}
275+ # noop ensuring that the "if" block is non-empty even if
276+ # activationDelay == null and postRun == ""
281+ before = [ "acme-certificates.target" ];
282+ wantedBy = [ "acme-certificates.target" ];
285+ description = "Set certificate for ${cert} live";
286+ path = with pkgs; [ rsync ];
291+ rsync -a --delete-after '${cpath}/' '${lpath}'
293+ postStop = data.postRun;
295+ selfsignedService = {
296+ description = "Create preliminary self-signed certificate for ${cert}";
297+ path = [ pkgs.openssl ];
299+ if [ ! -d '${cpath}' ]
302+ chmod ${rights} '${cpath}'
303+ chown '${data.user}:${data.group}' '${cpath}'
308+ workdir="$(mktemp -d)"
311+ openssl genrsa -des3 -passout pass:xxxx -out $workdir/ca.pass.key 2048
312+ openssl rsa -passin pass:xxxx -in $workdir/ca.pass.key -out $workdir/ca.key
313+ openssl req -new -key $workdir/ca.key -out $workdir/ca.csr \
314+ -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=Security Department/CN=example.com"
315+ openssl x509 -req -days 1 -in $workdir/ca.csr -signkey $workdir/ca.key -out $workdir/ca.crt
318+ openssl genrsa -des3 -passout pass:xxxx -out $workdir/server.pass.key 2048
319+ openssl rsa -passin pass:xxxx -in $workdir/server.pass.key -out $workdir/server.key
320+ openssl req -new -key $workdir/server.key -out $workdir/server.csr \
321+ -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
322+ openssl x509 -req -days 1 -in $workdir/server.csr -CA $workdir/ca.crt \
323+ -CAkey $workdir/ca.key -CAserial $workdir/ca.srl -CAcreateserial \
324+ -out $workdir/server.crt
326+ # Copy key to destination
327+ cp $workdir/server.key ${cpath}/key.pem
329+ # Create fullchain.pem (same format as "simp_le ... -f fullchain.pem" creates)
330+ cat $workdir/{server.crt,ca.crt} > "${cpath}/fullchain.pem"
332+ # Create full.pem for e.g. lighttpd
333+ cat $workdir/{server.key,server.crt,ca.crt} > "${cpath}/full.pem"
335+ # Give key acme permissions
336+ chown '${data.user}:${data.group}' "${cpath}/"{key,fullchain,full}.pem
337+ chmod ${rights} "${cpath}/"{key,fullchain,full}.pem
341+ PermissionsStartOnly = true;
347+ # Do not create self-signed key when key already exists
348+ ConditionPathExists = "!${cpath}/key.pem";
351+ "acme-selfsigned-certificates.target"
354+ "acme-selfsigned-certificates.target"
358+ [ { name = "acme-${cert}"; value = acmeService; } ]
359+ ++ optional cfg.preliminarySelfsigned { name = "acme-selfsigned-${cert}"; value = selfsignedService; }
360+ ++ optional (data.activationDelay != null) { name = "acme-setlive-${cert}"; value = delayService; }
362+ servicesAttr = listToAttrs services;
363+ injectServiceDep = {
364+ after = [ "acme-selfsigned-certificates.target" ];
365+ wants = [ "acme-selfsigned-certificates.target" "acme-certificates.target" ];
369+ (if config.services.nginx.enable then { nginx = injectServiceDep; } else {}) //
370+ (if config.services.lighttpd.enable then { lighttpd = injectServiceDep; } else {});
372+ systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair
375+ description = "Renew ACME Certificate for ${cert}";
376+ wantedBy = [ "timers.target" ];
378+ OnCalendar = cfg.renewInterval;
379+ Unit = "acme-${cert}.service";
382+ RandomizedDelaySec = "1h";
387+ systemd.targets."acme-selfsigned-certificates" = mkIf cfg.preliminarySelfsigned {};
388+ systemd.targets."acme-certificates" = {};
394+ maintainers = with lib.maintainers; [ abbradar fpletz globin ];
395+ #doc = ./acme.xml; ### CHANGED