diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2020-05-23 03:35:03 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2020-05-23 03:35:03 +0200 |
commit | cfda3cfc35445979225850f686f338e6d4ace372 (patch) | |
tree | a0f6bde8e7d04962085896e73a6467645b61c952 /modules | |
parent | db343436f0e678ef3a97e6f8ac559ffa0507e422 (diff) | |
download | Nix-cfda3cfc35445979225850f686f338e6d4ace372.tar.gz Nix-cfda3cfc35445979225850f686f338e6d4ace372.tar.zst Nix-cfda3cfc35445979225850f686f338e6d4ace372.zip |
Fix acme certificate generation
Diffstat (limited to 'modules')
-rw-r--r-- | modules/myids.nix | 2 | ||||
-rw-r--r-- | modules/private/certificates.nix | 151 | ||||
-rw-r--r-- | modules/private/monitoring/status.nix | 2 | ||||
-rw-r--r-- | modules/private/system/dilion.nix | 2 | ||||
-rw-r--r-- | modules/private/websites/papa/maison_bbc.nix | 4 |
5 files changed, 116 insertions, 45 deletions
diff --git a/modules/myids.nix b/modules/myids.nix index 79610af..1a1a5d6 100644 --- a/modules/myids.nix +++ b/modules/myids.nix | |||
@@ -3,6 +3,7 @@ | |||
3 | # Check that there is no clash with nixos/modules/misc/ids.nix | 3 | # Check that there is no clash with nixos/modules/misc/ids.nix |
4 | config = { | 4 | config = { |
5 | ids.uids = { | 5 | ids.uids = { |
6 | acme = 388; | ||
6 | backup = 389; | 7 | backup = 389; |
7 | vhost = 390; | 8 | vhost = 390; |
8 | openarc = 391; | 9 | openarc = 391; |
@@ -16,6 +17,7 @@ | |||
16 | }; | 17 | }; |
17 | ids.gids = { | 18 | ids.gids = { |
18 | nagios = 11; # commented in the ids file | 19 | nagios = 11; # commented in the ids file |
20 | acme = 388; | ||
19 | backup = 389; | 21 | backup = 389; |
20 | vhost = 390; | 22 | vhost = 390; |
21 | openarc = 391; | 23 | openarc = 391; |
diff --git a/modules/private/certificates.nix b/modules/private/certificates.nix index bbe4c3b..c568783 100644 --- a/modules/private/certificates.nix +++ b/modules/private/certificates.nix | |||
@@ -2,9 +2,13 @@ | |||
2 | { | 2 | { |
3 | options.myServices.certificates = { | 3 | options.myServices.certificates = { |
4 | enable = lib.mkEnableOption "enable certificates"; | 4 | enable = lib.mkEnableOption "enable certificates"; |
5 | webroot = lib.mkOption { | ||
6 | readOnly = true; | ||
7 | default = "/var/lib/acme/acme-challenges"; | ||
8 | }; | ||
5 | certConfig = lib.mkOption { | 9 | certConfig = lib.mkOption { |
6 | default = { | 10 | default = { |
7 | webroot = "/var/lib/acme/acme-challenges"; | 11 | webroot = lib.mkForce null; # avoids creation of tmpfiles |
8 | email = "ismael@bouya.org"; | 12 | email = "ismael@bouya.org"; |
9 | postRun = builtins.concatStringsSep "\n" [ | 13 | postRun = builtins.concatStringsSep "\n" [ |
10 | (lib.optionalString config.services.httpd.Prod.enable "systemctl reload httpdProd.service") | 14 | (lib.optionalString config.services.httpd.Prod.enable "systemctl reload httpdProd.service") |
@@ -13,6 +17,7 @@ | |||
13 | (lib.optionalString config.services.nginx.enable "systemctl reload nginx.service") | 17 | (lib.optionalString config.services.nginx.enable "systemctl reload nginx.service") |
14 | ]; | 18 | ]; |
15 | extraLegoRenewFlags = [ "--reuse-key" ]; | 19 | extraLegoRenewFlags = [ "--reuse-key" ]; |
20 | keyType = lib.mkDefault "ec256"; # https://github.com/NixOS/nixpkgs/pull/83121 | ||
16 | }; | 21 | }; |
17 | description = "Default configuration for certificates"; | 22 | description = "Default configuration for certificates"; |
18 | }; | 23 | }; |
@@ -20,13 +25,13 @@ | |||
20 | 25 | ||
21 | config = lib.mkIf config.myServices.certificates.enable { | 26 | config = lib.mkIf config.myServices.certificates.enable { |
22 | services.duplyBackup.profiles.system.excludeFile = '' | 27 | services.duplyBackup.profiles.system.excludeFile = '' |
23 | + /var/lib/acme/acme-challenges | 28 | + ${config.myServices.certificates.webroot} |
24 | ''; | 29 | ''; |
25 | services.nginx = { | 30 | services.nginx = { |
26 | recommendedTlsSettings = true; | 31 | recommendedTlsSettings = true; |
27 | virtualHosts = { | 32 | virtualHosts = { |
28 | "${config.hostEnv.fqdn}" = { | 33 | "${config.hostEnv.fqdn}" = { |
29 | acmeRoot = config.security.acme.certs."${name}".webroot; | 34 | acmeRoot = config.myServices.certificates.webroot; |
30 | useACMEHost = name; | 35 | useACMEHost = name; |
31 | forceSSL = true; | 36 | forceSSL = true; |
32 | }; | 37 | }; |
@@ -45,6 +50,15 @@ | |||
45 | }; | 50 | }; |
46 | }; | 51 | }; |
47 | 52 | ||
53 | users.users.acme = { | ||
54 | uid = config.ids.uids.acme; | ||
55 | group = "acme"; | ||
56 | description = "Acme user"; | ||
57 | }; | ||
58 | users.groups.acme = { | ||
59 | gid = config.ids.gids.acme; | ||
60 | }; | ||
61 | |||
48 | systemd.services = lib.attrsets.mapAttrs' (k: v: | 62 | systemd.services = lib.attrsets.mapAttrs' (k: v: |
49 | lib.attrsets.nameValuePair "acme-selfsigned-${k}" { | 63 | lib.attrsets.nameValuePair "acme-selfsigned-${k}" { |
50 | wantedBy = [ "acme-selfsigned-certificates.target" ]; | 64 | wantedBy = [ "acme-selfsigned-certificates.target" ]; |
@@ -62,50 +76,105 @@ | |||
62 | lib.attrsets.mapAttrs' (k: data: | 76 | lib.attrsets.mapAttrs' (k: data: |
63 | lib.attrsets.nameValuePair "acme-${k}" { | 77 | lib.attrsets.nameValuePair "acme-${k}" { |
64 | after = lib.mkAfter [ "bind.service" ]; | 78 | after = lib.mkAfter [ "bind.service" ]; |
65 | serviceConfig.ExecStartPre = | 79 | serviceConfig = |
66 | let | ||
67 | script = pkgs.writeScript "acme-pre-start" '' | ||
68 | #!${pkgs.runtimeShell} -e | ||
69 | mkdir -p '${data.webroot}/.well-known/acme-challenge' | ||
70 | chmod a+w '${data.webroot}/.well-known/acme-challenge' | ||
71 | #doesn't work for multiple concurrent runs | ||
72 | #chown -R '${data.user}:${data.group}' '${data.webroot}/.well-known/acme-challenge' | ||
73 | ''; | ||
74 | in | ||
75 | "+${script}"; | ||
76 | # This is a workaround to | ||
77 | # https://github.com/NixOS/nixpkgs/issues/84409 | ||
78 | # https://github.com/NixOS/nixpkgs/issues/84633 | ||
79 | serviceConfig.RemainAfterExit = lib.mkForce false; | ||
80 | serviceConfig.WorkingDirectory = lib.mkForce "/var/lib/acme/${k}/.lego"; | ||
81 | serviceConfig.StateDirectory = lib.mkForce "acme/${k}/.lego acme/${k} acme/.lego/${k} acme/.lego/accounts"; | ||
82 | serviceConfig.ExecStartPost = | ||
83 | let | 80 | let |
84 | keyName = builtins.replaceStrings ["*"] ["_"] data.domain; | 81 | cfg = config.security.acme; |
82 | hashOptions = let | ||
83 | domains = builtins.concatStringsSep "," ( | ||
84 | [ data.domain ] ++ (builtins.attrNames data.extraDomains) | ||
85 | ); | ||
86 | certOptions = builtins.concatStringsSep "," [ | ||
87 | (if data.ocspMustStaple then "must-staple" else "no-must-staple") | ||
88 | ]; | ||
89 | in | ||
90 | builtins.hashString "sha256" (builtins.concatStringsSep ";" [ data.keyType domains certOptions ]); | ||
91 | accountsDir = "accounts-${data.keyType}"; | ||
92 | lpath = "acme/${k}"; | ||
93 | apath = "/var/lib/${lpath}"; | ||
94 | spath = "/var/lib/acme/.lego/${k}"; | ||
85 | fileMode = if data.allowKeysForGroup then "640" else "600"; | 95 | fileMode = if data.allowKeysForGroup then "640" else "600"; |
86 | spath = "/var/lib/acme/${k}/.lego"; | 96 | dirFileMode = if data.allowKeysForGroup then "750" else "700"; |
87 | script = pkgs.writeScript "acme-post-start" '' | 97 | globalOpts = [ "-d" data.domain "--email" data.email "--path" "." "--key-type" data.keyType ] |
98 | ++ lib.optionals (cfg.acceptTerms) [ "--accept-tos" ] | ||
99 | ++ lib.optionals (data.dnsProvider != null && !data.dnsPropagationCheck) [ "--dns.disable-cp" ] | ||
100 | ++ lib.concatLists (lib.mapAttrsToList (name: root: [ "-d" name ]) data.extraDomains) | ||
101 | ++ (if data.dnsProvider != null then [ "--dns" data.dnsProvider ] else [ "--http" "--http.webroot" config.myServices.certificates.webroot ]) | ||
102 | ++ lib.optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)]; | ||
103 | certOpts = lib.optionals data.ocspMustStaple [ "--must-staple" ]; | ||
104 | runOpts = lib.escapeShellArgs (globalOpts ++ [ "run" ] ++ certOpts); | ||
105 | renewOpts = lib.escapeShellArgs (globalOpts ++ | ||
106 | [ "renew" "--days" (builtins.toString cfg.validMinDays) ] ++ | ||
107 | certOpts ++ data.extraLegoRenewFlags); | ||
108 | forceRenewOpts = lib.escapeShellArgs (globalOpts ++ | ||
109 | [ "renew" "--days" "999" ] ++ | ||
110 | certOpts ++ data.extraLegoRenewFlags); | ||
111 | keyName = builtins.replaceStrings ["*"] ["_"] data.domain; | ||
112 | in { | ||
113 | User = lib.mkForce "acme"; | ||
114 | Group = lib.mkForce "acme"; | ||
115 | WorkingDirectory = lib.mkForce spath; | ||
116 | StateDirectory = lib.mkForce "acme/.lego/${k} acme/.lego/${accountsDir}"; | ||
117 | ExecStartPre = | ||
118 | let | ||
119 | script = pkgs.writeScript "acme-prestart" '' | ||
120 | #!${pkgs.runtimeShell} -e | ||
121 | install -m 0755 -o acme -g acme -d ${config.myServices.certificates.webroot} | ||
122 | ''; | ||
123 | in | ||
124 | lib.mkForce "+${script}"; | ||
125 | ExecStart = lib.mkForce (pkgs.writeScript "acme-start" '' | ||
88 | #!${pkgs.runtimeShell} -e | 126 | #!${pkgs.runtimeShell} -e |
89 | cd /var/lib/acme/${k} | 127 | # lego doesn't check key type after initial creation, we |
90 | 128 | # need to check for him | |
91 | # Test that existing cert is older than new cert | 129 | if [ -L ${spath}/accounts -o -d ${spath}/accounts ]; then |
92 | KEY=${spath}/certificates/${keyName}.key | 130 | if [ -L ${spath}/accounts -a "$(readlink ${spath}/accounts)" != ../${accountsDir} ]; then |
93 | if [ -e $KEY -a $KEY -nt key.pem ]; then | 131 | ln -sfn ../${accountsDir} ${spath}/accounts |
94 | cp -p ${spath}/certificates/${keyName}.key key.pem | 132 | mv -f ${spath}/certificates/${keyName}.key ${spath}/certificates/${keyName}.key.old |
95 | cp -p ${spath}/certificates/${keyName}.crt fullchain.pem | 133 | fi |
96 | cp -p ${spath}/certificates/${keyName}.issuer.crt chain.pem | 134 | else |
97 | ln -sf fullchain.pem cert.pem | 135 | ln -s ../${accountsDir} ${spath}/accounts |
98 | cat key.pem fullchain.pem > full.pem | 136 | fi |
99 | 137 | # check if domain changed: lego doesn't check by itself | |
100 | ${data.postRun} | 138 | 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 |
139 | ${pkgs.lego}/bin/lego ${runOpts} | ||
140 | elif [ ! -f ${spath}/currentDomains -o "$(cat ${spath}/currentDomains)" != "${hashOptions}" ]; then | ||
141 | ${pkgs.lego}/bin/lego ${forceRenewOpts} | ||
142 | else | ||
143 | ${pkgs.lego}/bin/lego ${renewOpts} | ||
101 | fi | 144 | fi |
145 | ''); | ||
146 | ExecStartPost = | ||
147 | let | ||
148 | script = pkgs.writeScript "acme-post-start" '' | ||
149 | #!${pkgs.runtimeShell} -e | ||
150 | install -m 0755 -o root -g root -d /var/lib/acme | ||
151 | install -m 0${dirFileMode} -o ${data.user} -g ${data.group} -d /var/lib/acme/${k} | ||
152 | cd /var/lib/acme/${k} | ||
153 | |||
154 | # Test that existing cert is older than new cert | ||
155 | KEY=${spath}/certificates/${keyName}.key | ||
156 | KEY_CHANGED=no | ||
157 | if [ -e $KEY -a $KEY -nt key.pem ]; then | ||
158 | KEY_CHANGED=yes | ||
159 | cp -p ${spath}/certificates/${keyName}.key key.pem | ||
160 | cp -p ${spath}/certificates/${keyName}.crt fullchain.pem | ||
161 | cp -p ${spath}/certificates/${keyName}.issuer.crt chain.pem | ||
162 | ln -sf fullchain.pem cert.pem | ||
163 | cat key.pem fullchain.pem > full.pem | ||
164 | echo -n "${hashOptions}" > ${spath}/currentDomains | ||
165 | fi | ||
102 | 166 | ||
103 | chmod ${fileMode} *.pem | 167 | chmod ${fileMode} *.pem |
104 | chown '${data.user}:${data.group}' *.pem | 168 | chown '${data.user}:${data.group}' *.pem |
105 | ''; | ||
106 | in | ||
107 | lib.mkForce "+${script}"; | ||
108 | 169 | ||
170 | if [ "$KEY_CHANGED" = "yes" ]; then | ||
171 | : # noop in case postRun is empty | ||
172 | ${data.postRun} | ||
173 | fi | ||
174 | ''; | ||
175 | in | ||
176 | lib.mkForce "+${script}"; | ||
177 | }; | ||
109 | } | 178 | } |
110 | ) config.security.acme.certs // | 179 | ) config.security.acme.certs // |
111 | { | 180 | { |
diff --git a/modules/private/monitoring/status.nix b/modules/private/monitoring/status.nix index e0bc0e1..4f5f4bb 100644 --- a/modules/private/monitoring/status.nix +++ b/modules/private/monitoring/status.nix | |||
@@ -29,7 +29,7 @@ | |||
29 | recommendedGzipSettings = true; | 29 | recommendedGzipSettings = true; |
30 | recommendedProxySettings = true; | 30 | recommendedProxySettings = true; |
31 | virtualHosts."status.immae.eu" = { | 31 | virtualHosts."status.immae.eu" = { |
32 | acmeRoot = config.security.acme.certs."${name}".webroot; | 32 | acmeRoot = config.myServices.certificates.webroot; |
33 | useACMEHost = name; | 33 | useACMEHost = name; |
34 | forceSSL = true; | 34 | forceSSL = true; |
35 | locations."/".proxyPass = "http://unix:/run/naemon-status/socket.sock:/"; | 35 | locations."/".proxyPass = "http://unix:/run/naemon-status/socket.sock:/"; |
diff --git a/modules/private/system/dilion.nix b/modules/private/system/dilion.nix index b9e83f3..4860d07 100644 --- a/modules/private/system/dilion.nix +++ b/modules/private/system/dilion.nix | |||
@@ -1,5 +1,5 @@ | |||
1 | { privateFiles }: | 1 | { privateFiles }: |
2 | { config, pkgs, ... }: | 2 | { config, pkgs, name, ... }: |
3 | { | 3 | { |
4 | boot.kernelPackages = pkgs.linuxPackages_latest; | 4 | boot.kernelPackages = pkgs.linuxPackages_latest; |
5 | myEnv = import "${privateFiles}/environment.nix" // { inherit privateFiles; }; | 5 | myEnv = import "${privateFiles}/environment.nix" // { inherit privateFiles; }; |
diff --git a/modules/private/websites/papa/maison_bbc.nix b/modules/private/websites/papa/maison_bbc.nix index 5fbc62f..aa6d866 100644 --- a/modules/private/websites/papa/maison_bbc.nix +++ b/modules/private/websites/papa/maison_bbc.nix | |||
@@ -35,7 +35,7 @@ in { | |||
35 | root = varDir; | 35 | root = varDir; |
36 | extraConfig = [ | 36 | extraConfig = [ |
37 | '' | 37 | '' |
38 | Alias /.well-known/acme-challenge ${config.security.acme.certs.papa.webroot}/.well-known/acme-challenge | 38 | Alias /.well-known/acme-challenge ${config.myServices.certificates.webroot}/.well-known/acme-challenge |
39 | RedirectMatch 301 ^/((?!(\.well-known|add.php).*$).*)$ https://maison.bbc.bouya.org/$1 | 39 | RedirectMatch 301 ^/((?!(\.well-known|add.php).*$).*)$ https://maison.bbc.bouya.org/$1 |
40 | <Directory ${varDir}> | 40 | <Directory ${varDir}> |
41 | DirectoryIndex index.php index.htm index.html | 41 | DirectoryIndex index.php index.htm index.html |
@@ -45,7 +45,7 @@ in { | |||
45 | SetHandler "proxy:unix:${config.services.phpfpm.pools.papa_maison_bbc.socket}|fcgi://localhost" | 45 | SetHandler "proxy:unix:${config.services.phpfpm.pools.papa_maison_bbc.socket}|fcgi://localhost" |
46 | </FilesMatch> | 46 | </FilesMatch> |
47 | </Directory> | 47 | </Directory> |
48 | <Directory "${config.security.acme.certs.papa.webroot}"> | 48 | <Directory "${config.myServices.certificates.webroot}"> |
49 | Options Indexes FollowSymLinks | 49 | Options Indexes FollowSymLinks |
50 | AllowOverride None | 50 | AllowOverride None |
51 | Require all granted | 51 | Require all granted |