diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/acme2.nix | 353 | ||||
-rw-r--r-- | modules/default.nix | 1 | ||||
-rw-r--r-- | modules/webapps/mastodon.nix | 2 | ||||
-rw-r--r-- | modules/webapps/webstats/default.nix | 2 | ||||
-rw-r--r-- | modules/websites/default.nix | 28 | ||||
-rw-r--r-- | modules/websites/httpd-service-builder.nix | 68 | ||||
-rw-r--r-- | modules/websites/php-application.nix | 31 |
7 files changed, 53 insertions, 432 deletions
diff --git a/modules/acme2.nix b/modules/acme2.nix deleted file mode 100644 index b22e4ccc..00000000 --- a/modules/acme2.nix +++ /dev/null | |||
@@ -1,353 +0,0 @@ | |||
1 | { config, lib, pkgs, ... }: | ||
2 | |||
3 | with lib; | ||
4 | |||
5 | let | ||
6 | |||
7 | cfg = config.security.acme2; | ||
8 | |||
9 | certOpts = { name, ... }: { | ||
10 | options = { | ||
11 | webroot = mkOption { | ||
12 | type = types.str; | ||
13 | example = "/var/lib/acme/acme-challenges"; | ||
14 | description = '' | ||
15 | Where the webroot of the HTTP vhost is located. | ||
16 | <filename>.well-known/acme-challenge/</filename> directory | ||
17 | will be created below the webroot if it doesn't exist. | ||
18 | <literal>http://example.org/.well-known/acme-challenge/</literal> must also | ||
19 | be available (notice unencrypted HTTP). | ||
20 | ''; | ||
21 | }; | ||
22 | |||
23 | server = mkOption { | ||
24 | type = types.nullOr types.str; | ||
25 | default = null; | ||
26 | description = '' | ||
27 | ACME Directory Resource URI. Defaults to let's encrypt | ||
28 | production endpoint, | ||
29 | https://acme-v02.api.letsencrypt.org/directory, if unset. | ||
30 | ''; | ||
31 | }; | ||
32 | |||
33 | domain = mkOption { | ||
34 | type = types.str; | ||
35 | default = name; | ||
36 | description = "Domain to fetch certificate for (defaults to the entry name)"; | ||
37 | }; | ||
38 | |||
39 | email = mkOption { | ||
40 | type = types.nullOr types.str; | ||
41 | default = null; | ||
42 | description = "Contact email address for the CA to be able to reach you."; | ||
43 | }; | ||
44 | |||
45 | user = mkOption { | ||
46 | type = types.str; | ||
47 | default = "root"; | ||
48 | description = "User running the ACME client."; | ||
49 | }; | ||
50 | |||
51 | group = mkOption { | ||
52 | type = types.str; | ||
53 | default = "root"; | ||
54 | description = "Group running the ACME client."; | ||
55 | }; | ||
56 | |||
57 | allowKeysForGroup = mkOption { | ||
58 | type = types.bool; | ||
59 | default = false; | ||
60 | description = '' | ||
61 | Give read permissions to the specified group | ||
62 | (<option>security.acme2.cert.<name>.group</option>) to read SSL private certificates. | ||
63 | ''; | ||
64 | }; | ||
65 | |||
66 | postRun = mkOption { | ||
67 | type = types.lines; | ||
68 | default = ""; | ||
69 | example = "systemctl reload nginx.service"; | ||
70 | description = '' | ||
71 | Commands to run after new certificates go live. Typically | ||
72 | the web server and other servers using certificates need to | ||
73 | be reloaded. | ||
74 | |||
75 | Executed in the same directory with the new certificate. | ||
76 | ''; | ||
77 | }; | ||
78 | |||
79 | plugins = mkOption { | ||
80 | type = types.listOf (types.enum [ | ||
81 | "cert.der" "cert.pem" "chain.pem" "external.sh" | ||
82 | "fullchain.pem" "full.pem" "key.der" "key.pem" "account_key.json" "account_reg.json" | ||
83 | ]); | ||
84 | default = [ "fullchain.pem" "full.pem" "key.pem" "account_key.json" "account_reg.json" ]; | ||
85 | description = '' | ||
86 | Plugins to enable. With default settings simp_le will | ||
87 | store public certificate bundle in <filename>fullchain.pem</filename>, | ||
88 | private key in <filename>key.pem</filename> and those two previous | ||
89 | files combined in <filename>full.pem</filename> in its state directory. | ||
90 | ''; | ||
91 | }; | ||
92 | |||
93 | directory = mkOption { | ||
94 | type = types.str; | ||
95 | readOnly = true; | ||
96 | default = "/var/lib/acme/${name}"; | ||
97 | description = "Directory where certificate and other state is stored."; | ||
98 | }; | ||
99 | |||
100 | extraDomains = mkOption { | ||
101 | type = types.attrsOf (types.nullOr types.str); | ||
102 | default = {}; | ||
103 | example = literalExample '' | ||
104 | { | ||
105 | "example.org" = "/srv/http/nginx"; | ||
106 | "mydomain.org" = null; | ||
107 | } | ||
108 | ''; | ||
109 | description = '' | ||
110 | A list of extra domain names, which are included in the one certificate to be issued, with their | ||
111 | own server roots if needed. | ||
112 | ''; | ||
113 | }; | ||
114 | }; | ||
115 | }; | ||
116 | |||
117 | in | ||
118 | |||
119 | { | ||
120 | |||
121 | ###### interface | ||
122 | imports = [ | ||
123 | (mkRemovedOptionModule [ "security" "acme2" "production" ] '' | ||
124 | Use security.acme2.server to define your staging ACME server URL instead. | ||
125 | |||
126 | To use the let's encrypt staging server, use security.acme2.server = | ||
127 | "https://acme-staging-v02.api.letsencrypt.org/directory". | ||
128 | '' | ||
129 | ) | ||
130 | (mkRemovedOptionModule [ "security" "acme2" "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.") | ||
131 | (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") | ||
132 | (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") | ||
133 | ]; | ||
134 | options = { | ||
135 | security.acme2 = { | ||
136 | |||
137 | validMin = mkOption { | ||
138 | type = types.int; | ||
139 | default = 30 * 24 * 3600; | ||
140 | description = "Minimum remaining validity before renewal in seconds."; | ||
141 | }; | ||
142 | |||
143 | renewInterval = mkOption { | ||
144 | type = types.str; | ||
145 | default = "weekly"; | ||
146 | description = '' | ||
147 | Systemd calendar expression when to check for renewal. See | ||
148 | <citerefentry><refentrytitle>systemd.time</refentrytitle> | ||
149 | <manvolnum>7</manvolnum></citerefentry>. | ||
150 | ''; | ||
151 | }; | ||
152 | |||
153 | server = mkOption { | ||
154 | type = types.nullOr types.str; | ||
155 | default = null; | ||
156 | description = '' | ||
157 | ACME Directory Resource URI. Defaults to let's encrypt | ||
158 | production endpoint, | ||
159 | <literal>https://acme-v02.api.letsencrypt.org/directory</literal>, if unset. | ||
160 | ''; | ||
161 | }; | ||
162 | |||
163 | preliminarySelfsigned = mkOption { | ||
164 | type = types.bool; | ||
165 | default = true; | ||
166 | description = '' | ||
167 | Whether a preliminary self-signed certificate should be generated before | ||
168 | doing ACME requests. This can be useful when certificates are required in | ||
169 | a webserver, but ACME needs the webserver to make its requests. | ||
170 | |||
171 | With preliminary self-signed certificate the webserver can be started and | ||
172 | can later reload the correct ACME certificates. | ||
173 | ''; | ||
174 | }; | ||
175 | |||
176 | certs = mkOption { | ||
177 | default = { }; | ||
178 | type = with types; attrsOf (submodule certOpts); | ||
179 | description = '' | ||
180 | Attribute set of certificates to get signed and renewed. Creates | ||
181 | <literal>acme-''${cert}.{service,timer}</literal> systemd units for | ||
182 | each certificate defined here. Other services can add dependencies | ||
183 | to those units if they rely on the certificates being present, | ||
184 | or trigger restarts of the service if certificates get renewed. | ||
185 | ''; | ||
186 | example = literalExample '' | ||
187 | { | ||
188 | "example.com" = { | ||
189 | webroot = "/var/www/challenges/"; | ||
190 | email = "foo@example.com"; | ||
191 | extraDomains = { "www.example.com" = null; "foo.example.com" = "/var/www/foo/"; }; | ||
192 | }; | ||
193 | "bar.example.com" = { | ||
194 | webroot = "/var/www/challenges/"; | ||
195 | email = "bar@example.com"; | ||
196 | }; | ||
197 | } | ||
198 | ''; | ||
199 | }; | ||
200 | }; | ||
201 | }; | ||
202 | |||
203 | ###### implementation | ||
204 | config = mkMerge [ | ||
205 | (mkIf (cfg.certs != { }) { | ||
206 | |||
207 | systemd.services = let | ||
208 | services = concatLists servicesLists; | ||
209 | servicesLists = mapAttrsToList certToServices cfg.certs; | ||
210 | certToServices = cert: data: | ||
211 | let | ||
212 | lpath = "acme/${cert}"; | ||
213 | rights = if data.allowKeysForGroup then "750" else "700"; | ||
214 | cmdline = [ "-v" "-d" data.domain "--default_root" data.webroot "--valid_min" cfg.validMin ] | ||
215 | ++ optionals (data.email != null) [ "--email" data.email ] | ||
216 | ++ concatMap (p: [ "-f" p ]) data.plugins | ||
217 | ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains) | ||
218 | ++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)]; | ||
219 | acmeService = { | ||
220 | description = "Renew ACME Certificate for ${cert}"; | ||
221 | after = [ "network.target" "network-online.target" ]; | ||
222 | wants = [ "network-online.target" ]; | ||
223 | # simp_le uses requests, which uses certifi under the hood, | ||
224 | # which doesn't respect the system trust store. | ||
225 | # At least in the acme test, we provision a fake CA, impersonating the LE endpoint. | ||
226 | # REQUESTS_CA_BUNDLE is a way to teach python requests to use something else | ||
227 | environment.REQUESTS_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; | ||
228 | serviceConfig = { | ||
229 | Type = "oneshot"; | ||
230 | # With RemainAfterExit the service is considered active even | ||
231 | # after the main process having exited, which means when it | ||
232 | # gets changed, the activation phase restarts it, meaning | ||
233 | # the permissions of the StateDirectory get adjusted | ||
234 | # according to the specified group | ||
235 | # Edit: Timers will never run because of this | ||
236 | # RemainAfterExit = true; | ||
237 | SuccessExitStatus = [ "0" "1" ]; | ||
238 | User = data.user; | ||
239 | Group = data.group; | ||
240 | PrivateTmp = true; | ||
241 | StateDirectory = lpath; | ||
242 | StateDirectoryMode = rights; | ||
243 | ExecStartPre = | ||
244 | let | ||
245 | script = pkgs.writeScript "acme-pre-start" '' | ||
246 | #!${pkgs.runtimeShell} -e | ||
247 | mkdir -p '${data.webroot}/.well-known/acme-challenge' | ||
248 | chmod a+w '${data.webroot}/.well-known/acme-challenge' | ||
249 | #doesn't work for multiple concurrent runs | ||
250 | #chown -R '${data.user}:${data.group}' '${data.webroot}/.well-known/acme-challenge' | ||
251 | ''; | ||
252 | in | ||
253 | "+${script}"; | ||
254 | WorkingDirectory = "/var/lib/${lpath}"; | ||
255 | ExecStart = "${pkgs.simp_le_0_17}/bin/simp_le ${escapeShellArgs cmdline}"; | ||
256 | ExecStartPost = | ||
257 | let | ||
258 | script = pkgs.writeScript "acme-post-start" '' | ||
259 | #!${pkgs.runtimeShell} -e | ||
260 | ${data.postRun} | ||
261 | ''; | ||
262 | in | ||
263 | "+${script}"; | ||
264 | }; | ||
265 | |||
266 | }; | ||
267 | selfsignedService = { | ||
268 | description = "Create preliminary self-signed certificate for ${cert}"; | ||
269 | path = [ pkgs.openssl ]; | ||
270 | script = | ||
271 | '' | ||
272 | workdir="$(mktemp -d)" | ||
273 | |||
274 | # Create CA | ||
275 | openssl genrsa -des3 -passout pass:xxxx -out $workdir/ca.pass.key 2048 | ||
276 | openssl rsa -passin pass:xxxx -in $workdir/ca.pass.key -out $workdir/ca.key | ||
277 | openssl req -new -key $workdir/ca.key -out $workdir/ca.csr \ | ||
278 | -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=Security Department/CN=example.com" | ||
279 | openssl x509 -req -days 1 -in $workdir/ca.csr -signkey $workdir/ca.key -out $workdir/ca.crt | ||
280 | |||
281 | # Create key | ||
282 | openssl genrsa -des3 -passout pass:xxxx -out $workdir/server.pass.key 2048 | ||
283 | openssl rsa -passin pass:xxxx -in $workdir/server.pass.key -out $workdir/server.key | ||
284 | openssl req -new -key $workdir/server.key -out $workdir/server.csr \ | ||
285 | -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com" | ||
286 | openssl x509 -req -days 1 -in $workdir/server.csr -CA $workdir/ca.crt \ | ||
287 | -CAkey $workdir/ca.key -CAserial $workdir/ca.srl -CAcreateserial \ | ||
288 | -out $workdir/server.crt | ||
289 | |||
290 | # Copy key to destination | ||
291 | cp $workdir/server.key /var/lib/${lpath}/key.pem | ||
292 | |||
293 | # Create fullchain.pem (same format as "simp_le ... -f fullchain.pem" creates) | ||
294 | cat $workdir/{server.crt,ca.crt} > "/var/lib/${lpath}/fullchain.pem" | ||
295 | |||
296 | # Create full.pem for e.g. lighttpd | ||
297 | cat $workdir/{server.key,server.crt,ca.crt} > "/var/lib/${lpath}/full.pem" | ||
298 | |||
299 | # Give key acme permissions | ||
300 | chown '${data.user}:${data.group}' "/var/lib/${lpath}/"{key,fullchain,full}.pem | ||
301 | chmod ${rights} "/var/lib/${lpath}/"{key,fullchain,full}.pem | ||
302 | ''; | ||
303 | serviceConfig = { | ||
304 | Type = "oneshot"; | ||
305 | PrivateTmp = true; | ||
306 | StateDirectory = lpath; | ||
307 | User = data.user; | ||
308 | Group = data.group; | ||
309 | }; | ||
310 | unitConfig = { | ||
311 | # Do not create self-signed key when key already exists | ||
312 | ConditionPathExists = "!/var/lib/${lpath}/key.pem"; | ||
313 | }; | ||
314 | }; | ||
315 | in ( | ||
316 | [ { name = "acme-${cert}"; value = acmeService; } ] | ||
317 | ++ optional cfg.preliminarySelfsigned { name = "acme-selfsigned-${cert}"; value = selfsignedService; } | ||
318 | ); | ||
319 | servicesAttr = listToAttrs services; | ||
320 | in | ||
321 | servicesAttr; | ||
322 | |||
323 | # FIXME: this doesn't work for multiple users | ||
324 | systemd.tmpfiles.rules = | ||
325 | flip mapAttrsToList cfg.certs | ||
326 | (cert: data: "d ${data.webroot}/.well-known/acme-challenge - ${data.user} ${data.group}"); | ||
327 | |||
328 | systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair | ||
329 | ("acme-${cert}") | ||
330 | ({ | ||
331 | description = "Renew ACME Certificate for ${cert}"; | ||
332 | wantedBy = [ "timers.target" ]; | ||
333 | timerConfig = { | ||
334 | OnCalendar = cfg.renewInterval; | ||
335 | Unit = "acme-${cert}.service"; | ||
336 | Persistent = "yes"; | ||
337 | AccuracySec = "5m"; | ||
338 | RandomizedDelaySec = "1h"; | ||
339 | }; | ||
340 | }) | ||
341 | ); | ||
342 | |||
343 | systemd.targets.acme-selfsigned-certificates = mkIf cfg.preliminarySelfsigned {}; | ||
344 | systemd.targets.acme-certificates = {}; | ||
345 | }) | ||
346 | |||
347 | ]; | ||
348 | |||
349 | meta = { | ||
350 | maintainers = with lib.maintainers; [ abbradar fpletz globin ]; | ||
351 | #doc = ./acme.xml; | ||
352 | }; | ||
353 | } | ||
diff --git a/modules/default.nix b/modules/default.nix index 98dc77d8..9ff6ea62 100644 --- a/modules/default.nix +++ b/modules/default.nix | |||
@@ -19,5 +19,4 @@ | |||
19 | 19 | ||
20 | php-application = ./websites/php-application.nix; | 20 | php-application = ./websites/php-application.nix; |
21 | websites = ./websites; | 21 | websites = ./websites; |
22 | acme2 = ./acme2.nix; | ||
23 | } // (if builtins.pathExists ./private then import ./private else {}) | 22 | } // (if builtins.pathExists ./private then import ./private else {}) |
diff --git a/modules/webapps/mastodon.nix b/modules/webapps/mastodon.nix index eed9e3f6..68531cf3 100644 --- a/modules/webapps/mastodon.nix +++ b/modules/webapps/mastodon.nix | |||
@@ -27,7 +27,7 @@ in | |||
27 | ''; | 27 | ''; |
28 | }; | 28 | }; |
29 | socketsPrefix = lib.mkOption { | 29 | socketsPrefix = lib.mkOption { |
30 | type = lib.types.string; | 30 | type = lib.types.str; |
31 | default = "live"; | 31 | default = "live"; |
32 | description = '' | 32 | description = '' |
33 | The prefix to use for Mastodon sockets. | 33 | The prefix to use for Mastodon sockets. |
diff --git a/modules/webapps/webstats/default.nix b/modules/webapps/webstats/default.nix index e822645c..fe5f068d 100644 --- a/modules/webapps/webstats/default.nix +++ b/modules/webapps/webstats/default.nix | |||
@@ -23,7 +23,7 @@ in { | |||
23 | ''; | 23 | ''; |
24 | }; | 24 | }; |
25 | name = lib.mkOption { | 25 | name = lib.mkOption { |
26 | type = lib.types.string; | 26 | type = lib.types.str; |
27 | description = '' | 27 | description = '' |
28 | Domain name. Corresponds to the Apache file name and the | 28 | Domain name. Corresponds to the Apache file name and the |
29 | folder name in which the state will be saved. | 29 | folder name in which the state will be saved. |
diff --git a/modules/websites/default.nix b/modules/websites/default.nix index 767a7b23..3f46e65d 100644 --- a/modules/websites/default.nix +++ b/modules/websites/default.nix | |||
@@ -38,7 +38,7 @@ in | |||
38 | description = "Name of the httpd instance to assign this type to"; | 38 | description = "Name of the httpd instance to assign this type to"; |
39 | }; | 39 | }; |
40 | ips = mkOption { | 40 | ips = mkOption { |
41 | type = listOf string; | 41 | type = listOf str; |
42 | default = []; | 42 | default = []; |
43 | description = "ips to listen to"; | 43 | description = "ips to listen to"; |
44 | }; | 44 | }; |
@@ -59,7 +59,7 @@ in | |||
59 | options = { | 59 | options = { |
60 | enable = mkEnableOption "Add default no-ssl vhost for this instance"; | 60 | enable = mkEnableOption "Add default no-ssl vhost for this instance"; |
61 | host = mkOption { | 61 | host = mkOption { |
62 | type = string; | 62 | type = str; |
63 | description = "The hostname to use for this vhost"; | 63 | description = "The hostname to use for this vhost"; |
64 | }; | 64 | }; |
65 | root = mkOption { | 65 | root = mkOption { |
@@ -68,7 +68,7 @@ in | |||
68 | description = "The root folder to serve"; | 68 | description = "The root folder to serve"; |
69 | }; | 69 | }; |
70 | indexFile = mkOption { | 70 | indexFile = mkOption { |
71 | type = string; | 71 | type = str; |
72 | default = "index.html"; | 72 | default = "index.html"; |
73 | description = "The index file to show."; | 73 | description = "The index file to show."; |
74 | }; | 74 | }; |
@@ -79,8 +79,8 @@ in | |||
79 | description = "The fallback vhost that will be defined as first vhost in Apache"; | 79 | description = "The fallback vhost that will be defined as first vhost in Apache"; |
80 | type = submodule { | 80 | type = submodule { |
81 | options = { | 81 | options = { |
82 | certName = mkOption { type = string; }; | 82 | certName = mkOption { type = str; }; |
83 | hosts = mkOption { type = listOf string; }; | 83 | hosts = mkOption { type = listOf str; }; |
84 | root = mkOption { type = nullOr path; }; | 84 | root = mkOption { type = nullOr path; }; |
85 | extraConfig = mkOption { type = listOf lines; default = []; }; | 85 | extraConfig = mkOption { type = listOf lines; default = []; }; |
86 | }; | 86 | }; |
@@ -91,7 +91,7 @@ in | |||
91 | description = "List of no ssl vhosts to define for Apache"; | 91 | description = "List of no ssl vhosts to define for Apache"; |
92 | type = attrsOf (submodule { | 92 | type = attrsOf (submodule { |
93 | options = { | 93 | options = { |
94 | hosts = mkOption { type = listOf string; }; | 94 | hosts = mkOption { type = listOf str; }; |
95 | root = mkOption { type = nullOr path; }; | 95 | root = mkOption { type = nullOr path; }; |
96 | extraConfig = mkOption { type = listOf lines; default = []; }; | 96 | extraConfig = mkOption { type = listOf lines; default = []; }; |
97 | }; | 97 | }; |
@@ -102,25 +102,25 @@ in | |||
102 | description = "List of vhosts to define for Apache"; | 102 | description = "List of vhosts to define for Apache"; |
103 | type = attrsOf (submodule { | 103 | type = attrsOf (submodule { |
104 | options = { | 104 | options = { |
105 | certName = mkOption { type = string; }; | 105 | certName = mkOption { type = str; }; |
106 | addToCerts = mkOption { | 106 | addToCerts = mkOption { |
107 | type = bool; | 107 | type = bool; |
108 | default = false; | 108 | default = false; |
109 | description = "Use these to certificates. Is ignored (considered true) if certMainHost is not null"; | 109 | description = "Use these to certificates. Is ignored (considered true) if certMainHost is not null"; |
110 | }; | 110 | }; |
111 | certMainHost = mkOption { | 111 | certMainHost = mkOption { |
112 | type = nullOr string; | 112 | type = nullOr str; |
113 | description = "Use that host as 'main host' for acme certs"; | 113 | description = "Use that host as 'main host' for acme certs"; |
114 | default = null; | 114 | default = null; |
115 | }; | 115 | }; |
116 | hosts = mkOption { type = listOf string; }; | 116 | hosts = mkOption { type = listOf str; }; |
117 | root = mkOption { type = nullOr path; }; | 117 | root = mkOption { type = nullOr path; }; |
118 | extraConfig = mkOption { type = listOf lines; default = []; }; | 118 | extraConfig = mkOption { type = listOf lines; default = []; }; |
119 | }; | 119 | }; |
120 | }); | 120 | }); |
121 | }; | 121 | }; |
122 | watchPaths = mkOption { | 122 | watchPaths = mkOption { |
123 | type = listOf string; | 123 | type = listOf str; |
124 | default = []; | 124 | default = []; |
125 | description = '' | 125 | description = '' |
126 | Paths to watch that should trigger a reload of httpd | 126 | Paths to watch that should trigger a reload of httpd |
@@ -178,9 +178,9 @@ in | |||
178 | }; | 178 | }; |
179 | toVhost = ips: vhostConf: { | 179 | toVhost = ips: vhostConf: { |
180 | enableSSL = true; | 180 | enableSSL = true; |
181 | sslServerCert = "${config.security.acme2.certs."${vhostConf.certName}".directory}/cert.pem"; | 181 | sslServerCert = "${config.security.acme.certs."${vhostConf.certName}".directory}/cert.pem"; |
182 | sslServerKey = "${config.security.acme2.certs."${vhostConf.certName}".directory}/key.pem"; | 182 | sslServerKey = "${config.security.acme.certs."${vhostConf.certName}".directory}/key.pem"; |
183 | sslServerChain = "${config.security.acme2.certs."${vhostConf.certName}".directory}/chain.pem"; | 183 | sslServerChain = "${config.security.acme.certs."${vhostConf.certName}".directory}/chain.pem"; |
184 | logFormat = "combinedVhost"; | 184 | logFormat = "combinedVhost"; |
185 | listen = map (ip: { inherit ip; port = 443; }) ips; | 185 | listen = map (ip: { inherit ip; port = 443; }) ips; |
186 | hostName = builtins.head vhostConf.hosts; | 186 | hostName = builtins.head vhostConf.hosts; |
@@ -231,7 +231,7 @@ in | |||
231 | } | 231 | } |
232 | ) cfg.env; | 232 | ) cfg.env; |
233 | 233 | ||
234 | config.security.acme2.certs = let | 234 | config.security.acme.certs = let |
235 | typesToManage = attrsets.filterAttrs (k: v: v.enable) cfg.env; | 235 | typesToManage = attrsets.filterAttrs (k: v: v.enable) cfg.env; |
236 | flatVhosts = lists.flatten (attrsets.mapAttrsToList (k: v: | 236 | flatVhosts = lists.flatten (attrsets.mapAttrsToList (k: v: |
237 | attrValues v.vhostConfs | 237 | attrValues v.vhostConfs |
diff --git a/modules/websites/httpd-service-builder.nix b/modules/websites/httpd-service-builder.nix index d049202c..f0208ab5 100644 --- a/modules/websites/httpd-service-builder.nix +++ b/modules/websites/httpd-service-builder.nix | |||
@@ -11,8 +11,6 @@ let | |||
11 | 11 | ||
12 | httpd = mainCfg.package.out; | 12 | httpd = mainCfg.package.out; |
13 | 13 | ||
14 | version24 = !versionOlder httpd.version "2.4"; | ||
15 | |||
16 | httpdConf = mainCfg.configFile; | 14 | httpdConf = mainCfg.configFile; |
17 | 15 | ||
18 | php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ }; | 16 | php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ }; |
@@ -26,10 +24,9 @@ let | |||
26 | else [{ip = "*"; port = 80;}]; | 24 | else [{ip = "*"; port = 80;}]; |
27 | 25 | ||
28 | getListen = cfg: | 26 | getListen = cfg: |
29 | let list = (lib.optional (cfg.port != 0) {ip = "*"; port = cfg.port;}) ++ cfg.listen; | 27 | if cfg.listen == [] |
30 | in if list == [] | 28 | then defaultListen cfg |
31 | then defaultListen cfg | 29 | else cfg.listen; |
32 | else list; | ||
33 | 30 | ||
34 | listenToString = l: "${l.ip}:${toString l.port}"; | 31 | listenToString = l: "${l.ip}:${toString l.port}"; |
35 | 32 | ||
@@ -110,11 +107,10 @@ let | |||
110 | "auth_basic" "auth_digest" | 107 | "auth_basic" "auth_digest" |
111 | 108 | ||
112 | # Authentication: is the user who he claims to be? | 109 | # Authentication: is the user who he claims to be? |
113 | "authn_file" "authn_dbm" "authn_anon" | 110 | "authn_file" "authn_dbm" "authn_anon" "authn_core" |
114 | (if version24 then "authn_core" else "authn_alias") | ||
115 | 111 | ||
116 | # Authorization: is the user allowed access? | 112 | # Authorization: is the user allowed access? |
117 | "authz_user" "authz_groupfile" "authz_host" | 113 | "authz_user" "authz_groupfile" "authz_host" "authz_core" |
118 | 114 | ||
119 | # Other modules. | 115 | # Other modules. |
120 | "ext_filter" "include" "log_config" "env" "mime_magic" | 116 | "ext_filter" "include" "log_config" "env" "mime_magic" |
@@ -122,14 +118,9 @@ let | |||
122 | "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs" | 118 | "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs" |
123 | "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling" | 119 | "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling" |
124 | "userdir" "alias" "rewrite" "proxy" "proxy_http" | 120 | "userdir" "alias" "rewrite" "proxy" "proxy_http" |
125 | ] | 121 | "unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb" |
126 | ++ optionals version24 [ | ||
127 | "mpm_${mainCfg.multiProcessingModule}" | 122 | "mpm_${mainCfg.multiProcessingModule}" |
128 | "authz_core" | 123 | |
129 | "unixd" | ||
130 | "cache" "cache_disk" | ||
131 | "slotmem_shm" | ||
132 | "socache_shmcb" | ||
133 | # For compatibility with old configurations, the new module mod_access_compat is provided. | 124 | # For compatibility with old configurations, the new module mod_access_compat is provided. |
134 | "access_compat" | 125 | "access_compat" |
135 | ] | 126 | ] |
@@ -138,19 +129,8 @@ let | |||
138 | ++ extraApacheModules; | 129 | ++ extraApacheModules; |
139 | 130 | ||
140 | 131 | ||
141 | allDenied = if version24 then '' | 132 | allDenied = "Require all denied"; |
142 | Require all denied | 133 | allGranted = "Require all granted"; |
143 | '' else '' | ||
144 | Order deny,allow | ||
145 | Deny from all | ||
146 | ''; | ||
147 | |||
148 | allGranted = if version24 then '' | ||
149 | Require all granted | ||
150 | '' else '' | ||
151 | Order allow,deny | ||
152 | Allow from all | ||
153 | ''; | ||
154 | 134 | ||
155 | 135 | ||
156 | loggingConf = (if mainCfg.logFormat != "none" then '' | 136 | loggingConf = (if mainCfg.logFormat != "none" then '' |
@@ -183,9 +163,9 @@ let | |||
183 | 163 | ||
184 | 164 | ||
185 | sslConf = '' | 165 | sslConf = '' |
186 | SSLSessionCache ${if version24 then "shmcb" else "shm"}:${mainCfg.stateDir}/ssl_scache(512000) | 166 | SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000) |
187 | 167 | ||
188 | ${if version24 then "Mutex" else "SSLMutex"} posixsem | 168 | Mutex posixsem |
189 | 169 | ||
190 | SSLRandomSeed startup builtin | 170 | SSLRandomSeed startup builtin |
191 | SSLRandomSeed connect builtin | 171 | SSLRandomSeed connect builtin |
@@ -325,9 +305,7 @@ let | |||
325 | 305 | ||
326 | ServerRoot ${httpd} | 306 | ServerRoot ${httpd} |
327 | 307 | ||
328 | ${optionalString version24 '' | 308 | DefaultRuntimeDir ${mainCfg.stateDir}/runtime |
329 | DefaultRuntimeDir ${mainCfg.stateDir}/runtime | ||
330 | ''} | ||
331 | 309 | ||
332 | PidFile ${mainCfg.stateDir}/httpd.pid | 310 | PidFile ${mainCfg.stateDir}/httpd.pid |
333 | 311 | ||
@@ -361,7 +339,7 @@ let | |||
361 | ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } | 339 | ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } |
362 | ++ concatMap (svc: svc.extraModules) allSubservices | 340 | ++ concatMap (svc: svc.extraModules) allSubservices |
363 | ++ extraForeignModules; | 341 | ++ extraForeignModules; |
364 | in concatMapStrings load allModules | 342 | in concatMapStrings load (unique allModules) |
365 | } | 343 | } |
366 | 344 | ||
367 | AddHandler type-map var | 345 | AddHandler type-map var |
@@ -393,14 +371,6 @@ let | |||
393 | # Generate directives for the main server. | 371 | # Generate directives for the main server. |
394 | ${perServerConf true mainCfg} | 372 | ${perServerConf true mainCfg} |
395 | 373 | ||
396 | # Always enable virtual hosts; it doesn't seem to hurt. | ||
397 | ${let | ||
398 | listen = concatMap getListen allHosts; | ||
399 | uniqueListen = uniqList {inputList = listen;}; | ||
400 | directives = concatMapStrings (listen: "NameVirtualHost ${listenToString listen}\n") uniqueListen; | ||
401 | in optionalString (!version24) directives | ||
402 | } | ||
403 | |||
404 | ${let | 374 | ${let |
405 | makeVirtualHost = vhost: '' | 375 | makeVirtualHost = vhost: '' |
406 | <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}> | 376 | <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}> |
@@ -663,7 +633,7 @@ in | |||
663 | message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; } | 633 | message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; } |
664 | ]; | 634 | ]; |
665 | 635 | ||
666 | warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts); | 636 | warnings = map (cfg: "apache-httpd's extraSubservices option is deprecated. Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") (lib.filter (cfg: cfg.extraSubservices != []) allHosts); |
667 | 637 | ||
668 | users.users = optionalAttrs (withUsers && mainCfg.user == "wwwrun") (singleton | 638 | users.users = optionalAttrs (withUsers && mainCfg.user == "wwwrun") (singleton |
669 | { name = "wwwrun"; | 639 | { name = "wwwrun"; |
@@ -686,7 +656,7 @@ in | |||
686 | 656 | ||
687 | ; Don't advertise PHP | 657 | ; Don't advertise PHP |
688 | expose_php = off | 658 | expose_php = off |
689 | '' + optionalString (!isNull config.time.timeZone) '' | 659 | '' + optionalString (config.time.timeZone != null) '' |
690 | 660 | ||
691 | ; Apparently PHP doesn't use $TZ. | 661 | ; Apparently PHP doesn't use $TZ. |
692 | date.timezone = "${config.time.timeZone}" | 662 | date.timezone = "${config.time.timeZone}" |
@@ -713,10 +683,10 @@ in | |||
713 | '' | 683 | '' |
714 | mkdir -m 0750 -p ${mainCfg.stateDir} | 684 | mkdir -m 0750 -p ${mainCfg.stateDir} |
715 | [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir} | 685 | [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir} |
716 | ${optionalString version24 '' | 686 | |
717 | mkdir -m 0750 -p "${mainCfg.stateDir}/runtime" | 687 | mkdir -m 0750 -p "${mainCfg.stateDir}/runtime" |
718 | [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime" | 688 | [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime" |
719 | ''} | 689 | |
720 | mkdir -m 0700 -p ${mainCfg.logDir} | 690 | mkdir -m 0700 -p ${mainCfg.logDir} |
721 | 691 | ||
722 | # Get rid of old semaphores. These tend to accumulate across | 692 | # Get rid of old semaphores. These tend to accumulate across |
diff --git a/modules/websites/php-application.nix b/modules/websites/php-application.nix index 8ad7a0df..20e2a5dd 100644 --- a/modules/websites/php-application.nix +++ b/modules/websites/php-application.nix | |||
@@ -44,10 +44,15 @@ in | |||
44 | description = "Name of the socket to listen to. Defaults to app name if null"; | 44 | description = "Name of the socket to listen to. Defaults to app name if null"; |
45 | }; | 45 | }; |
46 | phpPool = mkOption { | 46 | phpPool = mkOption { |
47 | type = lines; | 47 | type = attrsOf str; |
48 | default = ""; | 48 | default = {}; |
49 | description = "Pool configuration to append"; | 49 | description = "Pool configuration to append"; |
50 | }; | 50 | }; |
51 | phpEnv = mkOption { | ||
52 | type = attrsOf str; | ||
53 | default = {}; | ||
54 | description = "Pool environment to append"; | ||
55 | }; | ||
51 | phpOptions = mkOption { | 56 | phpOptions = mkOption { |
52 | type = lines; | 57 | type = lines; |
53 | default = ""; | 58 | default = ""; |
@@ -135,7 +140,7 @@ in | |||
135 | services.phpApplication.phpListenPaths = mkOption { | 140 | services.phpApplication.phpListenPaths = mkOption { |
136 | type = attrsOf path; | 141 | type = attrsOf path; |
137 | default = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | 142 | default = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair |
138 | name "/run/phpfpm/${if icfg.phpListen == null then name else icfg.phpListen}.sock" | 143 | name config.services.phpfpm.pools."${name}".socket |
139 | ) cfg.apps; | 144 | ) cfg.apps; |
140 | readOnly = true; | 145 | readOnly = true; |
141 | description = '' | 146 | description = '' |
@@ -162,17 +167,17 @@ in | |||
162 | 167 | ||
163 | services.phpfpm.pools = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | 168 | services.phpfpm.pools = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair |
164 | name { | 169 | name { |
165 | listen = cfg.phpListenPaths."${name}"; | 170 | user = icfg.httpdUser; |
166 | extraConfig = '' | 171 | group = icfg.httpdUser; |
167 | user = ${icfg.httpdUser} | 172 | settings = { |
168 | group = ${icfg.httpdGroup} | 173 | "listen.owner" = icfg.httpdUser; |
169 | listen.owner = ${icfg.httpdUser} | 174 | "listen.group" = icfg.httpdGroup; |
170 | listen.group = ${icfg.httpdGroup} | 175 | "php_admin_value[open_basedir]" = builtins.concatStringsSep ":" ([icfg.app icfg.varDir] ++ icfg.phpWatchFiles ++ icfg.phpOpenbasedir); |
171 | ${optionalString (icfg.phpSession) '' | 176 | } |
172 | php_admin_value[session.save_path] = "${icfg.varDir}/phpSessions"''} | 177 | // optionalAttrs (icfg.phpSession) { "php_admin_value[session.save_path]" = "${icfg.varDir}/phpSessions"; } |
173 | php_admin_value[open_basedir] = "${builtins.concatStringsSep ":" ([icfg.app icfg.varDir] ++ icfg.phpWatchFiles ++ icfg.phpOpenbasedir)}" | 178 | // icfg.phpPool; |
174 | '' + icfg.phpPool; | ||
175 | phpOptions = config.services.phpfpm.phpOptions + icfg.phpOptions; | 179 | phpOptions = config.services.phpfpm.phpOptions + icfg.phpOptions; |
180 | inherit (icfg) phpEnv; | ||
176 | } | 181 | } |
177 | ) cfg.apps; | 182 | ) cfg.apps; |
178 | 183 | ||