Files
flock/pkg/agent/netpol_bridge.go
T
Donavan Fritz 39ede9130b
Build flock Image / build (push) Has been cancelled
netpol: NetworkPolicy v1 enforcement via nftables
New pkg/agent/netpol implementing standard networking.k8s.io/v1
NetworkPolicy. Pipeline:

  pods + policies + namespaces  →  Translate  →  Render  →  Apply

Supports ingress + egress, all three peer types (podSelector,
namespaceSelector, ipBlock with except), numeric ports + port ranges,
default-deny semantics derived from PolicyTypes (or inferred from
non-empty Spec.Egress when unset).

Apply path is `nft -f -` shell-out — single transaction, atomic, kernel
guarantees partial-failure rollback. Idempotent dedup via last-applied
script. Reconcile triggers: informer events, 30s self-heal tick, every
CNI ADD/DEL.

Verified against the three live cluster NetPols (calico-apiserver,
remote-proxies/lodge-home-assistant, storage/garage-admin-restrict).
Fuzz target stitches Translate + Render with random selector and peer
inputs; 21 unit tests cover the policy semantics.

Named ports skip with a warn — deferred until kubelet exposes them in a
form that doesn't require shadowing pod state.

Dockerfile: + nftables.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 09:25:58 -05:00

57 lines
1.3 KiB
Go

package agent
import (
"net"
"code.fritzlab.net/fritzlab/flock/pkg/agent/netpol"
)
// collectLocalPods bridges the agent's allocation store + pod informer
// cache into the netpol-package input shape. It returns one Pod per
// committed allocation that has a matching pod in the informer cache;
// allocations whose pod was just deleted (DEL race) are skipped.
//
// Called on every netpol reconcile pass, so it must be cheap. The work
// here is O(allocations) and reads from in-memory maps only.
func collectLocalPods(store *Store, pods *PodCache) []netpol.Pod {
allocs := store.Snapshot()
out := make([]netpol.Pod, 0, len(allocs))
for _, a := range allocs {
if a.State != StateCommitted {
continue
}
pod, ok := pods.Get(a.Namespace, a.PodName)
if !ok {
// Pod evicted but DEL hasn't fired yet; nothing to enforce.
continue
}
ips := allocationIPs(a)
if len(ips) == 0 {
continue
}
out = append(out, netpol.Pod{
Namespace: a.Namespace,
Name: a.PodName,
Labels: pod.Labels,
HostIface: HostIfaceName(a.ContainerID),
IPs: ips,
})
}
return out
}
func allocationIPs(a Allocation) []net.IP {
var out []net.IP
if a.IP6 != "" {
if ip := net.ParseIP(a.IP6); ip != nil {
out = append(out, ip)
}
}
if a.IP4 != "" {
if ip := net.ParseIP(a.IP4); ip != nil {
out = append(out, ip)
}
}
return out
}