πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Security advanced ⏱ 15 minutes K8s 1.30+

ValidatingAdmissionPolicy with CEL

Replace admission webhooks with ValidatingAdmissionPolicy and CEL expressions for in-process, low-latency Kubernetes policy enforcement.

By Luca Berton β€’ β€’ πŸ“– 5 min read

πŸ’‘ Quick Answer: ValidatingAdmissionPolicy uses CEL (Common Expression Language) to enforce policies directly in the API server β€” no webhooks, no external dependencies, sub-millisecond evaluation.

The Problem

Admission webhooks (OPA/Gatekeeper, Kyverno) add latency, create availability dependencies, and require separate infrastructure. If the webhook is down, cluster operations can stall or all requests get allowed.

The Solution

Basic Policy: Require Labels

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: require-team-label
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["deployments"]
  validations:
    - expression: "has(object.metadata.labels) && 'team' in object.metadata.labels"
      message: "All deployments must have a 'team' label"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: require-team-label-binding
spec:
  policyName: require-team-label
  validationActions:
    - Deny
  matchResources:
    namespaceSelector:
      matchExpressions:
        - key: environment
          operator: In
          values: ["production"]

Block Privileged Containers

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: deny-privileged
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE"]
        resources: ["pods"]
  validations:
    - expression: |
        object.spec.containers.all(c,
          !has(c.securityContext) ||
          !has(c.securityContext.privileged) ||
          c.securityContext.privileged == false
        )
      message: "Privileged containers are not allowed"

Resource Limits Required

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: require-resource-limits
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
  validations:
    - expression: |
        object.spec.containers.all(c,
          has(c.resources) &&
          has(c.resources.limits) &&
          has(c.resources.limits.memory)
        )
      message: "All containers must have memory limits set"

Parameterized Policy

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: max-replicas
spec:
  failurePolicy: Fail
  paramKind:
    apiVersion: v1
    kind: ConfigMap
  matchConstraints:
    resourceRules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["deployments"]
  validations:
    - expression: "object.spec.replicas <= int(params.data.maxReplicas)"
      messageExpression: "'Replicas ' + string(object.spec.replicas) + ' exceeds max ' + params.data.maxReplicas"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: max-replicas-params
  namespace: default
data:
  maxReplicas: "50"
graph LR
    A[API Request] --> B[API Server]
    B --> C{ValidatingAdmissionPolicy}
    C -->|CEL evaluates| D{Pass?}
    D -->|Yes| E[Request Admitted]
    D -->|No| F[Request Denied]
    Note over C: In-process evaluation<br/>No network call

Common Issues

CEL expression syntax errors Test expressions with kubectl apply --dry-run=server:

kubectl create deployment test --image=nginx --dry-run=server

Policy not enforcing Check that the PolicyBinding exists and validationActions includes Deny:

kubectl get validatingadmissionpolicybindings

Feature gate not enabled (pre-1.30) Requires ValidatingAdmissionPolicy feature gate on api-server in 1.28-1.29.

Best Practices

  • Start with validationActions: [Warn] before switching to [Deny]
  • Use Audit action to log violations without blocking
  • Prefer CEL over webhooks for stateless validation rules
  • Use parameterized policies for tenant-specific thresholds
  • Keep CEL expressions simple β€” complex logic should stay in webhooks
  • Test policies in a staging namespace with dry-run

Key Takeaways

  • ValidatingAdmissionPolicy is GA in 1.30 β€” no feature gates needed
  • CEL expressions evaluate in-process (microseconds, not milliseconds)
  • No external dependencies β€” policies work even if network is partitioned
  • PolicyBindings control where policies apply (namespace selectors, etc.)
  • Warn and Audit actions enable gradual rollout
  • Parameterized policies reuse logic with different thresholds per namespace
#admission-policy #cel #policy #validation
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens