kubernetes

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package kubernetes implements the executor.Executor interface by running each workload as an ephemeral Pod in a Kubernetes cluster.

It is the fourth backend, alongside localprocess (no isolation), container (a sandbox on the control-plane host) and remote (an enrolled edge device). Kubernetes is the one that scales: the control plane holds no compute, the cluster schedules the work, and a hosted deployment can run tenants' agents on nodes that share nothing with the server answering their HTTP requests.

Lifecycle

Start creates a Pod with generateName, restartPolicy Never and no long-lived identity, then returns. A watcher goroutine follows the Pod's phase; when the container reaches Running it also opens the Pod log API with follow=true and pipes every chunk into the same pkg/executor/internal/logbus every other driver uses, so the UI's event stream is byte-for-byte the same shape it was for a local process.

Signal deletes the Pod: Kubernetes has no signal API, and deletion is what makes the kubelet send SIGTERM (or SIGKILL at grace period zero). SignalInterrupt is therefore delivered as SIGTERM — the closest thing the platform offers — and that difference is documented rather than hidden, because a cloop run traps SIGINT to checkpoint and will instead see a TERM.

Credentials

The kubeconfig comes from a pkg/secretbroker lease and is consumed in memory; it never becomes a file on the control-plane host. The lease is held for the handle's lifetime (the driver needs it to watch, to read logs and above all to delete the Pod), renewed while the run continues, and released when the handle reaches a terminal state — on the failure paths as well as the success one. See credentials.go.

A git workspace needs a second, unrelated credential: the one that authenticates the fetch. It is leased separately, lives in the cluster for the length of one init container rather than for the length of the run, and never appears in the Pod object. See workspace.go.

Where the source tree comes from

A Pod shares no filesystem with the control plane, so unlike the container driver there is nothing to bind-mount. Spec.Workspace says how the tree arrives: kind git adds an init container that fetches it before the harness starts, and kind none means the workload wanted an empty directory. A kind bind spec is refused, because honouring it would mean starting the harness in an empty emptyDir and calling it the project's code.

What the confinement is, and is not

Every Pod is built by buildPod with runAsNonRoot, a read-only root filesystem, all capabilities dropped, seccomp RuntimeDefault, and automountServiceAccountToken false so no in-cluster API credential is handed to model-authored code. Those are not configurable.

Egress is filtered when — and only when — an operator configures it. With executors.kubernetes.egress_filter.enabled set, every Pod gets a companion NetworkPolicy created before it and deleted with it, selecting that Pod alone and compiled from the same pkg/netfilter policy the container driver's nftables ruleset comes from; see networkpolicy.go. Without it a Pod gets the cluster's pod network, which by default reaches every other Pod and the Internet, and this driver says so rather than implying otherwise.

Even with the filter on, one claim stays outside cloop's reach: a NetworkPolicy is inert unless the cluster's CNI implements it, and the API server accepts the object either way. Preflight reports that as a warning because it is a fact about the cluster, not about the driver.

Nor does it hide the workload's environment. Spec.Env lands in the Pod object, readable by anyone with `get pods` in the namespace. That is why a nil Spec.Env forwards *nothing* here (unlike os/exec, which would inherit the control plane's whole environment) and why the namespace should be one only the control plane can read.

Index

Constants

View Source
const (
	// DefaultNamespace is where Pods land when the config names no namespace.
	DefaultNamespace = "cloop"

	// DefaultKillGracePeriod is how long a Pod gets between SIGTERM and
	// SIGKILL when Signal(SignalKill) is called. Short by design: a kill is
	// a demand, not a request, and the caller has usually already tried
	// SignalTerminate.
	DefaultKillGracePeriod = 5 * time.Second

	// DefaultTerminationGracePeriod is the Pod's own grace period, used for
	// SignalTerminate and SignalInterrupt. Matches Kubernetes' default so a
	// harness that traps SIGTERM has the time it expects.
	DefaultTerminationGracePeriod = 30 * time.Second

	// DefaultOrphanGracePeriod protects a Pod young enough that it might
	// belong to a start still in flight. Only non-terminal Pods older than
	// this are garbage-collected; terminal ones are reaped immediately.
	DefaultOrphanGracePeriod = 10 * time.Minute
)
View Source
const (

	// AnnotationEgressWarnings carries netfilter's warnings about where the
	// rendered filter is necessarily wider than the authorisation it came from.
	//
	// An annotation and not a label: these are sentences, and the whole point is
	// that a human reads them. The one that matters is "your host allowlist
	// became the public Internet", which is invisible in the rendered spec —
	// 0.0.0.0/0 with a handful of excepts looks like a deliberate choice.
	AnnotationEgressWarnings = "cloop.dev/egress-warnings"

	// AnnotationEgressMode records the shape of the authorisation the policy was
	// compiled from: isolated, brokered or filtered. It answers "was this Pod
	// supposed to reach anything at all" without re-deriving it from the peers.
	AnnotationEgressMode = "cloop.dev/egress-mode"
)
View Source
const (
	LabelManaged    = "cloop.dev/managed"
	LabelExecutorID = "cloop.dev/executor-id"
	LabelTaskID     = "cloop.dev/task-id"
	LabelHandleID   = "cloop.dev/handle-id"
	LabelProject    = "cloop.dev/project"

	// AnnotationProjectPath carries the untruncated project path. Label
	// values are capped at 63 characters and constrained to a small
	// alphabet, so a path only survives intact as an annotation.
	AnnotationProjectPath = "cloop.dev/project-path"
	// AnnotationArgv records the command, for an operator reading
	// `kubectl describe pod`.
	AnnotationArgv = "cloop.dev/argv"
	// AnnotationSandboxHash records the .cloop/sandbox.yaml content hash the
	// Pod was shaped by. An annotation rather than a label: it is for reading,
	// not selecting, and a 64-character hash exceeds the label value cap.
	AnnotationSandboxHash = "cloop.dev/sandbox-hash"
)

Label keys applied to every Pod this driver creates. The GC sweep selects on ExecutorID plus the presence of TaskID, so both are always set — a Pod with no task is labelled "none" rather than left unlabelled, which would make it invisible to reconciliation.

