Skip to main content
5 min read·842 words

ValidatingAdmissionPolicy in Kubernetes

Required knowledge for the CKS certification.

ValidatingAdmissionPolicy is a declarative, in-process alternative to validating admission webhooks, stable and enabled by default as of Kubernetes v1.30. Where a ValidatingWebhookConfiguration requires a running webhook server, valid TLS certificates, and a network round-trip on every matching request, a ValidatingAdmissionPolicy evaluates Common Expression Language (CEL) expressions directly inside the API server process — no external server, no network hop, and no separate deployment to maintain.

Security teams use ValidatingAdmissionPolicy to reject requests that violate cluster policy: containers requesting excessive replica counts, images pulled from untrusted registries, pods missing required security contexts, or namespaces lacking mandatory labels. Because the policy evaluates at admission time, violations are caught before the object is persisted and visible to any controller or workload.


1. How ValidatingAdmissionPolicy Differs from Webhooks​

AspectValidatingWebhookConfigurationValidatingAdmissionPolicy
ExecutionExternal webhook serverIn-process, within the API server
LanguageAny (webhook implementation)CEL expressions
SetupTLS certificate, Service, DeploymentKubernetes API objects only
LatencyNetwork round-trip per requestSub-millisecond, evaluated in-process
Failure behaviorfailurePolicy: Ignore or FailfailurePolicy: Ignore or Fail
Available sinceKubernetes v1.0Stable GA since v1.30

The tradeoff: CEL is intentionally restricted. Policies requiring external state lookups, access to resources outside the CEL variable set, or complex imperative logic still require an external webhook. For field validation, label enforcement, replica limits, and image restriction use cases — the majority of admission control requirements — ValidatingAdmissionPolicy is simpler and more reliable.


2. Policy Components​

A complete policy requires two resources. A third (a parameter resource) is optional.

ResourcePurpose
ValidatingAdmissionPolicyDefines CEL validation rules and which resource types they apply to
ValidatingAdmissionPolicyBindingBinds the policy to a scope (namespaces, object selectors) and specifies enforcement actions
Parameter resourceAny Kubernetes object type; provides configuration accessible in CEL via the params variable

The parameter resource is only needed when spec.paramKind is set in the policy. Without it, CEL expressions reference only the incoming object's own fields.

Both a ValidatingAdmissionPolicy and a corresponding ValidatingAdmissionPolicyBinding must exist for the policy to have any effect.


3. Validation Actions​

Each ValidatingAdmissionPolicyBinding specifies one or more validationActions:

ActionBehavior
DenyValidation failure results in the request being rejected
WarnValidation failure is reported to the client as a warning; the request proceeds
AuditValidation failure is included in the audit event for the request

Constraint: Deny and Warn cannot be used together. Warn and Audit may be combined to surface and record violations without blocking requests — a pattern useful when rolling out a new policy.


4. Writing a Basic Policy​

The following policy denies any Deployment in a namespace labeled environment: test that requests more than five replicas.

ValidatingAdmissionPolicy​

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "replica-limit-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 5"
message: "Replicas must not exceed 5 in test namespaces."
reason: Invalid

ValidatingAdmissionPolicyBinding​

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "replica-limit-binding-test.example.com"
spec:
policyName: "replica-limit-policy.example.com"
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
environment: test

When a Deployment is created or updated in a namespace labeled environment: test and the replica count exceeds 5, the API server rejects the request with:

ValidatingAdmissionPolicy 'replica-limit-policy.example.com' with binding 'replica-limit-binding-test.example.com' denied request: failed expression: object.spec.replicas <= 5

5. Parameterized Policies​

Hard-coding values inside CEL expressions makes policies difficult to update across teams. Use a parameter resource to separate policy logic from configuration. Each team or namespace can bind to a different parameter object without modifying the policy itself.

Policy with paramKind​

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "replica-limit-policy.example.com"
spec:
failurePolicy: Fail
paramKind:
apiVersion: rules.example.com/v1
kind: ReplicaLimit
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= params.maxReplicas"
message: "Replicas exceed the limit defined in the cluster policy."
reason: Invalid

