Introducing Kubernetes Validation with — Common Expression Language — CEL

Seifeddine Rajhi
7 min readFeb 19, 2024

Simplify Complex Validations with CEL and Kyverno Policies ☯

🔆 Introduction:

Kubernetes is extensible and allows users to create Custom Resources (CR) and Custom Resource Definition (CRD) that will define the structure and meta-attributes of the future resource.

But, you have to write and deploy a validating webhook to create complex validations webhook for each CRD, this is an operational and development overhead when you have to develop & deploy numerous CRDs.

But now, Kubernetes provides a solution in the form of the Common Expression Language (CEL). CEL is a simple, yet powerful language that allows you to define complex validation rules directly in the Kubernetes API server.

With CEL, you can introduce complex validations without having to create your own validating webhooks in code.

Kyverno is a Kubernetes-native policy engine that allows you to define policies as code. With Kyverno, you can define policies that enforce best practices, security standards, and compliance requirements.

By using CEL expressions in Kyverno policies, you can simplify your validation rules and make them more expressive.

In this blog post, we’ll explore how to use CEL expressions in Kyverno policies to simplify your Kubernetes validation rules.

We’ll walk through some examples of how to use CEL expressions in Kyverno policies to enforce policies for your Kubernetes resources.

So, if you’re looking to simplify your Kubernetes validation rules and make them more expressive, read on to learn how to use CEL expressions in Kyverno policies.

Table of Contents: 
.
CEL and Validation in Kubernetes
·
A Deep Dive into CEL
· Kyverno: A Policy Engine for k8s Resource Validation
·
Using CEL Expressions in Kyverno Policies
CEL Preconditions in Kyverno Policies

CEL and Validation in Kubernetes:

Kubernetes Custom Resource Definitions (CRDs) support validation through structural schemas, OpenAPI v3 validation rules, custom validators, and admission webhooks.

CRD structural schemas provide type checking, while OpenAPI v3 validation rules offer regex, range, and size limits.

Custom validators can be written in various languages, and admission webhooks are used for more complex validation scenarios.

To address the need for self-contained validation, Kubernetes introduced Common Expression Language (CEL) into CRDs.

CEL is a lightweight, safe, and easy-to-use expression language that supports pre-parsing and type checking at CRD registration time.

This allows syntax and type errors to be caught before the CRD is deployed, improving cluster operability and reducing the need for webhooks.CEL is chosen for its simplicity, flexibility, and ability to express validation rules in a declarative manner.

This choice aligns with Kubernetes’ goal of providing a simple and consistent validation framework for CRDs

A Deep Dive into CEL:

Kubernetes CRDs support validation through CEL to ensure the correctness and consistency of custom resources.

This feature, introduced in Kubernetes 1.25, allows developers to define validation rules using CEL expressions, which are declarative and lightweight.

CEL validation rules are scoped to the location of the x-kubernetes-validations extension in the CRD schema, and the self variable in the CEL expression is bound to the scoped value.

All validation rules are scoped to the current object, and cross-object or stateful validation rules are not supported.

For example, a validation rule using x-kubernetes-validations could be:

openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cpu:
type: string
x-kubernetes-validations:
- rule: "self.cpu in ['1', '2', '4']"
message: "CPU should be one of 1, 2, or 4."
messageExpression: "CPU should be one of 1, 2, or 4."
properties:
...
cpu:
type: integer
required:
- cpu

CEL validation rules support a wide range of use cases, and they were promoted to GA in Kubernetes 1.29.

This feature has been adopted by many Kubernetes ecosystem projects and is widely used in the Kubernetes community.

CEL validation rules provide a lightweight and self-contained validation mechanism that reduces the need for admission webhooks and simplifies the development and operability of CRDs.

They also allow users to define complex validation rules in a declarative manner, improving the readability and maintainability of the CRD schema.

Kyverno: A Policy Engine for k8s Resource Validation:

Kyverno is a policy engine for Kubernetes that enables resource validation, change, and creation based on defined policies.

It uses CEL expressions for validation, reducing the need for admission webhooks. Kyverno policies consist of Match, Exclude, Validate, Mutate, Generate, and Verify Images clauses, allowing users to define complex validation rules in a declarative manner.

Kyverno policies provide a lightweight and self-contained validation mechanism that reduces the need for admission webhooks and simplifies the development and operability of CRDs.

They also allow users to define complex validation rules in a declarative manner, improving the readability and maintainability of the CRD schema.

Using CEL Expressions in Kyverno Policies:

CEL expressions in Kyverno policies are used in the validate section. The message field is used to display an error message when the expression evaluates to false. The validationFailureAction field is used to enforce the validation failure action when the expression evaluates to false.