View Source
const (
	// ContainerName is the harness container's name. Fixed, because the log
	// endpoint needs to name a container and a Pod with exactly one of them
	// gains nothing from a configurable name.
	ContainerName = "harness"

	// InitContainerName is the workspace provisioner's name. It is a *second*
	// named container in the Pod, so anything that reads a container status by
	// name — podWaitingDetail, classifyPod, the log endpoint — must keep
	// selecting ContainerName and not "the only one".
	InitContainerName = "workspace"

	// PodWorkspace is where the writable workspace volume is mounted, and the
	// container's working directory when the Spec does not override it.
	//
	// Unlike the container driver this is *not* a bind mount of a host path:
	// there is no host to bind from. Either the Spec's Workspace says how the
	// tree gets here (kind git, provisioned by the init container below) or it
	// says "none" and the workload genuinely wants an empty directory.
	PodWorkspace = "/workspace"

	// DefaultRunAsUser is the UID/GID the harness runs as. 65532 is the
	// "nonroot" user in distroless and Chainguard images, so the default
	// works with the images an operator is most likely to reach for.
	DefaultRunAsUser  int64 = 65532
	DefaultRunAsGroup int64 = 65532
)
View Source
const (
	// EnvWorkspaceToken carries the credential. It is *only* ever set through
	// valueFrom.secretKeyRef: a value: entry here would put the token into the
	// Pod object, which anybody with `get pods` in the namespace can read, and
	// into every audit log and `kubectl describe` that follows.
	//
	// It carries the bare token rather than a rendered "Basic <b64>"
	// Authorization header. Base64 is not protection — the header form is
	// exactly as recoverable — so the choice is decided by which one keeps a
	// single construction path: the provisioner rebuilds an
	// executor.GitCredential and calls executor.GitCredentialEnv, the same
	// function the remote agent calls, so the header is rendered by one piece
	// of code for every driver. Delivering a pre-rendered header would mean a
	// second renderer that could drift from the first.
	EnvWorkspaceToken = "CLOOP_WORKSPACE_TOKEN"
	// EnvWorkspaceUser carries the basic-auth username. It is a plain value,
	// because it is not a secret: for a GitHub PAT it is the fixed literal
	// "x-access-token", and hiding a constant would only make the Pod harder
	// to debug for no gain.
	EnvWorkspaceUser = "CLOOP_WORKSPACE_USER"
)

The environment the workspace init container reads its credential from.

These names are the contract between this driver and `cloop workspace provision`, which reads them from here rather than repeating the literals — the driver that emits the argv owns the names. They are here rather than in pkg/executor because they are a *delivery* detail — the remote agent runs the same plan by handing GitCredentialEnv straight to a child process and needs no environment names at all — and because the Pod object is where the names have to be spelled out.

View Source
const (
	// LevelOK: the check passed.
	LevelOK = "ok"
	// LevelWarn: usable, but with a caveat the operator should know about.
	LevelWarn = "warn"
	// LevelFail: workloads will not run until this is fixed.
	LevelFail = "fail"
)

Preflight check levels, matching the container driver's vocabulary so `cloop executor test` renders both the same way.

View Source
const CurrentProbeVersion = 1

CurrentProbeVersion is the revision of the experiment netpolprobe.go runs. Bump it whenever the experiment changes what it proves.

View Source
const DefaultID = "kubernetes"

DefaultID is the executor ID used when none is configured.

View Source
const DefaultImage = "ghcr.io/blechschmidt/cloop-harness:latest"

DefaultImage is the sandbox image used when the operator names none.

Unlike the container driver there is no bind-mounted host binary to fall back on, so the image must actually contain the harness. This default is a placeholder that an operator is expected to replace; preflight and `cloop executor test` say so.

View Source
const DefaultServiceAccountDir = "/var/run/secrets/kubernetes.io/serviceaccount"

DefaultServiceAccountDir is where the kubelet projects a Pod's ServiceAccount credentials.

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

DefaultVerdictMaxAge is how long a probe result is allowed to speak for the cluster.

A verdict is a measurement of someone else's infrastructure, and a CNI can be replaced by a platform team that has never heard of cloop. Expiring the record turns that from a silent, permanent over-permission into a placement refusal naming the command that refreshes it — the failure mode an operator can act on. Thirty days is long enough that nobody re-probes during an incident and short enough that a CNI swap is caught within a release cycle.

View Source
const LabelEgress = "cloop.dev/egress"

LabelEgress declares whether this workload is supposed to reach the network: "deny" when the project's sandbox spec asked for no egress, "allow" otherwise.

It is a *label*, unlike the sandbox hash, precisely so a NetworkPolicy can select on it. A Pod spec has no field that turns egress off; only a NetworkPolicy does, so the driver states the intent in the one place an enforcement mechanism can read it.

What enforces it depends on configuration, and the difference is worth being exact about. With executors.kubernetes.egress_filter enabled, this driver creates the policy itself — one per Pod, selecting that Pod by its handle-id label, denying everything when this label reads "deny" (see networkpolicy.go). Without it, nothing here enforces anything: the label is documentation for an operator's own namespace-wide default-deny policy, and a namespace without one leaves every Pod with the cluster's full egress.

The alternative was to refuse every sandbox spec that omits capabilities.network on Kubernetes, which is nearly all of them — a guarantee bought by making the executor unusable is not a trade worth making silently either.

Variables

View Source
var ErrNoKubeconfigGrant = errors.New("kubernetes: no kubeconfig grant is available for this executor")

ErrNoKubeconfigGrant is returned when the broker issued a lease that carries no kubeconfig this executor may use. It is a denial, not a misconfiguration to work around: without a kubeconfig there is no cluster to run in, and falling back to anything else would be host execution.

View Source
var ErrNotInCluster = errors.New(
	"kubernetes: in-cluster mode requires a projected ServiceAccount, but this process is not running in a Pod")

ErrNotInCluster is returned when in-cluster mode is requested from a process that is not running as a Pod. It is deliberately a hard failure: the alternative is silently falling back to some other credential, and "which identity is this executor actually using" must never be a question answered by whatever happened to be available.

View Source
var ErrProbeInconclusive = errors.New("kubernetes: network policy probe inconclusive")

ErrProbeInconclusive is returned when the experiment could not establish its control — i.e. the probe proved nothing, in either direction.

Distinct from a refutation on purpose, and callers must keep it distinct: "the probe failed" and "the cluster does not enforce policies" are different facts, and collapsing them would either strip a working cluster of a capability or hand an unenforcing one a verdict it did not earn.

Functions

func AuditCapabilities

func AuditCapabilities(opts Options) (executor.Capabilities, error)

AuditCapabilities returns what this driver claims to support, without the credential source a real constructor demands.

Placement reads nothing else, so a conformance suite asserting "every backend either delivers this or refuses it" needs the real answer from the real driver — a table of capabilities written out in a test would agree with itself forever while production drifted. New() cannot be used because it insists on a kubeconfig grant, which is correct for a driver that would otherwise fall back to host execution and useless for reading a struct.

func AuditPodImage

func AuditPodImage(ctx context.Context, opts Options, spec executor.Spec) (string, error)

AuditPodImage returns the image a Pod would actually be created with for spec, or the error that refuses it.

It is the answer to "what would the kubelet pull", which for a digest-pinned policy must be a digest and never the tag a project wrote. No credential source and no cluster are needed: nothing on this path touches either.

func AuditPodJSON

func AuditPodJSON(ctx context.Context, opts Options, spec executor.Spec, workspaceSecret string) ([]byte, error)

