Documentation
¶
Overview ¶
Package egress is the transparent egress PEP (ADR-0117, FEAT-0007/F81): the enforcement point F80's nftables substrate REDIRECTs remaining external worker TCP into. Per redirected connection the Linux driver recovers the pre-DNAT destination (SO_ORIGINAL_DST), authenticates the caller by source IP via the WorkerIndex, builds the FORWARDER-ATTESTED destination (the B1 invariant — client SNI/Host is never the authorization basis), asks the auth.Authorizer PDP whether that worker's principal (its Function or CatalogService) may egress::connect to that NetDestination, and splices on ALLOW / refuses on DENY — auditing every connection. It is default-deny, fail-closed, opt-in (server.network.egress), and Linux/containerd only. Disabled or on a non-Linux host New returns a no-op Gateway and worker egress stays open exactly as before. The SNI/Host parsers, the forwarder-attested destination build, and the decision flow are pure + unit-tested cross-platform; the SO_ORIGINAL_DST recovery, conn peek/replay, and splice are the Linux driver (Lima e2e verifies behavior).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AuditRecord ¶
type AuditRecord struct {
Namespace, Function, Domain string
IP netip.Addr
Port uint16
Allowed bool
Reason string
}
AuditRecord is one egress decision (allowed or blocked).
type AuditSink ¶
type AuditSink interface {
Egress(ctx context.Context, rec AuditRecord)
}
AuditSink records one egress decision to the funclog audit channel.
type Deps ¶
type Deps struct {
GatewayPort uint16 // F80's REDIRECT target (server.network.egressGatewayPort)
Workers WorkerIndex // src-IP → caller Ref (containerd runtime populates it, §5)
DNS DomainResolver // forwarder-attested (worker, dst-IP) → domains (the trust anchor)
Authz auth.Authorizer // the PDP (egress::connect over a NetDestination)
Audit AuditSink // every connection (allowed + blocked) → the funclog channel
Logger *slog.Logger // nil ⇒ slog.Default()
}
Deps wires the gateway. On a non-Linux host or when disabled, New returns a no-op Gateway.
type DomainResolver ¶
DomainResolver returns the domains THIS worker resolved to dst via the funcd forwarder — the authoritative domain→IP binding (never client SNI/Host). It includes any namespace wildcard pattern token an attested FQDN matched (so wildcard rules stay exact set-membership). Empty ⇒ only a CIDR rule can permit dst.
type Forwarder ¶
type Forwarder interface {
Serve(ctx context.Context) error
DomainResolver
}
Forwarder is the ONLY resolver a worker can reach (F80 redirects worker :53 into it). It forwards to the node resolver and records (src, domain)→resolved-IP set, exposing the reverse DomainsFor the gateway uses to authorize domain rules. It also loads the namespace EgressPolicy domain patterns to inject a matched wildcard token. It satisfies DomainResolver. Serve blocks until ctx is cancelled.
func NewForwarder ¶
func NewForwarder(listen, upstream netip.AddrPort, workers WorkerIndex, patterns WildcardPatterns) Forwarder
NewForwarder builds the forwarder over the node resolver (github.com/miekg/dns; BSD-3-Clause). The correlation map keys on the DNS client's source IP so a worker only inherits the domains IT resolved. workers maps that source IP → its namespace and patterns supplies that namespace's wildcards, so a resolved FQDN also records any "*.x.com" it matched (keeping wildcard authorization an exact set-membership check over a forwarder-derived token). Both may be nil on a deployment with no wildcard rules — then only exact attested FQDNs are recorded.
type Gateway ¶
Gateway is the transparent egress PEP: it accepts F80-redirected worker connections on GatewayPort, recovers the original destination, identifies the caller, asks the PDP, and splices or refuses — auditing every connection. Serve blocks until ctx is cancelled or Close is called.
type MemoryWorkerIndex ¶
type MemoryWorkerIndex struct {
// contains filtered or unexported fields
}
MemoryWorkerIndex is the in-memory WorkerIndex (ADR-0117, §5) the containerd runtime populates at worker provisioning: on worker-up it records the assigned funcd0 IP → the worker's principal Ref (its owner kind); on worker-down it removes it. The gateway reads it to authenticate the caller by source IP (never client-asserted). Concurrency-safe.
func NewMemoryWorkerIndex ¶
func NewMemoryWorkerIndex() *MemoryWorkerIndex
NewMemoryWorkerIndex returns an empty index.
func (*MemoryWorkerIndex) Add ¶
func (w *MemoryWorkerIndex) Add(ip netip.Addr, ref auth.EntityRef)
Add records a worker's funcd0 source IP → its principal Ref (worker-up).
func (*MemoryWorkerIndex) Lookup ¶
Lookup resolves a source IP to its principal Ref; ok==false ⇒ unknown worker (the gateway denies+audits).
func (*MemoryWorkerIndex) Remove ¶
func (w *MemoryWorkerIndex) Remove(ip netip.Addr)
Remove drops a worker's IP mapping (worker-down); safe if absent.
type WildcardPatterns ¶
type WildcardPatterns interface {
Wildcards(ns v1.NamespaceName) []string
}
WildcardPatterns supplies a namespace's EgressPolicy wildcard domain patterns (e.g. "*.x.com") so the forwarder can record which pattern an attested FQDN matched — the dependency the wildcard-token injection needs (ADR-0117, judge Major 2). It is derived from the same EgressPolicy set the PDP cache compiles; nil ⇒ no wildcard tokens (exact attested FQDNs only).