CEL expressions in Kyverno policies can be used to validate various aspects of Kubernetes resources, such as resource limits, labels, and annotations. Kyverno also supports the automatic generation of policy rules for higher-level controllers, such as Deployments, DaemonSets, StatefulSets, and CronJobs

To create a Kyverno policy that disallows CPU requests without a value, use the following example:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-cpu-requests-without-value
spec:
validationFailureAction: Enforce
background: false
rules:
- name: disallow-cpu-requests-without-value
match:
any:
- resources:
kinds:
- Pod
validate:
cel:
expressions:
- message: "CPU requests must have a value"
expression: "object.spec.containers.all(container, has(container.resources.requests.cpu))"

spec.rules.validate.cel contains CEL expressions that use the Common Expression Language (CEL) to validate the request. If an expression evaluates to false, the validation check is enforced according to the spec.validationFailureAction field.

Now, let’s try deploying a pod without CPU requests :

apiVersion: v1
kind: Pod
metadata:
name: cpu-pod
spec:
containers:
- name: cpu-container
image: nginx
resources:
requests:
memory: "256Mi"

We can see that our policy is enforced. Great!

$ kubectl apply -f pod.yaml 
Error from server: error when creating "pod.yaml": admission webhook "validate.kyverno.svc-fail" denied the request:

resource Pod/default/cpu-pod was blocked due to the following policies

disallow-cpu-requests-without-value:
disallow-cpu-requests-without-value: CPU requests must have a value

Some other useful variables that we can use in CEL expressions are

  1. oldObject: The existing object. The value is null for CREATE requests.
  2. authorizer: It can be used to perform authorization checks.
  3. authorizer.requestResource: A shortcut for an authorization check configured with the request resource (group, resource, (subresource), namespace, name).

CEL Preconditions in Kyverno Policies:

The below policy ensures the hostPort field is set to a value between 5000 and 6000 for pods whose metadata.name set to nginx

kubectl apply -f - <<EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-port-range
spec:
validationFailureAction: Enforce
background: false
rules:
- name: host-port-range
match:
any:
- resources:
kinds:
- Pod
celPreconditions:
- name: "first match condition in CEL"
expression: "object.metadata.name.matches('nginx')"
validate:
cel:
expressions:
- expression: "object.spec.containers.all(container, !has(container.ports) || container.ports.all(port, !has(port.hostPort) || (port.hostPort >= 5000 && port.hostPort <= 6000)))"
message: "The only permitted hostPorts are in the range 5000-6000."
EOF

spec.rules.celPreconditions are CEL expressions. All celPreconditions must be evaluated to true for the resource to be evaluated. Therefore, any Pod with nginx in its metadata.name will be evaluated.

Let’s try deploying an Apache server with hostPort set to 80.

kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: apache
spec:
containers:
- name: apache-server
image: httpd
ports:
- containerPort: 8080
hostPort: 80
EOF

You’ll see that it’s successfully created because the validation rule wasn’t applied on the new Pod as it doesn’t satisfy the celPreconditions. That’s exactly what we need.

Pod/apache created

Let’s try deploying an Nginx server with hostPort set to 80.

kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx-server
image: nginx
ports:
- containerPort: 8080
hostPort: 80
EOF

Since the new Pod satisfies the celPreconditions, the validation rule will be applied. As a result, the creation of the Pod will be blocked as it violates the rule.

Error from server: error when creating "STDIN": admission webhook "validate.kyverno.svc-fail" denied the request: 
resource Pod/default/nginx was blocked due to the following policies
disallow-host-port-range:
host-port-range: The only permitted hostPorts are in the range 5000-6000.

Summary:

The blog provides a good understanding of the Common Expression Language CEL and its application in K8S ValidatingAdmissionPolicies.

It shows the significance of CEL in validating Custom Resource Definitions (CRDs) and its integration into Kyverno policies for resource validation. The post covers the features introduced in Kubernetes ValidatingAdmissionPolicies and demonstrates the use of CEL expressions in Kyverno policies to validate resources.

It also emphasizes the benefits of using CEL for in-process validation, reducing the reliance on admission webhooks, and simplifying the development and operability of CRDs.

Until next time — つづく 🎉 🇵🇸

Photo by Tsuyuri Hara on Unsplash

Thank you for Reading !! 🙌🏻😁📃, see you in the next blog.🤘🇵🇸

🚀 Thank you for sticking up till the end. If you have any questions/feedback regarding this blog feel free to connect with me :

♻️ 🇵🇸LinkedIn: https://www.linkedin.com/in/rajhi-saif/

♻️🇵🇸 Twitter : https://twitter.com/rajhisaifeddine

The end ✌🏻

🔰 Keep Learning !! Keep Sharing !! 🔰

--

--

Seifeddine Rajhi

I build and break stuff, preferably in the cloud, ❤ OpenSource. Twitter: @rajhisaifeddine