ValidatingAdmissionPolicy
A ValidatingAdmissionPolicy is an in-process, declarative validating admission mechanism: instead of calling out to an external webhook, the API server evaluates a Common Expression Language (CEL) predicate against the object directly inside
kube-apiserver(Kubernetes — Validating Admission Policy). It exists to solve the operational fragility of validating webhooks — no network hop, no external Deployment to keep alive, no TLS certificate to rotate, and crucially it cannot wedge the cluster the way afailurePolicy: Failwebhook can. A policy is split across three resources: aValidatingAdmissionPolicy(the CEL logic), aValidatingAdmissionPolicyBinding(which resources it applies to and what to do on failure), and an optional parameter resource. ValidatingAdmissionPolicy reached GA (stable) in Kubernetes 1.30.
Mental Model
The defining trade of ValidatingAdmissionPolicy is giving up arbitrary logic to gain operational safety. A validating webhook can run any code — but it is an external service in the critical path of every write, and an unreachable one with failurePolicy: Fail can deadlock the cluster (see ValidatingAdmissionWebhook). ValidatingAdmissionPolicy moves the policy inside the apiserver as a CEL expression. CEL is a deliberately non-Turing-complete, side-effect-free, bounded-cost expression language — it always terminates, cannot loop unboundedly, and cannot make network calls. That restriction is the feature: a CEL policy cannot hang, cannot be unreachable, and adds microseconds rather than a round-trip.
The three-resource split is a deliberate separation of concerns: the policy is the reusable logic (written once, by a platform team), the binding is the scoping-and-enforcement decision (written per environment — Warn in staging, Deny in production), and the parameter resource is the tunable data (the actual numeric limit, supplied without rewriting the CEL).
flowchart TD P[ValidatingAdmissionPolicy<br/>spec.matchConstraints<br/>spec.validations CEL<br/>spec.paramKind] -->|policyName| B[ValidatingAdmissionPolicyBinding<br/>spec.matchResources<br/>spec.validationActions<br/>spec.paramRef] PR[Parameter resource<br/>ConfigMap or CRD<br/>e.g. maxReplicas: 5] -->|paramRef| B B --> E{Request matches?<br/>matchConstraints AND<br/>matchResources} E -->|yes| C[Evaluate CEL validations<br/>in-process, no network] E -->|no| S[Skip] C -->|expression false| A[validationActions:<br/>Deny / Warn / Audit] C -->|expression true| OK[Admit] style C fill:#e8f0ff
The three-resource model. The insight: the policy is reusable logic, the binding turns it on for a scope and picks the enforcement action, and the parameter resource supplies the data — so one CEL policy can be enforced strictly in prod and as a warning in staging by shipping two bindings, no policy edit needed.
Mechanical Walk-through
- A platform team writes a
ValidatingAdmissionPolicy. Itsspec.matchConstraintsdeclares which API operations/resources the policy can apply to (a coarse filter). Itsspec.validationsis a list of CEL expressions; each must evaluate to a boolean —truemeans the object passes,falsemeans it fails. Optionallyspec.paramKinddeclares the GVK of a parameter resource the CEL can reference asparams. - The policy on its own is inert. It does nothing until a
ValidatingAdmissionPolicyBindingreferences it bypolicyName. - The binding’s
spec.matchResourcesnarrows scope further (namespace/object label selectors,matchConditions). Itsspec.validationActionslists what happens when a validation fails. Its optionalspec.paramRefpoints at the concrete parameter object. - On a matching create/update/delete, the compiled-in
ValidatingAdmissionPolicyadmission plugin (see Admission Controllers) evaluates everyvalidationsexpression in-process. CEL expressions seeobject(the incoming object),oldObject(for updates),request(the admission attributes),params(the bound parameter resource),namespaceObject, andauthorizer(for authorization sub-checks). - If any expression is
false, the binding’svalidationActionsdecide the outcome:Deny— reject the request with a403.Warn— admit, but return aWarning:header thatkubectlprints.Audit— admit, but record the failure as an annotation in the audit log.AuditandWarnmay be combined;Denymay be combined withAudit;Deny + Warntogether is rejected as redundant.
- The policy’s
spec.failurePolicy(Fail/Ignore) governs what happens if the expression itself errors (e.g. a CEL type error or a missing field). Note this is “policy evaluation failed,” not “webhook unreachable” — there is no network, so the catastrophic webhook-down case simply does not exist.
Configuration / API Surface
# 1. The policy: reusable CEL logic
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: replica-limit.example.com
spec:
failurePolicy: Fail # behaviour if the CEL expression errors
paramKind: # optional: bind a parameter resource
apiVersion: rules.example.com/v1
kind: ReplicaLimit
matchConstraints: # coarse filter: which requests this policy can see
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= params.maxReplicas"
message: "Deployment replica count exceeds the configured maximum."
reason: Invalid
---
# 2. The binding: scope + enforcement action + parameter wiring
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: replica-limit-prod.example.com
spec:
policyName: replica-limit.example.com
validationActions: [Deny] # Deny | Warn | Audit (Warn+Audit combinable)
paramRef:
name: prod-replica-limit # the ReplicaLimit object holding maxReplicas
parameterNotFoundAction: Deny
matchResources: # fine filter: only production namespaces
namespaceSelector:
matchLabels:
environment: production
---
# 3. The parameter resource: the tunable data
apiVersion: rules.example.com/v1
kind: ReplicaLimit
metadata:
name: prod-replica-limit
maxReplicas: 5Field-by-field:
matchConstraintsvsmatchResources— the policy’smatchConstraintsis the upper bound of what it can match; the binding’smatchResourcesnarrows within that. A request must satisfy both.validations[].expression— a CEL boolean.object,oldObject,params,request,namespaceObject,authorizerare the available variables.validations[].messageExpression(a CEL string) can build a dynamic failure message.paramKind/paramRef—paramKinddeclares the type in the policy;paramRefbinds a concrete instance in the binding. The same policy enforced against different limits in different environments needs only different parameter objects.validationActions— the enforcement decision lives in the binding, not the policy. This is what lets a single policy be a non-blockingWarnwhile a team adopts it, then flip toDeny.failurePolicy— applies when the CEL expression cannot be evaluated (type error, missing field). Distinct from a webhook’sfailurePolicy, which covers an unreachable service.
Failure Modes
- CEL evaluation error under
failurePolicy: Fail. If an expression references a field that may be absent (object.spec.foo) and the field is missing, evaluation errors and — withfailurePolicy: Fail— the request is rejected. The fix is defensive CEL:has(object.spec.foo) && object.spec.foo == .... - Cannot read external data. CEL has no network access. A policy that needs “is this image signature valid against our Rekor log?” or “is this team within its cloud-cost budget?” cannot be expressed as a ValidatingAdmissionPolicy — that still requires a webhook.
- Expressiveness ceiling. CEL is not a general programming language. Complex multi-object reasoning, stateful checks, or anything requiring iteration beyond CEL’s bounded comprehensions hits a wall. The symptom is a policy that becomes an unreadable one-line CEL expression — at that point a webhook (or splitting the policy) is the honest answer.
matchConditionscost. Excessively complexmatchConditionsCEL is evaluated on every candidate request; an expensive match expression taxes the apiserver even for requests the policy ultimately skips.- Silent no-op. A policy with no binding does nothing. A common confusion: applying the
ValidatingAdmissionPolicyand expecting enforcement — theValidatingAdmissionPolicyBindingis mandatory.
Alternatives and When to Choose Them
- vs ValidatingAdmissionWebhook. ValidatingAdmissionPolicy wins on operations: in-process, no certs, no extra Deployment, cannot wedge the cluster, lower latency. The webhook wins on capability: arbitrary code, external data, stateful checks. The current Kubernetes direction is CEL-based policies absorbing the simple webhook cases while webhooks retain the genuinely complex ones.
- vs built-in plugins.
PodSecurity,ResourceQuota,LimitRangerare still the right tool for the specific things they do. ValidatingAdmissionPolicy is for custom invariants those plugins do not cover. - vs OPA Gatekeeper / Kyverno. Both policy engines are webhook-based; both are now adding CEL or generating ValidatingAdmissionPolicy objects under the hood. For a brand-new cluster with modest policy needs, native ValidatingAdmissionPolicy avoids running a policy-engine Deployment at all.
- vs MutatingAdmissionPolicy. ValidatingAdmissionPolicy can only reject; its sibling MutatingAdmissionPolicy can modify. If the goal is “default a missing field” rather than “reject a bad value,” the mutating policy is correct.
Production Notes
- ValidatingAdmissionPolicy reached GA in Kubernetes 1.30 (Kubernetes 1.30 release); it had been alpha in 1.26 and beta in 1.28. The compiled-in
ValidatingAdmissionPolicyadmission plugin is default-on in current Kubernetes. - Adoption pattern: ship the binding with
validationActions: [Audit, Warn]first, watch the audit log andkubectlwarnings for how many existing workloads would violate the policy, fix or exempt them, then flip the binding to[Deny]. The binding-level action makes this a one-line change with no policy edit. - Type-checking: the apiserver type-checks CEL expressions against the resource schema when the policy is created, surfacing many mistakes (typos in field names, type mismatches) at apply time rather than at admission time — a real advantage over webhooks, where such bugs only show up under live traffic.
See Also
- MutatingAdmissionPolicy — the mutation-side sibling, same CEL machinery
- CEL in Kubernetes — the expression language ValidatingAdmissionPolicy is built on
- ValidatingAdmissionWebhook — the external-webhook alternative this feature replaces for simple cases
- Admission Controllers — the compiled-in
ValidatingAdmissionPolicyplugin that evaluates these - API Server Request Flow — ValidatingAdmissionPolicy runs in the validating-admission stage
- OPA Gatekeeper / Kyverno — webhook-based policy engines for cases CEL cannot reach
- Custom Resource Definition — parameter resources are commonly CRDs
- Kubernetes Audit Logging — where
Audit-action failures are recorded - Kubernetes MOC — parent map (§12 Security)