AuditPodJSON returns the exact bytes that would be POSTed to the API server for spec, with workspaceSecret standing in for the Secret that Start would have created.

It exists for one assertion the conformance suite has to be able to make from outside this package: that a leased workspace credential never appears in the Pod object. That property is decided by buildPod — whether the token goes into an `env[].value` or into a `secretKeyRef` — and an in-package test of it would be checking the same file that would be edited to break it. Rendering the real object and letting an external suite grep the bytes is the only form of the check that a future refactor cannot quietly satisfy.

Like AuditPodImage it needs no cluster and no credential source: buildPodFor touches neither.

func ImageWarnings

func ImageWarnings(ref string) []string

ImageWarnings returns advisory notes about an otherwise-valid image reference. Separate from ValidateImageRef because a floating tag is a bad idea, not an error: plenty of legitimate deployments track :latest on purpose, and refusing to boot over it would be disproportionate.

func ProbeCommand added in v0.0.2

func ProbeCommand(executorID string) string

ProbeCommand is the command an operator runs to settle the question. It is quoted in the placement refusal, in preflight and in `cloop hub doctor`, because a refusal that does not name its own remedy is a refusal the operator resolves by disabling the feature.

func ValidateImageRef

func ValidateImageRef(ref string) error

ValidateImageRef rejects image references that are not usable as one.

It is deliberately shallow: full reference grammar lives in the registry clients, and re-implementing it here would reject valid references. What it does catch is the shapes that break something — a leading dash the API server would read as a flag in tooling downstream, whitespace, an empty tag — plus the ":latest" habit, which for an execution sandbox means the code you audited and the code you run are different artefacts.

func ValidateNamespace

func ValidateNamespace(ns string) error

ValidateNamespace checks a namespace name against the API's own rules, and refuses the namespaces a cloop workload must never be scheduled into.

kube-system in particular: a Pod there frequently inherits privileged PodSecurity exemptions and sits next to the control plane's own components. A workload running model-authored code has no business in that blast radius, and an operator who typed it into a config file almost certainly meant something else.

func ValidateQuantity

func ValidateQuantity(q string) error

ValidateQuantity checks a Kubernetes resource quantity string.

The full quantity grammar supports scientific notation and signed values; this accepts the subset that means anything for a CPU or memory allowance: a positive decimal with an optional binary ("Ki", "Mi", "Gi", "Ti", "Pi") or decimal ("n", "u", "m", "k", "M", "G", "T", "P", "E") suffix.

Rejecting rather than passing through is the point. A quantity the API server cannot parse fails the Pod create with a validation error that names the field but not the value, and an operator ends up bisecting their config to find which of six limits was the typo.

func ValidateRuntimeClass

func ValidateRuntimeClass(name string) error

ValidateRuntimeClass checks a RuntimeClass name. Empty is valid and means "the cluster default".

A RuntimeClass is a cluster-scoped object whose name is an RFC 1123 subdomain, so the namespace grammar applies unchanged. Checking it here rather than letting the API server reject the Pod turns a per-run 422 into a startup error against the config line that caused it.

Types

type APIError

type APIError struct {
	// Code is the HTTP status.
	Code int
	// Reason is the API's machine-readable reason ("NotFound", "Forbidden").
	Reason string
	// Message is the API's human-readable message.
	Message string
	// Verb and Path locate the call that failed.
	Verb string
	Path string
}

APIError is a non-2xx response from the API server, decoded.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) Expired

func (e *APIError) Expired() bool

Expired reports whether a watch was rejected because its resourceVersion aged out of the API server's history window.

func (*APIError) NotFound

func (e *APIError) NotFound() bool

NotFound reports whether the object does not exist. Deleting an already-absent Pod is success, not failure, so callers check this.

func (*APIError) Retryable

func (e *APIError) Retryable() bool

Retryable reports whether repeating the call could plausibly succeed. Authorization and validation failures are excluded: retrying them just turns one clear error into a slow one.

type BrokerSource

type BrokerSource struct {
	// Broker is the control plane's broker. Required.
	Broker *secretbroker.Broker
	// ExecutorID is the subject grants are matched against.
	ExecutorID string
	// SecretRef optionally pins which kubeconfig secret to use, by name or
	// ID. Empty accepts the first kubeconfig grant in the lease, which is the
	// common case (a project has one cluster).
	SecretRef string
	// Context selects a kubeconfig context. Empty uses current-context.
	Context string
	// Actor is recorded in the broker's audit trail.
	Actor string
}

BrokerSource leases kubeconfigs from a secretbroker.Broker.

func NewBrokerSource

func NewBrokerSource(b *secretbroker.Broker, executorID, secretRef, kubeContext string) (*BrokerSource, error)

NewBrokerSource validates and returns a broker-backed source.

func (*BrokerSource) Acquire

func (s *BrokerSource) Acquire(ctx context.Context, projectID string) (*Credentials, error)

Acquire implements CredentialSource.

func (*BrokerSource) Describe

func (s *BrokerSource) Describe() string

Describe implements CredentialSource.

func (*BrokerSource) Release

func (s *BrokerSource) Release(leaseID string)

Release implements CredentialSource.

func (*BrokerSource) Renew

func (s *BrokerSource) Renew(ctx context.Context, leaseID string) (*Credentials, error)

Renew implements CredentialSource.

type CredentialSource

type CredentialSource interface {
	// Acquire leases a kubeconfig for projectID.
	Acquire(ctx context.Context, projectID string) (*Credentials, error)
	// Renew re-evaluates the grants behind an existing lease. Returning an
	// error means the authority was withdrawn, and the caller terminates the
	// workload.
	Renew(ctx context.Context, leaseID string) (*Credentials, error)
	// Release drops a lease. Must be safe to call with an unknown ID.
	Release(leaseID string)
	// Describe is an audit-safe one-liner for diagnostics.
	Describe() string
}

CredentialSource supplies kubeconfig leases.

It is an interface so the driver can be tested against a fake API server without standing up a broker, an encrypted secret store and a grant — but the only production implementation is BrokerSource, and Options.Credentials has no default. An executor with no source configured refuses to start anything rather than reaching for a kubeconfig on disk.

type Credentials

type Credentials struct {
	// Rest is the parsed connection config. Holds the credential in memory.
	Rest *RESTConfig
	// LeaseID identifies the broker lease to renew and release.
	LeaseID string
	// ExpiresAt is when the lease stops being valid.
	ExpiresAt time.Time
	// SecretName and Summary are audit-safe descriptions of what was
	// delivered — never the credential itself.
	SecretName string
	Summary    string
	// Namespace is the namespace the grant pinned, when it pinned one. The
	// executor's configured namespace still wins; this is the fallback.
	Namespace string
}

Credentials is one acquired kubeconfig lease, already parsed.

type EgressFilter

