Skip to content

feat(node): isolate cocoon VMs at L2 via CNI portIsolation + macspoofchk#6

Open
tonicmuroq wants to merge 1 commit into
mainfrom
feat/cocoon-vm-l2-isolation
Open

feat(node): isolate cocoon VMs at L2 via CNI portIsolation + macspoofchk#6
tonicmuroq wants to merge 1 commit into
mainfrom
feat/cocoon-vm-l2-isolation

Conversation

@tonicmuroq

Copy link
Copy Markdown

What

Persist the cocoon VM↔VM isolation that's currently applied by hand on the internal-cocoon nodes into cocoon-net, using the CNI bridge plugin's L2 features + an L3 FORWARD drop — no br_netfilter.

  • writeCNIConflist (node/node.go): the generated 30-cocoon-dhcp.conflist bridge plugin now sets
    • portIsolation: true — kernel BR_ISOLATED on every VM veth → same-node VM↔VM blocked at L2 (unicast + ARP + broadcast), gateway/DHCP/egress still reachable.
    • macspoofchk: true — nftables (bridge family) source-MAC pin → anti MAC-spoof / FDB hijack. Stateless, no conntrack.
  • setupIPTables (node/iptables_linux.go): no longer calls ensureBridgeNFCall. The FORWARD -i cni0 -d <CIDR> DROP rules (from --drop-cidr, e.g. the fleet VM supernet 172.22.0.0/16) only match cross-node / off-bridge destinations, which are L3-routed and traverse FORWARD without br_netfilter. Removes now-unused ensureBridgeNFCall / readSysctl.

Why not br_netfilter

Enabling bridge-nf-call-iptables to catch same-node L2 traffic has real footguns at scale: conntrack tracks flows before the FORWARD DROP (so even blocked VM↔VM flows consume conntrack slots), the default nf_conntrack_max is undersized for hundreds of VMs/node, and it's a host-global toggle that also drags docker0 through netfilter. Doing same-node isolation at L2 (portIsolation) sidesteps all of it; cross-node is L3 and never needed br_netfilter.

Validation

Applied by hand and validated on internal-cocoon-node-1..6 (real Windows VMs, incl. hibernate/wake MAC-swap): same-node VM↔VM blocked (ARP too), cross-node blocked, VM→gateway/ingress(10.16.0.11)/internet/DNS OK, macspoofchk pins the guest's actual MAC and re-pins across wake, control→VM (guac RDP) + return traffic unaffected, 40/40 VMs Ready.

Out of scope

VM egress default-drop to the GKE master / kubelet:10250 / internal 10.0.0.0/8 (requirement ③) is intentionally not here — that's node-level egress policy for the node provisioner (needs an ESTABLISHED,RELATED accept before the 10/8 drop so control-plane→VM return traffic survives).

Deploy note

Rules are live on the fleet by hand today; merging + rolling this makes them survive node / cocoon-net restarts (previously the daemon regenerated the conflist without these fields on every start).

Same-node VM-to-VM isolation moves from iptables + bridge-nf-call-iptables
to the CNI bridge plugin, baked into the generated conflist:
  - portIsolation: kernel BR_ISOLATED per veth (blocks same-bridge VM-to-VM
    unicast/ARP/broadcast at L2, gateway still reachable)
  - macspoofchk: nftables source-MAC pin (anti MAC spoof / FDB hijack)

setupIPTables no longer enables bridge-nf-call-iptables: the FORWARD DROP
rules (--drop-cidr, e.g. the fleet VM supernet 172.22.0.0/16) target only
cross-node / off-bridge destinations, which are L3-routed and hit FORWARD
without br_netfilter. Drops ensureBridgeNFCall/readSysctl (now unused).

This avoids the br_netfilter footguns (conntrack table pressure incl. dropped
flows, host-global toggle affecting docker0). Same-node = L2 (portIsolation),
cross-node = L3 (FORWARD DROP), neither needs conntrack.

Out of scope (node provisioner): VM egress default-drop to GKE master /
kubelet:10250 / internal 10.0.0.0/8.
@tonicmuroq tonicmuroq force-pushed the feat/cocoon-vm-l2-isolation branch from a6f5f5a to a723332 Compare July 6, 2026 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant