diff --git a/custom-utils/default.nix b/custom-utils/default.nix new file mode 100644 index 0000000..66a9a15 --- /dev/null +++ b/custom-utils/default.nix @@ -0,0 +1,33 @@ +{ lib, ... }: + +let + filterPort = pm: port: ( + lib.attrsets.catAttrs port ( + lib.attrsets.attrValues ( + lib.attrsets.filterAttrs (n: v: v ? "${port}") pm + ) + ) + ); + onlyUniqueItemsInList = (x: lib.lists.length x == lib.lists.length (lib.lists.unique x)); + protocols = (x: lib.lists.unique (lib.flatten (map builtins.attrNames (lib.attrValues x)))); + mkRange = (x: lib.lists.range (builtins.elemAt x 0) (builtins.elemAt x 1)); + validateList = allowed: builtins.all (x: builtins.elem x allowed); +in +{ + validatePortAttrset = portmap: + if ! onlyUniqueItemsInList (lib.flatten (map + (x: + if lib.isInt x then x + else if lib.isList x then x + else if lib.isAttrs x then + ( + if ! validateList [ "range" ] (builtins.attrNames x) then builtins.abort "found invalid attribute name" + else if x ? "range" then if lib.lists.length x.range == 2 then mkRange x.range else builtins.abort "range needs a list with exactly two items" + else builtins.abort "found invalid attrset" + ) + else builtins.abort "found invalid entry in portmap" + ) + (filterPort portmap "udp"))) then builtins.abort "Found duplicate ports." + else if ! validateList [ "tcp" "udp" ] (protocols portmap) then builtins.abort "Found invalid protocol." + else portmap; +} diff --git a/custom-utils/ports.nix b/custom-utils/ports.nix new file mode 100644 index 0000000..66a9a15 --- /dev/null +++ b/custom-utils/ports.nix @@ -0,0 +1,33 @@ +{ lib, ... }: + +let + filterPort = pm: port: ( + lib.attrsets.catAttrs port ( + lib.attrsets.attrValues ( + lib.attrsets.filterAttrs (n: v: v ? "${port}") pm + ) + ) + ); + onlyUniqueItemsInList = (x: lib.lists.length x == lib.lists.length (lib.lists.unique x)); + protocols = (x: lib.lists.unique (lib.flatten (map builtins.attrNames (lib.attrValues x)))); + mkRange = (x: lib.lists.range (builtins.elemAt x 0) (builtins.elemAt x 1)); + validateList = allowed: builtins.all (x: builtins.elem x allowed); +in +{ + validatePortAttrset = portmap: + if ! onlyUniqueItemsInList (lib.flatten (map + (x: + if lib.isInt x then x + else if lib.isList x then x + else if lib.isAttrs x then + ( + if ! validateList [ "range" ] (builtins.attrNames x) then builtins.abort "found invalid attribute name" + else if x ? "range" then if lib.lists.length x.range == 2 then mkRange x.range else builtins.abort "range needs a list with exactly two items" + else builtins.abort "found invalid attrset" + ) + else builtins.abort "found invalid entry in portmap" + ) + (filterPort portmap "udp"))) then builtins.abort "Found duplicate ports." + else if ! validateList [ "tcp" "udp" ] (protocols portmap) then builtins.abort "Found invalid protocol." + else portmap; +} diff --git a/flake.nix b/flake.nix index 2d5ee3c..b693c5f 100644 --- a/flake.nix +++ b/flake.nix @@ -151,7 +151,10 @@ ./modules { - _module.args.inputs = inputs; + _module.args = { + inputs = inputs; + custom-utils = import ./custom-utils { lib = nixpkgs.lib; }; + }; } # deployment settings diff --git a/hosts/aluminium/ports.nix b/hosts/aluminium/ports.nix new file mode 100644 index 0000000..8dfe08a --- /dev/null +++ b/hosts/aluminium/ports.nix @@ -0,0 +1,7 @@ +{ lib, custom-utils, ... }: + +custom-utils.validatePortAttrset { + asterisk-rtp = { udp.range = [ 10000 10200 ]; }; + unifi.tcp = 8443; + doorbell-audiosocket.tcp = 9092; +} diff --git a/hosts/aluminium/services/asterisk/default.nix b/hosts/aluminium/services/asterisk/default.nix index 820689c..7080707 100644 --- a/hosts/aluminium/services/asterisk/default.nix +++ b/hosts/aluminium/services/asterisk/default.nix @@ -1,13 +1,15 @@ -{ config, lib, pkgs, ... }: +args@{ config, lib, pkgs, custom-utils, ... }: + let + ports = import ../../ports.nix args; secretConfigFiles = [ "ari" "pjsip" "voicemail" ]; rtp = { - start = 10000; - end = 10200; + start = builtins.elemAt ports.asterisk-rtp.udp.range 0; + end = builtins.elemAt ports.asterisk-rtp.udp.range 1; }; in { diff --git a/hosts/aluminium/services/doorbell.nix b/hosts/aluminium/services/doorbell.nix index 8136132..33a3bb3 100644 --- a/hosts/aluminium/services/doorbell.nix +++ b/hosts/aluminium/services/doorbell.nix @@ -1,4 +1,8 @@ -{ config, lib, pkgs, ... }: +args@{ config, lib, pkgs, custom-utils, ... }: + +let + ports = import ../ports.nix args; +in { sops.secrets.myintercom-doorbell-password = { sopsFile = ../secrets.yaml; @@ -11,7 +15,7 @@ passwordFile = config.sops.secrets.myintercom-doorbell-password.path; audiosocket = { address = "127.0.0.1"; - port = 9092; + port = ports.doorbell-audiosocket.tcp; uuid = "4960ab41-dbef-4773-a25e-90536d97345e"; }; callerId = "Sprechanlage"; diff --git a/hosts/aluminium/services/unifi-controller.nix b/hosts/aluminium/services/unifi-controller.nix index f34d12d..8004941 100644 --- a/hosts/aluminium/services/unifi-controller.nix +++ b/hosts/aluminium/services/unifi-controller.nix @@ -1,9 +1,13 @@ -{ pkgs, ... }: +args@{ pkgs, custom-utils, ... }: + +let + ports = import ../ports.nix args; +in { services.unifi = { enable = true; openFirewall = true; unifiPackage = pkgs.unifi; }; - networking.firewall.allowedTCPPorts = [ 8443 ]; + networking.firewall.allowedTCPPorts = [ ports.unifi.tcp ]; } diff --git a/hosts/iron/ports.nix b/hosts/iron/ports.nix new file mode 100644 index 0000000..64a0a26 --- /dev/null +++ b/hosts/iron/ports.nix @@ -0,0 +1,16 @@ +{ lib, custom-utils, ... }: + +custom-utils.validatePortAttrset { + jellyfin.tcp = 8096; + matrix-synapse.tcp = 8008; + navidrome.tcp = 4533; + nginx-http.tcp = 80; + nginx-https.tcp = 443; + postfix-relay.tcp = 25; + postfix-submission.tcp = [ 465 587 ]; + qbittorrent-torrent.tcp = 59832; + qbittorrent-webui.tcp = 8099; + radicale.tcp = 5232; + unifi.tcp = 8443; + wireguard-public-ip-tunnel.udp = 51000; +} diff --git a/hosts/iron/services/jellyfin.nix b/hosts/iron/services/jellyfin.nix index c758f74..01aff39 100644 --- a/hosts/iron/services/jellyfin.nix +++ b/hosts/iron/services/jellyfin.nix @@ -1,4 +1,7 @@ -{ lib, pkgs, ... }: +args@{ lib, pkgs, custom-utils, ... }: +let + ports = import ../ports.nix args; +in { services.jellyfin = { enable = true; @@ -57,7 +60,7 @@ # add_header X-XSS-Protection "1; mode=block"; # add_header X-Content-Type-Options "nosniff"; location / { - proxy_pass http://127.0.0.1:8096; + proxy_pass http://127.0.0.1:${toString ports.jellyfin.tcp}; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -67,7 +70,7 @@ proxy_buffering off; } location = /web/ { - proxy_pass http://127.0.0.1:8096/web/index.html; + proxy_pass http://127.0.0.1:${toString ports.jellyfin.tcp}/web/index.html; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -76,7 +79,7 @@ proxy_set_header X-Forwarded-Host $http_host; } location /socket { - proxy_pass http://127.0.0.1:8096; + proxy_pass http://127.0.0.1:${toString ports.jellyfin.tcp}; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; diff --git a/hosts/iron/services/mail.nix b/hosts/iron/services/mail.nix index 315f46a..4ed06b9 100644 --- a/hosts/iron/services/mail.nix +++ b/hosts/iron/services/mail.nix @@ -1,4 +1,8 @@ -{ config, pkgs, ... }: +args@{ config, pkgs, custom-utils, ... }: + +let + ports = import ../ports.nix args; +in { sops.secrets.hetzner-api-key = { sopsFile = ../secrets.yaml; @@ -12,6 +16,7 @@ mailserver = { enable = true; fqdn = "hha.jalr.de"; + relayPort = ports.postfix-relay.tcp; domains = [ { domain = "jalr.de"; diff --git a/hosts/iron/services/matrix/synapse.nix b/hosts/iron/services/matrix/synapse.nix index 92357c9..18d5eaf 100644 --- a/hosts/iron/services/matrix/synapse.nix +++ b/hosts/iron/services/matrix/synapse.nix @@ -1,9 +1,11 @@ -{ config, lib, pkgs, ... }: +args@{ config, lib, pkgs, custom-utils, ... }: + let cfg = config.services.matrix-synapse.settings; fqdn = "matrix.jalr.de"; domain = "jalr.de"; turnHost = "turn.jalr.de"; + ports = import ../../ports.nix args; in { sops.secrets = { @@ -23,7 +25,7 @@ in database.name = "sqlite3"; listeners = lib.singleton { - port = 8008; + port = ports.matrix-synapse.tcp; bind_addresses = [ "127.0.0.1" "::1" ]; type = "http"; tls = false; diff --git a/hosts/iron/services/navidrome.nix b/hosts/iron/services/navidrome.nix index 4341818..4f1044a 100644 --- a/hosts/iron/services/navidrome.nix +++ b/hosts/iron/services/navidrome.nix @@ -1,10 +1,11 @@ -{ config, lib, pkgs, utils, ... }: +args@{ config, lib, pkgs, utils, custom-utils, ... }: + let - port = 4533; + ports = import ../ports.nix args; settings = { # https://www.navidrome.org/docs/usage/configuration-options/#available-options Address = "127.0.0.1"; - Port = port; + Port = ports.navidrome.tcp; DevActivityPanel = false; }; passwordEncryptionKeyFile = config.sops.secrets.navidrome-password-encryption-key.path; @@ -38,7 +39,7 @@ in extraConfig = '' add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; location / { - proxy_pass http://127.0.0.1:${toString port}; + proxy_pass http://127.0.0.1:${toString ports.navidrome.tcp}; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/hosts/iron/services/nginx.nix b/hosts/iron/services/nginx.nix index dd381d3..71bdff2 100644 --- a/hosts/iron/services/nginx.nix +++ b/hosts/iron/services/nginx.nix @@ -1,7 +1,13 @@ -{ pkgs, ... }: +args@{ pkgs, custom-utils, ... }: + +let + ports = import ../ports.nix args; +in { services.nginx = { enable = true; + defaultHTTPListenPort = ports.nginx-http.tcp; + defaultSSLListenPort = ports.nginx-https.tcp; recommendedGzipSettings = true; recommendedOptimisation = true; recommendedProxySettings = true; diff --git a/hosts/iron/services/public-ip-tunnel.nix b/hosts/iron/services/public-ip-tunnel.nix index 5a0cce7..2e1dca8 100644 --- a/hosts/iron/services/public-ip-tunnel.nix +++ b/hosts/iron/services/public-ip-tunnel.nix @@ -1,7 +1,8 @@ -{ config, lib, pkgs, ... }: +args@{ config, lib, pkgs, custom-utils, ... }: let - listenPort = 51000; + ports = import ../ports.nix args; + listenPort = ports.wireguard-public-ip-tunnel.udp; remoteHost = "magnesium.jalr.de"; remotePort = 51000; publicKey = "ABZCQfzlHJ1/iNbWFf6jVvdqSmqjxm3w5bpa0SYclBU="; diff --git a/hosts/iron/services/radicale.nix b/hosts/iron/services/radicale.nix index a4c68e3..bf5196d 100644 --- a/hosts/iron/services/radicale.nix +++ b/hosts/iron/services/radicale.nix @@ -1,4 +1,8 @@ -{ config, ... }: +args@{ config, lib, custom-utils, ... }: + +let + ports = import ../ports.nix args; +in { sops.secrets.radicale-htpasswd = { owner = "nginx"; @@ -11,7 +15,7 @@ forceSSL = true; basicAuthFile = config.sops.secrets.radicale-htpasswd.path; locations."/radicale/" = { - proxyPass = "http://localhost:5232/"; + proxyPass = "http://127.0.0.1:${toString ports.radicale.tcp}/"; recommendedProxySettings = true; #basicAuthFile = ""; extraConfig = '' @@ -28,7 +32,7 @@ enable = true; settings = { server = { - hosts = "127.0.0.1:5232,[::1]:5232"; + hosts = "127.0.0.1:${toString ports.radicale.tcp},[::1]:${toString ports.radicale.tcp}"; ssl = false; }; encoding = { diff --git a/hosts/iron/services/sturzbach.nix b/hosts/iron/services/sturzbach.nix index 8b07325..ea0430a 100644 --- a/hosts/iron/services/sturzbach.nix +++ b/hosts/iron/services/sturzbach.nix @@ -1,10 +1,16 @@ +args@{ config, lib, custom-utils, ... }: + +let + ports = import ../ports.nix args; +in { jalr.qbittorrent = { enable = true; downloadDir = "/sturzbach"; fqdn = "sturzbach.jalr.de"; + webuiPort = ports.qbittorrent-webui.tcp; }; networking.firewall = { - allowedTCPPorts = [ 59832 ]; + allowedTCPPorts = [ ports.qbittorrent-torrent.tcp ]; }; } diff --git a/hosts/iron/services/unifi-controller.nix b/hosts/iron/services/unifi-controller.nix index f34d12d..8004941 100644 --- a/hosts/iron/services/unifi-controller.nix +++ b/hosts/iron/services/unifi-controller.nix @@ -1,9 +1,13 @@ -{ pkgs, ... }: +args@{ pkgs, custom-utils, ... }: + +let + ports = import ../ports.nix args; +in { services.unifi = { enable = true; openFirewall = true; unifiPackage = pkgs.unifi; }; - networking.firewall.allowedTCPPorts = [ 8443 ]; + networking.firewall.allowedTCPPorts = [ ports.unifi.tcp ]; } diff --git a/hosts/magnesium/ports.nix b/hosts/magnesium/ports.nix new file mode 100644 index 0000000..b35165c --- /dev/null +++ b/hosts/magnesium/ports.nix @@ -0,0 +1,12 @@ +{ lib, custom-utils, ... }: + +custom-utils.validatePortAttrset { + coturn-cli.tcp = 5766; + coturn-plain = { tcp = [ 3478 3479 ]; udp = [ 3478 3479 ]; }; + coturn-relay.udp.range = [ 49160 49200 ]; + coturn-tls = { tcp = [ 5349 5350 ]; udp = [ 5349 5350 ]; }; + mosquitto.tcp = 1883; + nginx-http.tcp = 80; + nginx-https.tcp = 443; + wireguard-public-ip-tunnel.udp = 51000; +} diff --git a/hosts/magnesium/services/coturn.nix b/hosts/magnesium/services/coturn.nix index 7a12474..93479ad 100644 --- a/hosts/magnesium/services/coturn.nix +++ b/hosts/magnesium/services/coturn.nix @@ -1,7 +1,8 @@ -{ config, lib, pkgs, ... }: -let - cfg = config.services.coturn; +args@{ config, lib, pkgs, custom-utils, ... }: +let + ports = import ../ports.nix args; + cfg = config.services.coturn; fqdn = "turn.jalr.de"; in { @@ -10,7 +11,21 @@ in sopsFile = ../secrets.yaml; }; - services.coturn = { + services.coturn = ( + if ports.coturn-plain.tcp != ports.coturn-plain.udp then builtins.abort "coturn: plain TCP and UDP ports must match." + else if ports.coturn-tls.tcp != ports.coturn-tls.udp then builtins.abort "coturn: TLS TCP and UDP ports must match." + else if lib.lists.length ports.coturn-plain.tcp != 2 then builtins.abort "coturn: exactly two plain ports must be given." + else if lib.lists.length ports.coturn-tls.tcp != 2 then builtins.abort "coturn: exactly two TLS ports must be given." + else { + listening-port = builtins.elemAt ports.coturn-plain.tcp 0; + alt-listening-port = builtins.elemAt ports.coturn-plain.tcp 1; + tls-listening-port = builtins.elemAt ports.coturn-tls.tcp 0; + alt-tls-listening-port = builtins.elemAt ports.coturn-tls.tcp 1; + cli-port = ports.coturn-cli.tcp; + min-port = builtins.elemAt ports.coturn-relay.udp.range 0; + max-port = builtins.elemAt ports.coturn-relay.udp.range 1; + } + ) // { enable = true; # config adapted from synapse’s turn howto: @@ -25,9 +40,6 @@ in cert = "/run/turnserver/fullchain.pem"; pkey = "/run/turnserver/key.pem"; - min-port = 49160; - max-port = 49200; - no-cli = true; extraConfig = '' @@ -87,12 +99,12 @@ in }; networking.firewall = { - allowedTCPPorts = with cfg; [ listening-port alt-listening-port tls-listening-port ]; - allowedUDPPorts = with cfg; [ listening-port alt-listening-port tls-listening-port ]; + allowedTCPPorts = with cfg; [ listening-port alt-listening-port tls-listening-port alt-tls-listening-port ]; + allowedUDPPorts = with cfg; [ listening-port alt-listening-port tls-listening-port alt-tls-listening-port ]; allowedUDPPortRanges = lib.singleton { - from = cfg.min-port; - to = cfg.max-port; + from = builtins.elemAt ports.coturn-relay.udp.range 0; + to = builtins.elemAt ports.coturn-relay.udp.range 1; }; }; } diff --git a/hosts/magnesium/services/mosquitto.nix b/hosts/magnesium/services/mosquitto.nix index 6cb7c38..e81027e 100644 --- a/hosts/magnesium/services/mosquitto.nix +++ b/hosts/magnesium/services/mosquitto.nix @@ -1,5 +1,7 @@ -{ config, lib, pkgs, ... }: -let port = 1883; +args@{ config, lib, pkgs, custom-utils, ... }: + +let + ports = import ../ports.nix args; in { services.mosquitto = { @@ -7,12 +9,12 @@ in persistence = true; listeners = [ { - port = port; + port = ports.mosquitto.tcp; settings = { allow_anonymous = true; }; } ]; }; - networking.firewall.allowedTCPPorts = [ port ]; + networking.firewall.allowedTCPPorts = [ ports.mosquitto.tcp ]; } diff --git a/hosts/magnesium/services/public-ip-tunnel.nix b/hosts/magnesium/services/public-ip-tunnel.nix index 0cd32b4..510fff8 100644 --- a/hosts/magnesium/services/public-ip-tunnel.nix +++ b/hosts/magnesium/services/public-ip-tunnel.nix @@ -1,7 +1,8 @@ -{ config, lib, pkgs, ... }: +args@{ config, lib, pkgs, custom-utils, ... }: let - listenPort = 51000; + ports = import ../ports.nix args; + listenPort = ports.wireguard-public-ip-tunnel.udp; publicKey = "GCmQs7upvDYFueEfqD2yJkkOZg3K7YaGluWWzdjsyTo="; in { diff --git a/hosts/magnesium/services/webserver.nix b/hosts/magnesium/services/webserver.nix index a37826f..9c55514 100644 --- a/hosts/magnesium/services/webserver.nix +++ b/hosts/magnesium/services/webserver.nix @@ -1,12 +1,16 @@ -{ config, lib, pkgs, ... }: +args@{ config, lib, pkgs, custom-utils, ... }: + let + ports = import ../ports.nix args; domain = "jalr.de"; matrixDomain = "matrix.jalr.de"; in { - networking.firewall.allowedTCPPorts = [ 80 443 ]; + networking.firewall.allowedTCPPorts = [ ports.nginx-http.tcp ports.nginx-https.tcp ]; services.nginx = { enable = true; + defaultHTTPListenPort = ports.nginx-http.tcp; + defaultSSLListenPort = ports.nginx-https.tcp; recommendedGzipSettings = true; recommendedOptimisation = true; recommendedProxySettings = true; diff --git a/hosts/weinturm-pretix-prod/ports.nix b/hosts/weinturm-pretix-prod/ports.nix new file mode 100644 index 0000000..9c3e0e7 --- /dev/null +++ b/hosts/weinturm-pretix-prod/ports.nix @@ -0,0 +1,8 @@ +{ lib, custom-utils, ... }: + +custom-utils.validatePortAttrset { + nginx-http.tcp = 80; + nginx-https.tcp = 443; + ports.postfix-relay.tcp = 25; + ports.postfix-submission.tcp = [ 465 587 ]; +} diff --git a/hosts/weinturm-pretix-prod/services/pretix.nix b/hosts/weinturm-pretix-prod/services/pretix.nix index 35bb209..dc506f4 100644 --- a/hosts/weinturm-pretix-prod/services/pretix.nix +++ b/hosts/weinturm-pretix-prod/services/pretix.nix @@ -1,4 +1,8 @@ -{ config, lib, pkgs, ... }: +args@{ config, lib, pkgs, custom-utils, ... }: + +let + ports = import ../ports.nix args; +in { nixpkgs.config.permittedInsecurePackages = [ "python3.10-requests-2.28.2" @@ -36,6 +40,7 @@ jalr.mailserver = { enable = true; fqdn = "tickets.weinturm.jalr.de"; + relayPort = ports.postfix-relay.tcp; domains = [ { domain = "tickets.weinturm-open-air.de"; diff --git a/modules/mailserver/default.nix b/modules/mailserver/default.nix index 353d14a..93abbbf 100644 --- a/modules/mailserver/default.nix +++ b/modules/mailserver/default.nix @@ -5,6 +5,11 @@ in { options.jalr.mailserver = with lib; with lib.types; { enable = mkEnableOption "simple mail server"; + relayPort = mkOption { + description = "SMTP port for relay mail relay."; + type = port; + default = 25; + }; fqdn = mkOption { type = str; description = '' diff --git a/modules/mailserver/postfix.nix b/modules/mailserver/postfix.nix index e5a8831..e09a48a 100644 --- a/modules/mailserver/postfix.nix +++ b/modules/mailserver/postfix.nix @@ -39,6 +39,8 @@ lib.mkIf cfg.enable { services.postfix = { enable = true; + relayPort = cfg.relayPort; + enableSubmission = true; # plain/STARTTLS (latter is forced in submissionOptions) enableSubmissions = true; # submission with implicit TLS (TCP/465)