//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 }