fix(nftables): local firewall

This commit is contained in:
arcnmx 2024-01-31 13:28:21 -08:00
parent 6dc06a746a
commit a283b4bf9a
9 changed files with 167 additions and 41 deletions

View file

@ -1,16 +1,18 @@
{ {
pkgs,
inputs, inputs,
config, config,
lib, lib,
... ...
}: let }: let
inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault; inherit (lib.modules) mkIf mkMerge mkBefore mkAfter mkDefault mkOptionDefault;
inherit (lib.options) mkOption; inherit (lib.options) mkOption mkEnableOption;
inherit (lib.lists) optionals; inherit (lib.lists) optionals;
inherit (lib.strings) concatStringsSep; inherit (lib.strings) concatStringsSep optionalString;
inherit (config.services) tailscale avahi; inherit (config.services) tailscale avahi;
inherit (config) networking; inherit (config) networking;
inherit (networking) hostName; inherit (networking) hostName;
cfg = config.networking.access;
cidrModule = { config, ... }: { cidrModule = { config, ... }: {
options = with lib.types; { options = with lib.types; {
all = mkOption { all = mkOption {
@ -41,6 +43,15 @@ in {
type = attrsOf (submodule cidrModule); type = attrsOf (submodule cidrModule);
default = { }; default = { };
}; };
localaddrs = {
enable = mkEnableOption "localaddrs" // {
default = networking.firewall.interfaces.local.nftables.enable;
};
stateDir = mkOption {
type = path;
default = "/var/lib/localaddrs";
};
};
}; };
config.networking.access = { config.networking.access = {
@ -50,11 +61,11 @@ in {
hasStaticAddress = eth0.address or [ ] != [ ] || eth0.addresses or [ ] != [ ]; hasStaticAddress = eth0.address or [ ] != [ ] || eth0.addresses or [ ] != [ ];
hasSLAAC = eth0.slaac.enable or false; hasSLAAC = eth0.slaac.enable or false;
in mkMerge [ 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")) (mkIf (avahi.enable && avahi.publish.enable) (mkOptionDefault "${hostName}.local"))
]; ];
tail = mkIf tailscale.enable "${hostName}.tail.${config.networking.domain}"; tail = mkIf tailscale.enable "${hostName}.tail.${networking.domain}";
global = mkIf (networking.enableIPv6 && networking.tempAddresses == "disabled") "${hostName}.${config.networking.domain}"; global = mkIf (networking.enableIPv6 && networking.tempAddresses == "disabled") "${hostName}.${networking.domain}";
}; };
cidrForNetwork = { cidrForNetwork = {
loopback = { loopback = {
@ -86,25 +97,125 @@ in {
}; };
}; };
config.networking.firewall = { config.networking = {
nftables.ruleset = mkBefore (''
define localrange6 = 2001:568::/29
'' + optionalString cfg.localaddrs.enable ''
include "${cfg.localaddrs.stateDir}/*.nft"
'');
firewall = {
interfaces.local = { interfaces.local = {
nftables.conditions = [ nftables.conditions = [
"ip saddr { ${concatStringsSep ", " networking.access.cidrForNetwork.local.v4} }" "ip saddr { ${concatStringsSep ", " networking.access.cidrForNetwork.local.v4} }"
(mkIf networking.enableIPv6 (mkIf networking.enableIPv6
"ip6 saddr { ${concatStringsSep ", " networking.access.cidrForNetwork.local.v6} }" "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}"
];
};
};
};
config._module.args.access = let config._module.args.access = let
systemFor = hostName: inputs.self.nixosConfigurations.${hostName}.config; systemFor = hostName: inputs.self.nixosConfigurations.${hostName}.config;
systemForOrNull = hostName: inputs.self.nixosConfigurations.${hostName}.config or null; systemForOrNull = hostName: inputs.self.nixosConfigurations.${hostName}.config or null;
in { in {
systemFor = hostName: if hostName == config.networking.hostName systemFor = hostName: if hostName == networking.hostName
then config then config
else systemFor hostName; else systemFor hostName;
systemForOrNull = hostName: if hostName == config.networking.hostName systemForOrNull = hostName: if hostName == networking.hostName
then config then config
else systemForOrNull hostName; else systemForOrNull hostName;
}; };

View file

@ -44,17 +44,24 @@ in {
}; };
config = { config = {
networking.firewall = mkIf cfg.enable { networking.firewall = let
allowedTCPPorts = mkIf (cfg.homekit.enable && cfg.homekit.openFirewall) ( homekitTcp = mkIf cfg.homekit.enable (
map ({ port, ... }: port) cfg.config.homekit or [ ] map ({ port, ... }: port) cfg.config.homekit or [ ]
); );
allowedUDPPortRanges = [ castUdpRanges = mkIf cfg.cast.enable [
(mkIf (cfg.cast.enable && cfg.cast.openFirewall) { {
from = 32768; from = 32768;
to = 60999; 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 # MDNS

View file

@ -2,10 +2,11 @@
let let
inherit (lib) types; inherit (lib) types;
inherit (lib.options) mkOption; inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf; inherit (lib.modules) mkIf;
inherit (lib.attrsets) mapAttrsToList; inherit (lib.attrsets) mapAttrsToList;
inherit (lib.strings) optionalString concatStringsSep concatMapStringsSep; inherit (lib.strings) optionalString concatStringsSep concatMapStringsSep;
inherit (lib.lists) optionals;
fwcfg = config.networking.firewall; fwcfg = config.networking.firewall;
cfg = config.networking.nftables; cfg = config.networking.nftables;
@ -41,7 +42,7 @@ let
concatStringsSep "\n" (mapAttrsToList (name: ifcfg: concatMapStringsSep "\n" (cond: concatStringsSep "\n" (mapAttrsToList (name: ifcfg: concatMapStringsSep "\n" (cond:
mkPorts "${cond} tcp" ifcfg.allowedTCPPorts ifcfg.allowedTCPPortRanges "accept" mkPorts "${cond} tcp" ifcfg.allowedTCPPorts ifcfg.allowedTCPPortRanges "accept"
+ mkPorts "${cond} udp" ifcfg.allowedUDPPorts ifcfg.allowedUDPPortRanges "accept" + mkPorts "${cond} udp" ifcfg.allowedUDPPorts ifcfg.allowedUDPPortRanges "accept"
) ifcfg.nftables.conditions) fwcfg.interfaces) ) (optionals ifcfg.nftables.enable ifcfg.nftables.conditions)) fwcfg.interfaces)
} }
# DHCPv6 # DHCPv6
@ -86,12 +87,19 @@ let
''; '';
interfaceModule = { config, name, ... }: { interfaceModule = { config, name, ... }: {
options = { options = {
nftables.conditions = mkOption { nftables = {
enable = mkEnableOption "nftables firewall" // {
default =
config.allowedTCPPorts != [ ] || config.allowedTCPPortRanges != [ ]
|| config.allowedUDPPorts != [ ] || config.allowedUDPPortRanges != [ ];
};
conditions = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = "iifname ${name}"; default = "iifname ${name}";
}; };
}; };
}; };
};
in { in {
options = { options = {

View file

@ -5,10 +5,10 @@
}: let }: let
inherit (lib.modules) mkIf mkBefore; inherit (lib.modules) mkIf mkBefore;
inherit (lib.options) mkOption mkEnableOption; inherit (lib.options) mkOption mkEnableOption;
inherit (lib.strings) concatMapStringsSep; inherit (lib.strings) concatMapStringsSep optionalString;
inherit (lib.lists) optionals; inherit (lib.lists) optionals;
inherit (config.services) tailscale; inherit (config.services) tailscale;
inherit (config.networking.access) cidrForNetwork; inherit (config.networking.access) cidrForNetwork localaddrs;
localModule = { config, ... }: { localModule = { config, ... }: {
options = with lib.types; { options = with lib.types; {
local = { local = {
@ -22,7 +22,9 @@
cidrForNetwork.loopback.all cidrForNetwork.loopback.all
++ cidrForNetwork.local.all ++ cidrForNetwork.local.all
++ optionals tailscale.enable cidrForNetwork.tail.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 '' in mkBefore ''
${allows} ${allows}
deny all; deny all;

View file

@ -10,7 +10,7 @@ let
inherit (lib.lists) optionals; inherit (lib.lists) optionals;
inherit (config.services) tailscale; inherit (config.services) tailscale;
inherit (config.services.nginx) virtualHosts; inherit (config.services.nginx) virtualHosts;
inherit (config.networking.access) cidrForNetwork; inherit (config.networking.access) cidrForNetwork localaddrs;
access = config.services.nginx.access.ldap; access = config.services.nginx.access.ldap;
allows = let allows = let
mkAllow = cidr: "allow ${cidr};"; mkAllow = cidr: "allow ${cidr};";
@ -18,7 +18,9 @@ let
cidrForNetwork.loopback.all cidrForNetwork.loopback.all
++ cidrForNetwork.local.all ++ cidrForNetwork.local.all
++ optionals tailscale.enable cidrForNetwork.tail.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 '' in ''
${allows} ${allows}
deny all; deny all;

View file

@ -85,7 +85,7 @@ in {
''; '';
}; };
extraConfig = '' extraConfig = ''
client_max_body_size 2048M; client_max_body_size 16384M;
''; '';
in { in {
${access.domain} = { ${access.domain} = {

View file

@ -15,7 +15,6 @@
hasIpv4 = any (hasInfix ".") config.systemd.network.networks.eth0.address or [ ]; hasIpv4 = any (hasInfix ".") config.systemd.network.networks.eth0.address or [ ];
in { in {
services.samba = { services.samba = {
openFirewall = mkDefault true;
enable = mkDefault true; enable = mkDefault true;
enableWinbindd = mkDefault false; enableWinbindd = mkDefault false;
enableNmbd = mkDefault hasIpv4; enableNmbd = mkDefault hasIpv4;
@ -73,7 +72,6 @@ in {
services.samba-wsdd = mkIf samba.enable { services.samba-wsdd = mkIf samba.enable {
enable = mkDefault true; enable = mkDefault true;
openFirewall = mkDefault true;
hostname = mkDefault config.networking.hostName; hostname = mkDefault config.networking.hostName;
}; };

View file

@ -50,7 +50,7 @@
if DEPLOY_HOSTNAME=$(nix eval --raw "''${NF_CONFIG_ROOT-${toString ../.}}"#"deploy.nodes.$ARG_HOSTNAME.hostname" 2>/dev/null); then 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) DEPLOY_USER=$(nix eval --raw "''${NF_CONFIG_ROOT-${toString ../.}}"#"deploy.nodes.$ARG_HOSTNAME.sshUser" 2>/dev/null || true)
ARG_HOSTNAME=$DEPLOY_HOSTNAME 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" ARG_HOSTNAME="$ARG_NODE.local"
fi fi
else else
@ -58,15 +58,15 @@
fi fi
fi 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 LOCAL_HOSTNAME=$ARG_NODE.local.gensokyo.zone
TAIL_HOSTNAME=$ARG_NODE.tail.gensokyo.zone TAIL_HOSTNAME=$ARG_NODE.tail.gensokyo.zone
GLOBAL_HOSTNAME=$ARG_NODE.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 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 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 ARG_HOSTNAME=$GLOBAL_HOSTNAME
fi fi
fi fi

View file

@ -30,8 +30,6 @@ in {
sops.defaultSopsFile = ./secrets.yaml; sops.defaultSopsFile = ./secrets.yaml;
services.home-assistant.homekit.openFirewall = true;
services.kanidm = { services.kanidm = {
package = package =
lib.warnIf lib.warnIf