From 42b0d356918264a5bb2c685a84118ca050c39190 Mon Sep 17 00:00:00 2001 From: Dennis Frieberg Date: Tue, 1 Jul 2025 15:52:42 +0200 Subject: [PATCH] redirects and proxy passes working (except for ssl which needs to migrate to dns) --- nixos/machines/cthulhu/configuration.nix | 25 +-- nixos/machines/cthulhu/proxies.nix | 13 ++ nixos/machines/cthulhu/redirects.nix | 67 ++++++++ nixos/modules/reverseProxy.nix | 202 +++++++++++++++++++++++ 4 files changed, 295 insertions(+), 12 deletions(-) create mode 100644 nixos/machines/cthulhu/proxies.nix create mode 100644 nixos/machines/cthulhu/redirects.nix create mode 100644 nixos/modules/reverseProxy.nix diff --git a/nixos/machines/cthulhu/configuration.nix b/nixos/machines/cthulhu/configuration.nix index bcb21aa..3b5e6aa 100644 --- a/nixos/machines/cthulhu/configuration.nix +++ b/nixos/machines/cthulhu/configuration.nix @@ -3,23 +3,24 @@ ./hardware-configuration.nix ../../roles ../../roles/vm.nix - ../../modules/vmNetwork.nix + ../../modules/reverseProxy.nix ]; # System configuration here networking.hostName = "cthulhu"; - vmNetwork.ipv4 = "192.168.0.16"; system.stateVersion = "25.05"; - - sops.secrets = { - backupKey = { - sopsFile = ./backupKey.secrets.yaml; - owner = "root"; - group = "root"; - mode = "0400"; - }; + services.reverseProxy = { + enable = true; + redirects = import ./redirects.nix; + proxies = import ./proxies.nix; }; - # TODO for the network rework, make a central record of hostnames to ip adresses where every - # machine can read out their ip address and also this machine + # sops.secrets = { + # backupKey = { + # sopsFile = ./backupKey.secrets.yaml; + # owner = "root"; + # group = "root"; + # mode = "0400"; + # }; + # }; } diff --git a/nixos/machines/cthulhu/proxies.nix b/nixos/machines/cthulhu/proxies.nix new file mode 100644 index 0000000..6bf75eb --- /dev/null +++ b/nixos/machines/cthulhu/proxies.nix @@ -0,0 +1,13 @@ +{ + "lists.mathebau.de"."/" = { + targetMachine = "lobon"; + }; + # matheball.de (192.168.0.21) + # mathechor.de (192.168.0.19:80) + # cloud.mathechor.de (192.168.0.20:80) + # gitea.mathebau.de (192.168.0.23:3000) + # codi.mathebau.de (192.168.0.18) + # fswiki.mathebau.de owowiki.mathebau.de surveys.mathebau.de (192.168.0.15) + # events.mathebau.de (192.168.0.13:8080) + # sprechstunden.mathebau.de (192.168.0.14:8080) +} diff --git a/nixos/machines/cthulhu/redirects.nix b/nixos/machines/cthulhu/redirects.nix new file mode 100644 index 0000000..46ea195 --- /dev/null +++ b/nixos/machines/cthulhu/redirects.nix @@ -0,0 +1,67 @@ +{ + "mathebau.de" = { + "/adventskalender" = "https://www.mathematik.tu-darmstadt.de/fachschaft/fachschaftarbeit/adventskalender/adventskalender.de.jsp"; + "/discord" = "https://www.mathematik.tu-darmstadt.de/fachschaft/orientierung/it_tutorial/nach_der_owo/discord/discord_1.de.jsp"; + "/lama" = "https://www.mathematik.tu-darmstadt.de/studium/studieninteressierte/entscheidungshilfen/lange_nacht_der_mathematik/index.de.jsp"; + "/angebote" = "https://www.mathematik.tu-darmstadt.de/fachschaft/fachschaftarbeit/angebote/index.de.jsp"; + "/elzm" = "https://moodle.tu-darmstadt.de/course/view.php?id=6427"; + "/feedback" = "https://surveys.mathebau.de/index.php/233595"; + "/frewe" = "https://events.mathebau.de/anmelden/cm0utz4vf3130arv92znr46sy"; + "/gewowe" = "https://www.mathematik.tu-darmstadt.de/fachschaft/orientierung/gewowe/index.de.jsp"; + # Wenn es jemand findet, darf er es löschen ;-) (found it, it's still here) + "/gonne" = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"; + "/heute" = "https://sharelatex.tu-darmstadt.de/6643831242fprjkkyqynkd"; + "/mentoring" = "https://www.mathematik.tu-darmstadt.de/fachschaft/orientierung/orientierungswoche/mentoringwahl/mentoringwahl.de.jsp"; + "/mumble" = "https://www.mathematik.tu-darmstadt.de/fachschaft/orientierung/it_tutorial/nach_der_owo/mumble/index.de.jsp"; + "/nebenfach" = "https://www.mathematik.tu-darmstadt.de/fachschaft/orientierung/orientierungswoche/nebenfachwahl/nebenfachwahl.de.jsp"; + "/owo" = "https://www.mathematik.tu-darmstadt.de/fachschaft/orientierung/orientierungswoche/index.de.jsp"; + "/owoinfo" = "https://www.mathematik.tu-darmstadt.de/fachschaft/orientierung/orientierungswoche/owo_info/index.de.jsp"; + "/owo-team" = "https://www.mathematik.tu-darmstadt.de/fachschaft/orientierung/orientierungswoche/owo_team/owo_team.de.jsp"; + "/it" = "https://www.mathematik.tu-darmstadt.de/fachschaft/orientierung/it_tutorial/it_tutorial_1/it_tutorial.de.jsp"; + "/tucan" = "https://www.mathematik.tu-darmstadt.de/fachschaft/orientierung/it_tutorial/waehrend_der_owo/tucan/tucan_1.de.jsp"; + "/mma" = "https://surveys.mathebau.de/index.php/286922"; + "/verein-flyer" = "https://www.mathematik.tu-darmstadt.de/media/fsmathe/verein/FS_Verein_Flyer.pdf"; + "/verein" = "https://www.mathematik.tu-darmstadt.de/fachschaft/fachschaftarbeit/foerderverein/index.de.jsp"; + "/" = "https://www.mathematik.tu-darmstadt.de/fachschaft/fachschaftarbeit/index.de.jsp"; + }; + "www.mathebau.de"."/" = { + target = "$scheme://mathebau.de$request_uri"; + code = "301"; + }; + "matheball.mathebau.de"."/" = { + target = "https://matheball.de$request_uri"; + code = "301"; + }; + "www.matheball.de"."/" = { + target = "https://matheball.de$request_uri"; + code = "301"; + }; + # these two were "$scheme://mathechor,de..." before + "intern.mathechor.de"."/" = "https://mathechor.de/intern$request_uri"; + "www.mathechor.de"."/" = "https://mathechor.de$request_uri"; + # there used to be stats.mathebau.de. while it seems to have something todo with + # prometheus, it also seems to be dead + "theaterskript.mathebau.de"."/" = "https://sharelatex.tu-darmstadt.de/2714416651sgmwmttcxjzn#76633a"; +} +# # Hosting +# download.mathebau.de Hosting +# theateraufnahmen.mathebau.de +# +# # Proxy passes +# +# lists.mathebau.de 192.168.0.22 (lobon?) +# matheball.de (192.168.0.21) +# mathechor.de (192.168.0.19:80) +# cloud.mathechor.de (192.168.0.20:80) +# gitea.mathebau.de (192.168.0.23:3000) +# codi.mathebau.de (192.168.0.18) +# fswiki.mathebau.de owowiki.mathebau.de surveys.mathebau.de (192.168.0.15) +# events.mathebau.de (192.168.0.13:8080) +# sprechstunden.mathebau.de (192.168.0.14:8080) +# +# challenges will be dns, so we don't need the last two from the proxies file +# (and the way we do it now, doesn't work with nix as acmebot needs to rewrite nginx +# config that is read only in nix) +# +# + diff --git a/nixos/modules/reverseProxy.nix b/nixos/modules/reverseProxy.nix new file mode 100644 index 0000000..88ff91d --- /dev/null +++ b/nixos/modules/reverseProxy.nix @@ -0,0 +1,202 @@ +# All our domains fall in one or more of three categories +# proxyPass, basically handle the tls and pass the http traffick on +# redirect, redirect to another uri +# webHost, host the content of some directory under this domain +# We will use this by defining helper functions that generates config +# for those categories and then apply them to the required parameters. +{ + lib, + config, + ... +}: let + cfg = config.services.reverseProxy; + # the equivalent of library imports in other languages + inherit (lib) isString mkEnableOption mkOption; + inherit (lib.attrsets) concatMapAttrs; + inherit (lib.lists) foldr; + withGeneralHostConfig = virtualHostConfig: + { + forceSSL = true; + useACMEHost = "mathebau.de"; + locations = + { + # notice that nix will also parse this string and we need to escape \ + # so after nix processing "~ /\.git/" will end up in the nginx config, + # with the proper regex escape. + # I find this behaiviour unexpected and a bit weird, but it catches some footguns + # Note that this also applies to redirects. + "~ /\\.git/" = { + extraConfig = "deny all;"; + }; + "~ /\\.ht" = { + extraConfig = "deny all;"; + }; + "/.well-known/security.txt" = { + return = '' + 200 "Contact: mailto:root@mathebau.de + Expires: 2029-12-31T23:00:00.000Z + Preferred-Languages: de,en" + ''; + }; + } + // virtualHostConfig.locations; + } + // virtualHostConfig; + # The function to generate proxyPass directives + # We always proxy pass "/", but in theory this can also work for other paths + proxyWithCache = let + proxyHelper = { + targetMachine, + targetPort, + websockets, + }: { + proxyPass = "http://${targetMachine}:${toString targetPort}"; + proxyWebsockets = websockets; + recommendedProxySettings = true; + }; + cacheHelper = targetData: + proxyHelper targetData + // { + extraConfig = '' + gzip on; + gzip_proxied any; + gzip_types *; + expires max; + ''; + }; + in + concatMapAttrs (domain: domainData: { + ${domain}.locations = + concatMapAttrs (path: targetData: { + ${path} = proxyHelper targetData; + # this instruncts clients to cache files with this list of extension for 24h. + # This might break things. For example if a machine tries to generate these + # things dynamically, but might be load reducing, i don't get why atom, rss, xml and txt are in this list + "~* ^${path}.+\\.(atom|bmp|bz2|doc|docx|eot|exe|gif|gz|ico|jpeg|jpg|mid|midi|mp4|ogg|ogv|otf|pdf|png|ppt|pptx|rar|rss|rtf|svg|svgz|swf|tar|tgz|ttf|txt|wav|webp|woff|woff2|xls|zip|css|js|xml)$" = cacheHelper targetData; + }) + domainData; + }); + proxyPasses = proxyWithCache cfg.proxies; + redirect = let + redirectHelper = { + target, + code ? "303", + }: {return = code + " " + target;}; + in + concatMapAttrs ( + domain: redirectData: { + ${domain}.locations = + concatMapAttrs ( + path: targetData: + if isString targetData + then { + ${path} = redirectHelper { + target = targetData; + }; + } + else {${path} = redirectHelper targetData;} + ) + redirectData; + } + ); + # TODO import these from a file + redirects = redirect cfg.redirects; + # Helper function to merge all the different configurations together. + # Later configurations take precedence. +in { + imports = []; + + options.services.reverseProxy = { + enable = mkEnableOption "mathebau reverse Proxy"; + redirects = mkOption { + type = lib.types.attrsOf (lib.types.attrsOf (lib.types.either lib.types.str (lib.types.submodule { + options = { + code = mkOption { + type = lib.types.str; # todo make matching regex + description = "the http status code of the redirect"; + default = "303"; + }; + target = mkOption { + type = lib.types.str; + description = "the target url of the redirect, for more details on format see the nginx docu"; + }; + }; + }))); + description = "the redirects that this reverse proxy should serve"; + default = {}; + }; + proxies = mkOption { + type = lib.types.attrsOf (lib.types.attrsOf (lib.types.submodule { + options = { + targetMachine = mkOption { + type = lib.types.str; + description = "the machine to proxy to"; + }; + targetPort = mkOption { + type = lib.types.port; + description = "the port the target machine listens on"; + default = 80; + }; + websockets = mkOption { + type = lib.types.bool; + description = "wether or not websocket support should be enabled"; + default = false; + }; + }; + })); + description = "the proxy pass directive this reverse proxy should serve"; + default = {}; + }; + }; + + config = lib.mkIf cfg.enable { + services.nginx = { + enable = true; + virtualHosts = concatMapAttrs (name: config: {${name} = withGeneralHostConfig config;}) (foldr lib.attrsets.recursiveUpdate {} [redirects proxyPasses]); + # We should already set this by default in roles/vm.nix for all vm that run nginx, but to be sure... + recommendedTlsSettings = lib.mkForce true; + # We set form-action to self so in general webforms are allowed. On a finer bases + # we could set this to none for some hosts (or even locations) but this would clutter + # the config. Same is true for base-uri + appendHttpConfig = '' + add_header Content-Security-Policy "base-uri 'self'; default-src 'self'; frame-ancestor 'none'; form-action 'self';" always; + add_header X-Content-Type-Options nosniff; + ''; + }; + # TODO: we need to rebuild this for dns challenges, + # this does not work with our proxy pass challenge hand through things. + security.acme = { + acceptTerms = true; + certs."mathebau.de" = { + webroot = "/var/lib/acme/acme-challenge"; + email = "root@mathebau.de"; + extraDomainNames = [ + "cloud.mathechor.de" + "download.mathebau.de" + "events.mathebau.de" + "fb04184.mathematik.tu-darmstadt.de" + "fswiki.mathebau.de" + "gitea.mathebau.de" + "imap.mathebau.de" + "intern.mathebau.de" + "lists.mathebau.de" + "matheball.de" + "matheball.mathebau.de" + "mathechor.de" + "meet.mathebau.de" + "owowiki.mathebau.de" + "smtp.mathebau.de" + "sprechstunden.mathebau.de" + "surveys.mathebau.de" + "theateraufnahmen.mathebau.de" + "theaterskript.mathebau.de" + "www.koma89.tu-darmstadt.de" + "www.matheball.de" + "www.mathebau.de" + "www.mathechor.de" + ]; + }; + }; + users.users.nginx.extraGroups = ["acme"]; + }; +}