controller

package
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: May 2, 2026 License: Apache-2.0 Imports: 42 Imported by: 0

Documentation

Overview

Package controller implements the Paddock reconcilers. See docs/internal/specs/0001-core-v0.1.md §3 for the design.

Index

Constants

View Source
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.

View Source
const CiliumGroupVersion = "cilium.io/v2"

CiliumGroupVersion is the API group/version that hosts CiliumNetworkPolicy. Stable across Cilium 1.x.

View Source
const DefaultAuditRetention = 30 * 24 * time.Hour

DefaultAuditRetention is the default window after which AuditEvents are reaped. See ADR-0016.

View Source
const DefaultCollectorImage = "paddock-collector:dev"

DefaultCollectorImage is used when the reconciler does not override it. Overridable via the manager's --collector-image flag (M7+).

View Source
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.

View Source
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.

View Source
const DefaultProxyImage = "paddock-proxy:dev"

DefaultProxyImage is used when the reconciler does not override it. Overridable via --proxy-image. Zero string disables the sidecar.

View Source
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.

View Source
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.

View Source
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

View Source
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

func APIServerIPsFromConfig(cfg *rest.Config) ([]net.IP, error)

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

func DetectNetworkPolicyCNI(ctx context.Context, c client.Reader) (bool, string, error)

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

func IsBrokerCodeFatal(err error) bool

IsBrokerCodeFatal reports whether a broker error is user-actionable (should fail the run) vs transient (should requeue).

func IsDigestPinnedImageRef

func IsDigestPinnedImageRef(ref string) bool

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

type BrokerCASource struct {
	Namespace string
	Name      string
}

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) Reconcile

func (r *BrokerPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

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

type ControllerAudit struct {
	Sink auditing.Sink
}

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

func (r *WorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

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.

Directories

Path Synopsis
Package testutil holds shared fakes and helpers for tests that exercise the controller package.
Package testutil holds shared fakes and helpers for tests that exercise the controller package.

Jump to

Keyboard shortcuts

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