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
- func ExtractPodCIDR(content string) string
- func Render(policies []*Policy, podCIDR string) (string, error)
- type Action
- type Config
- type Direction
- type Manager
- func (m *Manager) Add(ctx context.Context, name string, cidrs []string) error
- func (m *Manager) Create(ctx context.Context, p *Policy, cidrs []string, podCIDR string, force bool) (bool, error)
- func (m *Manager) Delete(ctx context.Context, name string) error
- func (m *Manager) Remove(ctx context.Context, name string, cidrs []string) error
- func (m *Manager) Set(ctx context.Context, name string, cidrs []string) error
- func (m *Manager) Show(ctx context.Context, name string) (string, error)
- type Policy
- type Runner
Constants ¶
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 ¶
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 Render ¶
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:
- deny drops (both directions)
- asymmetric reply-stamp restore
- stamp classification — specific (has an IP-set match)
- stamp classification — fallthrough (--from-entity world)
- ct state established,related accept (structural)
- drop (structural)
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.
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.
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 ¶
NewManagerWithConfig returns a Manager, filling any unset Config field with its production default.
func (*Manager) Add ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
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.