policy

package
v0.23.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package policy implements the `solo-provisioner network policy` verb family: it renders per-category classification / ACL rules into the `inet weaver` nftables table (the workload traffic plane), maintains a per-policy registry under /etc/solo-provisioner/policies/, and applies the full rendered chain to the live kernel with `nft -f` while atomically persisting /etc/solo-provisioner/network-weaver.nft.

Index

Constants

View Source
const (
	// TableName is the nftables table this package owns.
	TableName = "inet weaver"

	// WeaverNftPath is the on-disk artifact replayed at boot by the shared
	// solo-provisioner-network-nft.service oneshot. It lives under /etc (host OS
	// config on the root filesystem) so it is available early at boot, before any
	// late mounts. This package only writes the file and ensures the unit is
	// enabled.
	WeaverNftPath = "/etc/solo-provisioner/network-weaver.nft"

	// HostNftPath is the inet host artifact, owned by internal/network/firewall.
	// This package never writes it; it only checks for its presence to decide
	// whether the shared oneshot may be disabled.
	HostNftPath = "/etc/solo-provisioner/network-host.nft"

	// RegistryDir holds one JSON file per policy. The registry is the source of
	// truth for the static policy definition and drives the tier-order chain
	// re-render on every create/delete. CIDR membership is NOT stored here — it
	// lives in the live nft sets and is owned by the daemon poll loop.
	RegistryDir = "/etc/solo-provisioner/policies"

	// NetworkNftService is the shared oneshot unit that loads the network nft
	// tables at boot. It is shared with internal/network/firewall; this package
	// ensures it is installed and enabled on the first policy mutation but never
	// disables it.
	NetworkNftService = "solo-provisioner-network-nft.service"

	// NetworkNftServiceUnitPath is the absolute path where the shared unit file
	// is installed so systemd can discover it.
	NetworkNftServiceUnitPath = "/usr/lib/systemd/system/" + NetworkNftService

	// LockDir holds the cross-command apply lock, on tmpfs (/run) so it is
	// auto-cleared on reboot. Shared with internal/network/firewall and the
	// daemon poll loop.
	LockDir = "/run/solo-provisioner/network"

	// LockPath is the flock acquired (LOCK_EX) for the duration of any mutating
	// verb, so a hand-run operator command and the daemon poll loop can never
	// interleave nft transactions on the shared network tables.
	//
	// NOTE: these path/service constants intentionally mirror
	// internal/network/firewall by value. Hoisting them into a shared
	// internal/network package is a deliberate follow-up (kept out of scope here
	// to avoid churning already-merged firewall code); the values MUST stay in
	// sync until then, which the render/lock tests guard indirectly.
	LockPath = "/run/solo-provisioner/network/.applying"
)

Variables

This section is empty.

Functions

func ExtractPodCIDR

func ExtractPodCIDR(content string) string

ExtractPodCIDR recovers the pod CIDR last used to render network-weaver.nft. Mirrors internal/network/firewall's Parse(): it understands only the exact format Render produces, not a general nft parser, and exists so a caller that doesn't supply --pod-cidr (as --deny never does) can still correctly re-render unchanged --stamp siblings using the value they were already rendered with, instead of requiring every call to re-supply or re-detect a value that's effectively a deployment-wide constant. Returns "" if none is found (e.g. a deny-only chain, or the file doesn't exist yet).

func IsRegistryEmpty added in v0.23.0

func IsRegistryEmpty(dir string) (bool, error)

IsRegistryEmpty reports whether the policy registry at dir contains no entries. A missing directory is treated as empty. On error the bool is false so a failed read can never be mistaken for "empty" (len(nil)==0) and cause a caller to skip work it should not.

func Render

func Render(policies []*Policy, podCIDR string) (string, error)

Render produces the full `inet weaver` nft document for the given set of registry policies, in tier order. The same output feeds both the kernel apply (`nft -f`) and the on-disk artifact, so the live table and the persisted file can never diverge. Set *membership* is deliberately not rendered here — only set schemas and the static `--ports` elements — because membership is owned by the daemon poll loop and never persisted.

Rule position is determined by action type and match specificity, never by creation order:

  1. deny drops (both directions)
  2. asymmetric reply-stamp restore
  3. stamp classification — specific (has an IP-set match)
  4. stamp classification — fallthrough (--from-entity world)
  5. ct state established,related accept (structural)
  6. drop (structural)

func RenderWeaverNft added in v0.23.0