type EgressFilter struct {
	// Enabled turns the filter on.
	//
	// Off means *no NetworkPolicy is created at all*, not an empty one. An
	// empty one would deny everything, which is the safe default for a new
	// deployment and a silent, total outage for an existing one — and a
	// security feature that arrives without being switched on is a feature
	// operators learn to switch off.
	Enabled bool

	// CIDRs are the destination ranges a workload may reach, as "10.8.0.0/24".
	// They are also the only thing that waives netfilter's block set, exactly as
	// in the egress proxy: naming a private range explicitly buys that range and
	// nothing else.
	CIDRs []string

	// Ports bound every destination allow. Naming destinations without ports is
	// refused rather than guessed at — see netfilter.Compile.
	Ports []int

	// AllowPublicInternet opens every address outside the block set on Ports.
	// It is the only way to express "a hostname allowlist" at layer 3, and it is
	// a separate switch so that widening the filter to the whole Internet is
	// something an operator states rather than something that happens to them.
	AllowPublicInternet bool

	// Resolvers are DNS servers the sandbox may query directly, as "ip:port" or
	// as a bare address (port 53 assumed). Needed only when cluster DNS is not
	// the resolver the workload uses; AllowClusterDNS covers the usual case.
	Resolvers []string

	// AllowClusterDNS opens UDP and TCP 53 to the kube-system namespace.
	//
	// A pointer so that "unset" can mean true. Without it a default-deny egress
	// policy breaks name resolution, and the failure reads to everyone involved
	// as "the network is broken" rather than "DNS is denied" — a diagnosis that
	// costs an afternoon and ends with the filter being turned off.
	AllowClusterDNS *bool

	// AllowAllPorts waives the requirement to name ports alongside
	// destinations. No config key sets it, and none should: it exists for
	// forScope, where the bound being asked for is the *block set* rather than
	// a port list, and demanding a port list there would force a project that
	// said "the public Internet" to enumerate every port it might ever use.
	AllowAllPorts bool
}

EgressFilter is the executor's egress authorisation, in the shape config writes it.

It holds strings and ints rather than netip types because it is a deserialised config section; parsing happens once, in Input, and its errors name the key that has to change. That keeps the config package free of address handling and keeps this the only place that decides what a malformed CIDR means.

The zero value is "not filtering", and that is load-bearing: an existing deployment upgrading into this code must not have its Pods firewalled by a field nobody set. See Enabled.

func (EgressFilter) AllowsEgress

func (f EgressFilter) AllowsEgress() bool

AllowsEgress reports whether any destination is reachable at all.

It is what Capabilities().NetworkEgress answers with, so a filter that names no destination refuses placement for projects that require the network rather than accepting them into a Pod that can reach nothing.

Resolvers and cluster DNS deliberately do not count. A sandbox that can resolve names and connect to nothing has no egress in any sense a project requiring the network means.

func (EgressFilter) ClusterDNSAllowed

func (f EgressFilter) ClusterDNSAllowed() bool

ClusterDNSAllowed reports the effective setting, applying the on-by-default rule for an absent one.

func (EgressFilter) Compile

func (f EgressFilter) Compile() (netfilter.Policy, error)

Compile turns the filter into the ordered policy both backends render from.

Callers use it for validation as much as for rendering: compiling at config load is what turns a CIDR with a typo in it into a startup error naming the key, rather than a Pod that refuses to start at three in the morning.

func (EgressFilter) Describe

func (f EgressFilter) Describe() string

Describe renders the filter as a sentence for preflight and `cloop executor test`.

It compiles rather than paraphrasing the config, so what it reports is what the Pods actually get: the mode is derived by netfilter, and a filter that widened to the public Internet says so here rather than reading like the narrow allowlist someone thought they wrote.

func (EgressFilter) Input

func (f EgressFilter) Input() (netfilter.Input, error)

Input projects the config section onto the authorisation netfilter compiles.

Errors name the config key and the offending value, because the operator reading them is looking at YAML, not at a netip.Prefix.

func (EgressFilter) Normalize

func (f EgressFilter) Normalize() (EgressFilter, error)

Normalize trims the filter's string fields and proves it compiles.

It validates even when Enabled is false, matching the rest of the config surface: a broken section should be reported when it is written, not discovered months later at the moment someone flips a boolean.

type EnforcementStatus added in v0.0.2

type EnforcementStatus string

EnforcementStatus is the resolved answer, and the vocabulary the CLI, the preflight report and the placement refusal all use.

const (
	// EnforcementUnverified: nobody has said, and nothing has been proved.
	// Fails closed.
	EnforcementUnverified EnforcementStatus = "unverified"

	// EnforcementProven: a probe watched a connection that worked stop working
	// when a policy was applied.
	EnforcementProven EnforcementStatus = "proven"

	// EnforcementAsserted: an operator set network_policy_enforced: true and no
	// probe has contradicted them.
	EnforcementAsserted EnforcementStatus = "asserted"

	// EnforcementRefuted: a probe watched a connection survive a default-deny
	// policy. The cluster accepts NetworkPolicies and ignores them.
	EnforcementRefuted EnforcementStatus = "refuted"

	// EnforcementDenied: an operator set network_policy_enforced: false. It is
	// how you switch the capability off after a CNI change without waiting for
	// a recorded verdict to age out.
	EnforcementDenied EnforcementStatus = "denied"
)

func (EnforcementStatus) Enforced added in v0.0.2

func (s EnforcementStatus) Enforced() bool

Enforced reports whether the status is one that may carry a per-project egress scope. Only two of the five are, and both of them are a positive statement by someone: everything else — including every error, every expiry and every absence — lands on false.

type Executor

type Executor struct {
	// contains filtered or unexported fields
}

Executor runs workloads as ephemeral Pods. Safe for concurrent use.

func Ensure

func Ensure(reg *executor.Registry, opts Options) (*Executor, error)

Ensure registers a Kubernetes executor built from opts into reg (nil means executor.DefaultRegistry) unless one with the same ID is already present.

func New

func New(opts Options) (*Executor, error)

New returns a Kubernetes executor. It performs no cluster I/O: an unreachable cluster surfaces at HealthCheck or Start, not at construction, so a control plane still boots when the cluster is briefly down.

That promise survives Options.HandleStore. Rehydration reads the store — a local row read — and rebuilds the handle map synchronously, because a Pod that is not in the map before New returns is a Pod ReconcileOrphans may legitimately kill; every API round-trip an adopted handle needs happens afterwards, on that handle's own goroutine. See rehydrate.go.

func (*Executor) Attach added in v0.0.2

Attach implements executor.Attacher.

func (*Executor) AttachHandleStore

func (e *Executor) AttachHandleStore(store executor.HandleStore)

AttachHandleStore installs the durable handle store after construction and rehydrates from it immediately.

It exists because of boot order, not convenience: the drivers are built from config before the state database is open, so the executor that most needs a store is constructed without one. Calling this later is equivalent to having passed Options.HandleStore, because rehydration is idempotent — a handle already in the map is left alone — so a caller that does both, or that re-attaches the same store on a config reload, adopts each Pod once.

