From 8e24efe4a6386f06474356d732536948fca596b0 Mon Sep 17 00:00:00 2001 From: chen21019 Date: Sat, 12 Sep 2026 15:28:13 +0800 Subject: [PATCH 1/2] Reject legacy probe on nft-only Docker hosts --- COMPATIBILITY.md | 2 +- package/firewall-backend.sh | 13 +++++++++++++ scripts/test-firewall-backend | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) 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..a653857 100644 --- a/scripts/test-firewall-backend +++ b/scripts/test-firewall-backend @@ -63,6 +63,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 From c3a4bc0ea0e657cf9810c045c63ed981437f1cce Mon Sep 17 00:00:00 2001 From: chen21019 Date: Sat, 12 Sep 2026 15:28:39 +0800 Subject: [PATCH 2/2] Cover explicit legacy selection on old hosts --- scripts/test-firewall-backend | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/test-firewall-backend b/scripts/test-firewall-backend index a653857..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