From 1a64deeb894dc95e2645a75771732c6cc53a79ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Wed, 4 Oct 2023 01:35:06 +0200 Subject: 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 --- flakes/multi-apache-container/flake.lock | 36 +++ flakes/multi-apache-container/flake.nix | 389 +++++++++++++++++++++++++++++++ 2 files changed, 425 insertions(+) create mode 100644 flakes/multi-apache-container/flake.lock create mode 100644 flakes/multi-apache-container/flake.nix (limited to 'flakes/multi-apache-container') 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 @@ +{ + "nodes": { + "files-watcher": { + "locked": { + "lastModified": 1, + "narHash": "sha256-ZsdumUVoSPkV/DB6gO6dNDttjzalye0ToVBF9bl5W0k=", + "path": "../files-watcher", + "type": "path" + }, + "original": { + "path": "../files-watcher", + "type": "path" + } + }, + "myuids": { + "locked": { + "lastModified": 1, + "narHash": "sha256-HkW9YCLQCNBX3Em7J7MjraVEZO3I3PizkVV2QrUdULQ=", + "path": "../myuids", + "type": "path" + }, + "original": { + "path": "../myuids", + "type": "path" + } + }, + "root": { + "inputs": { + "files-watcher": "files-watcher", + "myuids": "myuids" + } + } + }, + "root": "root", + "version": 7 +} 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 @@ +{ + description = "Module to handle multiple separate apache instances (using containers)"; + inputs.myuids = { + url = "path:../myuids"; + }; + inputs.files-watcher = { + url = "path:../files-watcher"; + }; + + outputs = { self, myuids, files-watcher }: { + nixosModule = { lib, config, pkgs, options, ... }: + with lib; + let + cfg = config.services.websites; + hostConfig = config; + toHttpdConfig = icfg: + let + nosslVhost = ips: cfg: { + listen = map (ip: { inherit ip; port = 80; }) ips; + hostName = cfg.host; + logFormat = "combinedVhost"; + documentRoot = cfg.root; + extraConfig = '' + + DirectoryIndex ${cfg.indexFile} + AllowOverride None + Require all granted + + RewriteEngine on + RewriteRule ^/(.+) / [L] + + ''; + }; + toVhost = ips: vhostConf: { + acmeRoot = hostConfig.security.acme.certs.${vhostConf.certName}.webroot; + forceSSL = vhostConf.forceSSL or true; + useACMEHost = vhostConf.certName; + logFormat = "combinedVhost"; + listen = if vhostConf.forceSSL + then lists.flatten (map (ip: [{ inherit ip; port = 443; ssl = true; } { inherit ip; port = 80; }]) ips) + else map (ip: { inherit ip; port = 443; ssl = true; }) ips; + hostName = builtins.head vhostConf.hosts; + serverAliases = builtins.tail vhostConf.hosts or []; + documentRoot = vhostConf.root; + extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig; + }; + toVhostNoSSL = ips: vhostConf: { + logFormat = "combinedVhost"; + listen = map (ip: { inherit ip; port = 80; }) ips; + hostName = builtins.head vhostConf.hosts; + serverAliases = builtins.tail vhostConf.hosts or []; + documentRoot = vhostConf.root; + extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig; + }; + in { + enable = true; + logPerVirtualHost = true; + mpm = "event"; + # https://ssl-config.mozilla.org/#server=apache&version=2.4.41&config=intermediate&openssl=1.0.2t&guideline=5.4 + # test with https://www.ssllabs.com/ssltest/analyze.html?d=www.immae.eu&s=176.9.151.154&latest + sslProtocols = "all -SSLv3 -TLSv1 -TLSv1.1"; + sslCiphers = builtins.concatStringsSep ":" [ + "ECDHE-ECDSA-AES128-GCM-SHA256" "ECDHE-RSA-AES128-GCM-SHA256" + "ECDHE-ECDSA-AES256-GCM-SHA384" "ECDHE-RSA-AES256-GCM-SHA384" + "ECDHE-ECDSA-CHACHA20-POLY1305" "ECDHE-RSA-CHACHA20-POLY1305" + "DHE-RSA-AES128-GCM-SHA256" "DHE-RSA-AES256-GCM-SHA384" + ]; + inherit (icfg) adminAddr; + logFormat = "combinedVhost"; + extraModules = lists.unique icfg.modules; + extraConfig = builtins.concatStringsSep "\n" icfg.extraConfig; + + virtualHosts = with attrsets; { + ___fallbackVhost = toVhost icfg.ips icfg.fallbackVhost; + } // (optionalAttrs icfg.nosslVhost.enable { + nosslVhost = nosslVhost icfg.ips icfg.nosslVhost; + }) // (mapAttrs' (n: v: nameValuePair ("nossl_" + n) (toVhostNoSSL icfg.ips v)) icfg.vhostNoSSLConfs) + // (mapAttrs' (n: v: nameValuePair ("ssl_" + n) (toVhost icfg.ips v)) icfg.vhostConfs); + }; + in + { + options.services.websites = with types; { + env = mkOption { + default = {}; + description = "Each type of website to enable will target a distinct httpd server"; + type = attrsOf (submodule ({ name, config, ... }: { + options = { + enable = mkEnableOption "Enable websites of this type"; + moduleType = mkOption { + type = enum [ "container" "main" ]; + default = "container"; + description = '' + How to deploy the web environment: + - container -> inside a dedicated container (running only httpd) + - main -> as main services.httpd module + ''; + }; + adminAddr = mkOption { + type = str; + description = "Admin e-mail address of the instance"; + }; + user = mkOption { + type = str; + description = "Username of httpd service"; + readOnly = true; + default = if config.moduleType == "container" + then hostConfig.containers."httpd-${name}".config.services.httpd.user + else hostConfig.services.httpd.user; + }; + group = mkOption { + type = str; + description = "Group of httpd service"; + readOnly = true; + default = if config.moduleType == "container" + then hostConfig.containers."httpd-${name}".config.services.httpd.group + else hostConfig.services.httpd.group; + }; + httpdName = mkOption { + type = str; + description = "Name of the httpd instance to assign this type to"; + }; + ips = mkOption { + type = listOf str; + default = []; + description = "ips to listen to"; + }; + bindMounts = mkOption { + type = attrsOf unspecified; + default = {}; + description = "bind mounts to add to container"; + }; + modules = mkOption { + type = listOf str; + default = []; + description = "Additional modules to load in Apache"; + }; + extraConfig = mkOption { + type = listOf lines; + default = []; + description = "Additional configuration to append to Apache"; + }; + nosslVhost = mkOption { + description = "A default nossl vhost for captive portals"; + default = {}; + type = submodule { + options = { + enable = mkEnableOption "Add default no-ssl vhost for this instance"; + host = mkOption { + type = str; + description = "The hostname to use for this vhost"; + }; + root = mkOption { + type = path; + description = "The root folder to serve"; + }; + indexFile = mkOption { + type = str; + default = "index.html"; + description = "The index file to show."; + }; + }; + }; + }; + fallbackVhost = mkOption { + description = "The fallback vhost that will be defined as first vhost in Apache"; + type = submodule { + options = { + certName = mkOption { type = str; }; + hosts = mkOption { type = listOf str; }; + root = mkOption { type = nullOr path; }; + forceSSL = mkOption { + type = bool; + default = true; + description = '' + Automatically create a corresponding non-ssl vhost + that will only redirect to the ssl version + ''; + }; + extraConfig = mkOption { type = listOf lines; default = []; }; + }; + }; + }; + vhostNoSSLConfs = mkOption { + default = {}; + description = "List of no ssl vhosts to define for Apache"; + type = attrsOf (submodule { + options = { + hosts = mkOption { type = listOf str; }; + root = mkOption { type = nullOr path; }; + extraConfig = mkOption { type = listOf lines; default = []; }; + }; + }); + }; + vhostConfs = mkOption { + default = {}; + description = "List of vhosts to define for Apache"; + type = attrsOf (submodule { + options = { + certName = mkOption { type = str; }; + hosts = mkOption { type = listOf str; }; + root = mkOption { type = nullOr path; }; + forceSSL = mkOption { + type = bool; + default = true; + description = '' + Automatically create a corresponding non-ssl vhost + that will only redirect to the ssl version + ''; + }; + extraConfig = mkOption { type = listOf lines; default = []; }; + }; + }); + }; + watchPaths = mkOption { + type = listOf str; + default = []; + description = '' + Paths to watch that should trigger a reload of httpd + ''; + }; + }; + })); + }; + }; + + config = lib.mkMerge [ + { + assertions = [ + { + assertion = builtins.length (builtins.attrNames (lib.filterAttrs (k: v: v.enable && v.moduleType == "main") cfg.env)) <= 1; + message = '' + Only one enabled environment can have moduleType = "main" + ''; + } + ]; + } + + { + environment.etc = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair + "httpd/${name}/httpd.conf" { source = (pkgs.nixos { + imports = [ + { + config.security.acme.acceptTerms = true; + config.security.acme.preliminarySelfsigned = false; + config.security.acme.certs = + lib.mapAttrs (n: lib.filterAttrs (n': v': n' != "directory")) config.security.acme.certs; + config.security.acme.defaults = config.security.acme.defaults; + config.networking.hostName = "${hostConfig.networking.hostName}-${name}"; + config.services.httpd = toHttpdConfig icfg; + } + ]; + }).config.services.httpd.configFile; + }) (lib.filterAttrs (k: v: v.moduleType == "container" && v.enable) cfg.env); + + system.activationScripts.httpd-containers = { + deps = [ "etc" ]; + text = builtins.concatStringsSep "\n" ( + lib.mapAttrsToList (n: v: '' + install -d -m 0750 -o ${v.user} -g ${v.group} /var/log/httpd/${n} /var/lib/nixos-containers/httpd-${n}-mounts/conf + install -Dm644 -o ${v.user} -g ${v.group} /etc/httpd/${n}/httpd.conf /var/lib/nixos-containers/httpd-${n}-mounts/conf/ + '') (lib.filterAttrs (k: v: v.moduleType == "container" && v.enable) cfg.env) + ); + }; + + security.acme.certs = lib.mkMerge (lib.mapAttrsToList (name: icfg: + let + containerCertNames = lib.unique (lib.mapAttrsToList (n: v: v.certName) icfg.vhostConfs + ++ [ icfg.fallbackVhost.certName ]); + in + lib.genAttrs containerCertNames (n: + { postRun = "machinectl shell httpd-${name} /run/current-system/sw/bin/systemctl reload httpd.service"; } + ) + ) (lib.filterAttrs (k: v: v.moduleType == "container" && v.enable) cfg.env) + ); + containers = let hostConfig = config; in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair + "httpd-${name}" { + autoStart = true; + privateNetwork = false; + bindMounts = { + "/var/log/httpd" = { + hostPath = "/var/log/httpd/${name}"; + isReadOnly = false; + }; + "/etc/httpd" = { + hostPath = "/var/lib/nixos-containers/httpd-${name}-mounts/conf"; + }; + } // icfg.bindMounts; + + config = { config, options, ... }: { + imports = [ + myuids.nixosModule + files-watcher.nixosModule + ]; + config = lib.mkMerge [ + { + # This value determines the NixOS release with which your system is + # to be compatible, in order to avoid breaking some software such as + # database servers. You should change this only after NixOS release + # notes say you should. + # https://nixos.org/nixos/manual/release-notes.html + system.stateVersion = "23.05"; # Did you read the comment? + } + { + users.mutableUsers = false; + users.allowNoPasswordLogin = true; + users.users.acme.uid = config.ids.uids.acme; + users.users.acme.group = "acme"; + users.groups.acme.gid = config.ids.gids.acme; + } + { + services.logrotate.settings.httpd.enable = false; + } + { + environment.etc."httpd/httpd.conf".enable = false; + services.httpd = { + enable = true; + configFile = "/etc/httpd/httpd.conf"; + }; + + services.filesWatcher.http-config-reload = { + paths = [ "/etc/httpd/httpd.conf" ]; + waitTime = 2; + restart = true; + }; + services.filesWatcher.httpd = { + paths = icfg.watchPaths; + waitTime = 5; + }; + + users.users.${icfg.user}.extraGroups = [ "acme" "keys" ]; + systemd.services.http-config-reload = { + wants = [ "httpd.service" ]; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ config.services.httpd.configFile ]; + serviceConfig.Type = "oneshot"; + serviceConfig.TimeoutSec = 60; + serviceConfig.RemainAfterExit = true; + script = '' + if ${pkgs.systemd}/bin/systemctl -q is-active httpd.service ; then + ${config.services.httpd.package.out}/bin/httpd -f ${config.services.httpd.configFile} -t && \ + ${pkgs.systemd}/bin/systemctl reload httpd.service + fi + ''; + }; + } + ]; + }; + }) (lib.filterAttrs (k: v: v.moduleType == "container" && v.enable) cfg.env); + } + + { + services.httpd = lib.concatMapAttrs (name: toHttpdConfig) + (lib.filterAttrs (k: v: v.moduleType == "main" && v.enable) cfg.env); + + users.users = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair + config.services.httpd.user { extraGroups = [ "acme" ]; } + ) (lib.filterAttrs (k: v: v.moduleType == "main" && v.enable) cfg.env); + + services.filesWatcher = attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair + "httpd" { + paths = icfg.watchPaths; + waitTime = 5; + } + ) (lib.filterAttrs (k: v: v.moduleType == "main" && v.enable) cfg.env); + + services.logrotate.settings.httpd.enable = false; + systemd.services = lib.concatMapAttrs (name: v: { + httpd.restartTriggers = lib.mkForce []; + }) + (lib.filterAttrs (k: v: v.moduleType == "main" && v.enable) cfg.env); + + security.acme.certs = lib.mkMerge (lib.mapAttrsToList (name: icfg: + let + containerCertNames = lib.unique (lib.mapAttrsToList (n: v: v.certName) icfg.vhostConfs + ++ [ icfg.fallbackVhost.certName ]); + in + lib.genAttrs containerCertNames (n: + { postRun = "systemctl reload httpd.service"; } + ) + ) (lib.filterAttrs (k: v: v.moduleType == "main" && v.enable) cfg.env) + ); + + } + ]; + }; + }; +} + + -- cgit v1.2.3