A nil store is ignored rather than clearing the current one: "forget how to find your Pods" is not something a caller should be able to ask for by passing a zero value.

func (*Executor) Capabilities

func (e *Executor) Capabilities() executor.Capabilities

Capabilities implements executor.Executor.

Isolation is IsolationRemote rather than IsolationContainer: kube-scheduler places the Pod on a cluster node, which in any deployment that is not deliberately co-located is a different machine from the control plane. Even when it is co-located the boundary is still a container, so the claim is never weaker than IsolationContainer would have been.

SharesHostFilesystem is false, and that is the substantive difference from the container driver: there is no bind mount, so Spec.WorkDir names a path *inside* the Pod and the control plane cannot read what the workload wrote.

NetworkEgress follows the configured egress filter. With no filter a Pod joins the cluster network and can reach it, so it is true — reporting false would claim an isolation cloop does not enforce. With a filter that names no destination it is false, because nothing is reachable, and a project that requires the network is better refused at placement than scheduled into a Pod whose every packet is dropped.

Virtualized is set from the configured RuntimeClass, and it is why that is a field of its own rather than another Isolation value. A Kata Pod is remote *and* virtualized: the machine is not the control plane's and the kernel is not the node's. Isolation can carry only one of those, so it keeps the one this driver always has and Virtualized carries the one it sometimes adds.

func (*Executor) Close

func (e *Executor) Close()

Close stops every watcher, releases every lease and closes every client.

It deliberately does not delete running Pods. A control plane restarting for an upgrade should not destroy work in flight; the Pods carry their own activeDeadlineSeconds where one is configured, and ReconcileOrphans collects whatever is left on the way back up.

func (*Executor) EnforcementState added in v0.0.2

func (e *Executor) EnforcementState() (EnforcementStatus, string, ProbeVerdict)

EnforcementState reports whether this cluster is known to enforce a NetworkPolicy, the sentence explaining why, and the verdict it rests on.

The sentence is not decoration: it is what preflight prints, what the placement refusal carries and what `cloop hub doctor` renders, and having one derivation feed all three is what keeps the report and the refusal from disagreeing about the same cluster.

func (*Executor) EnforcementStatus added in v0.0.2

func (e *Executor) EnforcementStatus() EnforcementStatus

EnforcementStatus is the gate alone, for callers that do not need the sentence.

Capabilities() calls this for every candidate on every placement decision, so it resolves without building the explanation — see NetworkPolicyEnforcement.resolve.

func (*Executor) ExplainEgressScopeRefusal added in v0.0.2

func (e *Executor) ExplainEgressScopeRefusal() string

ExplainEgressScopeRefusal implements executor.EgressScopeExplainer: it says why this executor is refusing a project's egress scope, and what to run.

The two refusals are not the same problem and must not read as though they were. "Nobody has checked" is settled by running the probe; "the probe found your CNI ignores NetworkPolicy" is settled by changing CNI, and telling that operator to re-run the probe would send them round a loop that cannot terminate.

func (*Executor) HandleStatuses

func (e *Executor) HandleStatuses(ctx context.Context) ([]executor.Status, error)

HandleStatuses implements executor.Lister from the driver's own bookkeeping. It deliberately does not call the API server: the Executors panel reads this for every card it renders, and a slow cluster must not turn a status column into a page-load stall.

func (*Executor) Handles

func (e *Executor) Handles() []string

Handles returns the IDs of every handle this executor still knows about, newest first.

func (*Executor) HealthCheck

func (e *Executor) HealthCheck(ctx context.Context) error

HealthCheck implements executor.Executor: acquire a lease, reach /version, release. It exercises the whole credential path rather than just pinging an endpoint, because "the cluster is up but the grant was revoked" is the failure an operator actually hits.

func (*Executor) HoldsLease

func (e *Executor) HoldsLease(leaseID string) bool

HoldsLease reports whether any Pod this executor tracks was started with material from leaseID.

func (*Executor) ID

func (e *Executor) ID() string

ID implements executor.Executor.

func (*Executor) Image

func (e *Executor) Image() string

Image reports the harness image this executor runs.

func (*Executor) KernelIsolated added in v0.0.2

func (e *Executor) KernelIsolated() bool

KernelIsolated reports whether a Pod's system calls are served by something other than the node's kernel — a Kata guest kernel or gVisor's Sentry.

The RuntimeClass name is the only evidence available and the whole mechanism: a cluster administrator registers `gvisor` or `kata` as a RuntimeClass, the node's containerd maps it to a shim, and a client can see the name but never the mapping. That is the same trust the Virtualized claim already rests on, and Preflight is where a mismatch is surfaced.

func (*Executor) Kind

func (e *Executor) Kind() string

Kind implements executor.Executor.

func (*Executor) Leases

func (e *Executor) Leases() []string

Leases lists the lease IDs this executor is holding material for.

func (*Executor) Namespace

func (e *Executor) Namespace() string

Namespace reports the configured namespace, which may be empty when the grant or kubeconfig supplies it per-run.

func (*Executor) Preflight

func (e *Executor) Preflight(ctx context.Context) PreflightReport

Preflight checks everything between this control plane and a running Pod.

func (*Executor) ProbeNetworkPolicyEnforcement added in v0.0.2

func (e *Executor) ProbeNetworkPolicyEnforcement(ctx context.Context, opts ProbeOptions) (ProbeVerdict, error)

ProbeNetworkPolicyEnforcement runs the experiment and returns what it saw.

A returned verdict is always a measurement: Enforced true means a connection that worked stopped working when a policy was applied, and false means it did not stop. Anything the probe could not establish is an error wrapping ErrProbeInconclusive, never a verdict — see the file comment.

func (*Executor) ReconcileOrphans

func (e *Executor) ReconcileOrphans(ctx context.Context) ([]string, error)

ReconcileOrphans deletes Pods this executor owns but no longer tracks.

It is the answer to the failure mode that has no other answer: a control plane killed mid-run loses its in-memory handles, and the Pods it created keep running — burning a node's CPU, holding a ResourceQuota slot, and producing output nobody reads. Nothing else notices, because from the cluster's point of view they are perfectly healthy Pods.

Terminal Pods are removed immediately. Non-terminal ones are removed only once they are older than OrphanGracePeriod, so a Pod belonging to a Start still in flight — or to another control plane that has just booted with the same executor ID — is not killed out from under it.

It returns the namespaced names of the Pods removed.

func (*Executor) RecordNetworkPolicyVerdict added in v0.0.2

func (e *Executor) RecordNetworkPolicyVerdict(v ProbeVerdict)

RecordNetworkPolicyVerdict installs a probe result on a live driver, so a `cloop hub doctor --probe-network-policy` run against a hub's own executor takes effect without waiting for a restart.