func RenderWeaverNft(registryDir, weaverNftPath, podCIDR string) error

RenderWeaverNft loads all policies from registryDir, renders the full `inet weaver` nft document, and atomically writes it to weaverNftPath (mode 0644). If the on-disk content is already identical (SHA-256 match) the write is skipped — making it safe to call from idempotent install flows.

podCIDR is required only when at least one registered policy is a --stamp policy. If the caller passes "" and the existing weaverNftPath is readable, the pod CIDR is recovered from that file automatically — the same recovery path Manager.Create uses. An error is returned only when a stamp policy is present and no podCIDR can be resolved.

func RestartNetworkNftService added in v0.23.0

func RestartNetworkNftService(ctx context.Context) error

RestartNetworkNftService restarts the shared solo-provisioner-network-nft.service oneshot so the kernel picks up any nft files written since the last restart. The unit's RemainAfterExit=yes state is updated to reflect both network-host.nft and network-weaver.nft as loaded.

Types

type Action

type Action string

Action is the nft verdict a policy renders: classify-and-accept (stamp) or drop (deny). Every policy is exactly one of the two.

const (
	// ActionStamp classifies matching packets into an HTB priority class
	// (`meta priority set <value> accept`).
	ActionStamp Action = "stamp"
	// ActionDeny drops matching packets in both directions, before the
	// established/related fast-path.
	ActionDeny Action = "deny"
)

type Config

type Config struct {
	Runner        Runner
	WeaverNftPath string
	RegistryDir   string
	LockPath      string
	EnsureService func(ctx context.Context) error
}

Config customises a Manager. The zero value is not useful; prefer NewManager. Tests inject a fake Runner, temp paths, and a no-op service func so the package builds and runs on any platform.

type Direction

type Direction string

Direction selects which half of the forward chain a stamp rule renders into. It is empty for deny policies (which always apply to both directions). For stamp policies it is not a caller-supplied value: Validate derives it from the --stamp class (every class in the mark map has exactly one direction), so it can never contradict the class it names.

const (
	// DirectionIngress renders into the peer→pod block (`ip daddr POD_CIDR …
	// tcp dport`).
	DirectionIngress Direction = "ingress"
	// DirectionEgress renders into the pod→peer block (`ip saddr POD_CIDR …
	// tcp sport`).
	DirectionEgress Direction = "egress"
)

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager implements `network policy create` against the `inet weaver` table. create takes the shared apply lock, writes the policy's registry file, re-renders the full chain from the registry in tier order, applies it to the live kernel with `nft -f`, atomically rewrites network-weaver.nft, and ensures the shared boot oneshot is enabled.

create is create-if-missing, mirroring internal/network/firewall: an existing policy is left untouched (warn, no-op) unless --force is passed, which replaces its config and membership from the given flags/--cidrs.

The rendered chain always begins with `delete table; add table` (membership is never part of it), so every Apply() destroys and recreates every policy's live set, not just the one being created. Create snapshots every policy's membership first and restores it afterward -- see snapshotMembership below.

func NewManager

func NewManager() *Manager

NewManager returns a Manager wired to the live kernel and the production paths.

func NewManagerWithConfig

func NewManagerWithConfig(cfg Config) *Manager

NewManagerWithConfig returns a Manager, filling any unset Config field with its production default.

func (*Manager) Add

func (m *Manager) Add(ctx context.Context, name string, cidrs []string) error

Add appends cidrs to the live set for a named policy. The set is mutated directly with `nft add element` — no chain re-render occurs, so network-weaver.nft is not updated (membership is never persisted). Returns an error if the policy does not exist, has no CIDR set (--from-entity world), or the live kernel table is not present.

func (*Manager) Create

func (m *Manager) Create(ctx context.Context, p *Policy, cidrs []string, podCIDR string, force bool) (bool, error)

Create adds a policy, or replaces an existing one when force is set. create-if-missing: a policy that doesn't exist is always created. A policy that already exists is left untouched (returns false) unless force is true, in which case its config and membership are replaced (not merged) from p and cidrs. cidrs is set membership, applied to the live kernel only — never persisted.

func (*Manager) Delete

func (m *Manager) Delete(ctx context.Context, name string) error

Delete removes a named policy: re-renders the `inet weaver` chain without it, applies the result to the live kernel, restores the remaining policies' live membership (which the destructive re-render wipes), removes the registry file, and atomically rewrites network-weaver.nft.

