netpolicy

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Apr 25, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package netpolicy renders space-level egress policies into iptables rules and applies them on the host firewall. The public surface is:

  • Policy: an already-validated policy-for-a-space, derived from a Space's intmodel.EgressPolicy.
  • BuildRules: pure rule generator — no I/O, no iptables invocations.
  • Enforcer: interface for applying/removing rules on the host.
  • IptablesEnforcer: concrete enforcer that shells out to iptables.

Design choice (per issue #45): host entries are resolved to IPs at apply time. The resolved IPs are embedded in iptables rules, so hostname-based entries do not reflect DNS changes until the space is re-applied. See the Space manifest docs for the operator-visible caveat.

Index

Constants

View Source
const MasterChainName = "KUKEON-EGRESS"

MasterChainName is the top-level FORWARD dispatch chain that holds one "jump to per-space chain" entry per space with an active policy. It is shared across all spaces; the per-space chain names differ.

Variables

This section is empty.

Functions

This section is empty.

Types

type CommandRunner

type CommandRunner interface {
	Run(ctx context.Context, args ...string) ([]byte, error)
}

CommandRunner executes an iptables invocation and returns its combined stdout+stderr. Tests inject a fake to capture invocations and return canned output for read-only calls like "-S" or "-L".

type Enforcer

type Enforcer interface {
	// Apply installs the policy idempotently. A nil policy is a no-op.
	Apply(ctx context.Context, p *Policy) error
	// Remove tears down the policy for the given realm+space. It is safe
	// to call when no policy was installed.
	Remove(ctx context.Context, realmName, spaceName string) error
}

Enforcer applies and removes per-space egress policies on the host firewall. The interface is narrow so tests and `--no-daemon` paths can substitute a no-op implementation.

type HostResolver

type HostResolver interface {
	LookupIP(ctx context.Context, host string) ([]net.IP, error)
}

HostResolver resolves a hostname to its IP addresses at policy-apply time. The interface is explicit so tests can substitute deterministic lookups.

type IptablesEnforcer

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

IptablesEnforcer invokes the iptables command to realize policies.

func NewIptablesEnforcer

func NewIptablesEnforcer(logger *slog.Logger) *IptablesEnforcer

NewIptablesEnforcer returns an enforcer that shells out to the iptables binary on PATH. Logger is required and should be the daemon-scoped slog.

func NewIptablesEnforcerWithRunner

func NewIptablesEnforcerWithRunner(logger *slog.Logger, runner CommandRunner) *IptablesEnforcer

NewIptablesEnforcerWithRunner is the test-hook constructor.

func (*IptablesEnforcer) Apply

func (e *IptablesEnforcer) Apply(ctx context.Context, p *Policy) error

Apply creates/flushes the per-space chain, loads the rules, and wires the dispatch jump from the master chain. A nil policy is a no-op.

func (*IptablesEnforcer) Remove

func (e *IptablesEnforcer) Remove(ctx context.Context, realmName, spaceName string) error

Remove deletes every dispatch rule targeting the per-space chain, then flushes and deletes the chain itself. Idempotent.

type NetHostResolver

type NetHostResolver struct{}

NetHostResolver uses net.DefaultResolver. IPv6 addresses are filtered out because the iptables applier only emits IPv4 rules today; IPv6 support is out of scope for the MVP.

func (NetHostResolver) LookupIP

func (NetHostResolver) LookupIP(ctx context.Context, host string) ([]net.IP, error)

type NoopEnforcer

type NoopEnforcer struct{}

NoopEnforcer satisfies Enforcer without touching the host firewall. It is the safe default for test fixtures and for code paths that must never mutate iptables (e.g., `--no-daemon` read-only clients).

func (NoopEnforcer) Apply

func (NoopEnforcer) Apply(_ context.Context, _ *Policy) error

func (NoopEnforcer) Remove

func (NoopEnforcer) Remove(_ context.Context, _, _ string) error

type Policy

type Policy struct {
	// Bridge is the Linux bridge device name (as written in the CNI
	// conflist) that the policy guards.
	Bridge string
	// RealmName and SpaceName identify the space for chain naming and log
	// tagging.
	RealmName string
	SpaceName string
	// Default is the fallthrough action when no Allow rule matches.
	Default intmodel.EgressDefault
	// Allow is the list of validated allowlist entries.
	Allow []ResolvedRule
}

Policy is the validated, per-space egress policy ready for rule generation. Use FromInternal to build one from the internal Space model.

func FromInternal

func FromInternal(realmName, spaceName, bridge string, in *intmodel.EgressPolicy) (*Policy, error)

FromInternal validates an internal EgressPolicy and returns an intermediate Policy with hosts *not yet resolved*. Call Resolve to populate IPs.

Returns (nil, nil) when egress is effectively a no-op: either the policy pointer is nil, or Default=allow with no Allow entries. Callers treat a nil result as "nothing to enforce".

func (*Policy) ChainName

func (p *Policy) ChainName() string

ChainName returns the deterministic iptables chain name for this policy. Format: "KUKE-EGR-<8-hex-fnv>". The FNV-1a hash of "<realm>/<space>" keeps the name short enough for any iptables version while avoiding collisions across spaces in different realms.

func (*Policy) CommentTag

func (p *Policy) CommentTag() string

CommentTag returns the short identifier embedded in every rule's --comment argument so operators can grep iptables output by space.

func (*Policy) Resolve

func (p *Policy) Resolve(ctx context.Context, resolver HostResolver) error

Resolve expands every Allow rule that uses a Host into a rule populated with IPs. CIDR-based rules are passed through unchanged. A host that resolves to zero IPv4 addresses is a fatal error so operators don't silently end up with a deny-only policy.

type ResolvedRule

type ResolvedRule struct {
	// OriginalHost is the user-supplied host, kept so rule comments can
	// name the DNS origin. Empty when the rule came from a CIDR entry.
	OriginalHost string
	// CIDR is set when the rule was supplied as a literal CIDR.
	CIDR string
	// IPs is the list of IPs the rule targets. When CIDR is set, IPs is
	// empty and CIDR is used as the iptables -d target.
	IPs []net.IP
	// Ports is the list of TCP destination ports. Empty means "any port".
	Ports []int
}

ResolvedRule is a single allowlist entry where Host (if any) has already been resolved to IPs. Callers constructing Policy by hand must fill either CIDR or IPs (the resolver populates IPs from a Host lookup).

type Rule

type Rule struct {
	// Op is "-A" (append), "-I 1" (insert at head), etc.
	Op string
	// Chain is the target chain for Op.
	Chain string
	// Args is the remaining iptables argument list.
	Args []string
}

Rule represents a single iptables command in a form the applier can execute or a test can compare. It is chain-qualified ("-A <chain>" or "-I <chain> 1") plus arguments.

func BuildRules

func BuildRules(p *Policy) []Rule

BuildRules returns the ordered iptables rules that implement Policy on the per-space chain. The caller is responsible for ensuring the chain exists (flushed) and for wiring the dispatch jump from the master chain.

Rule ordering matters:

  1. Return on RELATED/ESTABLISHED so reply traffic is never dropped.
  2. Each allowlist entry emits one or more RETURN rules.
  3. Final action: DROP (when Default=deny) or RETURN (when Default=allow).

The generator is pure: no I/O, no iptables invocations.

func DispatchRule

func DispatchRule(p *Policy) Rule

DispatchRule returns the single "-A <master> -i <bridge> -j <per-space>" rule that funnels bridge-sourced traffic into the per-space chain. It is emitted on Apply and removed on Remove.

Jump to

Keyboard shortcuts

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