Kubernetes RBAC decides who may create a pod, but not what that pod may contain, so without admission control any user or service account allowed to deploy can run a privileged container from any registry. Admission control is the policy checkpoint inside the API server that inspects every create and update request after authentication and authorisation, and can reject or modify it before it is stored. This guide explains how the admission chain works, compares the built-in options (Pod Security Admission and ValidatingAdmissionPolicy with CEL) with the two main open source policy engines, OPA Gatekeeper and Kyverno, and covers failure policies, exemptions, rollout, measurement and the bypasses attackers look for. It is written for platform engineers and security architects who own cluster guardrails. Wider cluster hardening is in the Kubernetes Security Guide, and permissions in the Kubernetes RBAC Guide.
Key takeaways
- Admission is the only place to enforce what is deployed. It runs after RBAC on every write to the API, so it can block privileged pods, untrusted images and missing controls regardless of who submits them.
- Start with what is built in. Pod Security Admission (stable since v1.25) covers pod hardening, and ValidatingAdmissionPolicy (stable since v1.30) runs CEL rules in the API server with no webhook to keep alive.
- Gatekeeper and Kyverno add what CEL alone does not. Audit of existing resources, mutation, generation, image signature verification, reporting and policy libraries; both can now generate ValidatingAdmissionPolicies from their own policies.
- Failure policy is a security decision. A webhook that fails open (
Ignore) silently stops enforcing when it is down; Gatekeeper's webhook defaults to fail open, Kyverno's to fail closed. - Exemptions are the attack surface. Excluded namespaces, ignore labels and broad exceptions are where policy is bypassed, so they need owners, review and alerting.
How the admission chain works
A request to the API server passes authentication, then authorisation (RBAC), then admission, and only then is persisted to etcd. Admission runs in two phases: mutating controllers first, then validating controllers. If any controller in either phase rejects the request, the whole request is rejected. Admission applies to create, update and delete requests and some connect requests such as pods/exec; it does not apply to reads.
There are four extension points, all enabled by default in current Kubernetes releases:
| Extension point | Phase | How it works | Notes |
|---|---|---|---|
| MutatingAdmissionWebhook | Mutating | API server calls an external HTTPS service with an AdmissionReview and applies the returned patch | Used by Gatekeeper mutation, Kyverno mutate rules, sidecar injectors |
| MutatingAdmissionPolicy | Mutating | CEL mutations in the API server, as an apply configuration or JSON patch | Newer feature; check its maturity and feature gate in your Kubernetes version before relying on it |
| ValidatingAdmissionPolicy | Validating | CEL expressions evaluated in-process in the API server | Stable since v1.30; no network call, so it can fail closed without availability risk from a webhook |
| ValidatingAdmissionWebhook | Validating | External service returns allowed or denied, optionally with warnings | Used by Gatekeeper and Kyverno validation; sees the object after all mutations |
Compiled-in controllers also matter for security. PodSecurity, ServiceAccount, LimitRanger and ResourceQuota are on by default. NodeRestriction (limits what a kubelet can modify), AlwaysPullImages (forces image pulls so cached private images cannot be reused without credentials), EventRateLimit and DenyServiceExternalIPs must be enabled with --enable-admission-plugins. Managed Kubernetes services decide this list for you, so check what your provider enables.
Built-in options: Pod Security Admission and ValidatingAdmissionPolicy
Pod Security Admission
Pod Security Admission enforces the three Pod Security Standards (privileged, baseline, restricted) per namespace using labels of the form pod-security.kubernetes.io/<mode>: <level>, with an optional <mode>-version label to pin the standard to a Kubernetes version.
- Modes:
enforcerejects violating pods;auditadds an annotation to the audit log event;warnreturns a warning to the client. A namespace can set different levels per mode, for example enforce baseline while warning on restricted. - Workload resources: audit and warn are applied to Deployments, Jobs and other workload objects, but enforce applies only to the resulting pods. A non-compliant Deployment is accepted and its pods then fail, which shows up in ReplicaSet events, not in
kubectl applyoutput. - Exemptions: configured statically in the API server's
AdmissionConfigurationbyusernames,runtimeClassNamesornamespaces. Exempt requests skip all three modes, and the metricpod_security_exemptions_totalcounts them. - Limits: fixed rule sets only, no registry or label rules, no mutation, and namespace labels can be changed by anyone with update rights on the namespace.
ValidatingAdmissionPolicy
A ValidatingAdmissionPolicy holds CEL validations, optional matchConditions, variables, messageExpression and auditAnnotations. A ValidatingAdmissionPolicyBinding attaches it to resources and chooses validationActions: Deny, Warn or Audit (Deny and Warn cannot be combined). Policies can be parameterised with paramKind and a binding's paramRef, whose parameterNotFoundAction must be set. Because evaluation is in-process, a failurePolicy: Fail policy does not depend on an external service, and CEL has cost limits that stop runaway expressions.
What it does not do on its own: audit resources that already exist, mutate (that is MutatingAdmissionPolicy), look up other cluster objects beyond its parameters, verify image signatures or produce compliance reports.
OPA Gatekeeper and Kyverno compared
Both engines are CNCF projects that run as admission webhooks plus background controllers. They differ mainly in policy language and in how much beyond validation they do.
| Capability | OPA Gatekeeper | Kyverno |
|---|---|---|
| Policy model | ConstraintTemplate (logic and parameter schema) plus Constraint instances (match scope and parameters) | ClusterPolicy or Policy with rules, plus newer CEL-based kinds such as ValidatingPolicy, MutatingPolicy, GeneratingPolicy, DeletingPolicy and ImageValidatingPolicy |
| Language | Rego; CEL in templates via the K8sNativeValidation engine (stable since Gatekeeper v3.18) | YAML patterns (pattern, anyPattern, deny, foreach), JMESPath, CEL |
| Enforcement modes | enforcementAction: deny (default), dryrun, warn, or scoped with per-enforcement-point actions | Per-rule failureAction: Enforce or Audit, with failureActionOverrides per namespace (the older validationFailureAction field is deprecated); emitWarning for audit warnings |
| Audit of existing resources | Periodic audit, every 60 seconds by default, results in constraint status | Background scans producing PolicyReport and ClusterPolicyReport resources |
| Mutation and generation | Mutation CRDs (Assign, AssignMetadata, ModifySet); no generation | Mutate (including existing resources), generate (for example default NetworkPolicies per namespace), cleanup |
| Image verification | Through external data providers | Built-in verify image rules for Sigstore and Notary signatures and attestations |
| Native VAP integration | Generates ValidatingAdmissionPolicies from CEL templates (beta in v3.20, enabled by default) | ValidatingPolicy can generate a ValidatingAdmissionPolicy via autogen.validatingAdmissionPolicy.enabled |
| Testing tools | gator CLI (gator verify, gator test) | Kyverno CLI to apply and test policies against manifests |
| Default webhook failure policy | Ignore (fail open); can be changed to Fail | Fail (fail closed); forceFailurePolicyIgnore switches to fail open |
Choosing between them
- Built-in only: small clusters or a single team, where Pod Security Admission at
restrictedplus a handful of CEL policies (allowed registries, required labels) covers the need with nothing extra to operate. - Gatekeeper: organisations already using OPA and Rego elsewhere (API authorisation, Terraform checks), who want one policy language across systems and strong separation between policy logic and parameters.
- Kyverno: platform teams who want policies written as Kubernetes YAML, need mutation, generation or image signature verification in the same tool, and want reports as Kubernetes resources.
- Large and multi-cluster estates: standardise on one engine, distribute policy through GitOps, and push simple, high-volume checks down to ValidatingAdmissionPolicy where possible, keeping the webhook for checks CEL cannot express.
Running two engines on the same resources is possible but doubles the latency, failure modes and places to look when a deployment is rejected.
Policies that matter most
| Policy | Threat addressed | ATT&CK v20 |
|---|---|---|
| Images only from approved registries, by digest, with valid signatures | Malicious or implanted images | T1204.003 Malicious Image, T1525 Implant Internal Image |
No privileged containers, host namespaces, hostPath or dangerous capabilities | Escape from container to node | T1611 Escape to Host |
| Restrict who can create pods in system namespaces and block new privileged DaemonSets | Attacker-deployed workloads | T1610 Deploy Container, T1543.005 Container Service |
| Restrict CronJob and Job creation outside approved namespaces | Scheduled persistence | T1053.007 Container Orchestration Job |
Deny bindings to cluster-admin and wildcard roles except from the platform pipeline | Privilege escalation through RBAC | T1098.006 Additional Container Cluster Roles |
Require resource limits and deny unexpected nodeSelector to GPU pools | Cryptomining on cluster capacity | T1496.001 Compute Hijacking |
Deny environment variables that look like static cloud keys; require automountServiceAccountToken: false unless annotated | Credential theft from pods | T1552.007 Container API |
The Kubeflow cryptomining attacks illustrate the first and third rows: attackers created notebook servers running an image from a public repository and used a mounted service account to deploy miners. An allowed-registry policy and a rule that notebook namespaces cannot create arbitrary workloads would have blocked both routes. The Tesla Kubernetes cryptojacking case, where a pod held AWS credentials, is the argument for the last row. Pair these with the Container Image Security Guide for signing, and the Helm Security Guide for chart-level checks.
Webhook reliability and failure policy
- failurePolicy:
Failrejects requests when the webhook cannot be reached, times out or returns a malformed response;Ignorelets them through. The Kubernetes default for a webhook configuration isFail, but Gatekeeper ships its validating webhook asIgnore. An explicit denial always blocks, whatever the failure policy. - Timeouts: the default webhook timeout is 10 seconds. Shorter timeouts (Gatekeeper's fail-closed example uses 3 seconds; Kyverno's
webhookTimeoutaccepts 1 to 30) limit how long a slow engine can stall every deployment. - Scope: use
namespaceSelector,objectSelector, rules and CELmatchConditions(up to 64 per webhook) so the webhook sees only what it needs. Excluding the engine's own namespace prevents a deadlock where the engine cannot restart because its own webhook rejects its pods. - Availability: run multiple replicas across zones with a PodDisruptionBudget and a high priority class, and alert on webhook latency and rejection errors in API server metrics.
- Side effects and mutation order: declare
sideEffects: Nonewhere true, make mutating webhooks idempotent, and do security validation in a validating webhook or policy, which sees the final object after all mutations.reinvocationPolicy: IfNeededlets a mutating webhook run again if later plugins change the object.
The decision is a trade-off: fail closed for production namespaces where an unchecked privileged pod is worse than a delayed deployment, with fail-open or narrower scope for system components that must always be able to start. Moving critical checks to ValidatingAdmissionPolicy removes the dilemma for those checks, which is a major reason both engines now generate VAPs.
Common bypasses and failure patterns
- Fail-open during an outage: with
failurePolicy: Ignore, deleting or overloading the policy engine pods turns enforcement off. Audit catches the result later, not at admission time. Alert on engine pod restarts and on webhook configuration changes. - Exemption labels and namespaces: Gatekeeper's
admission.gatekeeper.sh/ignorenamespace label and Kyverno's default exclusion ofkube-systemand its own namespace are needed for stability, but anyone who can label a namespace or deploy into an excluded one bypasses policy. Gatekeeper requires the matching--exempt-namespaceflag before it honours the label for this reason; restrict namespace update rights with RBAC. - Policy on pods only: rules written for
Podbut not for Deployments, Jobs and CronJobs give late, confusing failures, and rules written only for controllers miss bare pods. Use Kyverno autogen, Gatekeeper expansion or match both. - Webhook configuration as a target: an identity that can edit
ValidatingWebhookConfigurationor delete a policy can remove the control entirely. Treat those permissions as cluster-admin equivalent and monitor them in the audit log. - Audit mode forever: policies deployed in
dryrun,warnorAuditto avoid breaking teams, then never promoted to enforcement, produce reports nobody acts on. - Mutable tags: allowing
registry/app:latestfrom an approved registry still lets the image change after review. Require digests or signature verification.
How to implement
- Baseline the cluster: label every namespace with Pod Security Admission
warnandauditatrestricted, and read the audit annotations and client warnings for two weeks to find non-compliant workloads. - Enforce built-in standards: set
enforce: baselineon all application namespaces, thenrestrictedwhere workloads comply. Rollback is removing or downgrading one namespace label. - Choose and install the engine: if Gatekeeper or Kyverno is needed, install with multiple replicas, exclude its own namespace, and keep every policy in audit or dry-run mode at first (
enforcementAction: dryrunorfailureAction: Audit). - Test policies as code: store policies in Git, run
gator verifyor the Kyverno CLI against known-good and known-bad manifests in CI, and run the same checks against Helm and Kustomize output before it reaches the cluster. - Promote to enforcement by ring: warn first so developers see messages at deploy time, then enforce in development clusters, then one production cluster, then all. Each step needs a named owner and an exception process.
- Decide failure policy explicitly: document fail-closed or fail-open per webhook, test it by scaling the engine to zero in a staging cluster, and confirm the result matches the decision.
- Govern exceptions: use scoped exception resources with an owner and expiry date rather than namespace-wide exclusions, and review them quarterly.
Detection and measurement
- Policy coverage: percentage of namespaces with Pod Security Admission enforce labels and percentage of the agreed policy set in enforce mode.
- Open violations: count of existing resources failing policy (Gatekeeper constraint status, Kyverno PolicyReports), trending down.
- Exemption count and age: number of exempt namespaces, users and exceptions, and how many are past their expiry date; track
pod_security_exemptions_total. - Webhook health: admission webhook latency, timeout and rejection rates from API server metrics; any period where an
Ignorewebhook was unavailable is an enforcement gap. - Time to enforce: days from a policy entering audit mode to enforcement.
In the Kubernetes audit log, watch for changes to validatingwebhookconfigurations, mutatingwebhookconfigurations, validatingadmissionpolicies and their bindings, constraint and policy deletions, and namespace label changes on pod-security.kubernetes.io/* or ignore labels. Gatekeeper's --log-denies flag logs every deny, dry-run and warn result for SIEM ingestion. Runtime detection is the backstop when admission is bypassed; see the Container Runtime Security Guide.
Where identity fits
Admission control is also an identity control for workloads. Policies can require that pods use a dedicated service account rather than default, disable token automounting where the API is not used, block static cloud keys in specs and enforce that workload identity annotations point only to approved cloud roles. The policy engines themselves are high-value non-human identities: their service accounts can read most cluster objects, and in Kyverno's case generate and mutate resources, so scope their RBAC tightly. See the Kubernetes NHI Security Guide.
Framework mapping
| Framework | Reference | How admission control supports it |
|---|---|---|
| NIST CSF 2.0 | PR.PS-01, PR.PS-05, PR.AA-05, GV.PO-01 | Configuration management enforced at deploy time; blocking unauthorised software; enforcing access policy; policy established and enforced |
| NIST SP 800-53 Rev. 5 | CM-2, CM-3, CM-6, CM-7, CM-14, SI-7, AC-6 | Baselines; change control; configuration settings; least functionality; signed components; integrity; least privilege |
| NIST SP 800-190 | Orchestrator and image countermeasures | Controls on which images may run and how workloads are configured |
| CIS Controls v8.1 | Control 2, Control 4 | Inventory and control of software assets; secure configuration |
| ISO/IEC 27001:2022 Annex A | A.8.9, A.8.19, A.8.32 | Configuration management; installation of software on operational systems; change management |
| MITRE D3FEND | D3-EAL | Executable allowlisting applied to container images |
Practitioner checklist
- Label every namespace with Pod Security Admission levels, enforcing at least
baselineandrestrictedwherever workloads comply. - Confirm which admission plugins your API server or managed service enables, including
NodeRestriction. - Use ValidatingAdmissionPolicy for simple, high-volume checks and a policy engine only for what CEL cannot do.
- Enforce approved registries, image digests or signatures, and no privileged or host-namespace pods.
- Set and test each webhook's
failurePolicydeliberately; run engines highly available with tight timeouts. - Treat webhook configurations, policies and namespace label rights as privileged, and alert on changes.
- Keep policies in Git with CI tests using
gatoror the Kyverno CLI. - Promote policies from warn or audit to enforce on a dated plan, ring by ring.
- Replace namespace-wide exclusions with scoped, expiring exceptions that have owners.
Standards and references
- Kubernetes: Admission Control in Kubernetes
- Kubernetes: Validating Admission Policy
- Kubernetes: Dynamic Admission Control
- Kubernetes: Pod Security Admission
- OPA Gatekeeper: Failing Closed
- Kyverno: Validate Rules
- NIST: SP 800-190 Application Container Security Guide
- MITRE ATT&CK: Containers Matrix
Related NHI Mgmt Group resources: Kubernetes Security Guide · Kubernetes RBAC Guide · Container Security Buyer's Guide · Top 10 Kubernetes Security Risks · Helm Security Guide