If this is the last policy, an empty chain (policy drop, no rules) is applied and the boot oneshot is left enabled.

func (*Manager) Remove

func (m *Manager) Remove(ctx context.Context, name string, cidrs []string) error

Remove deletes cidrs from the live set for a named policy. Like Add, only the live kernel set is changed — no chain re-render and no .nft update.

func (*Manager) Set

func (m *Manager) Set(ctx context.Context, name string, cidrs []string) error

Set atomically replaces the live set for a named policy with cidrs in a single `flush set + add element` kernel transaction. An empty cidrs slice clears the set. Like Add/Remove, only the live kernel set is changed.

func (*Manager) Show

func (m *Manager) Show(ctx context.Context, name string) (string, error)

Show returns a human-readable summary of a named policy: its registry config (action, class, ports, created_at) followed by the live set membership from the kernel (`nft list set inet weaver <name>`). No lock is taken — Show is read-only.

type Policy

type Policy struct {
	Name            string    `json:"name"`
	Action          Action    `json:"action"`
	Stamp           string    `json:"stamp"`             // HTB class (from --stamp); "" for deny
	ReplyStamp      string    `json:"reply_stamp"`       // reply class (from --reply-stamp); "" if unset
	Direction       Direction `json:"direction"`         // derived from Stamp's class by Validate; "" for deny
	Ports           []string  `json:"ports"`             // workload listener ports (from --ports); nil if none
	FromEntityWorld bool      `json:"from_entity_world"` // true if --from-entity world (no IP-set clause)
	CreatedAt       time.Time `json:"created_at"`        // tiebreaker within a tier, preserved across a --force replace
}

Policy is the static definition of one named category, mirroring the registry JSON schema. CIDR membership is deliberately NOT a field: it lives in the live nft set and is owned by the daemon poll loop, never persisted to the registry or the .nft file. The initial `--cidrs` membership supplied at create time is applied to the live kernel separately (see Manager.Create).

func (*Policy) Validate

func (p *Policy) Validate(cidrs []string) error

Validate rejects any policy + initial-CIDR combination that would be unsafe or nonsensical to render. It is the single gate before the renderer; every untrusted token (name, class, ports, CIDRs) is checked so a malformed value can never break the atomic nft transaction or smuggle in nft syntax.

type Runner

type Runner interface {
	// Apply loads a full nft document into the kernel (`nft -f -`). The
	// rendered document carries the idempotent `add table / delete table / add
	// table` prefix, so a re-apply atomically replaces the inet weaver table.
	Apply(ctx context.Context, doc string) error
	// AddElements adds initial membership to a policy's set
	// (`nft add element inet weaver <set> { … }`). Applied to the live kernel
	// only — set membership is never persisted.
	AddElements(ctx context.Context, set string, elements []string) error
	// DeleteElements removes specific elements from a policy's set
	// (`nft delete element inet weaver <set> { … }`). Applied to the live
	// kernel only.
	DeleteElements(ctx context.Context, set string, elements []string) error
	// SetElements atomically replaces a policy's set membership
	// (`flush set inet weaver <set>` followed by `add element … { … }` in a
	// single nft document — one kernel transaction). An empty elements slice
	// clears the set without adding any new entries.
	SetElements(ctx context.Context, set string, elements []string) error
	// ListElements returns one set's live elements
	// (`nft list set inet weaver <set>`), or nil if the set has no elements
	// or does not exist. Used by Manager.Create to snapshot every policy's
	// membership before the destructive delete/recreate Apply() performs, so
	// it can be restored afterward (see Manager.Create for why).
	ListElements(ctx context.Context, set string) ([]string, error)
	// List returns the rendered inet weaver table (`nft list table inet weaver`).
	List(ctx context.Context) (string, error)
	// Delete removes the inet weaver table (`nft delete table inet weaver`).
	Delete(ctx context.Context) error
	// Exists reports whether the inet weaver table is present in the kernel.
	Exists(ctx context.Context) (bool, error)
}

Runner is the seam over the system `nft` binary. Unlike the firewall package (which applies via a systemd service restart), `network policy` applies the rendered chain directly with `nft -f` — the shared boot oneshot does not load network-weaver.nft yet. Tests substitute a fake so the package builds and unit-tests on any platform (including macOS) without touching the kernel.

func NewExecRunner

func NewExecRunner() Runner

NewExecRunner resolves the nft binary path and returns a Runner that applies changes to the live kernel.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL