Files
flock/pkg/agent/netpol/apply_linux.go
T

86 lines
2.2 KiB
Go
Raw Normal View History

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