podmutate

package
v0.0.0-...-4a59a6a Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package podmutate contains the controller's pod-mutating admission webhook. It does these things on pod CREATE, mostly mirroring Istio's sidecar injector:

  • Namespace auto-injection: a pod created in a namespace labeled aether.io/managed=true is given the aether.io/managed=true POD label so the CNI meshes it — no per-pod label needed. A pod that explicitly sets aether.io/managed=false opts OUT (left unmanaged), so individual workloads (Jobs, the prober, infra) can be excluded from an otherwise-managed namespace.
  • dnsConfig ndots: injects a low ndots into managed pods so a mesh FQDN (<svc>.<meshDomain>, e.g. 2 dots) is tried as an absolute name BEFORE the cluster.local search list. Without it the k8s default ndots:5 makes the resolver apply the search domains first; glibc tolerates the fall-through to the bare name, but musl (Alpine) trips on the churn and fails to resolve mesh names. The chart ships ndots injection ON (`controller.injectPodNdots: true`) alongside mesh DNS.
  • dnsConfig timeout/attempts: bounds what a single LOST DNS datagram costs. The resolver a managed pod talks to is node-local (the CNI DNATs :53 to the node's mesh-DNS), so a query that gets no answer is not a slow answer, it is a dropped one — and the stock resolv.conf default makes the client wait a full 5s before retransmitting. Because Go's resolver singleflights by name, that one drop stalls EVERY concurrent lookup of that name for the whole 5s, turning a lost packet into a seconds-long per-name outage (issue #726: a mesh-DNS surge handoff closes the predecessor's SO_REUSEPORT socket, the datagram in flight to it is discarded, and the client eats 5s). A 1s retransmit against a node-local resolver is generous, and 3 attempts leave a 3s worst case against the 10s the defaults allow.
  • Egress identity gate (#1053, see identitygate.go): the aether-identity-ready init container, placed first, which holds the pod's app containers until SPIRE has issued the pod's SVID — so the app cannot send before the node proxy has a client certificate for it. Opt out per pod with aether.io/identity-gate=false; the chart turns it off mesh-wide with controller.webhook.identityGate.enabled=false.
  • UDS carrier check (proposal 039 Phase 2, see udscarrier.go): a managed pod whose Unix socket could never be delivered — an endpoint.aether.io/uds-socket annotation naming an emptyDir (the removed carrier) or an undeclared volume, a file name over the AF_UNIX budget, a csi.aether.io volume without securityContext.fsGroup, or two of them — is DENIED with a message naming the fix. This is the one thing the webhook refuses rather than mutates.

The webhook is wired with two rules: an objectSelector (aether.io/managed=true pods, in any namespace) and a namespaceSelector (pods in aether.io/managed=true namespaces). Both dispatch here; Handle is idempotent for either entry point.

Index

Constants

View Source
const (
	// IdentityGateContainerName is the init container the webhook injects. It is
	// also the idempotency key: a pod that already carries an init container of
	// this name is left alone.
	IdentityGateContainerName = "aether-identity-ready"
	// IdentityGateVolumeName is the csi.spiffe.io volume that exposes the SPIRE
	// agent's Workload API socket — to the init container ONLY.
	IdentityGateVolumeName = "aether-identity-gate-spiffe"
	// IdentityGateCommand is where the binary sits in the agent image
	// (//agent/cmd/agent:agent_image, extra layer).
	IdentityGateCommand = "/identity-ready"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type IdentityGate

type IdentityGate struct {
	// Image runs /identity-ready: the agent image, already on every node.
	Image string
	// PullPolicy of the init container.
	PullPolicy corev1.PullPolicy
	// WorkloadSocket is the SPIRE Workload API socket path INSIDE the init
	// container; the CSI volume is mounted at its directory.
	WorkloadSocket string
	// Timeout, when non-zero, makes identity-ready give up (exit 1) after that
	// long without an SVID. Zero waits forever: the pod stays in Init (fail
	// closed).
	Timeout time.Duration
	// Resources of the init container.
	Resources corev1.ResourceRequirements
}

IdentityGate configures the egress identity gate (#1053): an init container that holds a mesh-managed pod's app containers until SPIRE has issued the pod's X.509 SVID, so the app cannot send before the node proxy has a client certificate for it. It is the egress twin of the inbound-readiness promotion gate, which holds an endpoint UNHEALTHY until an mTLS handshake with the pod's own inbound listener succeeds.

func (*IdentityGate) Validate

func (g *IdentityGate) Validate() error

Validate reports a configuration the webhook cannot inject from.

type Mutator

type Mutator struct {
	NDots string
	// Gate is the egress identity gate; nil disables it.
	Gate *IdentityGate
	Log  *slog.Logger
}

Mutator injects the mesh resolv.conf options into a pod on CREATE: ndots=NDots plus the retransmit budget (timeout/attempts). NDots is the dot count of <svc>.<meshDomain> (= the label count of meshDomain; 2 for aether.internal), so mesh FQDNs are resolved absolute-first while shorter k8s names keep their search behavior.

func NewMutator

func NewMutator(ndots string, log *slog.Logger) *Mutator

NewMutator builds the pod-ndots mutator.

func (*Mutator) Handle

Handle reaches here for a pod matched either by the managed-pod objectSelector or the managed-namespace namespaceSelector. It (1) ensures the aether.io/managed label so the CNI meshes the pod — unless the pod explicitly opts out with aether.io/managed=false — and (2) injects the mesh resolv.conf options into managed pods, and (3) injects the egress identity gate unless disabled or opted out. A pod that opts out is left entirely untouched. Idempotent: re-admission (or a pod that already carries the label / the options) produces no spurious patch.

func (*Mutator) WithIdentityGate

func (m *Mutator) WithIdentityGate(g *IdentityGate) *Mutator

WithIdentityGate enables the egress identity gate (#1053); nil disables it.

Jump to

Keyboard shortcuts

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