diff --git a/hosts/vm/reverseproxy/reverseproxy.nix b/hosts/vm/reverseproxy/reverseproxy.nix index 3401084..237df0d 100644 --- a/hosts/vm/reverseproxy/reverseproxy.nix +++ b/hosts/vm/reverseproxy/reverseproxy.nix @@ -1,4 +1,7 @@ -{ pkgs, ... }: +{ + pkgs, + ... +}: let formatJSON = pkgs.formats.json { }; @@ -128,28 +131,94 @@ let ]; }; + fetchFromCrans = + opts: + pkgs.fetchFromGitLab ( + { + domain = "gitlab.adm.crans.org"; + owner = "nounous"; + } + // opts + ); + + installPartySite = pkgs.python3Packages.buildPythonApplication { + name = "site-install-party"; + pyproject = false; + + src = fetchFromCrans { + repo = "site-install-party"; + rev = "master"; + hash = "sha256-KVB4m/ms0WuArrkLn05INVLqhaGxzCg30GMICI6tM5E="; + }; + + build-system = with pkgs.python3Packages; [ + mkdocs + mkdocs-material + ]; + buildPhase = '' + python3 -m mkdocs -- build -d $out + ''; + }; + + homepagePkg = pkgs.stdenv.mkDerivation { + name = "site-homepage"; + + src = fetchFromCrans { + repo = "homepage"; + rev = "master"; + hash = "sha256-e9tttzKEWcBhogQY2ITDqqTbix/zcnBAylj1eKY791E="; + }; + + nativeBuildInputs = with pkgs; [ + hugo + ]; + + buildPhase = '' + hugo build -d $out + ''; + }; in { crans = { reverseProxy = { enable = true; virtualHosts = { + "autoconfig".serveLocalFiles = ./staticsites/autoconfig; + "cas".proxyPass = "172.16.10.120"; "collabora" = { - target = "172.16.10.149"; + proxyPass = "172.16.10.149"; proxyWebsockets = true; }; "eclat" = { anubisConfig = "${anubisMirrors}"; httpOnly = true; - target = "172.16.10.104"; + proxyPass = "172.16.10.104"; }; "eclats" = { anubisConfig = "${anubisMirrors}"; - target = "172.16.10.104"; + proxyPass = "172.16.10.104"; + }; + "element".proxyPass = "172.16.10.118"; + "ethercalc" = { + proxyPass = "172.16.10.133:8000"; + serverAliases = [ "excel" ]; + }; + "framadate".proxyPass = "172.16.10.109"; + "grafana" = { + proxyPass = "172.16.10.121:3000"; + serverAliases = [ "fyre" ]; + }; + "imprimante" = { + proxyPass = "172.16.10.131"; + serverAliases = [ "helloworld" ]; + }; + "intranet" = { + proxyPass = "172.16.10.156"; + serverAliases = [ "re2o" ]; }; "install-party" = { anubisConfig = "${anubisChallenge}"; - target = "/var/www/install-party.crans.org"; + serveLocalFiles = installPartySite; serverAliases = [ "i-p" "adopteunmanchot" @@ -158,38 +227,68 @@ in }; "lists" = { anubisConfig = "${anubisChallenge}"; - target = "172.16.10.110"; + proxyPass = "172.16.10.110"; }; "mediawiki" = { anubisConfig = "${anubisChallenge}"; - target = "172.16.10.144"; - serverAliases = [ - "mediakiwi" - ]; + proxyPass = "172.16.10.144"; + serverAliases = [ "mediakiwi" ]; }; "mirrors" = { anubisConfig = "${anubisMirrors}"; - target = "172.16.10.104"; + proxyPass = "172.16.10.104"; }; "mirror" = { anubisConfig = "${anubisMirrors}"; httpOnly = true; - target = "172.16.10.104"; + proxyPass = "172.16.10.104"; + }; + "nekorale".globalRedirect = "perso.crans.org/club-nekorale"; + "nextcloud".proxyPass = "172.16.10.146"; + "owncloud".proxyPass = "172.16.10.136"; + "pad".proxyPass = "172.16.10.130:9001"; + "pdf" = { + proxyPass = "172.16.10.140"; + serverAliases = [ + "livre" + "stirling" + "stirling-pdf" + ]; }; "perso" = { anubisConfig = "${anubisPerso}"; - target = "172.16.10.31"; - serverAliases = [ - "clubs" - ]; + proxyPass = "172.16.10.31"; + serverAliases = [ "clubs" ]; + }; + "pot-vieux".globalRedirect = "perso.crans.org/club-vieux"; + "re2o-dev".proxyPass = "172.16.10.166"; + "services".serveLocalFiles = fetchFromCrans { + repo = "services-page"; + rev = "master"; + hash = "sha256-ov4r6Oeta+vRcEv8bp7lFQg+2n4JTG4buetram5kN08="; + }; + "snl".globalRedirect = "perso.crans.org/sonetlumens"; + "test-melon".proxyPass = "172.16.10.150:8080"; + "tmpad".proxyPass = "172.16.10.130:9002"; + "vaultwarden" = { + proxyPass = "172.16.10.159"; + serverAliases = [ "pass" ]; + }; + "webirc".proxyPass = "172.16.10.31:9000"; + "webmail" = { + proxyPass = "172.16.10.107"; + serverAliases = [ "roundcube" ]; }; "wiki" = { anubisConfig = "${anubisChallenge}"; - target = "172.16.10.161"; - serverAliases = [ - "wikipedia" - ]; + proxyPass = "172.16.10.161"; + serverAliases = [ "wikipedia" ]; }; + "www" = { + serveLocalFiles = homepagePkg; + serverAliases = [ "." ]; + }; + "zero".proxyPass = "172.16.10.130"; }; }; @@ -197,4 +296,5 @@ in acme.enable = true; }; }; + services.nginx.virtualHosts."www-alias-crans.org".default = true; } diff --git a/hosts/vm/reverseproxy/staticsites/autoconfig/mail/config-v1.1.xml b/hosts/vm/reverseproxy/staticsites/autoconfig/mail/config-v1.1.xml new file mode 100644 index 0000000..95ad1ea --- /dev/null +++ b/hosts/vm/reverseproxy/staticsites/autoconfig/mail/config-v1.1.xml @@ -0,0 +1,23 @@ + + + crans.org + crans.eu + crans.fr + Cr@ns Mail + Cr@ns + + imap.crans.org + 993 + SSL + %EMAILLOCALPART% + plain + + + smtp.crans.org + 465 + SSL + %EMAILLOCALPART% + plain + + + diff --git a/modules/services/acme.nix b/modules/services/acme.nix index 1b90021..f08bd9a 100644 --- a/modules/services/acme.nix +++ b/modules/services/acme.nix @@ -24,6 +24,8 @@ in dnsPropagationCheck = false; }; + # Certificat wildcard par défaut pour les services utilisant acme + # TODO: À déplacer sur les VMs qui en ont besoin (e.g. coturn…). certs."crans.org" = { domain = "*.crans.org"; dnsProvider = "rfc2136"; diff --git a/modules/services/reverseproxy.nix b/modules/services/reverseproxy.nix index 5098771..874635e 100644 --- a/modules/services/reverseproxy.nix +++ b/modules/services/reverseproxy.nix @@ -1,5 +1,4 @@ { - pkgs, lib, config, ... @@ -8,28 +7,6 @@ let cfg = config.crans.reverseProxy; - formatJSON = pkgs.formats.json { }; - - allowAll = formatJSON.generate "allow_all.json" { - bots = [ - { - name = "allow_all"; - path_regex = ".*"; - action = "ALLOW"; - } - ]; - }; - - open_graph = formatJSON.generate "opengraph.json" { - openGraph = [ - { - enabled = true; - considerHost = true; - ttl = "24h"; - } - ]; - }; - mainTld = "org"; otherTld = [ "fr" @@ -65,18 +42,36 @@ in ''; }; - target = mkOption { - type = types.str; - default = ""; + proxyPass = mkOption { + type = types.nullOr types.str; + default = null; description = '' - Indique la destination. Il peut s'agir du chemin vers des fichiers statiques. + Indique la destination à proxy. ''; example = "172.16.10.128:8000"; }; + serveLocalFiles = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Chemin vers un dossier à exposer statiquement. + ''; + example = "/var/local/adopter-un-manchot"; + }; + + globalRedirect = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Si définie, toutes les requêtes sont redirigées (via 301) sur cet hôte. + ''; + example = "perso.crans.org/club"; + }; + anubisConfig = mkOption { - type = types.str; - default = ""; + type = types.nullOr types.str; + default = null; description = '' Chemin du fichier de configuration ''; @@ -95,7 +90,7 @@ in type = types.bool; default = false; description = '' - Interdit les connexions en ssh + Interdit les connexions en https ''; example = "true"; }; @@ -134,107 +129,187 @@ in wantedBy = [ "multi-user.target" ]; }) cfg.virtualHosts; + # On fait un certificat commun pour tous les hosts + security.acme.certs."all.crans.all" = mkIf cfg.enable { + domain = "*.crans.${mainTld}"; + dnsProvider = "rfc2136"; + # Contient le serveur à contacter avec le protocole + # et le mot de passe + environmentFile = config.age.secrets.acme-env.path; + extraDomainNames = + lib.concatMap (tld: [ + "*.crans.${tld}" + "crans.${tld}" + ]) otherTld + ++ [ + "crans.${mainTld}" + ]; + group = config.services.nginx.group; + }; + + # Création des dossiers de logs + system.activationScripts = { + nginx-logs = { + text = + lib.concatMapStrings (f: "mkdir -p /var/log/nginx/${f}/\n") ( + [ "anubis" ] ++ (lib.map (hn: "host/${hn}") (builtins.attrNames cfg.virtualHosts)) + ) + + "chown -R ${config.services.nginx.user}:${config.services.nginx.group} /var/log/nginx/\n"; + }; + }; + services = mkIf cfg.enable { anubis = { defaultOptions.group = "nginx"; - instances = lib.mapAttrs (vhostName: vhostConfig: { - enable = true; - settings = { - BIND = "/run/anubis/anubis-${vhostName}/socket.sock"; - BIND_NETWORK = "unix"; - METRICS_BIND = "/run/anubis/anubis-${vhostName}/anubis-${vhostName}-metrics.sock"; - TARGET = "unix:///run/nginx/nginx-${vhostName}.sock"; - COOKIE_DOMAIN = "crans.org"; - REDIRECT_DOMAINS = "${vhostName}.crans.org"; - SOCKET_MODE = "0660"; - # OpenGraph config - OG_PASSTHROUGH = vhostConfig.anubisOpenGraph; - OG_EXPIRY_TIME = "24h"; - OG_CACHE_CONSIDER_HOST = true; - # Policy config - POLICY_FNAME = if (vhostConfig.anubisConfig == "") then "${allowAll}" else vhostConfig.anubisConfig; - }; - }) cfg.virtualHosts; + instances = lib.mapAttrs ( + vhostName: vhostConfig: + mkIf (vhostConfig.anubisConfig != null) { + enable = true; + settings = { + BIND = "/run/anubis/anubis-${vhostName}/socket.sock"; + BIND_NETWORK = "unix"; + METRICS_BIND = "/run/anubis/anubis-${vhostName}/anubis-${vhostName}-metrics.sock"; + TARGET = "unix:///run/nginx/nginx-${vhostName}.sock"; + COOKIE_DOMAIN = "crans.${mainTld}"; + REDIRECT_DOMAINS = "${vhostName}.crans.${mainTld}"; + SOCKET_MODE = "0660"; + # OpenGraph config + OG_PASSTHROUGH = vhostConfig.anubisOpenGraph; + OG_EXPIRY_TIME = "24h"; + OG_CACHE_CONSIDER_HOST = true; + # Policy config + POLICY_FNAME = vhostConfig.anubisConfig; + }; + } + ) cfg.virtualHosts; }; + logrotate.settings = + let + # https://github.com/NixOS/nixpkgs/blob/nixos-26.05/nixos/modules/services/web-servers/nginx/default.nix + base = { + su = "${config.services.nginx.user} ${config.services.nginx.group}"; + postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`"; + frequency = "daily"; + ifempty = true; + compress = true; + delaycompress = true; + dateyesterday = true; + }; + in + { + nginx-anubis = base // { + files = [ "/var/log/nginx/anubis/*.log" ]; + rotate = 7; # une semaine de log pour débugguer + }; + nginx-hosts = base // { + files = [ "/var/log/nginx/host/*/*.log" ]; + rotate = 173; # 6 mois de logs + dateext = true; + dateformat = "%Y-%m-%d"; + }; + }; + nginx = let - # Configuration du serveur principal. - mainConfig = lib.mapAttrs' ( + configVhost = vhostName: vhostConfig: - lib.nameValuePair (vhostName + "-anubis") { - enableACME = !vhostConfig.httpOnly; - forceSSL = !vhostConfig.httpOnly; - rejectSSL = vhostConfig.httpOnly; - locations."/" = { - proxyPass = "http://unix:/run/anubis/anubis-${vhostName}/socket.sock"; - proxyWebsockets = vhostConfig.proxyWebsockets; + let + sslConf = { + forceSSL = !vhostConfig.httpOnly; + rejectSSL = vhostConfig.httpOnly; + useACMEHost = mkIf (!vhostConfig.httpOnly) "all.crans.all"; + acmeRoot = null; }; - serverName = "${vhostName}.crans.${mainTld}"; - extraConfig = " - set_real_ip_from 172.16.0.0/16; - set_real_ip_from fd00::/56; - real_ip_header X-Real-Ip; - "; - } - ) cfg.virtualHosts; - # Redirections - redirectConfig = lib.mapAttrs (vhostName: vhostConfig: { - # Redirection vers d'autres machines - locations = mkIf (!lib.strings.hasPrefix "/" vhostConfig.target) { - "/favicon.ico".root = "/var/www/logo/"; - "/" = { - proxyPass = "http://${vhostConfig.target}"; - proxyWebsockets = vhostConfig.proxyWebsockets; - }; - }; - # Redirection vers des fichiers locaux - root = mkIf (lib.strings.hasPrefix "/" vhostConfig.target) vhostConfig.target; - listen = [ - { addr = "unix:/run/nginx/nginx-${vhostName}.sock"; } - ]; - serverName = "${vhostName}.crans.${mainTld}"; - extraConfig = " - set_real_ip_from unix:; - real_ip_header X-Real-IP; - "; - }) cfg.virtualHosts; - - # Génération des alias - getAliases = name: config: lib.foldr ( - tld: acc: - acc - ++ - (lib.foldr (alias: acc: acc ++ ["${alias}.crans.${tld}"]) ["${name}.crans.${tld}"] config.serverAliases) - ) - (lib.foldr (alias: acc: acc ++ ["${alias}.crans.${mainTld}"]) [] config.serverAliases) - otherTld; - - # Configuration des alias - aliasConfig = lib.foldr ( - vhost: acc: - acc - // - lib.foldr ( - alias: acc: - acc - // - { - "${vhost.name}-alias-${alias}" = rec { - rejectSSL = vhost.value.httpOnly; - forceSSL = !rejectSSL; - enableACME = !rejectSSL; - serverName = alias; - globalRedirect = "${vhost.name}.crans.${mainTld}"; - }; + # Conf pour l’hôte en entrée + entryExtraConf = { + extraConfig = '' + set_real_ip_from 172.16.0.0/16; + set_real_ip_from fd00::/56; + real_ip_header X-Real-Ip; + ''; } - ) { } (getAliases vhost.name vhost.value) - ) { } (lib.attrsToList cfg.virtualHosts); + // sslConf; + # Conf supplémentaire pour le proxy principal + vhostExtraConf = + if vhostConfig.anubisConfig != null then + # Il reçoit les requêtes d’Anubis + { + listen = [ + { addr = "unix:/run/nginx/nginx-${vhostName}.sock"; } + ]; + extraConfig = '' + set_real_ip_from unix:; + real_ip_header X-Real-IP; + ''; + } + else + # Il est en entrée + entryExtraConf; + + logHostConf = '' + access_log /var/log/nginx/host/${vhostName}/access.log; + error_log /var/log/nginx/host/${vhostName}/error.log; + ''; + + # Les alias : vhostName × otherTld U serverAliases × allTld + mkHostName = als: tld: if als == "." then "crans.${tld}" else "${als}.crans.${tld}"; + aliases = + lib.foldr + ( + tld: acc: + acc + ++ (lib.foldr (alias: acc: acc ++ [ (mkHostName alias tld) ]) [ + (mkHostName vhostName tld) + ] vhostConfig.serverAliases) + ) + (lib.foldr (alias: acc: acc ++ [ (mkHostName alias mainTld) ]) [ ] vhostConfig.serverAliases) + otherTld; + in + { + # Configuration du service à proxy. + "${vhostName}" = vhostExtraConf // { + serverName = mkHostName vhostName mainTld; + locations."/" = mkIf (vhostConfig.proxyPass != null) { + proxyPass = "http://${vhostConfig.proxyPass}"; + proxyWebsockets = vhostConfig.proxyWebsockets; + }; + root = vhostConfig.serveLocalFiles; + globalRedirect = vhostConfig.globalRedirect; + extraConfig = vhostExtraConf.extraConfig + logHostConf; + }; + + # Entrée d’Anubis + "${vhostName}-anubis" = mkIf (vhostConfig.anubisConfig != null) ( + entryExtraConf + // { + serverName = mkHostName vhostName mainTld; + locations."/" = { + proxyPass = "http://unix:/run/anubis/anubis-${vhostName}/socket.sock"; + proxyWebsockets = vhostConfig.proxyWebsockets; + }; + extraConfig = entryExtraConf.extraConfig + '' + access_log /var/log/nginx/anubis/access.log; + error_log /var/log/nginx/anubis/error.log; + ''; + } + ); + } + # Les alias + // lib.mergeAttrsList ( + lib.map (alias: { + "${vhostName}-alias-${alias}" = sslConf // { + serverName = alias; + globalRedirect = lib.defaultTo "${vhostName}.crans.${mainTld}" vhostConfig.globalRedirect; + extraConfig = logHostConf; + }; + }) aliases + ); in { enable = true; - virtualHosts = redirectConfig // aliasConfig // mainConfig; + virtualHosts = lib.concatMapAttrs configVhost cfg.virtualHosts; }; }; };