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 ¶
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 ¶
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 ¶
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.
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.
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).
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 ¶
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 ¶
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 ¶
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:
- Return on RELATED/ESTABLISHED so reply traffic is never dropped.
- Each allowlist entry emits one or more RETURN rules.
- Final action: DROP (when Default=deny) or RETURN (when Default=allow).
The generator is pure: no I/O, no iptables invocations.
func DispatchRule ¶
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.