It does not persist: durability belongs to the caller that holds the database (see reconcile.SaveNetworkPolicyVerdict). Splitting them keeps this package free of statedb, and keeps a probe run by a CLI against someone else's cluster from writing into a hub's records.

func (*Executor) Revocations

func (e *Executor) Revocations() []executor.RevokeOutcome

Revocations reports this executor's revocation log for the Secrets panel.

func (*Executor) RevokeLease

RevokeLease takes one lease's material back from every Pod holding it.

It returns an outcome rather than an error for the reason the remote driver documents: the caller's next move depends on which failure, and an error alone cannot say whether the material is gone, in doubt, or still out there. The log entry is written before any deletion starts, so a revocation is never lost to a crash between the two.

func (*Executor) RuntimeClass

func (e *Executor) RuntimeClass() string

RuntimeClass reports the RuntimeClass every Pod is created with, empty when the cluster default is in use.

func (*Executor) ServerVersion

func (e *Executor) ServerVersion(ctx context.Context) (string, error)

ServerVersion reports the cluster's version, for diagnostics. It acquires and releases its own lease.

func (*Executor) Signal

func (e *Executor) Signal(ctx context.Context, handleID string, sig executor.Signal) error

Signal implements executor.Executor.

Kubernetes has no signal API. Deletion is the only lever, and it is what makes the kubelet send SIGTERM followed by SIGKILL at the grace period. So:

SignalKill                    → delete with a short grace period
SignalTerminate/SignalInterrupt → delete with the Pod's grace period

SignalInterrupt is therefore *not* SIGINT. A cloop run traps SIGINT to checkpoint; under this driver it will see SIGTERM instead. Pretending otherwise would be worse than saying it.

func (*Executor) Start

func (e *Executor) Start(ctx context.Context, spec executor.Spec) (executor.Handle, error)

Start implements executor.Executor: create a Pod and return its handle. The Pod outlives ctx, which bounds only the act of starting.

func (*Executor) Status

func (e *Executor) Status(ctx context.Context, handleID string) (executor.Status, error)

Status implements executor.Executor.

func (*Executor) Stream

func (e *Executor) Stream(ctx context.Context, handleID string) (<-chan executor.LogLine, error)

Stream implements executor.Executor.

func (*Executor) SupportsRevocation

func (e *Executor) SupportsRevocation() bool

SupportsRevocation reports whether a revocation issued now would be honoured.

True whenever the executor is open. Unlike a remote agent, there is no peer whose protocol version or connectivity the guarantee depends on: the objects are in the API server, and the same client that created them deletes them. A closed executor answers false — its clients are shut and it can reach nothing.

func (*Executor) Virtualized

func (e *Executor) Virtualized() bool

Virtualized reports whether Pods run behind a hypervisor.

type Finding

type Finding struct {
	Name    string `json:"name"`
	Level   string `json:"level"`
	Message string `json:"message"`
	Fix     string `json:"fix,omitempty"`
}

Finding is one preflight observation.

type InClusterSource

type InClusterSource struct {
	// Dir holds the projected ServiceAccount files. Empty means
	// DefaultServiceAccountDir; tests point it at a fixture.
	Dir string
	// Host and Port address the API server. Empty means the
	// KUBERNETES_SERVICE_HOST / KUBERNETES_SERVICE_PORT environment the
	// kubelet injects into every Pod.
	Host string
	Port string
	// Namespace overrides the projected namespace. The executor's configured
	// namespace still wins over both; this is the fallback that makes "run
	// Pods next to the hub" the zero-config behaviour.
	Namespace string
	// contains filtered or unexported fields
}

InClusterSource authenticates as the Pod's own ServiceAccount.

It implements CredentialSource without touching the secret broker, so a hub deployed by the Helm chart needs no kubeconfig secret, no grant and no CLOOP_SECRET_KEY to run workloads in the cluster it lives in.

func NewInClusterSource

func NewInClusterSource(namespaceOverride string) (*InClusterSource, error)

NewInClusterSource validates the ambient environment and returns a source.

It fails rather than deferring, because the useful time to learn that a Deployment forgot automountServiceAccountToken is at startup, next to the executor registration that mentions it — not twenty minutes later inside a task whose output is a 401 from an API server nobody expected to be called.

func (*InClusterSource) Acquire

func (s *InClusterSource) Acquire(ctx context.Context, projectID string) (*Credentials, error)

Acquire implements CredentialSource. projectID is unused: an in-cluster identity is a property of the Pod, not of the tenant, and pretending otherwise would imply a per-project isolation this mode does not provide. Per-project isolation in a cluster comes from binding projects to different executors (and therefore different namespaces), not from this file.

func (*InClusterSource) Describe

func (s *InClusterSource) Describe() string

Describe implements CredentialSource.

func (*InClusterSource) Release

func (s *InClusterSource) Release(string)

Release implements CredentialSource. There is no lease to give back: the credential is a file the kubelet owns, and its lifetime is the Pod's.

func (*InClusterSource) Renew

func (s *InClusterSource) Renew(ctx context.Context, leaseID string) (*Credentials, error)

Renew implements CredentialSource by re-reading the projected token, which is what picks up a kubelet rotation.

type NetworkPolicyEnforcement added in v0.0.2

type NetworkPolicyEnforcement struct {
	// Assertion is executors.kubernetes.network_policy_enforced.
	Assertion *bool

	// Verdict is the recorded probe result, if the hub has one.
	Verdict ProbeVerdict

	// MaxVerdictAge overrides DefaultVerdictMaxAge. Zero uses the default.
	MaxVerdictAge time.Duration
}

NetworkPolicyEnforcement is everything known about whether this cluster enforces a NetworkPolicy, before it is resolved into a single answer.

Assertion is a *bool so that "the operator said no" is distinguishable from "the operator said nothing". They resolve differently and must: the first is a deliberate veto, the second is the default every existing deployment already has.

func (NetworkPolicyEnforcement) Resolve added in v0.0.2

func (n NetworkPolicyEnforcement) Resolve(executorID string, now time.Time) (EnforcementStatus, string)

Resolve reduces the evidence to one status and the sentence explaining it.

The precedence is not arbitrary, and the ordering is the security argument:

  1. An explicit `false` wins outright. It is the restrictive direction, and it is the only control an operator has that takes effect *immediately* — the alternative would be editing config, then racing a recorded verdict that is still inside its expiry.
  2. A fresh probe wins over an assertion, in both directions. This is the "proof beats testimony" rule the capability rests on: an operator who asserted enforcement on a cluster that demonstrably ignores policies has made a mistake, and the measurement is what catches it.
  3. An assertion wins over nothing.
  4. Nothing is unverified, and unverified is not Enforced().

A stale verdict is treated as no verdict — it falls through to the assertion — because an expired measurement is not evidence against anything, and demoting an asserting operator's cluster because a probe ran 31 days ago would be noise, not safety.

func (NetworkPolicyEnforcement) Status added in v0.0.2

