fix: isolate boxes with the bridge's port-isolation flag, not an nft rule
The nft bridge-family rule from the previous commit is LIVE on the host
and boxes still reach each other:
table bridge claudebox {
chain forward { ... meta ibrname "claudenet" meta obrname "claudenet" drop }
}
FAIL BOX A REACHES BOX B — sibling isolation does NOT hold [tcp: refused]
So the rule is not wrong about intent, it is wrong about mechanism —
whatever path these frames take, that hook does not stop them. Rather
than reason harder about netfilter (reasoning is what put the hole there
in the first place), use the mechanism Incus provides for exactly this:
security.port_isolation on the bridged NIC, which sets the kernel bridge
port's isolated flag so two isolated ports cannot exchange frames at all.
The nft rule stays as a second layer — it costs nothing — but the
profile flag is what carries the guarantee. doctor.sh checks it, because
the absence of this one is invisible: everything works and boxes can
simply reach each other.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
bd849181bd
commit
3bed7c2f2a
2 changed files with 20 additions and 0 deletions
|
|
@ -91,6 +91,15 @@ fi
|
||||||
|
|
||||||
head_ "Profile — claude-dev (the NIC is the isolation contract)"
|
head_ "Profile — claude-dev (the NIC is the isolation contract)"
|
||||||
if incus profile show claude-dev >/dev/null 2>&1; then
|
if incus profile show claude-dev >/dev/null 2>&1; then
|
||||||
|
iso="$(incus profile device get claude-dev eth0 security.port_isolation 2>/dev/null)"
|
||||||
|
if [ "$iso" = "true" ]; then
|
||||||
|
ok "security.port_isolation = true — boxes cannot reach each other at L2"
|
||||||
|
else
|
||||||
|
no "security.port_isolation is NOT set — BOXES CAN REACH EACH OTHER"
|
||||||
|
inf "an L3 ACL cannot do this: two boxes on one bridge are on the same L2"
|
||||||
|
inf "segment, so their frames are switched, never routed past the ACL."
|
||||||
|
inf "fix: re-run ~/.local/share/claudebox/host/setup-host.sh"
|
||||||
|
fi
|
||||||
for k in security.mac_filtering security.ipv4_filtering; do
|
for k in security.mac_filtering security.ipv4_filtering; do
|
||||||
v="$(incus profile device get claude-dev eth0 "$k" 2>/dev/null)"
|
v="$(incus profile device get claude-dev eth0 "$k" 2>/dev/null)"
|
||||||
if [ -z "$v" ]; then
|
if [ -z "$v" ]; then
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,17 @@ devices:
|
||||||
type: nic
|
type: nic
|
||||||
network: claudenet
|
network: claudenet
|
||||||
name: eth0
|
name: eth0
|
||||||
|
# Boxes must not reach each other. This is the mechanism that actually does
|
||||||
|
# it: the kernel bridge's port-isolation flag, which stops two isolated
|
||||||
|
# ports exchanging frames at L2.
|
||||||
|
#
|
||||||
|
# It is not an ACL rule, and it cannot be. Incus ACLs are L3/L4, and two
|
||||||
|
# boxes on one bridge are on the same L2 segment — their frames are switched
|
||||||
|
# between ports and never traverse the netfilter path an ACL lives on. That
|
||||||
|
# is why the ACL's drop on 10.0.0.0/8 (which contains claudenet) and its
|
||||||
|
# default ingress drop BOTH looked airtight while box→box was wide open: a
|
||||||
|
# live probe found box A's SYN arriving at box B and B answering with a RST.
|
||||||
|
security.port_isolation: "true"
|
||||||
root:
|
root:
|
||||||
type: disk
|
type: disk
|
||||||
pool: default
|
pool: default
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue