Documentation
¶
Overview ¶
Package controller implements the Paddock reconcilers. See docs/internal/specs/0001-core-v0.1.md §3 for the design.
Index ¶
- Constants
- Variables
- func APIServerIPsFromConfig(cfg *rest.Config) ([]net.IP, error)
- func DetectCiliumCNP(d CiliumNetworkPolicyDiscovery) (bool, error)
- func DetectNetworkPolicyCNI(ctx context.Context, c client.Reader) (bool, string, error)
- func IsBrokerCodeFatal(err error) bool
- func IsDigestPinnedImageRef(ref string) bool
- type AuditEventReconciler
- type BrokerCASource
- type BrokerHTTPClient
- type BrokerIssuer
- type BrokerPolicyReconciler
- type CiliumNetworkPolicyDiscovery
- type ControllerAudit
- func (c *ControllerAudit) EmitBrokerCredsTampered(ctx context.Context, runName, namespace string, prunedKeys []string)
- func (c *ControllerAudit) EmitCAProjected(ctx context.Context, runName, namespace, secretName string)
- func (c *ControllerAudit) EmitCredentialIssuedSummary(ctx context.Context, runName, namespace string, count int)
- func (c *ControllerAudit) EmitInteractiveRunTerminated(ctx context.Context, runName, namespace, reason string)
- func (c *ControllerAudit) EmitNetworkPolicyEnforcementWithdrawn(ctx context.Context, runName, namespace, reason string)
- func (c *ControllerAudit) EmitRunCAMisconfigured(ctx context.Context, runName, namespace, reason string)
- func (c *ControllerAudit) EmitRunCompleted(ctx context.Context, runName, namespace string, ...)
- func (c *ControllerAudit) EmitRunFailed(ctx context.Context, runName, namespace, reason, message string)
- func (c *ControllerAudit) EmitWorkspaceCAMisconfigured(ctx context.Context, wsName, namespace, reason string)
- type HarnessRunReconciler
- type NetworkPolicyEnforceMode
- type ProxyBrokerConfig
- type WorkspaceReconciler
Constants ¶
const BrokerLeasesFinalizer = "paddock.dev/broker-leases-finalizer"
BrokerLeasesFinalizer prevents a HarnessRun from being deleted before the broker has been told to revoke every lease minted for the run. reconcileDelete walks run.Status.IssuedLeases and posts /v1/revoke per entry; if the broker is unreachable past brokerRevokeBudget, the finalizer is force-cleared with a loud warning + Event so test teardown cannot leak runs (project memory: sequence first, wait, then force-clear). F-11.
const CiliumGroupVersion = "cilium.io/v2"
CiliumGroupVersion is the API group/version that hosts CiliumNetworkPolicy. Stable across Cilium 1.x.
const DefaultAuditRetention = 30 * 24 * time.Hour
DefaultAuditRetention is the default window after which AuditEvents are reaped. See ADR-0016.
const DefaultCollectorImage = "paddock-collector:dev"
DefaultCollectorImage is used when the reconciler does not override it. Overridable via the manager's --collector-image flag (M7+).
const DefaultHomeInitImage = "busybox:1.36"
DefaultHomeInitImage is overridable via --home-init-image. Kept tag-pinned so a controller image upgrade doesn't silently shift the init image.
const DefaultIPTablesInitImage = "paddock-iptables-init:dev"
DefaultIPTablesInitImage is used when the reconciler does not override it. Overridable via --iptables-init-image. Only injected when the resolved interception mode is transparent.
const DefaultProxyImage = "paddock-proxy:dev"
DefaultProxyImage is used when the reconciler does not override it. Overridable via --proxy-image. Zero string disables the sidecar.
const HarnessRunFinalizer = "paddock.dev/harnessrun-finalizer"
Finalizer that lets the run cancel its Job and release the Workspace.status.activeRunRef before its object is garbage-collected.
const KubeSystemNamespace = "kube-system"
KubeSystemNamespace is where Kubernetes distros place CNI DaemonSets. We look here to identify the CNI; nothing else in the controller reads this namespace.
const WorkspaceFinalizer = "paddock.dev/workspace-finalizer"
Finalizer that blocks Workspace deletion while an activeRunRef is set. HarnessRun controller (M3) sets/clears activeRunRef; in M2 it's only exercised by tests.
Variables ¶
var CiliumNetworkPolicyGVK = schema.GroupVersionKind{
Group: "cilium.io",
Version: "v2",
Kind: "CiliumNetworkPolicy",
}
CiliumNetworkPolicyGVK is the GroupVersionKind for cilium.io/v2 CiliumNetworkPolicy. Used to construct unstructured.Unstructured objects without taking a Go-type dependency on the Cilium API.
Functions ¶
func APIServerIPsFromConfig ¶
APIServerIPsFromConfig returns the IPv4 set the controller's kubeconfig resolves the kube-apiserver to. Used to seed a per-run NetworkPolicy allow rule on TCP/443 from sidecar pods (collector, adapter) so AuditEvent emission and ConfigMap writes can reach the apiserver.
Behaviour:
- cfg.Host is parsed as a URL. The scheme/port are stripped.
- If the host is an IPv4 literal, returns that single IP.
- If the host is a hostname, net.LookupIP is called and all returned IPv4 addresses are returned.
- IPv6 addresses are filtered out — every existing NP rule in the codebase uses IPv4 ipBlock CIDRs. Dual-stack support is future work.
- An empty result (no IPv4 resolved, or the host was empty/invalid) returns an error so manager startup can fail fast rather than starting with a permissively-configured controller.
func DetectCiliumCNP ¶
func DetectCiliumCNP(d CiliumNetworkPolicyDiscovery) (bool, error)
DetectCiliumCNP reports whether the cluster has the CiliumNetworkPolicy resource registered. Called once at controller- manager startup; callers fall back to standard NetworkPolicy when this returns false.
Treats group-not-found as "not Cilium" rather than an error: most non-Cilium clusters do not register cilium.io/v2 at all.
func DetectNetworkPolicyCNI ¶
DetectNetworkPolicyCNI inspects kube-system for well-known CNI pods whose presence indicates NetworkPolicy is actually enforced. Returns (enforced, reason) where reason is either the matched selector (on enforcement) or a diagnostic ("no known NP-capable CNI" / error).
Called once at manager startup by cmd/main.go when --networkpolicy-enforce=auto. Production installs usually set on / off explicitly; auto is for the default chart install where the operator hasn't yet decided.
Uses a client.Reader (not client.Client) because cmd/main.go runs the probe before the manager's controller cache is primed.
func IsBrokerCodeFatal ¶
IsBrokerCodeFatal reports whether a broker error is user-actionable (should fail the run) vs transient (should requeue).
func IsDigestPinnedImageRef ¶
IsDigestPinnedImageRef reports whether ref is content-addressed by a digest (image@<algorithm>:<hex>) rather than a mutable tag. Recognises any algorithm (sha256, sha512, future), per the OCI image spec. A trailing "@" with no algorithm or no encoded hash is not a valid digest pin. Used to decide whether the seed image needs ImagePullPolicy=Always (F-49 / ADR-0018). Exported so cmd/main.go's startup-warning path can reuse the same recognition rule.
Types ¶
type AuditEventReconciler ¶
type AuditEventReconciler struct {
client.Client
Scheme *runtime.Scheme
// Retention is the TTL window. Zero selects DefaultAuditRetention.
Retention time.Duration
// contains filtered or unexported fields
}
AuditEventReconciler deletes AuditEvents whose spec.timestamp is older than Retention. It runs on the controller-manager, not the broker — emitters write; reconcilers reap.
func (*AuditEventReconciler) Reconcile ¶
func (r *AuditEventReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)
Reconcile is called per-object by the watch path. When an AuditEvent is older than Retention, it's deleted; otherwise the next check is requeued for the exact moment it ages out.
func (*AuditEventReconciler) SetupWithManager ¶
func (r *AuditEventReconciler) SetupWithManager(mgr ctrl.Manager) error
SetupWithManager registers the reconciler with the manager.
type BrokerCASource ¶
BrokerCASource names the Secret in paddock-system whose ca.crt is copied into every run's broker-ca Secret.
type BrokerHTTPClient ¶
type BrokerHTTPClient struct {
// TokenReader, when non-nil, overrides the inner client's TokenReader on
// every Issue call. NewBrokerHTTPClient initialises this field and the
// inner client's TokenReader to the same closure (re-reads tokenPath on
// every call), so production paths see no behavioural change. Tests can
// mutate this field after construction to inject inline byte slices,
// which the next Issue call propagates to the inner client.
TokenReader brokerclient.TokenReader
// contains filtered or unexported fields
}
BrokerHTTPClient talks to the broker over mTLS-secured HTTPS, authenticating with a ProjectedServiceAccountToken. Set Endpoint to "" to disable — NewBrokerHTTPClient then returns nil + nil and the reconciler treats any template with requires.credentials as a hard BrokerReady=False, useful for envtest setups without a broker.
func NewBrokerHTTPClient ¶
func NewBrokerHTTPClient(endpoint, tokenPath, caPath string) (*BrokerHTTPClient, error)
NewBrokerHTTPClient builds a client. Returns nil + nil when endpoint is empty — the reconciler takes that to mean "no broker configured".
func (*BrokerHTTPClient) Issue ¶
func (b *BrokerHTTPClient) Issue(ctx context.Context, runName, runNamespace, credentialName string) (*brokerapi.IssueResponse, error)
Issue asks the broker for one named credential on behalf of the given run. Wraps POST /v1/issue.
func (*BrokerHTTPClient) Revoke ¶
func (b *BrokerHTTPClient) Revoke(ctx context.Context, runName, runNamespace string, lease paddockv1alpha1.IssuedLease) error
Revoke posts to /v1/revoke. Returns nil on 204 NoContent; a *brokerclient.BrokerError on a 4xx/5xx response (caller can errors.As into BrokerError to inspect the status code, e.g. to treat 404 from an older broker as success-equivalent).
Note: brokerclient.Client.Do treats only 200 as success and wraps other 2xx codes (including 204) in *BrokerError, so Revoke unwraps the 204 case explicitly.
type BrokerIssuer ¶
type BrokerIssuer interface {
Issue(ctx context.Context, runName, runNamespace, credentialName string) (*brokerapi.IssueResponse, error)
Revoke(ctx context.Context, runName, runNamespace string, lease paddockv1alpha1.IssuedLease) error
}
BrokerIssuer is the reconciler's view of the broker. Injected so tests can supply a fake.
type BrokerPolicyReconciler ¶
type BrokerPolicyReconciler struct {
client.Client
Scheme *runtime.Scheme
// Now returns the current time. Tests can override; production sets
// it to time.Now in SetupWithManager / wiring.
Now func() time.Time
}
BrokerPolicyReconciler watches BrokerPolicies and maintains discovery-related conditions on Status. It is intentionally narrow — only DiscoveryModeActive and DiscoveryExpired live here. The pre-existing BrokerPolicyConditionReady is unset by anything; lifting it into this reconciler is a separate refactor explicitly deferred from Plan D.
Time is injectable via Now so tests can pin the reconciler's clock. Production wires Now=time.Now in cmd/main.go.
func (*BrokerPolicyReconciler) SetupWithManager ¶
func (r *BrokerPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error
type CiliumNetworkPolicyDiscovery ¶
type CiliumNetworkPolicyDiscovery interface {
ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error)
}
CiliumNetworkPolicyDiscovery is the minimum subset of discovery.DiscoveryInterface this package uses. Defined locally so tests can supply a fake without dragging in client-go's full fake discovery client.
type ControllerAudit ¶
ControllerAudit wraps an auditing.Sink with the controller's per-emit helpers. All helpers are fail-open: errors are logged at Error level but never returned to the caller; status conditions are the canonical signal for run state. F-40.
func (*ControllerAudit) EmitBrokerCredsTampered ¶
func (c *ControllerAudit) EmitBrokerCredsTampered(ctx context.Context, runName, namespace string, prunedKeys []string)
EmitBrokerCredsTampered records that the controller detected unexpected keys on a run's broker-creds Secret (e.g., a tenant `kubectl edit secret <run>-broker-creds` injecting an extra envFrom key) and pruned them via CreateOrUpdate. prunedKeys is the sorted list of removed key names; values are never recorded. F-41 residual.
func (*ControllerAudit) EmitCAProjected ¶
func (c *ControllerAudit) EmitCAProjected(ctx context.Context, runName, namespace, secretName string)
EmitCAProjected records the controller's first touch of per-run CA material in a tenant namespace — either creating the <run>-broker-ca Secret directly (broker-ca path), or creating the cert-manager Certificate that produces <run>-proxy-tls (proxy-tls path). Same operator-visible audit semantics across both paths; different K8s resource. F-18 / Phase 2f flipped the proxy-tls path from a Secret-byte-copy to a Certificate-create.
func (*ControllerAudit) EmitCredentialIssuedSummary ¶
func (c *ControllerAudit) EmitCredentialIssuedSummary(ctx context.Context, runName, namespace string, count int)
EmitCredentialIssuedSummary records that the controller projected `count` credentials into <run>-broker-creds. Disambiguated from the broker's per-credential events by the paddock.dev/component label (set by KubeSink) and a non-zero Spec.Count.
func (*ControllerAudit) EmitInteractiveRunTerminated ¶
func (c *ControllerAudit) EmitInteractiveRunTerminated(ctx context.Context, runName, namespace, reason string)
EmitInteractiveRunTerminated records a watchdog-driven termination of an Interactive run. Reason is one of "idle", "detach", "max-lifetime" (matching watchdogAction.Reason()). Decision is always Granted because these are planned terminations under operator-configured timeouts; an error-driven termination would route through EmitRunFailed instead.
func (*ControllerAudit) EmitNetworkPolicyEnforcementWithdrawn ¶
func (c *ControllerAudit) EmitNetworkPolicyEnforcementWithdrawn(ctx context.Context, runName, namespace, reason string)
EmitNetworkPolicyEnforcementWithdrawn is called when the reconciler observes that a run admitted with enforcement=true no longer has its NetworkPolicy (e.g., operator deleted it via kubectl). The reconciler re-creates the NP on the same pass; this audit records the withdrawal attempt for the operator's trail. F-43 / Phase 2d.
func (*ControllerAudit) EmitRunCAMisconfigured ¶
func (c *ControllerAudit) EmitRunCAMisconfigured(ctx context.Context, runName, namespace, reason string)
EmitRunCAMisconfigured records a terminal CA-misconfigured event for a HarnessRun whose source-Secret key is missing/empty (typo'd key, blanked source) or whose cert-manager Certificate has hit a permanent failure. F-44.
Companion to EmitWorkspaceCAMisconfigured; both share the AuditKindCAMisconfigured kind and "ca-misconfigured" log action.
func (*ControllerAudit) EmitRunCompleted ¶
func (c *ControllerAudit) EmitRunCompleted(ctx context.Context, runName, namespace string, decision paddockv1alpha1.AuditDecision, reason string)
EmitRunCompleted records a terminal-phase commit (Succeeded / Failed / Cancelled). Decision is granted on Succeeded, denied on Failed, warned on Cancelled.
func (*ControllerAudit) EmitRunFailed ¶
func (c *ControllerAudit) EmitRunFailed(ctx context.Context, runName, namespace, reason, message string)
EmitRunFailed records a terminal-failure decision (BrokerDenied, WorkspaceRequired, etc.). Reason carries the failure cause; message the user-facing detail.
func (*ControllerAudit) EmitWorkspaceCAMisconfigured ¶
func (c *ControllerAudit) EmitWorkspaceCAMisconfigured(ctx context.Context, wsName, namespace, reason string)
EmitWorkspaceCAMisconfigured records a terminal CA-misconfigured event for a Workspace whose source-Secret key is missing/empty (typo'd key, blanked source) or whose cert-manager Certificate has hit a permanent failure. The wsName argument is the Workspace name; it is prefixed seed- here to match the F-52 runRef convention. F-51.
Named EmitWorkspaceCAMisconfigured (not EmitCAMisconfigured) so Theme 6 can add EmitRunCAMisconfigured without prefix duplication. I2.
type HarnessRunReconciler ¶
type HarnessRunReconciler struct {
client.Client
Scheme *runtime.Scheme
Recorder record.EventRecorder
// CollectorImage is the image used for the generic collector
// sidecar. When empty, DefaultCollectorImage is used.
CollectorImage string
// RingMaxEvents caps status.recentEvents at decode time.
// Mirrors the collector's ring-max-events flag (ADR-0007);
// the controller trims the parsed list to this count as
// belt-and-braces against ConfigMap-side drift. 0 disables.
RingMaxEvents int
// BrokerClient, when non-nil, is used to issue per-run credentials
// for templates with non-empty spec.requires.credentials. nil means
// no broker is configured — runs against templates with requires
// are held with BrokerReady=False.
BrokerClient BrokerIssuer
// ProxyAllowList is a static comma-separated host:port allow-list
// passed to every run's proxy sidecar via --allow. Populated from
// --proxy-allow at manager startup. M7 replaces the static list
// with live broker.ValidateEgress calls.
ProxyAllowList string
// IPTablesInitImage is the image used for the NET_ADMIN init
// container that installs the transparent-mode REDIRECT chain
// (ADR-0013 §7.2). Empty disables transparent mode entirely —
// every run resolves to cooperative regardless of PSA labels.
IPTablesInitImage string
// HomeInitImage overrides the image used for the paddock-home-init
// init container. Empty falls back to DefaultHomeInitImage.
HomeInitImage string
// Audit emits per-decision AuditEvents. Nil-safe — when unset (e.g.
// in unit tests), all emits are no-ops. F-40.
Audit *ControllerAudit
// ProxyBrokerConfig carries the shared cluster-and-manager config
// used to render run-pod proxy sidecars and per-run NetworkPolicies.
// Populated once in cmd/main.go and embedded in both reconcilers.
ProxyBrokerConfig
}
HarnessRunReconciler reconciles a HarnessRun object.
func (*HarnessRunReconciler) Reconcile ¶
func (r *HarnessRunReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)
Reconcile drives a HarnessRun through its lifecycle. See docs/internal/specs/0001-core-v0.1.md §3.3 for the state machine.
func (*HarnessRunReconciler) SetupWithManager ¶
func (r *HarnessRunReconciler) SetupWithManager(mgr ctrl.Manager) error
SetupWithManager wires up the reconciler and the owned-resource watches.
type NetworkPolicyEnforceMode ¶
type NetworkPolicyEnforceMode string
NetworkPolicyEnforceMode selects whether the controller emits a per-run NetworkPolicy. Auto defers to a CNI probe at manager startup; on always emits; off never does. See ADR-0013 §7.4.
const ( NetworkPolicyEnforceAuto NetworkPolicyEnforceMode = "auto" NetworkPolicyEnforceOn NetworkPolicyEnforceMode = "on" NetworkPolicyEnforceOff NetworkPolicyEnforceMode = "off" )
type ProxyBrokerConfig ¶
type ProxyBrokerConfig struct {
// ProxyImage is the image used for the per-run egress proxy
// sidecar. Empty disables the proxy sidecar (the run still
// proceeds; EgressConfigured stays False with reason=ProxyNotConfigured).
ProxyImage string
// BrokerEndpoint is the in-cluster broker URL the proxy sidecar
// calls for ValidateEgress + SubstituteAuth. Empty disables
// broker-backed proxy enforcement (proxy falls back to
// --proxy-allow static list).
BrokerEndpoint string
// BrokerNamespace is the namespace where the broker is deployed
// (default `paddock-system`). Used by the per-pod NetworkPolicy
// to allow broker egress when NP enforcement is on.
BrokerNamespace string
// BrokerPort is the broker's TLS service port. Defaults to 8443
// when 0; populated from --broker-port at manager startup.
// (Previously defaulted inside buildBrokerEgressRule with no CLI
// override; promoted to a real flag in this refactor.)
BrokerPort int32
// BrokerCASource names the cert-manager-issued broker-serving-cert
// Secret whose ca.crt is copied into per-run/per-workspace
// broker-ca Secrets. Zero Name disables broker-CA copy.
BrokerCASource BrokerCASource
// ProxyCAClusterIssuer is the cert-manager ClusterIssuer (kind:
// CA) that signs per-run intermediate CAs (F-18 / Phase 2f).
// Empty disables proxy-TLS integration.
ProxyCAClusterIssuer string
// NetworkPolicyEnforce selects whether per-pod NetworkPolicy
// objects are emitted. "auto" defers to the CNI probe.
NetworkPolicyEnforce NetworkPolicyEnforceMode
// NetworkPolicyAutoEnabled is set at manager startup from the
// CNI probe when NetworkPolicyEnforce="auto".
NetworkPolicyAutoEnabled bool
// ClusterPodCIDR is the cluster's pod CIDR. Excluded from
// per-pod NetworkPolicy public-internet egress (F-19).
ClusterPodCIDR string
// ClusterServiceCIDR is the cluster's service CIDR. Same
// purpose as ClusterPodCIDR.
ClusterServiceCIDR string
// APIServerIPs is the set of IPv4 addresses the controller
// resolves the kube-apiserver to (F-41). Each becomes a /32
// allow rule in the per-pod NP.
APIServerIPs []net.IP
// CiliumCNPAvailable reports whether the cluster has the
// CiliumNetworkPolicy CRD registered. Set at controller-manager
// startup via DetectCiliumCNP. When true, ensureRunNetworkPolicy
// emits a CiliumNetworkPolicy variant; when false, it emits a
// standard NetworkPolicy. Issue #79.
CiliumCNPAvailable bool
}
ProxyBrokerConfig is the shared cluster-and-manager configuration that both reconcilers (HarnessRun + Workspace) need to render proxy-sidecar pod specs and per-pod NetworkPolicies. Defined once here, embedded in both reconciler structs, populated from one set of CLI flags in cmd/main.go.
Adding a new flag now requires editing only this struct, the flag-parsing block, and the populate-then-assign pair in main.go — not four places (two reconciler structs + two assignments).
type WorkspaceReconciler ¶
type WorkspaceReconciler struct {
client.Client
Scheme *runtime.Scheme
Recorder record.EventRecorder
// SeedImage overrides the default alpine/git image. Primarily for
// tests; production uses defaultSeedImage.
SeedImage string
// Audit is the canonical sink for terminal-condition events emitted
// by the Workspace reconciler (F-51 ca-misconfigured). Optional;
// nil falls back to silent, with status conditions remaining the
// primary signal.
Audit *ControllerAudit
// ProxyBrokerConfig carries the shared cluster-and-manager config
// used to render seed-pod proxy sidecars and per-seed-Pod
// NetworkPolicies. Populated once in cmd/main.go and embedded in
// both reconcilers.
ProxyBrokerConfig
}
WorkspaceReconciler reconciles a Workspace object.
func (*WorkspaceReconciler) Reconcile ¶
Reconcile brings a Workspace to its desired state. See package doc and docs/internal/specs/0001-core-v0.1.md §3.2 for the state machine.
func (*WorkspaceReconciler) SetupWithManager ¶
func (r *WorkspaceReconciler) SetupWithManager(mgr ctrl.Manager) error
SetupWithManager registers the reconciler with the manager and wires up watches for owned resources.
Source Files
¶
- apiserver_ips.go
- audit.go
- auditevent_controller.go
- broker_ca.go
- broker_client.go
- broker_credentials.go
- brokerpolicy_controller.go
- cilium_network_policy.go
- cni_probe.go
- conditions.go
- harnessrun_controller.go
- interactive_watchdog.go
- metrics.go
- network_policy.go
- pod_spec.go
- proxy_tls.go
- proxybroker_config.go
- template_resolve.go
- workspace_broker.go
- workspace_controller.go
- workspace_seed.go
- workspace_seed_helpers.go