Best Practices for Optimizing CEL in Kyverno

Mohd KamaalMohd Kamaal
4 min read

Kyverno’s integration with Common Expression Language (CEL), combined with its similarity with the validatingAdmissionPolicy structure in validatingPolicy and imageValidatingPolicy, empowers Kubernetes administrators to craft robust, declarative policies. Yet, unoptimized CEL expressions can grow verbose, and managing policies across diverse resource types can add complexity. This post details best practices to streamline CEL policies using optionals, variables, Kyverno’s built-in functions, and techniques for multi-resource checks—while showcasing how Kyverno’s autogen feature simplifies the process.


The Challenge: Redundancy and Multi-Resource Complexity

CEL policies often involve repetitive checks like has() for field existence, leading to cluttered expressions. For example:

object.spec.containers.all(container, 
  has(container.securityContext) && 
  has(container.securityContext.allowPrivilegeEscalation) && 
  container.securityContext.allowPrivilegeEscalation == false)

Additionally, applying policies across multiple resource kinds (e.g., Pods, Deployments) can lead to conflicts or unnecessary execution. Let’s tackle both issues efficiently.


Best Practices

1. Use Optionals to Simplify Field Access

CEL’s optional chaining (.?) and default values (.orValue()) eliminate redundant has() checks, making expressions concise and clear.

Before:

has(container.securityContext) && 
has(container.securityContext.allowPrivilegeEscalation) && 
container.securityContext.allowPrivilegeEscalation == false

After:

container.?securityContext.?allowPrivilegeEscalation.orValue(true) == false

Why it works: Optional chaining navigates nested fields safely, with .orValue() providing a fallback for absent fields.


2. Define Variables for Reusable Logic

Variables consolidate complex or repeated logic into a single definition, reducing duplication and enhancing maintainability. Note that variables cannot be defined within loops like all.containers(), and loop-specific logic must stay within its scope.

Before:

(object.spec.containers + 
 (has(object.spec.initContainers) ? object.spec.initContainers : []) + 
 (has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : []))

After:

variables:
- name: ctnrs
  expression: >-
    object.spec.containers + 
    object.spec.?initContainers.orValue([]) + 
    object.spec.?ephemeralContainers.orValue([])
validations:
- expression: variables.ctnrs.all(c, ...)

Why it works: The variable centralizes logic for reuse and easier updates.


Note: These features are currently supported as alpha in the main branch and are slated for full support in the Kyverno 1.14 release.

3. Leverage Kyverno’s Built-In Functions

Kyverno provides built-in functions in its library to quickly access resources, removing the need for repetitive CEL expressions. Functions such as imagedata.Get(), resource.Get(), resource.List(), parseServiceAccount(), http.Get , http.Post and the Image function—which can inspect image internals like architecture, digest, manifest, and more—along with globalContext.Get() and others, streamline policy creation in validatingPolicy and imageValidatingPolicy, while maintaining structural alignment with validatingAdmissionPolicy. These functions minimise redundancy, accelerate resource retrieval, and integrate smoothly with custom variables and expressions, enhancing policy development efficiency.


4. Handle Multi-Resource Checks and Conflicts

When a policy applies to multiple resource kinds (e.g., Pods, Deployments, StatefulSets), expressions may conflict or execute unnecessarily. For example, checking object.spec.containers works for Pods but fails for Deployments, where the path is object.spec.template.spec.containers. Blindly applying expressions across kinds can lead to errors or unintended behavior.

Solution: Scope Expressions by Resource Kind

You can use CEL to conditionally execute checks based on the resource’s kind, ensuring expressions only run when relevant. The object.kind field lets you target specific resources.

Example:
object.kind == "Pod" ? 
  object.spec.containers.all(container, 
    container.?securityContext.?allowPrivilegeEscalation.orValue(true) == false) : 
  object.kind == "Deployment" ? 
    object.spec.template.spec.containers.all(container, 
      container.?securityContext.?allowPrivilegeEscalation.orValue(true) == false) : 
    true

Why it works: The ternary operator (?:) checks object.kind and applies the appropriate expression, defaulting to true (no enforcement) for unmatched kinds, avoiding conflicts.

With Variables:
variables:
- name: ctnrs
  expression: >-
    object.kind == "Pod" ? object.spec.containers : 
    object.kind == "Deployment" ? object.spec.template.spec.containers : []
validations:
- expression: variables.ctnrs.all(c, c.?securityContext.?allowPrivilegeEscalation.orValue(true) == false)

Kyverno’s Autogen Simplifies This

Fortunately, Kyverno’s autogen feature reduces the need for such manual scoping. When you write a policy for a single resource kind (e.g., Pod or Deployment), Kyverno can automatically generate equivalent policies for related kinds like CronJobs, StatefulSets, or DaemonSets. This means you can focus on crafting a single, well-optimized expression, and Kyverno handles the multi-resource adaptation for you—no need to manually account for every kind.


5. Keep Names Descriptive

Use meaningful variable names (e.g., ctnrs for containers, secCtx for security context) to make policies intuitive and self-documenting.


6. Test Expressions First

Validate your CEL expressions in the CEL Playground and test in policies in Kyverno Playground before deploying them. This ensures correctness, especially for multi-resource logic.


Conclusion

By combining optionals, variables, Kyverno’s built-in functions, and either kind-specific checks or its autogen feature, you can craft CEL policies that are elegant, efficient, and conflict-free. Apply these best practices in your Kyverno workflows to enforce Kubernetes policies with precision and ease. Test, refine, and deploy—your cluster will benefit from the clarity and control!

0
Subscribe to my newsletter

Read articles from Mohd Kamaal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mohd Kamaal
Mohd Kamaal

Open source enthusiast | Blogger