How to block unapproved container images in kubernetes

Key takeaways
- Admission time is the only cluster-wide enforcement point - it sits after authentication and authorization but before persistence, so it catches manual applies and GitOps alike.
- ValidatingAdmissionPolicy (stable in Kubernetes v1.30, CEL-based) covers structural rules in-process, with no webhook to keep available; signature checks still need Kyverno or Sigstore's policy-controller.
- A durable policy asserts four things: approved registry, digest not tag, a required signature, and attestation content such as an SBOM or builder identity.
- Roll out in audit mode, exempt infrastructure namespaces with a dated expiry, enforce on new namespaces first, and choose your failure policy deliberately.
- Enforcement decides whether an image runs, never what is inside it - pair it with hardened images on a published remediation SLA.
Kubernetes decides whether to accept a workload at admission time - after authentication and authorization, before the object is persisted. That is the only place you can reject an unsigned, unscanned or unapproved image cluster-wide without relying on developers to do the right thing. Getting this right takes three pieces: a policy engine, a verifiable signal on the image, and a rollout that fails closed only once you know it won't page you at 2am.
What is admission control, and why is it the right enforcement point?
The Kubernetes documentation defines a Kubernetes admission controller as code that intercepts requests to the API server prior to persistence of the resource, but after the request is authenticated and authorized. Controllers run in two phases:
- Mutating - the controller can modify the incoming object before it is stored. This is how a sidecar gets injected, or a tag gets rewritten to a digest.
- Validating - the controller inspects the object and accepts or rejects it. This is where image policy belongs.
Scanning in CI is necessary but not sufficient, because CI only sees what goes through CI. Admission control sees everything that reaches the API server: a kubectl run from a laptop, a Helm install from an operator, a GitOps controller reconciling a chart someone edited. That is the difference between a guideline and a control you can evidence in an audit.
Which enforcement mechanism should you use?
There are four realistic choices, and they are not equivalent.
A few specifics worth knowing before you choose:
- ValidatingAdmissionPolicy reached stable in Kubernetes v1.30. Rules are written in Common Expression Language (CEL), and it ships as two resources: ValidatingAdmissionPolicy defines the rules, ValidatingAdmissionPolicyBinding scopes them to particular resources. Because it runs in-process, there is no webhook to keep highly available.
- Sigstore's policy-controller validates signatures and attestations produced by cosign, and resolves image tags to digests so the object that runs is the object that was admitted. Its primary CRD is ClusterImagePolicy, which names the images to check and the authorities that must vouch for them. Authorities can be key-based (inline, secret or KMS), keyless via Fulcio, or static allow/deny. A secondary TrustRoot CRD supports custom or air-gapped Sigstore instances.
- ImagePolicyWebhook is the built-in controller that delegates image decisions to a remote webhook service - useful if you already run a decision service, rarely the shortest path if you don't.
- Kyverno and OPA/Gatekeeper are covered alongside the rest of the ecosystem in our Kubernetes security tools roundup; Kyverno supports image verification policies natively, Gatekeeper's strength is mature Rego governance across more than just Kubernetes.
What should the policy actually say?
Most teams write one rule and stop. A policy that holds up in an audit usually asserts four things at once.
- Registry allow-list. Only images from registries you control or have contracted for. This alone blocks the majority of accidental docker.io pulls.
- Digest, not tag. Reject any image reference that isn't @sha256:. A mutable tag means the thing you approved and the thing that runs can differ. If you use a controller that resolves tags to digests at admission, let it do the rewrite and then enforce the rule.
- Signature required. The image must carry a signature from an identity you trust - a KMS key you own, or a keyless identity bound to your build workflow.
- Attestation content. Beyond "was it signed", assert something about the build: that an SBOM attestation exists, that it came from the expected builder, that the scan attestation is younger than your policy window.
The fourth one is where the control stops being theatre. A signature proves provenance; an attestation lets you say what was true about the artifact at build time.
How do you roll this out without breaking production?
The failure mode here is predictable: someone enables a deny policy cluster-wide on a Friday and blocks the platform team's own tooling.
- Start in audit mode. Every engine has one - Kyverno's validationFailureAction: Audit, Gatekeeper's dryrun, ValidatingAdmissionPolicy's validationActions: [Warn, Audit]. Run it for at least one full release cycle.
- Read the violations as an inventory, not a to-do list. The first audit run is the most honest image inventory you will ever get. Expect kube-system, your CNI, your monitoring agents and at least one forgotten job.
- Exempt infrastructure namespaces explicitly, with an expiry. Write the exemption as a dated entry in the policy, not as an untracked carve-out. Then work them down.
- Enforce on new namespaces first. New workloads have no legacy, so they are free to enforce. Backfill existing namespaces one at a time.
- Decide your failure policy deliberately. A webhook with failurePolicy: Fail that becomes unavailable stops all admissions. Ignore keeps the cluster up and silently stops enforcing. In-process ValidatingAdmissionPolicy sidesteps this trade-off entirely, which is a real argument for using it for the structural rules even if a webhook handles signatures.
- Alert on denials. A denial that only appears in a rejected kubectl apply teaches nobody. Route them somewhere a human reads.
What this control cannot do for you
Admission control decides whether an image runs. It has nothing to say about what is inside it. A signed, digest-pinned image from an approved registry can still ship a thousand CVEs in its base layer.
That is the other half of the job: reduce what is in the image in the first place, and keep it patched on a schedule. Echo's approach is to remove the vulnerable surface at the source - hardened base images and libraries with a 7-day SLA for critical and high CVEs, delivered as rebuilt artifacts without customer action (echo.ai, How automated patch SLAs reduce enterprise risk). Pair that with the build-time practices in our Dockerfile hardening guide, and admission control becomes the gate rather than the whole strategy.
FAQ
What is an admission controller in Kubernetes?
It is code that intercepts requests to the Kubernetes API server after authentication and authorization but before the resource is persisted. Controllers run in a mutating phase, which can modify the object, and a validating phase, which accepts or rejects it. Image policy belongs in the validating phase, because that is where a request for an unapproved image can be refused cluster-wide.
Can I enforce image policy without installing a webhook?
Partly. Validating Admission Policy became stable in Kubernetes v1.30 and runs inside the API server, using CEL rules bound to resources via Validating Admission Policy Binding. It handles structural rules well - registry allow-lists, digest-only references, banned tags. It does not verify cryptographic signatures, so signature enforcement still requires a controller such as Kyverno or Sigstore's policy-controller.
Should the policy fail open or fail closed?
Fail closed is the correct end state, but not the correct starting state. A webhook set to failurePolicy: Fail blocks every admission if the webhook is unavailable, which turns a policy outage into a cluster outage. Run in audit mode first, size the availability of your webhook deployment, and move to fail-closed once you have real violation data and a tested exemption list.
How is this different from scanning images in CI?
CI scanning only inspects artifacts that travel through CI. Admission control inspects everything that reaches the API server, including manual applies, operator-driven installs and GitOps reconciliation of a chart edited outside your pipeline. The two are complementary: scanning tells you what is wrong with an image, admission control is what actually prevents it from running.
Does signing an image mean it is secure?
No. A signature proves an artifact came from an identity you trust and has not been altered since. It says nothing about the vulnerabilities inside it. That is why mature policies also assert attestation content - an SBOM, an expected builder identity, a scan result within a defined freshness window - and why reducing the vulnerable surface at the image source matters independently.



.avif)
.avif)