func (n NetworkPolicyEnforcement) Status(executorID string, now time.Time) EnforcementStatus

Status is the decision alone, without building the sentence that explains it.

type Options

type Options struct {
	// ID is the executor's registry identifier. Empty means DefaultID.
	ID string
	// Namespace is where Pods are created. Empty means the grant's pinned
	// namespace, then the kubeconfig context's, then DefaultNamespace.
	Namespace string
	// Image is the harness image. Empty means DefaultImage.
	Image string
	// ImagePullPolicy is "", "Always", "IfNotPresent" or "Never".
	ImagePullPolicy string
	// ImagePullSecrets names Secrets in Namespace holding registry auth.
	ImagePullSecrets []string
	// ServiceAccountName runs the Pod under a named ServiceAccount. Its
	// token is still not mounted (automountServiceAccountToken is forced
	// false); the name is for image-pull secrets and Pod Security admission.
	ServiceAccountName string

	// CPURequest/CPULimit/MemoryRequest/MemoryLimit are Kubernetes quantity
	// strings ("500m", "2", "512Mi", "4Gi"). Empty means unset.
	CPURequest    string
	CPULimit      string
	MemoryRequest string
	MemoryLimit   string
	// EphemeralStorageLimit bounds writable scratch ("10Gi").
	EphemeralStorageLimit string
	// WorkspaceSizeLimit bounds the workspace emptyDir specifically.
	WorkspaceSizeLimit string

	// NodeSelector pins scheduling to labelled nodes.
	NodeSelector map[string]string
	// Tolerations lets Pods schedule onto tainted nodes.
	Tolerations []Toleration
	// RuntimeClass names a RuntimeClass for every Pod this executor creates.
	// Empty leaves the cluster's default handler (runc) in place.
	//
	// A Kata class ("kata", "kata-qemu", "kata-clh") is how a remote Kata
	// sandbox is requested: kube-scheduler places the Pod on a node whose
	// RuntimeClass advertises the handler, and the workload boots in a VM
	// with its own kernel. Capabilities reports Virtualized accordingly.
	//
	// Kata nodes are usually tainted so that ordinary workloads do not land
	// on them, so this is normally set together with Tolerations and often
	// NodeSelector.
	RuntimeClass string

	// ActiveDeadlineSeconds is the server-side wall-clock ceiling applied
	// when a Spec requests no timeout. Zero means unbounded, matching the
	// project-wide decision (Task 20148) that runs are long-lived.
	ActiveDeadlineSeconds int64
	// TerminationGracePeriod is the Pod's grace period. Zero means
	// DefaultTerminationGracePeriod.
	TerminationGracePeriod time.Duration
	// KillGracePeriod is used for Signal(SignalKill). Zero means
	// DefaultKillGracePeriod; negative means "no grace at all".
	KillGracePeriod time.Duration

	// RunAsUser/RunAsGroup override the non-root UID/GID. Zero means the
	// distroless "nonroot" defaults. Zero *values* are rejected: this
	// executor always sets runAsNonRoot.
	RunAsUser  int64
	RunAsGroup int64

	// KeepCompletedPods leaves finished Pods in the cluster instead of
	// deleting them once their final status and logs have been captured.
	// Off by default, because a namespace that accumulates one Terminated
	// Pod per task hits its ResourceQuota's pod count long before anyone
	// looks at them. Turn it on to debug a failing image; ReconcileOrphans
	// then collects them on the next control-plane start.
	KeepCompletedPods bool

	// OrphanGracePeriod protects young Pods from the reconcile sweep. Zero
	// means DefaultOrphanGracePeriod.
	OrphanGracePeriod time.Duration

	// MaxConcurrent caps simultaneously-running Pods. Zero means unbounded
	// (the cluster's ResourceQuota is the real ceiling).
	MaxConcurrent int

	// EgressFilter turns the Pod's cluster egress into an enforced allowlist by
	// creating a NetworkPolicy alongside each Pod. The zero value creates no
	// policy and leaves egress exactly as the cluster grants it, which is what
	// keeps an upgrade from silently firewalling a running deployment. See
	// networkpolicy.go.
	EgressFilter EgressFilter

	// NetworkPolicyEnforcement carries what is known about whether this
	// cluster's CNI honours a NetworkPolicy: the operator's assertion from
	// executors.kubernetes.network_policy_enforced, and any verdict a previous
	// probe recorded. The zero value is "unverified", which refuses
	// per-project egress scopes — the direction a missing answer has to fail.
	// See enforcement.go.
	NetworkPolicyEnforcement NetworkPolicyEnforcement

	// Credentials supplies the kubeconfig lease. Required.
	Credentials CredentialSource

	// Workspace leases the credential a git workspace fetch needs. Optional.
	//
	// A nil one is not an error and does not disable workspace provisioning:
	// an unauthenticated fetch of a public repository still works, because the
	// init container simply runs with no token in its environment. What a nil
	// one means is that no *private* repository can be fetched, which preflight
	// reports as a warning rather than leaving an operator to discover it from
	// a git authentication failure inside a Pod.
	//
	// It is an interface from pkg/executor, not a *secretbroker.Broker, so this
	// driver keeps knowing nothing about the hub's secret store — and so a test
	// can supply a credential without a database or a sealing key.
	Workspace executor.WorkspaceCredentialSource

	// ImagePolicy constrains the images a *project* may name in its
	// .cloop/sandbox.yaml (Task 20177). The zero value constrains nothing.
	//
	// It matters more here than on the container backend. There, a tag is
	// resolved against a local store and the digest is what runs; a cluster
	// has no store the control plane can read, so a tag in a Pod spec is
	// resolved by a kubelet, on a node, at a time nobody controls. Setting
	// require_digest is the only way to make a Kubernetes run reproducible.
	ImagePolicy imagepolicy.Policy

	// HandleStore persists handle identity so a control plane that restarts
	// can find the Pods it already dispatched (Task 20191). Optional.
	//
	// A nil one is not a degraded mode to apologise for, it is exactly the
	// behaviour this driver had before: the handle map is the only record a
	// Pod exists, and a hub that restarts comes back believing it dispatched
	// nothing while the Pods keep running. Embedders with no state database —
	// the tests in this package, `cloop executor test` — want that, and a
	// driver that refused to construct without a store would make the state
	// database a hard dependency of a package that deliberately has none.
	//
	// It is read once at construction and written on every dispatch and every
	// terminal transition. See rehydrate.go for what a row carries and why.
	HandleStore executor.HandleStore
	// contains filtered or unexported fields
}

Options configures a Kubernetes executor instance. Every field maps to an executors.kubernetes.* config key.

The zero value is deliberately *not* usable: Credentials has no default, because the alternative to a brokered kubeconfig is reading one off the control-plane host, and a driver that silently did that would defeat the isolation the operator chose it for.

func (Options) Normalize

func (o Options) Normalize() (Options, error)

