]> git.immae.eu Git - perso/Immae/Config/Nix.git/blame - modules/private/certificates.nix
Remove duply-backup
[perso/Immae/Config/Nix.git] / modules / private / certificates.nix
CommitLineData
6e9f30f4 1{ lib, pkgs, config, name, ... }:
3013caf1 2{
8415083e
IB
3 options.myServices.certificates = {
4 enable = lib.mkEnableOption "enable certificates";
cfda3cfc
IB
5 webroot = lib.mkOption {
6 readOnly = true;
7 default = "/var/lib/acme/acme-challenges";
8 };
3013caf1
IB
9 certConfig = lib.mkOption {
10 default = {
cfda3cfc 11 webroot = lib.mkForce null; # avoids creation of tmpfiles
3013caf1 12 email = "ismael@bouya.org";
6e9f30f4
IB
13 postRun = builtins.concatStringsSep "\n" [
14 (lib.optionalString config.services.httpd.Prod.enable "systemctl reload httpdProd.service")
15 (lib.optionalString config.services.httpd.Tools.enable "systemctl reload httpdTools.service")
16 (lib.optionalString config.services.httpd.Inte.enable "systemctl reload httpdInte.service")
17 (lib.optionalString config.services.nginx.enable "systemctl reload nginx.service")
18 ];
f5761aac 19 extraLegoRenewFlags = [ "--reuse-key" ];
cfda3cfc 20 keyType = lib.mkDefault "ec256"; # https://github.com/NixOS/nixpkgs/pull/83121
3013caf1
IB
21 };
22 description = "Default configuration for certificates";
23 };
24 };
25
8415083e 26 config = lib.mkIf config.myServices.certificates.enable {
6e9f30f4
IB
27 services.nginx = {
28 recommendedTlsSettings = true;
3ffa15ba
IB
29 virtualHosts = {
30 "${config.hostEnv.fqdn}" = {
cfda3cfc 31 acmeRoot = config.myServices.certificates.webroot;
3ffa15ba
IB
32 useACMEHost = name;
33 forceSSL = true;
34 };
35 };
6e9f30f4 36 };
8415083e
IB
37 services.websites.certs = config.myServices.certificates.certConfig;
38 myServices.databasesCerts = config.myServices.certificates.certConfig;
39 myServices.ircCerts = config.myServices.certificates.certConfig;
7df420c2 40
258dd18b 41 security.acme.acceptTerms = true;
5400b9b6 42 security.acme.preliminarySelfsigned = true;
3013caf1 43
5400b9b6 44 security.acme.certs = {
6e9f30f4 45 "${name}" = config.myServices.certificates.certConfig // {
619e4f46 46 domain = config.hostEnv.fqdn;
3013caf1
IB
47 };
48 };
017cb76f 49
cfda3cfc
IB
50 users.users.acme = {
51 uid = config.ids.uids.acme;
52 group = "acme";
53 description = "Acme user";
54 };
55 users.groups.acme = {
56 gid = config.ids.gids.acme;
57 };
58
017cb76f 59 systemd.services = lib.attrsets.mapAttrs' (k: v:
2fe37e49
IB
60 lib.attrsets.nameValuePair "acme-selfsigned-${k}" {
61 wantedBy = [ "acme-selfsigned-certificates.target" ];
62 script = lib.mkAfter ''
63 cp $workdir/server.crt ${config.security.acme.certs."${k}".directory}/cert.pem
64 chown '${v.user}:${v.group}' ${config.security.acme.certs."${k}".directory}/cert.pem
65 chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.certs."${k}".directory}/cert.pem
258dd18b 66
2fe37e49
IB
67 cp $workdir/ca.crt ${config.security.acme.certs."${k}".directory}/chain.pem
68 chown '${v.user}:${v.group}' ${config.security.acme.certs."${k}".directory}/chain.pem
69 chmod ${if v.allowKeysForGroup then "750" else "700"} ${config.security.acme.certs."${k}".directory}/chain.pem
70 '';
71 }
72 ) config.security.acme.certs //
5400b9b6
IB
73 lib.attrsets.mapAttrs' (k: data:
74 lib.attrsets.nameValuePair "acme-${k}" {
37465bc7 75 after = lib.mkAfter [ "bind.service" ];
cfda3cfc 76 serviceConfig =
364b709f 77 let
cfda3cfc
IB
78 cfg = config.security.acme;
79 hashOptions = let
80 domains = builtins.concatStringsSep "," (
81 [ data.domain ] ++ (builtins.attrNames data.extraDomains)
82 );
83 certOptions = builtins.concatStringsSep "," [
84 (if data.ocspMustStaple then "must-staple" else "no-must-staple")
85 ];
86 in
87 builtins.hashString "sha256" (builtins.concatStringsSep ";" [ data.keyType domains certOptions ]);
88 accountsDir = "accounts-${data.keyType}";
89 lpath = "acme/${k}";
90 apath = "/var/lib/${lpath}";
91 spath = "/var/lib/acme/.lego/${k}";
364b709f 92 fileMode = if data.allowKeysForGroup then "640" else "600";
cfda3cfc
IB
93 dirFileMode = if data.allowKeysForGroup then "750" else "700";
94 globalOpts = [ "-d" data.domain "--email" data.email "--path" "." "--key-type" data.keyType ]
95 ++ lib.optionals (cfg.acceptTerms) [ "--accept-tos" ]
96 ++ lib.optionals (data.dnsProvider != null && !data.dnsPropagationCheck) [ "--dns.disable-cp" ]
97 ++ lib.concatLists (lib.mapAttrsToList (name: root: [ "-d" name ]) data.extraDomains)
98 ++ (if data.dnsProvider != null then [ "--dns" data.dnsProvider ] else [ "--http" "--http.webroot" config.myServices.certificates.webroot ])
99 ++ lib.optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)];
100 certOpts = lib.optionals data.ocspMustStaple [ "--must-staple" ];
101 runOpts = lib.escapeShellArgs (globalOpts ++ [ "run" ] ++ certOpts);
102 renewOpts = lib.escapeShellArgs (globalOpts ++
103 [ "renew" "--days" (builtins.toString cfg.validMinDays) ] ++
104 certOpts ++ data.extraLegoRenewFlags);
105 forceRenewOpts = lib.escapeShellArgs (globalOpts ++
106 [ "renew" "--days" "999" ] ++
107 certOpts ++ data.extraLegoRenewFlags);
108 keyName = builtins.replaceStrings ["*"] ["_"] data.domain;
109 in {
110 User = lib.mkForce "acme";
111 Group = lib.mkForce "acme";
112 WorkingDirectory = lib.mkForce spath;
113 StateDirectory = lib.mkForce "acme/.lego/${k} acme/.lego/${accountsDir}";
114 ExecStartPre =
115 let
116 script = pkgs.writeScript "acme-prestart" ''
117 #!${pkgs.runtimeShell} -e
118 install -m 0755 -o acme -g acme -d ${config.myServices.certificates.webroot}
119 '';
120 in
121 lib.mkForce "+${script}";
122 ExecStart = lib.mkForce (pkgs.writeScript "acme-start" ''
364b709f 123 #!${pkgs.runtimeShell} -e
cfda3cfc
IB
124 # lego doesn't check key type after initial creation, we
125 # need to check for him
126 if [ -L ${spath}/accounts -o -d ${spath}/accounts ]; then
127 if [ -L ${spath}/accounts -a "$(readlink ${spath}/accounts)" != ../${accountsDir} ]; then
128 ln -sfn ../${accountsDir} ${spath}/accounts
129 mv -f ${spath}/certificates/${keyName}.key ${spath}/certificates/${keyName}.key.old
130 fi
131 else
132 ln -s ../${accountsDir} ${spath}/accounts
133 fi
134 # check if domain changed: lego doesn't check by itself
135 if [ ! -e ${spath}/certificates/${keyName}.crt -o ! -e ${spath}/certificates/${keyName}.key -o ! -e "${spath}/accounts/acme-v02.api.letsencrypt.org/${data.email}/account.json" ]; then
136 ${pkgs.lego}/bin/lego ${runOpts}
137 elif [ ! -f ${spath}/currentDomains -o "$(cat ${spath}/currentDomains)" != "${hashOptions}" ]; then
138 ${pkgs.lego}/bin/lego ${forceRenewOpts}
139 else
140 ${pkgs.lego}/bin/lego ${renewOpts}
364b709f 141 fi
cfda3cfc
IB
142 '');
143 ExecStartPost =
144 let
145 script = pkgs.writeScript "acme-post-start" ''
146 #!${pkgs.runtimeShell} -e
147 install -m 0755 -o root -g root -d /var/lib/acme
148 install -m 0${dirFileMode} -o ${data.user} -g ${data.group} -d /var/lib/acme/${k}
149 cd /var/lib/acme/${k}
150
151 # Test that existing cert is older than new cert
152 KEY=${spath}/certificates/${keyName}.key
153 KEY_CHANGED=no
154 if [ -e $KEY -a $KEY -nt key.pem ]; then
155 KEY_CHANGED=yes
156 cp -p ${spath}/certificates/${keyName}.key key.pem
157 cp -p ${spath}/certificates/${keyName}.crt fullchain.pem
158 cp -p ${spath}/certificates/${keyName}.issuer.crt chain.pem
159 ln -sf fullchain.pem cert.pem
160 cat key.pem fullchain.pem > full.pem
161 echo -n "${hashOptions}" > ${spath}/currentDomains
162 fi
364b709f 163
cfda3cfc
IB
164 chmod ${fileMode} *.pem
165 chown '${data.user}:${data.group}' *.pem
364b709f 166
cfda3cfc
IB
167 if [ "$KEY_CHANGED" = "yes" ]; then
168 : # noop in case postRun is empty
169 ${data.postRun}
170 fi
171 '';
172 in
173 lib.mkForce "+${script}";
174 };
5400b9b6
IB
175 }
176 ) config.security.acme.certs //
177 {
6e9f30f4
IB
178 httpdProd = lib.mkIf config.services.httpd.Prod.enable
179 { after = [ "acme-selfsigned-certificates.target" ]; wants = [ "acme-selfsigned-certificates.target" ]; };
180 httpdTools = lib.mkIf config.services.httpd.Tools.enable
181 { after = [ "acme-selfsigned-certificates.target" ]; wants = [ "acme-selfsigned-certificates.target" ]; };
182 httpdInte = lib.mkIf config.services.httpd.Inte.enable
183 { after = [ "acme-selfsigned-certificates.target" ]; wants = [ "acme-selfsigned-certificates.target" ]; };
017cb76f 184 };
3013caf1
IB
185 };
186}