diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 0f28139..74fa033 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -28,7 +28,7 @@ The following are compatibility adapters, not PastureStack branding: The default metadata endpoint is the brand-neutral link-local address `http://169.254.169.250/2015-12-19`; it does not depend on an internal DNS alias. -For Docker's native nftables bridge firewall, select `nftables` or let `auto` discover `ip docker-bridges`. The router does not write host firewall rules in this mode: the active network-plugin-manager must manage the overlay subnet's forwarding mark and exclude overlay destinations from its own egress masquerade. Docker must be configured to accept mark `0x1068/0x1068`. An xtables `ACCEPT` rule in a different nftables base chain is not a valid replacement for that NAT exclusion. The router does not alter Docker-owned chains, host FORWARD policy, or legacy kernel modules. If Docker's native table is absent, it chooses the live `iptables-nft` DOCKER NAT chain, or an already-loaded legacy DOCKER NAT chain for an old host; it fails closed if neither is identifiable. An explicit xtables selection is rejected while Docker's native table exists. +For Docker's native nftables bridge firewall, select `nftables` or let `auto` discover `ip docker-bridges`. The router does not write host firewall rules in this mode: the active network-plugin-manager must manage the overlay subnet's forwarding mark and exclude overlay destinations from its own egress masquerade. Docker must be configured to accept mark `0x1068/0x1068`. An xtables `ACCEPT` rule in a different nftables base chain is not a valid replacement for that NAT exclusion. The router does not alter Docker-owned chains, host FORWARD policy, or legacy kernel modules. If Docker's native table is absent, it chooses the live `iptables-nft` DOCKER NAT chain, or an already-loaded legacy DOCKER NAT chain for an old host; it fails closed if neither is identifiable. An explicit xtables selection is rejected while Docker's native table exists. Explicit `iptables-legacy` also refuses an active Docker `iptables-nft` chain or an unloaded legacy NAT table before invoking the legacy CLI, so a mistaken choice on Ubuntu 26.04 cannot load legacy modules merely by probing them. The VXLAN router's historical POSTROUTING rule executes only inside its own container network namespace, not in the IPsec host-XFRM namespace. The IPsec firewall selection does not change that independent runtime path. diff --git a/package/firewall-backend.sh b/package/firewall-backend.sh index d0bd055..2f38bb6 100644 --- a/package/firewall-backend.sh +++ b/package/firewall-backend.sh @@ -36,6 +36,19 @@ resolve_firewall_backend() { echo "Docker uses native nftables; refusing an xtables overlay backend" >&2 return 1 fi + if [ "$requested" = iptables-legacy ]; then + # A legacy inspection on an nft-only host can itself load the + # forbidden legacy modules. Reject a live nft Docker chain + # and require an already-loaded legacy NAT table first. + if host_netns_cmd iptables-nft -t nat -S DOCKER >/dev/null 2>&1; then + echo "Docker uses iptables-nft; refusing an iptables-legacy overlay backend" >&2 + return 1 + fi + if ! host_netns_cmd grep -qx nat /proc/net/ip_tables_names; then + echo "No active legacy NAT table; refusing an iptables-legacy probe" >&2 + return 1 + fi + fi host_netns_cmd "$requested" -t nat -S DOCKER >/dev/null || return 1 ;; esac diff --git a/scripts/test-firewall-backend b/scripts/test-firewall-backend index 8fca284..6e6b21a 100644 --- a/scripts/test-firewall-backend +++ b/scripts/test-firewall-backend @@ -55,6 +55,13 @@ case " ${commands[*]} " in *) echo "Expected active legacy Docker chain" >&2; exit 1 ;; esac +commands=() +TEST_DOCKER_BACKEND=iptables-legacy +PASTURESTACK_FIREWALL_BACKEND=iptables-legacy +resolve_firewall_backend +[ "$PASTURESTACK_FIREWALL_BACKEND" = iptables-legacy ] +[[ " ${commands[*]} " == *"iptables-legacy -t nat -S DOCKER"* ]] + commands=() TEST_DOCKER_BACKEND=nftables PASTURESTACK_FIREWALL_BACKEND=iptables-legacy @@ -63,6 +70,28 @@ if resolve_firewall_backend; then exit 1 fi +commands=() +TEST_DOCKER_BACKEND=iptables-nft +PASTURESTACK_FIREWALL_BACKEND=iptables-legacy +if resolve_firewall_backend; then + echo "Explicit legacy must fail when Docker uses iptables-nft" >&2 + exit 1 +fi +case " ${commands[*]} " in + *iptables-legacy*) echo "Mismatch probed legacy despite active Docker nft frontend" >&2; exit 1 ;; +esac + +commands=() +TEST_DOCKER_BACKEND=unknown +PASTURESTACK_FIREWALL_BACKEND=iptables-legacy +if resolve_firewall_backend; then + echo "Explicit legacy requires an already-loaded legacy NAT table" >&2 + exit 1 +fi +case " ${commands[*]} " in + *iptables-legacy*) echo "Absent legacy table must not trigger a module-loading probe" >&2; exit 1 ;; +esac + commands=() TEST_DOCKER_BACKEND=unknown PASTURESTACK_FIREWALL_BACKEND=auto