The CEL expression reads params.maxReplicas from the ReplicaLimit object referenced in the binding. Different bindings can reference different parameter objects, providing per-namespace or per-team policy thresholds from a single policy definition.


6. Scoping with matchResources​

The binding controls which requests activate the policy. Use matchResources to restrict scope by namespace labels and object labels:

spec:
matchResources:
namespaceSelector:
matchLabels:
environment: production
objectSelector:
matchExpressions:
- key: app.kubernetes.io/managed-by
operator: In
values: ["helm"]

This applies the policy only to objects in production-labeled namespaces and only to objects carrying the app.kubernetes.io/managed-by=helm label — leaving system pods and manually-created objects unaffected.


7. Warn and Audit Modes​

To detect violations without blocking traffic — useful when evaluating a new policy before enforcing it — use Warn and Audit together:

spec:
validationActions: [Warn, Audit]

With this configuration:

  • The violation message appears as a warning in the API response the client receives.
  • The violation is written to the API server audit log under the validation.policy.admission.k8s.io/validation_failure annotation key.
  • The request is not denied.

Monitor the audit log during the evaluation period. Once violations drop to zero or to an acceptable baseline, change the binding to validationActions: [Deny] to enforce the policy.


8. Failure Policy​

failurePolicy controls behavior when the CEL expression cannot be evaluated, not when it evaluates to false:

  • Fail (default): The admission request is rejected when the expression fails to evaluate. Use this for security-critical policies.
  • Ignore: The admission request proceeds when the expression fails to evaluate. Use this only for non-blocking informational checks.

9. Naming Constraints​

Names ending in .static.k8s.io are reserved for Manifest-Based Admission Control and cannot be used for API-based ValidatingAdmissionPolicy or ValidatingAdmissionPolicyBinding objects when the ManifestBasedAdmissionControlConfig feature gate is enabled.

Use a domain-qualified naming scheme (e.g., policy-name.security.corp.com) to avoid collisions and indicate ownership.


10. Audit Validation Failures​

Validation failures appear in the API server audit log under the annotation key validation.policy.admission.k8s.io/validation_failure. Each event identifies the policy name, binding name, and the CEL expression that failed.

# Find validation failure audit events in an audit log file
grep "validation.policy.admission.k8s.io" /var/log/kubernetes/audit.log \
| jq '.annotations["validation.policy.admission.k8s.io/validation_failure"]'

Use these events to detect bypass attempts, measure how often a policy fires before enabling Deny, and produce evidence for compliance audits.


11. Discovering Existing Policies​

# List all ValidatingAdmissionPolicies in the cluster
kubectl get validatingadmissionpolicies

# List all bindings with their policies and enforcement actions
kubectl get validatingadmissionpolicybindings -A \
-o custom-columns="NAME:.metadata.name,POLICY:.spec.policyName,ACTIONS:.spec.validationActions"

12. Verification​

After applying a policy and binding, confirm enforcement works as expected:

# Attempt a CREATE that the policy should deny
kubectl create deployment nginx-test --image=nginx --replicas=10 -n test-namespace

# Expected output when the namespace is labeled environment: test:
# error: failed to create deployment: ValidatingAdmissionPolicy 'replica-limit-policy.example.com'
# with binding 'replica-limit-binding-test.example.com' denied request:
# failed expression: object.spec.replicas <= 5

# Confirm the namespace carries the required selector label
kubectl get namespace test-namespace --show-labels

# Confirm a compliant request is accepted
kubectl create deployment nginx-ok --image=nginx --replicas=3 -n test-namespace

Also verify that existing objects are not retroactively blocked. ValidatingAdmissionPolicy evaluates on CREATE and UPDATE requests as declared in matchConstraints.resourceRules.operations. Existing objects are unaffected until they are modified.


References​

This article is based on information from the following official sources:

  1. Validating Admission Policy - Kubernetes
  2. Kubernetes v1.30 Release - Kubernetes Blog