forked from heavy-duty/box
fix(expose): accept established flows back from boxnet — the input drop was eating the door's replies
Reproduced the drill's E failure on a live stack (Incus 6.0.4, container box, same setup-host/box-firewall): tcpdump on boxnet shows the SYN leaving masqueraded as the gateway and the box answering SYN/ACK instantly — which then dies at the host's input hook. The inet-box input chain dropped ALL boxnet input except DNS/DHCP, stateless: the reply to the very connection the door opened. UFW hosts never had this hole (before.rules accepts RELATED,ESTABLISHED); the nft fallback now matches that semantics with a ct state established,related accept ahead of the drop. Boxes still cannot INITIATE toward the host — a box-originated SYN is a NEW flow, which is what the drop is for. Also rebuild the chains on every run (add chain + flush + re-add) instead of skip-if-present: the existence guard pinned every host to the rule set of the release that first ran there, so an upgraded rule never landed. Verified end-to-end on the repro stack: curl 127.0.0.1:18091 → HTTP 200; box→host initiation still times out; DNS carve-out intact; non-exposed port still dropped; --remove kills the door; re-expose returns 200. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
32bb203ddb
commit
edf8309f99
1 changed files with 23 additions and 15 deletions
|
|
@ -18,16 +18,25 @@ if command -v ufw >/dev/null && ufw status 2>/dev/null | grep -q "Status: active
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# No UFW: protect the host's own sockets with a dedicated nft table.
|
# No UFW: protect the host's own sockets with a dedicated nft table.
|
||||||
# Guard on the CHAIN, not the table — the expose-snat section below also
|
# Flush + rebuild rather than skip-if-present: a guard that only checks
|
||||||
# lives in this table, and on a UFW host it creates the table first; a later
|
# existence pins every host to the rule set of the release that FIRST ran
|
||||||
# run without UFW must still install the input drop.
|
# here, and an upgraded rule never lands. 'add chain' with the same spec is
|
||||||
if ! nft list chain inet box input >/dev/null 2>&1; then
|
# a no-op, so this converges.
|
||||||
nft add table inet box
|
#
|
||||||
nft 'add chain inet box input { type filter hook input priority -5 ; }'
|
# The established,related accept is load-bearing for 'box expose' (#55): the
|
||||||
nft add rule inet box input iifname "$NET" udp dport '{ 53, 67 }' accept
|
# door's traffic reaches the box masqueraded as the gateway, so the box's
|
||||||
nft add rule inet box input iifname "$NET" tcp dport 53 accept
|
# REPLY arrives here as input on boxnet — a stateless drop eats it and the
|
||||||
nft add rule inet box input iifname "$NET" drop
|
# door times out (drill-found). Boxes still cannot INITIATE toward the host:
|
||||||
fi
|
# a box-originated SYN is a NEW flow, and NEW is what the drop is for.
|
||||||
|
# (UFW hosts get the same semantics from ufw's built-in RELATED,ESTABLISHED
|
||||||
|
# accept in before.rules — this branch must match it.)
|
||||||
|
nft add table inet box
|
||||||
|
nft 'add chain inet box input { type filter hook input priority -5 ; }'
|
||||||
|
nft flush chain inet box input
|
||||||
|
nft add rule inet box input iifname "$NET" ct state established,related accept
|
||||||
|
nft add rule inet box input iifname "$NET" udp dport '{ 53, 67 }' accept
|
||||||
|
nft add rule inet box input iifname "$NET" tcp dport 53 accept
|
||||||
|
nft add rule inet box input iifname "$NET" drop
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Sibling isolation: a box must not reach another box --------------------
|
# --- Sibling isolation: a box must not reach another box --------------------
|
||||||
|
|
@ -83,11 +92,10 @@ if [ -e "/proc/sys/net/ipv4/conf/$NET/route_localnet" ]; then
|
||||||
else
|
else
|
||||||
echo "box-firewall: $NET does not exist yet — route_localnet not set; expose's loopback door stays dead until this script runs again" >&2
|
echo "box-firewall: $NET does not exist yet — route_localnet not set; expose's loopback door stays dead until this script runs again" >&2
|
||||||
fi
|
fi
|
||||||
if ! nft list chain inet box expose-snat >/dev/null 2>&1; then
|
nft add table inet box
|
||||||
nft add table inet box
|
nft "add chain inet box expose-snat { type nat hook postrouting priority 110 ; }"
|
||||||
nft "add chain inet box expose-snat { type nat hook postrouting priority 110 ; }"
|
nft flush chain inet box expose-snat
|
||||||
nft add rule inet box expose-snat oifname "$NET" ip saddr 127.0.0.0/8 masquerade
|
nft add rule inet box expose-snat oifname "$NET" ip saddr 127.0.0.0/8 masquerade
|
||||||
fi
|
|
||||||
|
|
||||||
# Docker rewrites FORWARD policy to DROP; DOCKER-USER is its escape hatch.
|
# Docker rewrites FORWARD policy to DROP; DOCKER-USER is its escape hatch.
|
||||||
if command -v docker >/dev/null && iptables -L DOCKER-USER -n >/dev/null 2>&1; then
|
if command -v docker >/dev/null && iptables -L DOCKER-USER -n >/dev/null 2>&1; then
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue