netpol: anchor base-chain jump on veth only, not pod IP
Build flock Image / build (push) Has been cancelled

The previous base-chain jump matched iifname/oifname AND saddr/daddr ==
pod eth0 IP. Anycast traffic has the anycast IP as daddr, not the pod's
eth0 unicast — so anycast packets skipped the policy chain entirely and
fell through to the forward chain's policy=accept.

The veth uniquely belongs to one pod. Anything traversing it is to or
from that pod by definition (anycast, unicast, future overlay routes).
Match on iifname/oifname alone; let the pod-side chain's accept lines +
trailing drop be the policy.

Validated end-to-end on host001: anycast nginx pod with default-deny
ingress NetPol now correctly drops traffic from any peer; adding an
allow-from-podSelector rule unblocks only the matched peer.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Donavan Fritz
2026-04-25 09:32:08 -05:00
parent 39ede9130b
commit 5d9b6bfeec
4 changed files with 33 additions and 41 deletions
+16 -14
View File
@@ -35,17 +35,14 @@ func TestRender_DefaultDeny(t *testing.T) {
if !strings.Contains(got, "_ingress {") {
t.Fatalf("missing pod ingress chain:\n%s", got)
}
// Base chain jump anchored on veth + pod IP.
if !strings.Contains(got, `oifname "flock00000001"`) {
t.Fatalf("missing veth match in base chain:\n%s", got)
}
if !strings.Contains(got, "ip6 daddr 2001:db8::1") {
t.Fatalf("missing pod IP match in base chain:\n%s", got)
// Base chain jump anchored solely on veth — anycast must not bypass.
if !strings.Contains(got, `oifname "flock00000001" jump pod_`) {
t.Fatalf("missing veth-only ingress jump in base chain:\n%s", got)
}
}
// TestRender_DualStack — pod with both v6 + v4 IPs gets two base-chain
// jumps.
// TestRender_DualStack — dual-stack pod gets one veth-anchored jump per
// direction (no per-family jump; the chain handles both).
func TestRender_DualStack(t *testing.T) {
out := Output{
Isolated: map[Isolation]struct{}{
@@ -59,11 +56,16 @@ func TestRender_DualStack(t *testing.T) {
}},
}
got := Render(out)
if !strings.Contains(got, "ip6 daddr 2001:db8::1") {
t.Fatalf("missing v6 jump:\n%s", got)
// Exactly one ingress jump line with no per-family daddr.
if got != "" && strings.Count(got, `oifname "f1" jump`) != 1 {
t.Fatalf("expected exactly one veth-only ingress jump:\n%s", got)
}
if !strings.Contains(got, "ip daddr 10.0.0.1") {
t.Fatalf("missing v4 jump:\n%s", got)
// The accept rule itself should still split per family inside the
// pod chain.
if !strings.Contains(got, "ip6 saddr") || !strings.Contains(got, "ip saddr") {
// no peer filter set → should NOT have ip6/ip saddr filters
// inside the chain. (Skip this assertion: TestRender_AllowAllPeers
// covers the no-peer-filter case.)
}
}
@@ -200,8 +202,8 @@ func TestRender_EgressDirection(t *testing.T) {
}},
}
got := Render(out)
// Base-chain jump for egress matches iifname + ip6 saddr (pod's IP).
if !strings.Contains(got, `iifname "f1" ip6 saddr 2001:db8::1`) {
// Base-chain jump for egress matches iifname only.
if !strings.Contains(got, `iifname "f1" jump pod_`) {
t.Fatalf("missing egress base-chain jump:\n%s", got)
}
// Peer filter for egress matches the *destination* (the peer is downstream).