netpol: NetworkPolicy v1 enforcement via nftables
Build flock Image / build (push) Has been cancelled
Build flock Image / build (push) Has been cancelled
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>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
//go:build linux
|
||||
|
||||
package netpol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Applier hands rendered nft scripts to the kernel via `nft -f -`.
|
||||
// nftables guarantees the entire script applies atomically — if any line
|
||||
// is rejected, the previous ruleset stays intact.
|
||||
//
|
||||
// Applier maintains the last-applied script string and skips the exec
|
||||
// when the new render is byte-identical, so a 5s reconcile tick on a
|
||||
// quiet cluster is cheap.
|
||||
type Applier struct {
|
||||
// NftPath is the path to the nft binary. Empty means "look up `nft`
|
||||
// on PATH". Tests set this to a fake.
|
||||
NftPath string
|
||||
|
||||
// Timeout bounds an individual nft invocation; if zero, defaults to
|
||||
// 5 seconds.
|
||||
Timeout time.Duration
|
||||
|
||||
last string
|
||||
}
|
||||
|
||||
// Apply runs `nft -f -` with the supplied script. Idempotent: if script
|
||||
// equals the last successful application, this is a no-op.
|
||||
//
|
||||
// Returns an error from nft (with stderr captured) if the script is
|
||||
// malformed or the kernel rejects it.
|
||||
func (a *Applier) Apply(ctx context.Context, script string) error {
|
||||
if script == a.last {
|
||||
return nil
|
||||
}
|
||||
timeout := a.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = 5 * time.Second
|
||||
}
|
||||
bin := a.NftPath
|
||||
if bin == "" {
|
||||
bin = "nft"
|
||||
}
|
||||
cctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(cctx, bin, "-f", "-")
|
||||
cmd.Stdin = bytes.NewBufferString(script)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("nft -f -: %w: %s", err, stderr.String())
|
||||
}
|
||||
a.last = script
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear tears down the flock NetworkPolicy table — used by graceful
|
||||
// shutdown so a stopping agent doesn't leave stale enforcement behind.
|
||||
// Best-effort: if nft is missing or the table doesn't exist, returns
|
||||
// nil.
|
||||
func (a *Applier) Clear(ctx context.Context) error {
|
||||
timeout := a.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = 5 * time.Second
|
||||
}
|
||||
bin := a.NftPath
|
||||
if bin == "" {
|
||||
bin = "nft"
|
||||
}
|
||||
cctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(cctx, bin, "destroy", "table", "inet", "flock_netpol")
|
||||
if err := cmd.Run(); err != nil {
|
||||
// nft returns non-zero if the table doesn't exist — that's a
|
||||
// success for our purposes.
|
||||
return nil
|
||||
}
|
||||
a.last = ""
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user