Normalize fills defaults and validates. It returns a copy so a caller's struct is never mutated, and is exported so pkg/config can validate an operator's YAML against exactly the rules New will apply.

It deliberately does not require Credentials: pkg/config validates a config section long before a broker exists. New does require it.

type PreflightReport

type PreflightReport struct {
	ExecutorID string    `json:"executor_id"`
	Server     string    `json:"server,omitempty"`
	Namespace  string    `json:"namespace,omitempty"`
	Image      string    `json:"image,omitempty"`
	Findings   []Finding `json:"findings"`
}

PreflightReport is the result of checking an executor's environment.

func (PreflightReport) Err

func (r PreflightReport) Err() error

Err returns an error summarising the fatal findings, or nil when none.

func (PreflightReport) OK

func (r PreflightReport) OK() bool

OK reports whether nothing fatal was found.

type ProbeOptions added in v0.0.2

type ProbeOptions struct {
	// Image overrides the busybox image all three Pods run. It must provide
	// `sh`, `httpd` and `wget`.
	Image string

	// Timeout bounds the whole probe. Zero means probeDefaultTimeout.
	Timeout time.Duration

	// Logf, when set, narrates each step. `cloop hub doctor` wires it to the
	// terminal so an operator watching a 40-second probe can see which of the
	// three Pods it is waiting on.
	Logf func(format string, args ...any)
}

ProbeOptions tunes the experiment. The zero value is the intended one.

type ProbeVerdict added in v0.0.2

type ProbeVerdict struct {
	// Enforced is the measurement: true when the control connection worked and
	// the policed connection did not.
	Enforced bool `json:"enforced"`

	// ObservedAt is when the probe finished. Zero means "no verdict", which is
	// why Resolve checks it before trusting the rest of the struct.
	ObservedAt time.Time `json:"observed_at"`

	// Namespace and ExecutorID scope the claim. A verdict is about one
	// namespace on one cluster; replaying it against a different executor would
	// be a guess wearing a measurement's clothes, so LoadVerdict keys on the
	// executor and Resolve rejects a mismatch.
	Namespace  string `json:"namespace,omitempty"`
	ExecutorID string `json:"executor_id,omitempty"`

	// Detail is the operator-facing sentence: what was tried, what happened.
	Detail string `json:"detail,omitempty"`

	// ProbeVersion is the experiment's revision. A future probe that tests
	// something stronger must not silently inherit this one's authority, so a
	// verdict from an older version is treated as absent rather than as
	// evidence for a claim it never made.
	ProbeVersion int `json:"probe_version,omitempty"`
}

ProbeVerdict is one recorded run of the enforcement probe.

It is JSON because it is persisted verbatim by the hub (see reconcile.SaveNetworkPolicyVerdict) and rendered by `cloop hub doctor --json`. Everything here is an observation, never a conclusion: Enforced says what the probe saw, ObservedAt says when, and Detail says how — so that a verdict an operator disagrees with can be argued with rather than merely overridden.

func (ProbeVerdict) Age added in v0.0.2

func (v ProbeVerdict) Age(now time.Time) time.Duration

Age is how long ago the probe ran, measured against now.

func (ProbeVerdict) Recorded added in v0.0.2

func (v ProbeVerdict) Recorded() bool

Recorded reports whether v holds an actual observation rather than a zero value that unmarshalled cleanly.

func (ProbeVerdict) Stale added in v0.0.2

func (v ProbeVerdict) Stale(now time.Time, maxAge time.Duration) bool

Stale reports whether the verdict has aged past maxAge, or was produced by a probe version this binary does not recognise.

A verdict from the *future* — a clock that jumped, a record copied from another host — is stale too. Trusting it would let a bad timestamp buy unlimited authority for a claim nobody re-checked, and the cost of being wrong here is a firewall that is not there.

type RESTConfig

type RESTConfig struct {
	// Server is the API server base URL ("https://10.0.0.1:6443").
	Server string
	// Namespace is the context's default namespace, if it named one. The
	// executor's configured namespace takes precedence over it.
	Namespace string
	// Context is the surviving kubeconfig context name, for diagnostics.
	Context string

	// CAData is the PEM bundle that signs the API server's certificate.
	CAData []byte
	// ServerName overrides the SNI/verification hostname (tls-server-name).
	ServerName string
	// Insecure disables server certificate verification. Honoured because
	// kind/minikube clusters legitimately need it, but surfaced in
	// Describe() so it can never be an invisible downgrade.
	Insecure bool

	// BearerToken authenticates with Authorization: Bearer.
	BearerToken string
	// ClientCertData/ClientKeyData authenticate with mTLS.
	ClientCertData []byte
	ClientKeyData  []byte

	// ProxyURL routes requests through an HTTP proxy (proxy-url).
	ProxyURL string
}

RESTConfig is everything needed to talk to one API server. It holds credentials in memory and is never serialised to disk.

func ParseKubeconfig

func ParseKubeconfig(raw []byte, contextName string) (*RESTConfig, error)

ParseKubeconfig builds a RESTConfig from a kubeconfig document.

contextName selects a context; empty uses current-context, and a document with exactly one context is accepted without either. The broker's MinimizeKubeconfig has usually already reduced the document to the contexts a grant permits, so in practice this picks from a very short list.

func (*RESTConfig) Describe

func (rc *RESTConfig) Describe() string

Describe returns an audit-safe one-line summary: endpoint, context, namespace and auth *method*, never the credential itself.

func (*RESTConfig) Transport added in v0.0.2

func (rc *RESTConfig) Transport(responseHeaderTimeout time.Duration) (*http.Transport, error)

Transport returns an http.Transport that reaches this API server with this config's TLS material. It performs no I/O.

responseHeaderTimeout bounds how long the server may take to send response headers; pass zero for streaming callers, where any such bound is a bug.

Exported because pkg/kubeguard proxies to an API server described by a RESTConfig it obtained the same way this driver does, and a second TLS construction there would be a second place for "does this kubeconfig say insecure" to be answered. One function, so the two cannot drift.

type Toleration

type Toleration struct {
	Key      string `yaml:"key,omitempty" json:"key,omitempty"`
	Operator string `yaml:"operator,omitempty" json:"operator,omitempty"`
	Value    string `yaml:"value,omitempty" json:"value,omitempty"`
	Effect   string `yaml:"effect,omitempty" json:"effect,omitempty"`
	// Seconds is tolerationSeconds; 0 means unset.
	Seconds int64 `yaml:"seconds,omitempty" json:"seconds,omitempty"`
}

Toleration is the operator-facing form of a Pod toleration, parsed from config. It exists separately from the API type so pkg/config can validate one without importing JSON tags it does not care about.

func (Toleration) Validate

func (t Toleration) Validate() error

Validate checks a toleration against the API's own rules, so a typo is caught by `cloop config set` rather than by a Pod that never schedules.

Jump to

Keyboard shortcuts

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