From a283b4bf9a1d2996dc3e0b8e489a8a034fcaf9c7 Mon Sep 17 00:00:00 2001 From: arcnmx Date: Wed, 31 Jan 2024 13:28:21 -0800 Subject: [PATCH] fix(nftables): local firewall --- modules/nixos/access.nix | 143 +++++++++++++++++++++++++++---- modules/nixos/home-assistant.nix | 17 ++-- modules/nixos/nftables.nix | 18 ++-- modules/nixos/nginx-local.nix | 8 +- nixos/access/ldap.nix | 6 +- nixos/access/proxmox.nix | 2 +- nixos/samba.nix | 2 - packages/default.nix | 10 +-- systems/tei/nixos.nix | 2 - 9 files changed, 167 insertions(+), 41 deletions(-) diff --git a/modules/nixos/access.nix b/modules/nixos/access.nix index 429ea8be..c59a0be8 100644 --- a/modules/nixos/access.nix +++ b/modules/nixos/access.nix @@ -1,16 +1,18 @@ { + pkgs, inputs, config, lib, ... }: let - inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault; - inherit (lib.options) mkOption; + inherit (lib.modules) mkIf mkMerge mkBefore mkAfter mkDefault mkOptionDefault; + inherit (lib.options) mkOption mkEnableOption; inherit (lib.lists) optionals; - inherit (lib.strings) concatStringsSep; + inherit (lib.strings) concatStringsSep optionalString; inherit (config.services) tailscale avahi; inherit (config) networking; inherit (networking) hostName; + cfg = config.networking.access; cidrModule = { config, ... }: { options = with lib.types; { all = mkOption { @@ -41,6 +43,15 @@ in { type = attrsOf (submodule cidrModule); default = { }; }; + localaddrs = { + enable = mkEnableOption "localaddrs" // { + default = networking.firewall.interfaces.local.nftables.enable; + }; + stateDir = mkOption { + type = path; + default = "/var/lib/localaddrs"; + }; + }; }; config.networking.access = { @@ -50,11 +61,11 @@ in { hasStaticAddress = eth0.address or [ ] != [ ] || eth0.addresses or [ ] != [ ]; hasSLAAC = eth0.slaac.enable or false; in mkMerge [ - (mkIf (hasStaticAddress || hasSLAAC) (mkDefault "${hostName}.local.${config.networking.domain}")) + (mkIf (hasStaticAddress || hasSLAAC) (mkDefault "${hostName}.local.${networking.domain}")) (mkIf (avahi.enable && avahi.publish.enable) (mkOptionDefault "${hostName}.local")) ]; - tail = mkIf tailscale.enable "${hostName}.tail.${config.networking.domain}"; - global = mkIf (networking.enableIPv6 && networking.tempAddresses == "disabled") "${hostName}.${config.networking.domain}"; + tail = mkIf tailscale.enable "${hostName}.tail.${networking.domain}"; + global = mkIf (networking.enableIPv6 && networking.tempAddresses == "disabled") "${hostName}.${networking.domain}"; }; cidrForNetwork = { loopback = { @@ -86,14 +97,114 @@ in { }; }; - config.networking.firewall = { - interfaces.local = { - nftables.conditions = [ - "ip saddr { ${concatStringsSep ", " networking.access.cidrForNetwork.local.v4} }" - (mkIf networking.enableIPv6 - "ip6 saddr { ${concatStringsSep ", " networking.access.cidrForNetwork.local.v6} }" - ) - ]; + config.networking = { + nftables.ruleset = mkBefore ('' + define localrange6 = 2001:568::/29 + '' + optionalString cfg.localaddrs.enable '' + include "${cfg.localaddrs.stateDir}/*.nft" + ''); + firewall = { + interfaces.local = { + nftables.conditions = [ + "ip saddr { ${concatStringsSep ", " networking.access.cidrForNetwork.local.v4} }" + (mkIf networking.enableIPv6 + "ip6 saddr { $localrange6, ${concatStringsSep ", " networking.access.cidrForNetwork.local.v6} }" + ) + ]; + }; + }; + }; + config.systemd.services = let + localaddrs = pkgs.writeShellScript "localaddrs" '' + set -eu + getaddrs() { + local PREFIX=$1 PATTERN=$2 IPADDRS + IPADDRS=$(${pkgs.iproute2}/bin/ip -o addr show to "$PREFIX") || return $? + IPADDRS=$(printf '%s\n' "$IPADDRS" | ${pkgs.gnugrep}/bin/grep -o "$PATTERN") || return $? + if [[ -z $IPADDRS ]]; then + return 1 + fi + printf '%s\n' "$IPADDRS" + } + getaddrs4() { + getaddrs 10.1.1.0/24 '[0-9]*\.[0-9.]*/[0-9]*' + } + getaddrs6() { + getaddrs 2001:568::/29 '[0-9a-f:]*:[0-9a-f:]*/[0-9]*' + } + mkdir -p $STATE_DIRECTORY + if LOCALADDRS4=$(getaddrs4); then + printf '%s\n' "$LOCALADDRS4" > $STATE_DIRECTORY/localaddrs4 + else + echo WARNING: localaddr4 not found >&2 + fi + if LOCALADDRS6=$(getaddrs6); then + echo "$LOCALADDRS6" > $STATE_DIRECTORY/localaddrs6 + else + echo WARNING: localaddr6 not found >&2 + fi + ''; + localaddrs-nftables = pkgs.writeShellScript "localaddrs-nftables" '' + set -eu + LOCALADDR6=$(head -n1 "${cfg.localaddrs.stateDir}/localaddrs6" || true) + if [[ -n $LOCALADDR6 ]]; then + printf 'redefine localrange6 = %s\n' "$LOCALADDR6" > ${cfg.localaddrs.stateDir}/ranges.nft + fi + ''; + localaddrs-nginx = pkgs.writeShellScript "localaddrs-nginx" '' + set -eu + LOCALADDR6=$(head -n1 "${cfg.localaddrs.stateDir}/localaddrs6" || true) + if [[ -n $LOCALADDR6 ]]; then + printf 'allow %s;\n' "$LOCALADDR6" > ${cfg.localaddrs.stateDir}/allow.nginx.conf + fi + LOCALADDR4=$(head -n1 "${cfg.localaddrs.stateDir}/localaddrs4" || true) + if [[ -n $LOCALADDR4 ]]; then + printf 'allow %s;\n' "$LOCALADDR4" >> ${cfg.localaddrs.stateDir}/allow.nginx.conf + fi + ''; + localaddrs-reload = pkgs.writeShellScript "localaddrs-reload" '' + ${config.systemd.package}/bin/systemctl reload localaddrs 2>/dev/null || + ${config.systemd.package}/bin/systemctl restart localaddrs || + true + ''; + in { + localaddrs = mkIf cfg.localaddrs.enable { + unitConfig = { + After = [ "network-online.target" ]; + }; + serviceConfig = rec { + StateDirectory = "localaddrs"; + ExecStart = mkMerge [ + [ "${localaddrs}" ] + (mkIf networking.nftables.enable (mkAfter [ + "${localaddrs-nftables}" + ])) + (mkIf config.services.nginx.enable (mkAfter [ + "${localaddrs-nginx}" + ])) + ]; + ExecReload = ExecStart; + Type = "oneshot"; + RemainAfterExit = true; + }; + }; + nftables = mkIf (networking.nftables.enable && cfg.localaddrs.enable) rec { + wants = [ "localaddrs.service" ]; + after = wants; + serviceConfig = { + ExecReload = mkBefore [ + "+${localaddrs-reload}" + ]; + }; + }; + nginx = mkIf (config.services.nginx.enable && cfg.localaddrs.enable) rec { + wants = [ "localaddrs.service" ]; + after = wants; + serviceConfig = { + ExecReload = mkBefore [ + "+${localaddrs-reload}" + ]; + }; }; }; @@ -101,10 +212,10 @@ in { systemFor = hostName: inputs.self.nixosConfigurations.${hostName}.config; systemForOrNull = hostName: inputs.self.nixosConfigurations.${hostName}.config or null; in { - systemFor = hostName: if hostName == config.networking.hostName + systemFor = hostName: if hostName == networking.hostName then config else systemFor hostName; - systemForOrNull = hostName: if hostName == config.networking.hostName + systemForOrNull = hostName: if hostName == networking.hostName then config else systemForOrNull hostName; }; diff --git a/modules/nixos/home-assistant.nix b/modules/nixos/home-assistant.nix index 877f843d..7c63b82f 100644 --- a/modules/nixos/home-assistant.nix +++ b/modules/nixos/home-assistant.nix @@ -44,17 +44,24 @@ in { }; config = { - networking.firewall = mkIf cfg.enable { - allowedTCPPorts = mkIf (cfg.homekit.enable && cfg.homekit.openFirewall) ( + networking.firewall = let + homekitTcp = mkIf cfg.homekit.enable ( map ({ port, ... }: port) cfg.config.homekit or [ ] ); - allowedUDPPortRanges = [ - (mkIf (cfg.cast.enable && cfg.cast.openFirewall) { + castUdpRanges = mkIf cfg.cast.enable [ + { from = 32768; to = 60999; - }) + } ]; + in mkIf cfg.enable { + interfaces.local = { + allowedTCPPorts = mkIf (!cfg.homekit.openFirewall) homekitTcp; + allowedUDPPortRanges = mkIf (!cfg.cast.openFirewall) castUdpRanges; + }; + allowedTCPPorts = mkIf cfg.homekit.openFirewall homekitTcp; + allowedUDPPortRanges = mkIf cfg.cast.openFirewall castUdpRanges; }; # MDNS diff --git a/modules/nixos/nftables.nix b/modules/nixos/nftables.nix index 0c9a6a4d..4e652681 100644 --- a/modules/nixos/nftables.nix +++ b/modules/nixos/nftables.nix @@ -2,10 +2,11 @@ let inherit (lib) types; - inherit (lib.options) mkOption; + inherit (lib.options) mkOption mkEnableOption; inherit (lib.modules) mkIf; inherit (lib.attrsets) mapAttrsToList; inherit (lib.strings) optionalString concatStringsSep concatMapStringsSep; + inherit (lib.lists) optionals; fwcfg = config.networking.firewall; cfg = config.networking.nftables; @@ -41,7 +42,7 @@ let concatStringsSep "\n" (mapAttrsToList (name: ifcfg: concatMapStringsSep "\n" (cond: mkPorts "${cond} tcp" ifcfg.allowedTCPPorts ifcfg.allowedTCPPortRanges "accept" + mkPorts "${cond} udp" ifcfg.allowedUDPPorts ifcfg.allowedUDPPortRanges "accept" - ) ifcfg.nftables.conditions) fwcfg.interfaces) + ) (optionals ifcfg.nftables.enable ifcfg.nftables.conditions)) fwcfg.interfaces) } # DHCPv6 @@ -86,9 +87,16 @@ let ''; interfaceModule = { config, name, ... }: { options = { - nftables.conditions = mkOption { - type = types.listOf types.str; - default = "iifname ${name}"; + nftables = { + enable = mkEnableOption "nftables firewall" // { + default = + config.allowedTCPPorts != [ ] || config.allowedTCPPortRanges != [ ] + || config.allowedUDPPorts != [ ] || config.allowedUDPPortRanges != [ ]; + }; + conditions = mkOption { + type = types.listOf types.str; + default = "iifname ${name}"; + }; }; }; }; diff --git a/modules/nixos/nginx-local.nix b/modules/nixos/nginx-local.nix index 38981318..b68661c3 100644 --- a/modules/nixos/nginx-local.nix +++ b/modules/nixos/nginx-local.nix @@ -5,10 +5,10 @@ }: let inherit (lib.modules) mkIf mkBefore; inherit (lib.options) mkOption mkEnableOption; - inherit (lib.strings) concatMapStringsSep; + inherit (lib.strings) concatMapStringsSep optionalString; inherit (lib.lists) optionals; inherit (config.services) tailscale; - inherit (config.networking.access) cidrForNetwork; + inherit (config.networking.access) cidrForNetwork localaddrs; localModule = { config, ... }: { options = with lib.types; { local = { @@ -22,7 +22,9 @@ cidrForNetwork.loopback.all ++ cidrForNetwork.local.all ++ optionals tailscale.enable cidrForNetwork.tail.all; - allows = concatMapStringsSep "\n" mkAllow allowAddresses; + allows = concatMapStringsSep "\n" mkAllow allowAddresses + optionalString localaddrs.enable '' + include ${localaddrs.stateDir}/*.nginx.conf; + ''; in mkBefore '' ${allows} deny all; diff --git a/nixos/access/ldap.nix b/nixos/access/ldap.nix index 764a307f..f6f746b3 100644 --- a/nixos/access/ldap.nix +++ b/nixos/access/ldap.nix @@ -10,7 +10,7 @@ let inherit (lib.lists) optionals; inherit (config.services) tailscale; inherit (config.services.nginx) virtualHosts; - inherit (config.networking.access) cidrForNetwork; + inherit (config.networking.access) cidrForNetwork localaddrs; access = config.services.nginx.access.ldap; allows = let mkAllow = cidr: "allow ${cidr};"; @@ -18,7 +18,9 @@ let cidrForNetwork.loopback.all ++ cidrForNetwork.local.all ++ optionals tailscale.enable cidrForNetwork.tail.all; - allows = concatMapStringsSep "\n" mkAllow allowAddresses; + allows = concatMapStringsSep "\n" mkAllow allowAddresses + optionalString localaddrs.enable '' + include ${localaddrs.stateDir}/*.nginx.conf; + ''; in '' ${allows} deny all; diff --git a/nixos/access/proxmox.nix b/nixos/access/proxmox.nix index 6c316a95..4719eb34 100644 --- a/nixos/access/proxmox.nix +++ b/nixos/access/proxmox.nix @@ -85,7 +85,7 @@ in { ''; }; extraConfig = '' - client_max_body_size 2048M; + client_max_body_size 16384M; ''; in { ${access.domain} = { diff --git a/nixos/samba.nix b/nixos/samba.nix index bf11b099..5b8dcda8 100644 --- a/nixos/samba.nix +++ b/nixos/samba.nix @@ -15,7 +15,6 @@ hasIpv4 = any (hasInfix ".") config.systemd.network.networks.eth0.address or [ ]; in { services.samba = { - openFirewall = mkDefault true; enable = mkDefault true; enableWinbindd = mkDefault false; enableNmbd = mkDefault hasIpv4; @@ -73,7 +72,6 @@ in { services.samba-wsdd = mkIf samba.enable { enable = mkDefault true; - openFirewall = mkDefault true; hostname = mkDefault config.networking.hostName; }; diff --git a/packages/default.nix b/packages/default.nix index d6169218..baa236ad 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -50,7 +50,7 @@ if DEPLOY_HOSTNAME=$(nix eval --raw "''${NF_CONFIG_ROOT-${toString ../.}}"#"deploy.nodes.$ARG_HOSTNAME.hostname" 2>/dev/null); then DEPLOY_USER=$(nix eval --raw "''${NF_CONFIG_ROOT-${toString ../.}}"#"deploy.nodes.$ARG_HOSTNAME.sshUser" 2>/dev/null || true) ARG_HOSTNAME=$DEPLOY_HOSTNAME - if ! timeout 2 ping -c1 "$DEPLOY_HOSTNAME" >/dev/null 2>&1; then + if ! ping -w2 -c1 "$DEPLOY_HOSTNAME" >/dev/null 2>&1; then ARG_HOSTNAME="$ARG_NODE.local" fi else @@ -58,15 +58,15 @@ fi fi fi - if ! timeout 2 ping -c1 "$ARG_HOSTNAME" >/dev/null 2>&1; then + if ! ping -w2 -c1 "$ARG_HOSTNAME" >/dev/null 2>&1; then LOCAL_HOSTNAME=$ARG_NODE.local.gensokyo.zone TAIL_HOSTNAME=$ARG_NODE.tail.gensokyo.zone GLOBAL_HOSTNAME=$ARG_NODE.gensokyo.zone - if timeout 2 ping -c1 "$LOCAL_HOSTNAME" >/dev/null 2>&1; then + if ping -w2 -c1 "$LOCAL_HOSTNAME" >/dev/null 2>&1; then ARG_HOSTNAME=$LOCAL_HOSTNAME - elif timeout 2 ping -c1 "$TAIL_HOSTNAME" >/dev/null 2>&1; then + elif ping -w2 -c1 "$TAIL_HOSTNAME" >/dev/null 2>&1; then ARG_HOSTNAME=$TAIL_HOSTNAME - elif timeout 2 ping -c1 "$GLOBAL_HOSTNAME" >/dev/null 2>&1; then + elif ping -w2 -c1 "$GLOBAL_HOSTNAME" >/dev/null 2>&1; then ARG_HOSTNAME=$GLOBAL_HOSTNAME fi fi diff --git a/systems/tei/nixos.nix b/systems/tei/nixos.nix index 5fac16bc..314ae710 100644 --- a/systems/tei/nixos.nix +++ b/systems/tei/nixos.nix @@ -30,8 +30,6 @@ in { sops.defaultSopsFile = ./secrets.yaml; - services.home-assistant.homekit.openFirewall = true; - services.kanidm = { package = lib.warnIf