> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Add A CEL Validation Policy

> How to add a Kubernetes ValidatingAdmissionPolicy using CEL to validate WebApp resources, enforcing image tag pinning and replica bounds at admission time

In this lesson we'll add admission-time validation for a custom WebApp resource by authoring a ValidatingAdmissionPolicy that evaluates CEL expressions. The WebApp controller itself does not change — instead the policy executes inside the API server and decides whether CREATE/UPDATE requests are allowed before objects are persisted.

Create the ValidatingAdmissionPolicy that targets the WebApp resource. Note the API version `admissionregistration.k8s.io/v1`, which is the Kubernetes API group that defines validating admission policies.

```yaml theme={null}
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: webapp-validation
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["webapp.kodekloud.com"]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["webapps"]
  validations:
    - expression: "!object.spec.image.endsWith(':latest')"
      message: "spec.image must not use ':latest'"
    - expression: "!has(object.spec.replicas) || (object.spec.replicas >= 1 && object.spec.replicas <= 10)"
      message: "spec.replicas must be between 1 and 10"
      messageExpression: "'spec.replicas must be between 1 and 10, got ' + string(object.spec.replicas)"
```

<Callout icon="lightbulb" color="#1CB2FE">
  Using `failurePolicy: Fail` ensures that if the policy cannot be evaluated, or a validation fails, the API server will block the matching request rather than allowing it to proceed silently.
</Callout>

Policy fields — concise explanation:

* `matchConstraints.resourceRules` — scopes the policy so it only evaluates requests for the `webapps` resource in the `webapp.kodekloud.com/v1` API group during `CREATE` and `UPDATE` operations.
* `validations` — an array of CEL expressions that must evaluate to true for the request to be allowed. The incoming object is available in CEL as `object`.
  * First validation:
    * Expression: `!object.spec.image.endsWith(':latest')`\
      This denies requests where `spec.image` ends with `:latest`, enforcing pinned image tags.
  * Second validation:
    * Expression: `!has(object.spec.replicas) || (object.spec.replicas >= 1 && object.spec.replicas <= 10)`\
      Because `replicas` is optional in the CRD, this expression allows missing `replicas` but requires present values to be between 1 and 10.
    * `messageExpression` constructs a rejection message that embeds the actual invalid value for clearer errors.

Summary of validations:

|                                       Purpose | CEL expression                                                                              | Error message                                                               |
| --------------------------------------------: | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|                  Disallow `:latest` image tag | `!object.spec.image.endsWith(':latest')`                                                    | `spec.image must not use ':latest'`                                         |
| Enforce replicas between 1 and 10 (or absent) | `!has(object.spec.replicas) \|\| (object.spec.replicas >= 1 && object.spec.replicas <= 10)` | `spec.replicas must be between 1 and 10` (enhanced via `messageExpression`) |

Next, create a ValidatingAdmissionPolicyBinding to activate the policy. The binding attaches the policy to evaluation and sets the validation action to `Deny`.

```yaml theme={null}
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: webapp-validation-binding
spec:
  policyName: webapp-validation
  validationActions: ["Deny"]
```

Apply the policy and its binding so the API server can begin evaluating matching WebApp CREATE and UPDATE requests:

```bash theme={null}
$ kubectl apply -f config/policy/webapp-policy.yaml
validatingadmissionpolicy.admissionregistration.k8s.io/webapp-validation created
$ kubectl apply -f config/policy/webapp-policy-binding.yaml
validatingadmissionpolicybinding.admissionregistration.k8s.io/webapp-validation-binding created
$ kubectl get validatingadmissionpolicy webapp-validation
NAME                VALIDATIONS   PARAMKIND   AGE
webapp-validation   2             <unset>     10s
$ kubectl get validatingadmissionpolicybinding webapp-validation-binding
NAME                      POLICYNAME         PARAMREF   AGE
webapp-validation-binding  webapp-validation   <unset>    9s
```

After the binding is observed by the admission plugin, the API server will evaluate matching WebApp CREATE and UPDATE requests using the policy before persisting them to etcd or sending them to any controller.

Test the policy with sample WebApp manifests:

1. Apply a WebApp that uses an unpinned image (`:latest`):

```yaml theme={null}
# config/samples/bad-latest-webapp.yaml
apiVersion: webapp.kodekloud.com/v1
kind: WebApp
metadata:
  name: bad-latest
  namespace: webapp-demo
spec:
  image: nginx:latest
```

```bash theme={null}
$ kubectl apply -f config/samples/bad-latest-webapp.yaml
The webapps "bad-latest" is invalid: ValidatingAdmissionPolicy 'webapp-validation' with binding 'webapp-validation-binding' denied request: spec.image must not use ':latest'
```

The API server denies the request with the image rule message — proof the CEL policy ran at admission-time.

2. Apply a WebApp with an acceptable image but invalid replicas (50):

```yaml theme={null}
# config/samples/bad-replicas-webapp.yaml
apiVersion: webapp.kodekloud.com/v1
kind: WebApp
metadata:
  name: bad-replicas
  namespace: webapp-demo
spec:
  image: nginx:1.27
  replicas: 50
```

```bash theme={null}
$ kubectl apply -f config/samples/bad-replicas-webapp.yaml
The webapps "bad-replicas" is invalid: ValidatingAdmissionPolicy 'webapp-validation' with binding 'webapp-validation-binding' denied request: spec.replicas must be between 1 and 10, got 50
```

The denial message includes the rejected replica count, thanks to the configured `messageExpression`.

3. Apply a WebApp that satisfies both rules (pinned image and valid replicas):

```yaml theme={null}
# config/samples/good-webapp.yaml
apiVersion: webapp.kodekloud.com/v1
kind: WebApp
metadata:
  name: good-webapp
  namespace: webapp-demo
spec:
  image: nginx:1.27
  replicas: 3
```

```bash theme={null}
$ kubectl apply -f config/samples/good-webapp.yaml
webapp.webapp.kodekloud.com/good-webapp created
```

Same CRD and controller — but the API server now prevents invalid desired state from entering the system, reducing wasted controller processing and improving feedback to API clients.

Inspecting policy status:

```bash theme={null}
$ kubectl get validatingadmissionpolicy webapp-validation -o jsonpath='{.status}'
```

When to use validating admission policies with CEL:

* Good for centralized, fast checks on object-local properties: image tag pinning, numeric bounds, required labels/annotations, simple string patterns.
* Not intended for complex business logic that requires cross-resource checks or external calls — those belong in controllers or external admission webhooks.

References:

* [Validating Admission Policy API (admissionregistration.k8s.io)](https://kubernetes.io/docs/reference/validation/)
* [Kubernetes Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/)
* [Common Expression Language (CEL) — specification](https://opensource.google/docs/cel/spec/)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/kubernetes-operators/module/06ac03ac-518a-4bc6-b2f8-6ed63fcb26d5/lesson/5de52805-a91c-4d75-b5f3-538d09c68779" />

  <Card title="Practice Lab" icon="flask-conical" cta="Learn more" href="https://learn.kodekloud.com/user/courses/kubernetes-operators/module/06ac03ac-518a-4bc6-b2f8-6ed63fcb26d5/lesson/e511ca5d-b23f-4037-873e-9b377026af20" />
</CardGroup>
