Blog

Introducing Kubernetes Validation with Common Expression Language (CEL) ☯
Photo by Saifeddine Rajhi

Introducing Kubernetes Validation with Common Expression Language (CEL) ☯

6mins read
  • Kubernetes
  • Validation
  • Common Expression Language
  • k8s
  • Kyverno
  • DevOps

    Content

    Simplify Complex Validations with CEL and Kyverno Policies ☯

    🔆 Introduction

    Kubernetes is extensible and allows users to create Custom Resources (CR) and Custom Resource Definitions (CRD) that define the structure and meta-attributes of future resources. However, creating complex validation webhooks for each CRD can be an operational and development overhead.

    Kubernetes now 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 creating 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 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 examples of using 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.

    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."
            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))"

    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:

    • oldObject: The existing object. The value is null for CREATE requests.
    • authorizer: It can be used to perform authorization checks.
    • 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 is 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 to 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 Kubernetes 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, つづく 🎉

    💡 Thank you for Reading !! 🙌🏻😁📃, see you in the next blog.🤘 Until next time 🎉

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

    ♻️ LinkedIn: Saifeddine Rajhi

    ♻️ X/Twitter: @rajhisaifeddine

    The end ✌🏻

    🔰 Keep Learning !! Keep Sharing !! 🔰

    📅 Stay updated

    Subscribe to our newsletter for more insights on AWS cloud computing and containers.