diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2023-10-04 01:35:06 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2023-10-04 02:11:48 +0200 |
commit | 1a64deeb894dc95e2645a75771732c6cc53a79ad (patch) | |
tree | 1b9df4838f894577a09b9b260151756272efeb53 /flakes/multi-apache-container | |
parent | fa25ffd4583cc362075cd5e1b4130f33306103f0 (diff) | |
download | Nix-1a64deeb894dc95e2645a75771732c6cc53a79ad.tar.gz Nix-1a64deeb894dc95e2645a75771732c6cc53a79ad.tar.zst Nix-1a64deeb894dc95e2645a75771732c6cc53a79ad.zip |
Squash changes containing private information
There were a lot of changes since the previous commit, but a lot of them
contained personnal information about users. All thos changes got
stashed into a single commit (history is kept in a different place) and
private information was moved in a separate private repository
Diffstat (limited to 'flakes/multi-apache-container')
-rw-r--r-- | flakes/multi-apache-container/flake.lock | 36 | ||||
-rw-r--r-- | flakes/multi-apache-container/flake.nix | 389 |
2 files changed, 425 insertions, 0 deletions
diff --git a/flakes/multi-apache-container/flake.lock b/flakes/multi-apache-container/flake.lock new file mode 100644 index 0000000..f8dda19 --- /dev/null +++ b/flakes/multi-apache-container/flake.lock | |||
@@ -0,0 +1,36 @@ | |||
1 | { | ||
2 | "nodes": { | ||
3 | "files-watcher": { | ||
4 | "locked": { | ||
5 | "lastModified": 1, | ||
6 | "narHash": "sha256-ZsdumUVoSPkV/DB6gO6dNDttjzalye0ToVBF9bl5W0k=", | ||
7 | "path": "../files-watcher", | ||
8 | "type": "path" | ||
9 | }, | ||
10 | "original": { | ||
11 | "path": "../files-watcher", | ||
12 | "type": "path" | ||
13 | } | ||
14 | }, | ||
15 | "myuids": { | ||
16 | "locked": { | ||
17 | "lastModified": 1, | ||
18 | "narHash": "sha256-HkW9YCLQCNBX3Em7J7MjraVEZO3I3PizkVV2QrUdULQ=", | ||
19 | "path": "../myuids", | ||
20 | "type": "path" | ||
21 | }, | ||
22 | "original": { | ||
23 | "path": "../myuids", | ||
24 | "type": "path" | ||
25 | } | ||
26 | }, | ||
27 | "root": { | ||
28 | "inputs": { | ||
29 | "files-watcher": "files-watcher", | ||
30 | "myuids": "myuids" | ||
31 | } | ||
32 | } | ||
33 | }, | ||
34 | "root": "root", | ||
35 | "version": 7 | ||
36 | } | ||
diff --git a/flakes/multi-apache-container/flake.nix b/flakes/multi-apache-container/flake.nix new file mode 100644 index 0000000..fd788f7 --- /dev/null +++ b/flakes/multi-apache-container/flake.nix | |||
@@ -0,0 +1,389 @@ | |||
1 | { | ||
2 | description = "Module to handle multiple separate apache instances (using containers)"; | ||
3 | inputs.myuids = { | ||
4 | url = "path:../myuids"; | ||
5 | }; | ||
6 | inputs.files-watcher = { | ||
7 | url = "path:../files-watcher"; | ||
8 | }; | ||
9 | |||
10 | outputs = { self, myuids, files-watcher }: { | ||
11 | nixosModule = { lib, config, pkgs, options, ... }: | ||
12 | with lib; | ||
13 | let | ||
14 | cfg = config.services.websites; | ||
15 | hostConfig = config; | ||
16 | toHttpdConfig = icfg: | ||
17 | let | ||
18 | nosslVhost = ips: cfg: { | ||
19 | listen = map (ip: { inherit ip; port = 80; }) ips; | ||
20 | hostName = cfg.host; | ||
21 | logFormat = "combinedVhost"; | ||
22 | documentRoot = cfg.root; | ||
23 | extraConfig = '' | ||
24 | <Directory ${cfg.root}> | ||
25 | DirectoryIndex ${cfg.indexFile} | ||
26 | AllowOverride None | ||
27 | Require all granted | ||
28 | |||
29 | RewriteEngine on | ||
30 | RewriteRule ^/(.+) / [L] | ||
31 | </Directory> | ||
32 | ''; | ||
33 | }; | ||
34 | toVhost = ips: vhostConf: { | ||
35 | acmeRoot = hostConfig.security.acme.certs.${vhostConf.certName}.webroot; | ||
36 | forceSSL = vhostConf.forceSSL or true; | ||
37 | useACMEHost = vhostConf.certName; | ||
38 | logFormat = "combinedVhost"; | ||
39 | listen = if vhostConf.forceSSL | ||
40 | then lists.flatten (map (ip: [{ inherit ip; port = 443; ssl = true; } { inherit ip; port = 80; }]) ips) | ||
41 | else map (ip: { inherit ip; port = 443; ssl = true; }) ips; | ||
42 | hostName = builtins.head vhostConf.hosts; | ||
43 | serverAliases = builtins.tail vhostConf.hosts or []; | ||
44 | documentRoot = vhostConf.root; | ||
45 | extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig; | ||
46 | }; | ||
47 | toVhostNoSSL = ips: vhostConf: { | ||
48 | logFormat = "combinedVhost"; | ||
49 | listen = map (ip: { inherit ip; port = 80; }) ips; | ||
50 | hostName = builtins.head vhostConf.hosts; | ||
51 | serverAliases = builtins.tail vhostConf.hosts or []; | ||
52 | documentRoot = vhostConf.root; | ||
53 | extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig; | ||
54 | }; | ||
55 | in { | ||
56 | enable = true; | ||
57 | logPerVirtualHost = true; | ||
58 | mpm = "event"; | ||
59 | # https://ssl-config.mozilla.org/#server=apache&version=2.4.41&config=intermediate&openssl=1.0.2t&guideline=5.4 | ||
60 | # test with https://www.ssllabs.com/ssltest/analyze.html?d=www.immae.eu&s=176.9.151.154&latest | ||
61 | sslProtocols = "all -SSLv3 -TLSv1 -TLSv1.1"; | ||
62 | sslCiphers = builtins.concatStringsSep ":" [ | ||
63 | "ECDHE-ECDSA-AES128-GCM-SHA256" "ECDHE-RSA-AES128-GCM-SHA256" | ||
64 | "ECDHE-ECDSA-AES256-GCM-SHA384" "ECDHE-RSA-AES256-GCM-SHA384" | ||
65 | "ECDHE-ECDSA-CHACHA20-POLY1305" "ECDHE-RSA-CHACHA20-POLY1305" | ||
66 | "DHE-RSA-AES128-GCM-SHA256" "DHE-RSA-AES256-GCM-SHA384" | ||
67 | ]; | ||
68 | inherit (icfg) adminAddr; | ||
69 | logFormat = "combinedVhost"; | ||
70 | extraModules = lists.unique icfg.modules; | ||
71 | extraConfig = builtins.concatStringsSep "\n" icfg.extraConfig; | ||
72 | |||
73 | virtualHosts = with attrsets; { | ||
74 | ___fallbackVhost = toVhost icfg.ips icfg.fallbackVhost; | ||
75 | } // (optionalAttrs icfg.nosslVhost.enable { | ||
76 | nosslVhost = nosslVhost icfg.ips icfg.nosslVhost; | ||
77 | }) // (mapAttrs' (n: v: nameValuePair ("nossl_" + n) (toVhostNoSSL icfg.ips v)) icfg.vhostNoSSLConfs) | ||
78 | // (mapAttrs' (n: v: nameValuePair ("ssl_" + n) (toVhost icfg.ips v)) icfg.vhostConfs); | ||
79 | }; | ||
80 | in | ||
81 | { | ||
82 | options.services.websites = with types; { | ||
83 | env = mkOption { | ||
84 | default = {}; | ||
85 | description = "Each type of website to enable will target a distinct httpd server"; | ||
86 | type = attrsOf (submodule ({ name, config, ... }: { | ||
87 | options = { | ||
88 | enable = mkEnableOption "Enable websites of this type"; | ||
89 | moduleType = mkOption { | ||
90 | type = enum [ "container" "main" ]; | ||
91 | default = "container"; | ||
92 | description = '' | ||
93 | How to deploy the web environment: | ||
94 | - container -> inside a dedicated container (running only httpd) | ||
95 | - main -> as main services.httpd module | ||
96 | ''; | ||
97 | }; | ||
98 | adminAddr = mkOption { | ||
99 | type = str; | ||
100 | description = "Admin e-mail address of the instance"; | ||
101 | }; | ||
102 | user = mkOption { | ||
103 | type = str; | ||
104 | description = "Username of httpd service"; | ||
105 | readOnly = true; | ||
106 | default = if config.moduleType == "container" | ||
107 | then hostConfig.containers."httpd-${name}".config.services.httpd.user | ||
108 | else hostConfig.services.httpd.user; | ||
109 | }; | ||
110 | group = mkOption { | ||
111 | type = str; | ||
112 | description = "Group of httpd service"; | ||
113 | readOnly = true; | ||
114 | default = if config.moduleType == "container" | ||
115 | then hostConfig.containers."httpd-${name}".config.services.httpd.group | ||
116 | else hostConfig.services.httpd.group; | ||
117 | }; | ||
118 | httpdName = mkOption { | ||
119 | type = str; | ||
120 | description = "Name of the httpd instance to assign this type to"; | ||
121 | }; | ||
122 | ips = mkOption { | ||
123 | type = listOf str; | ||
124 | default = []; | ||
125 | description = "ips to listen to"; | ||
126 | }; | ||
127 | bindMounts = mkOption { | ||
128 | type = attrsOf unspecified; | ||
129 | default = {}; | ||
130 | description = "bind mounts to add to container"; | ||
131 | }; | ||
132 | modules = mkOption { | ||
133 | type = listOf str; | ||
134 | default = []; | ||
135 | description = "Additional modules to load in Apache"; | ||
136 | }; | ||
137 | extraConfig = mkOption { | ||
138 | type = listOf lines; | ||
139 | default = []; | ||
140 | description = "Additional configuration to append to Apache"; | ||
141 | }; | ||
142 | nosslVhost = mkOption { | ||
143 | description = "A default nossl vhost for captive portals"; | ||
144 | default = {}; | ||
145 | type = submodule { | ||
146 | options = { | ||
147 | enable = mkEnableOption "Add default no-ssl vhost for this instance"; | ||
148 | host = mkOption { | ||
149 | type = str; | ||
150 | description = "The hostname to use for this vhost"; | ||
151 | }; | ||
152 | root = mkOption { | ||
153 | type = path; | ||
154 | description = "The root folder to serve"; | ||
155 | }; | ||
156 | indexFile = mkOption { | ||
157 | type = str; | ||
158 | default = "index.html"; | ||
159 | description = "The index file to show."; | ||
160 | }; | ||
161 | }; | ||
162 | }; | ||
163 | }; | ||
164 | fallbackVhost = mkOption { | ||
165 | description = "The fallback vhost that will be defined as first vhost in Apache"; | ||
166 | type = submodule { | ||
167 | options = { | ||
168 | certName = mkOption { type = str; }; | ||
169 | hosts = mkOption { type = listOf str; }; | ||
170 | root = mkOption { type = nullOr path; }; | ||
171 | forceSSL = mkOption { | ||
172 | type = bool; | ||
173 | default = true; | ||
174 | description = '' | ||
175 | Automatically create a corresponding non-ssl vhost | ||
176 | that will only redirect to the ssl version | ||
177 | ''; | ||
178 | }; | ||
179 | extraConfig = mkOption { type = listOf lines; default = []; }; | ||
180 | }; | ||
181 | }; | ||
182 | }; | ||
183 | vhostNoSSLConfs = mkOption { | ||
184 | default = {}; | ||
185 | description = "List of no ssl vhosts to define for Apache"; | ||
186 | type = attrsOf (submodule { | ||
187 | options = { | ||
188 | hosts = mkOption { type = listOf str; }; | ||
189 | root = mkOption { type = nullOr path; }; | ||
190 | extraConfig = mkOption { type = listOf lines; default = []; }; | ||
191 | }; | ||
192 | }); | ||
193 | }; | ||
194 | vhostConfs = mkOption { | ||
195 | default = {}; | ||
196 | description = "List of vhosts to define for Apache"; | ||
197 | type = attrsOf (submodule { | ||
198 | options = { | ||
199 | certName = mkOption { type = str; }; | ||
200 | hosts = mkOption { type = listOf str; }; | ||
201 | root = mkOption { type = nullOr path; }; | ||
202 | forceSSL = mkOption { | ||
203 | type = bool; | ||
204 | default = true; | ||
205 | description = '' | ||
206 | Automatically create a corresponding non-ssl vhost | ||
207 | that will only redirect to the ssl version | ||
208 | ''; | ||
209 | }; | ||
210 | extraConfig = mkOption { type = listOf lines; default = []; }; | ||
211 | }; | ||
212 | }); | ||
213 | }; | ||
214 | watchPaths = mkOption { | ||
215 | type = listOf str; | ||
216 | default = []; | ||
217 | description = '' | ||
218 | Paths to watch that should trigger a reload of httpd | ||
219 | ''; | ||
220 | }; | ||
221 | }; | ||
222 | })); | ||
223 | }; | ||
224 | }; | ||
225 | |||
226 | config = lib.mkMerge [ | ||
227 | { | ||
228 | assertions = [ | ||
229 | { | ||
230 | assertion = builtins.length (builtins.attrNames (lib.filterAttrs (k: v: v.enable && v.moduleType == "main") cfg.env)) <= 1; | ||
231 | message = '' | ||
232 | Only one enabled environment can have moduleType = "main" | ||
233 | ''; | ||
234 | } | ||
235 | ]; | ||
236 | } | ||
237 | |||
238 | { | ||
239 | environment.etc = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
240 | "httpd/${name}/httpd.conf" { source = (pkgs.nixos { | ||
241 | imports = [ | ||
242 | { | ||
243 | config.security.acme.acceptTerms = true; | ||
244 | config.security.acme.preliminarySelfsigned = false; | ||
245 | config.security.acme.certs = | ||
246 | lib.mapAttrs (n: lib.filterAttrs (n': v': n' != "directory")) config.security.acme.certs; | ||
247 | config.security.acme.defaults = config.security.acme.defaults; | ||
248 | config.networking.hostName = "${hostConfig.networking.hostName}-${name}"; | ||
249 | config.services.httpd = toHttpdConfig icfg; | ||
250 | } | ||
251 | ]; | ||
252 | }).config.services.httpd.configFile; | ||
253 | }) (lib.filterAttrs (k: v: v.moduleType == "container" && v.enable) cfg.env); | ||
254 | |||
255 | system.activationScripts.httpd-containers = { | ||
256 | deps = [ "etc" ]; | ||
257 | text = builtins.concatStringsSep "\n" ( | ||
258 | lib.mapAttrsToList (n: v: '' | ||
259 | install -d -m 0750 -o ${v.user} -g ${v.group} /var/log/httpd/${n} /var/lib/nixos-containers/httpd-${n}-mounts/conf | ||
260 | install -Dm644 -o ${v.user} -g ${v.group} /etc/httpd/${n}/httpd.conf /var/lib/nixos-containers/httpd-${n}-mounts/conf/ | ||
261 | '') (lib.filterAttrs (k: v: v.moduleType == "container" && v.enable) cfg.env) | ||
262 | ); | ||
263 | }; | ||
264 | |||
265 | security.acme.certs = lib.mkMerge (lib.mapAttrsToList (name: icfg: | ||
266 | let | ||
267 | containerCertNames = lib.unique (lib.mapAttrsToList (n: v: v.certName) icfg.vhostConfs | ||
268 | ++ [ icfg.fallbackVhost.certName ]); | ||
269 | in | ||
270 | lib.genAttrs containerCertNames (n: | ||
271 | { postRun = "machinectl shell httpd-${name} /run/current-system/sw/bin/systemctl reload httpd.service"; } | ||
272 | ) | ||
273 | ) (lib.filterAttrs (k: v: v.moduleType == "container" && v.enable) cfg.env) | ||
274 | ); | ||
275 | containers = let hostConfig = config; in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
276 | "httpd-${name}" { | ||
277 | autoStart = true; | ||
278 | privateNetwork = false; | ||
279 | bindMounts = { | ||
280 | "/var/log/httpd" = { | ||
281 | hostPath = "/var/log/httpd/${name}"; | ||
282 | isReadOnly = false; | ||
283 | }; | ||
284 | "/etc/httpd" = { | ||
285 | hostPath = "/var/lib/nixos-containers/httpd-${name}-mounts/conf"; | ||
286 | }; | ||
287 | } // icfg.bindMounts; | ||
288 | |||
289 | config = { config, options, ... }: { | ||
290 | imports = [ | ||
291 | myuids.nixosModule | ||
292 | files-watcher.nixosModule | ||
293 | ]; | ||
294 | config = lib.mkMerge [ | ||
295 | { | ||
296 | # This value determines the NixOS release with which your system is | ||
297 | # to be compatible, in order to avoid breaking some software such as | ||
298 | # database servers. You should change this only after NixOS release | ||
299 | # notes say you should. | ||
300 | # https://nixos.org/nixos/manual/release-notes.html | ||
301 | system.stateVersion = "23.05"; # Did you read the comment? | ||
302 | } | ||
303 | { | ||
304 | users.mutableUsers = false; | ||
305 | users.allowNoPasswordLogin = true; | ||
306 | users.users.acme.uid = config.ids.uids.acme; | ||
307 | users.users.acme.group = "acme"; | ||
308 | users.groups.acme.gid = config.ids.gids.acme; | ||
309 | } | ||
310 | { | ||
311 | services.logrotate.settings.httpd.enable = false; | ||
312 | } | ||
313 | { | ||
314 | environment.etc."httpd/httpd.conf".enable = false; | ||
315 | services.httpd = { | ||
316 | enable = true; | ||
317 | configFile = "/etc/httpd/httpd.conf"; | ||
318 | }; | ||
319 | |||
320 | services.filesWatcher.http-config-reload = { | ||
321 | paths = [ "/etc/httpd/httpd.conf" ]; | ||
322 | waitTime = 2; | ||
323 | restart = true; | ||
324 | }; | ||
325 | services.filesWatcher.httpd = { | ||
326 | paths = icfg.watchPaths; | ||
327 | waitTime = 5; | ||
328 | }; | ||
329 | |||
330 | users.users.${icfg.user}.extraGroups = [ "acme" "keys" ]; | ||
331 | systemd.services.http-config-reload = { | ||
332 | wants = [ "httpd.service" ]; | ||
333 | wantedBy = [ "multi-user.target" ]; | ||
334 | restartTriggers = [ config.services.httpd.configFile ]; | ||
335 | serviceConfig.Type = "oneshot"; | ||
336 | serviceConfig.TimeoutSec = 60; | ||
337 | serviceConfig.RemainAfterExit = true; | ||
338 | script = '' | ||
339 | if ${pkgs.systemd}/bin/systemctl -q is-active httpd.service ; then | ||
340 | ${config.services.httpd.package.out}/bin/httpd -f ${config.services.httpd.configFile} -t && \ | ||
341 | ${pkgs.systemd}/bin/systemctl reload httpd.service | ||
342 | fi | ||
343 | ''; | ||
344 | }; | ||
345 | } | ||
346 | ]; | ||
347 | }; | ||
348 | }) (lib.filterAttrs (k: v: v.moduleType == "container" && v.enable) cfg.env); | ||
349 | } | ||
350 | |||
351 | { | ||
352 | services.httpd = lib.concatMapAttrs (name: toHttpdConfig) | ||
353 | (lib.filterAttrs (k: v: v.moduleType == "main" && v.enable) cfg.env); | ||
354 | |||
355 | users.users = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
356 | config.services.httpd.user { extraGroups = [ "acme" ]; } | ||
357 | ) (lib.filterAttrs (k: v: v.moduleType == "main" && v.enable) cfg.env); | ||
358 | |||
359 | services.filesWatcher = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair | ||
360 | "httpd" { | ||
361 | paths = icfg.watchPaths; | ||
362 | waitTime = 5; | ||
363 | } | ||
364 | ) (lib.filterAttrs (k: v: v.moduleType == "main" && v.enable) cfg.env); | ||
365 | |||
366 | services.logrotate.settings.httpd.enable = false; | ||
367 | systemd.services = lib.concatMapAttrs (name: v: { | ||
368 | httpd.restartTriggers = lib.mkForce []; | ||
369 | }) | ||
370 | (lib.filterAttrs (k: v: v.moduleType == "main" && v.enable) cfg.env); | ||
371 | |||
372 | security.acme.certs = lib.mkMerge (lib.mapAttrsToList (name: icfg: | ||
373 | let | ||
374 | containerCertNames = lib.unique (lib.mapAttrsToList (n: v: v.certName) icfg.vhostConfs | ||
375 | ++ [ icfg.fallbackVhost.certName ]); | ||
376 | in | ||
377 | lib.genAttrs containerCertNames (n: | ||
378 | { postRun = "systemctl reload httpd.service"; } | ||
379 | ) | ||
380 | ) (lib.filterAttrs (k: v: v.moduleType == "main" && v.enable) cfg.env) | ||
381 | ); | ||
382 | |||
383 | } | ||
384 | ]; | ||
385 | }; | ||
386 | }; | ||
387 | } | ||
388 | |||
389 | |||