Virtualization · Image Supply Chain
Admission-Time Verification and Digest Pinning
What a cluster can check between a pod specification and an image pull, why the kubelet is the wrong place for that check, and how digest pinning interacts with tag resolution order.
Verification is only as strong as its position in the pipeline. A signature checked by a developer’s tool before the image is pushed establishes that the developer’s tool saw a valid signature. A signature checked by the runtime that is about to execute the image establishes that the thing being executed is the thing that was signed. Everything between those two points is a place where the object under verification can change, and the design question is where to place the check so that the gap closes.
The image reference is resolved in two different places
A pod specification names an image, usually by tag. That reference is consumed twice, by two different components, at two different times.
The scheduler and the API server see the pod spec and treat the image string as an opaque identifier. They do not resolve it; nothing in the scheduling decision depends on what the tag points to. The node’s container runtime resolves the reference when it pulls, and the resolution happens on the node, at pull time, potentially minutes after the pod was admitted.
This split is the root of the problem. Any check performed on the API server sees a string, not a digest. Any check performed on the node sees the digest but is running on a machine that may be less trusted than the control plane. A policy that says “only signed images may run” has to bridge that gap, and the two available bridges have different properties.
Admission sees the string, so the string must carry the identity
The control plane can resolve a tag itself, but doing so introduces a registry dependency into the admission path: every pod creation would require a registry round trip, and the availability of the cluster’s ability to accept workloads would depend on the availability of a registry. Caching the resolution narrows the window but does not eliminate it, and the cache introduces a new question: how long a stale tag-to-digest mapping is acceptable.
The alternative is to require the workload to name the digest. A pod spec with image: registry.example/api@sha256:b1e4... carries the identity in the spec. Admission can then verify a property of the string itself (that a digest is present and well-formed) and can verify that the digest is one the policy allows, using data it already has or can fetch from a trusted source.
Pinning has a second effect that is easy to overlook. It changes what a tag means for the lifetime of the pod. An unpinned pod that is rescheduled after a tag moves pulls different content; a pinned pod pulls the same content, or fails. For rollouts, this makes the deployed artifact identical across replicas and across restarts, which is the property that makes a rollback meaningful.
The cost is operational. A pinned digest has to be updated whenever the image changes, which means the deployment process must resolve the tag and write the digest, and the tooling that does so becomes part of the trust boundary. If that tool resolves the tag at deploy time and the tag was repointed between build and deploy, the pin records the attacker’s digest and the policy that accepts pinned digests accepts it. Pinning moves the resolution earlier; it does not remove it.
Where the check belongs
Four placements are used in practice, and each has a distinct failure mode.
In the CI pipeline, before push. The pipeline verifies the image it just built and signs it. This establishes that the build produced something the pipeline endorsed. It does not establish that the artifact later pulled is that artifact, because the registry between them can serve anything.
In admission, on the control plane. Admission sees the spec and can enforce that a digest is present, that the digest appears in an allowlist, or that a signature exists for it. It cannot see the bytes the node will pull. If the registry serves different bytes for the digest, the node’s own verification (the digest check during pull) is the only thing that catches it, and content addressing makes that catch reliable.
In the node’s container runtime, at pull. The runtime resolves and pulls, and can verify signatures if configured to do so. This is the closest check to execution, and it is the only one that sees the bytes. Its weakness is deployment: runtime configuration lives on nodes, and a node that is not configured correctly silently skips verification. Policy that depends on node configuration is policy that can be partially applied.
In a sidecar or an admission webhook that mutates the spec. A webhook can rewrite a tag to a digest at admission time, which combines resolution with enforcement. The webhook’s registry access becomes a dependency of pod creation, and the webhook’s cache determines the window in which a repointed tag goes unnoticed.
The arrangement that closes most of the gap uses several of these together: the pipeline signs, admission requires a digest and a policy-satisfying signature, and the runtime verifies content on pull. Each layer covers a gap the others leave. A single check at any one point is a check that can be bypassed by the component downstream of it.
The TOCTOU window and what closes it
The gap between admission and pull is a time-of-check to time-of-use window. Between the two, a tag can move, a signature can be deleted, a policy can change, and a registry can be compromised.
Pinning closes the tag-movement part of the window completely: once admission has recorded a digest, no subsequent registry state can change which bytes that digest names. It does not close the signature-deletion part, because verification at pull time would fail if the referrer was removed, and it does not close the policy-change part, because a policy evaluated at admission is not re-evaluated later.
The signature part is addressed by verifying at pull time, or by caching the verification result with the digest so that the node does not need to re-fetch. Caching a verification result is a trade: it removes a registry dependency from the node’s pull path and it means a revoked signature remains accepted until the cache expires. The expiry interval is the revocation latency, and it should be chosen with that in mind rather than for convenience.
The policy part has no clean closure. A pod admitted under one policy continues running under a changed policy until it is rescheduled, and a cluster that tightens its policy has a population of running workloads that predate it. This is a property of declarative systems rather than a defect, and it means policy changes need an explicit remediation step for existing objects rather than only a validation step for new ones.
Namespaces, service accounts, and the granularity of policy
Verification policy is rarely uniform across a cluster. A system namespace running infrastructure components has different trust requirements from a namespace running user workloads, and a build namespace that pushes images has different requirements again.
Admission policies are typically scoped by namespace or by workload labels, which means the policy’s identity is a property of the request context rather than of the image. This has a subtle consequence: an attacker who can create a pod in a permissive namespace can run an image that would be rejected elsewhere. Namespace-level policy is only as strong as the control over namespace creation and over which namespaces a workload can be scheduled into.
The service account attached to the pod is a second axis. A policy that permits an unsigned image when the pod runs under a service account with no permissions, and requires a signature when the service account can read secrets, expresses a real risk trade. Expressing it requires the policy engine to see both the image reference and the pod’s identity, which most admission interfaces do provide.
Verification is not the same as enforcement of the artifact’s contents
An image can pass every check described here and still be dangerous. A signed, pinned, provenance-backed image built from a compromised dependency is exactly the artifact that supply-chain verification is supposed to catch, and it catches it only if the provenance records the dependency and the policy constrains it.
The constraints available at admission are the ones expressible over the metadata: builder identity, source repository, base image digest, and the presence of an SBOM. Constraints over content (that a package version is not vulnerable, that no shell is present, that the entrypoint is the expected binary) require inspecting the unpacked filesystem, which admission does not do.
The practical split is that admission enforces provenance and identity, while scanning enforces content, and the two are connected by the digest. A scan result attached as a referrer to a digest is a claim about that digest, and a policy that requires a passing scan for a digest is a policy over the same identity that admission pins. The discovery mechanism for those attachments, and its limitations, are covered in Referrers, Attachments, and the Artifact Graph.
The failure mode that matters most is silent non-enforcement
Every enforcement mechanism described above fails in two ways. It can reject something it should accept, which is visible and annoying. Or it can accept something it should reject, which is invisible until it matters.
Silent acceptance has recognizable causes. A webhook that is not registered for the relevant resource version stops being called after an upgrade. A policy that matches on a field the workload does not set evaluates against a default and passes. A verification step that treats a fetch failure as “no signature found” and a policy that treats “no signature” as acceptable when the key set is empty. A runtime that falls back to pulling without verification when the verifier is unreachable.
Each of these is a fail-open path, and the design question for each is whether the default on error is acceptance or rejection. For an enforcement point in a supply chain, the answer is rejection, and the cost is that a dependency outage becomes a deployment outage. That cost is real and is the reason fail-open paths exist; the defect is not their existence but their being undocumented and untested. A cluster that has never had its verifier taken offline has not established which way it fails.
What the whole arrangement depends on is a trust anchor that is not itself fetched from the same registry: a key set, a certificate root, or a policy bundle distributed through a channel the registry cannot influence. Pull-Through Caches and Where the Trust Anchor Sits takes up that question, because a cache that serves both content and trust material collapses the separation the rest of the design relies on.
References
- Kubernetes Documentation — Admission Control — Kubernetes project
- Kubernetes Documentation — Images — Kubernetes project
- OCI Image Format Specification — Open Container Initiative