Skill v1.0.0
currentAutomated scan100/100name: cel-k8s description: Write production-ready CEL (Common Expression Language) code for Kubernetes ValidatingAdmissionPolicies, CRD validation rules, and security policies. Use when users need to create admission policies, validate Kubernetes resources, enforce security constraints, or write CEL expressions for Kubernetes. metadata: version: "1.0.0"
CEL for Kubernetes - Production-Ready Policy Generator
Generate solid, high-quality, production-ready CEL (Common Expression Language) code for Kubernetes admission control, CRD validation, and security policy enforcement.
When to Use This Skill
Use this skill when the user wants to:
- Write ValidatingAdmissionPolicy resources with CEL expressions
- Create CRD validation rules using x-kubernetes-validations
- Enforce security policies (Pod Security Standards, image restrictions, etc.)
- Validate resource configurations (labels, annotations, resource limits)
- Build admission control without external webhooks
- Migrate from OPA/Gatekeeper/Kyverno to native Kubernetes CEL
- Debug or optimize existing CEL expressions
CEL Quick Reference
Core Operators
// Comparison== != < <= > >=// Logical&& || !// Arithmetic+ - * / %// Membershipin // Check if element exists in collection// Ternarycondition ? trueValue : falseValue
Essential Functions
// Field existence (CRITICAL - always check before accessing optional fields)has(object.spec.field)// String functionssize(string) // Lengthcontains(string, substring) // Contains checkstartsWith(string, prefix) // Prefix checkendsWith(string, suffix) // Suffix checkmatches(string, regex) // Regex matchsplit(string, delimiter) // Split to listlower(string) // Lowercaseupper(string) // Uppercasetrim(string) // Remove whitespace// Collection functionssize(list) // List lengthall(list, var, condition) // All elements satisfyexists(list, var, condition) // Any element satisfiesexists_one(list, var, condition) // Exactly one satisfiesfilter(list, var, condition) // Filter elementsmap(list, var, transformation) // Transform elements// Kubernetes-specificquantity(string) // Parse K8s quantity (e.g., "2Gi", "500m")isQuantity(string) // Validate quantity formaturl(string) // Parse URL
Available Context Variables
In ValidatingAdmissionPolicy:
object- The incoming resource being validatedoldObject- The existing resource (UPDATE operations)request- Admission request metadata (user, operation, namespace)params- Parameters from ValidatingAdmissionPolicyBindingnamespaceObject- The namespace resource
In CRD Validation (x-kubernetes-validations):
self- The field being validatedoldSelf- Previous field value (UPDATE)
Instructions for Writing CEL Policies
Step 1: Understand the Requirement
Before writing any CEL:
- What resource types need validation? (Deployments, Pods, Services, etc.)
- What operations should trigger validation? (CREATE, UPDATE, DELETE)
- What specific conditions must be enforced?
- Should violations block the request or just audit?
Step 2: Design the Expression
Follow these principles:
1. Always use `has()` for optional fields:
// CORRECT - Safe field accesshas(object.spec.template.spec.securityContext) &&object.spec.template.spec.securityContext.runAsNonRoot == true// WRONG - Will error if field doesn't existobject.spec.template.spec.securityContext.runAsNonRoot == true
2. Handle null/missing values gracefully:
// Check for labels existence before accessinghas(object.metadata.labels) &&'app' in object.metadata.labels &&object.metadata.labels['app'] == 'myapp'
3. Use short-circuit evaluation:
// Fast checks first, expensive operations lasthas(object.metadata.labels) && // Fast: field existence'app' in object.metadata.labels && // Medium: map lookupobject.metadata.labels['app'].matches('^[a-z]+$') // Slow: regex
4. Prefer positive assertions:
// BETTER - Clear intentobject.spec.replicas >= 1 && object.spec.replicas <= 10// AVOID - Double negatives!(object.spec.replicas < 1 || object.spec.replicas > 10)
Step 3: Write the ValidatingAdmissionPolicy
Use this structure:
apiVersion: admissionregistration.k8s.io/v1kind: ValidatingAdmissionPolicymetadata:name: "policy-name.example.com"spec:failurePolicy: Fail # or Ignore for non-critical policiesmatchConstraints:resourceRules:- apiGroups: ["apps"]apiVersions: ["v1"]operations: ["CREATE", "UPDATE"]resources: ["deployments"]validations:- expression: "CEL expression here"message: "Human-readable error message"messageExpression: "'Dynamic message with ' + object.metadata.name"
Step 4: Create the Binding
apiVersion: admissionregistration.k8s.io/v1kind: ValidatingAdmissionPolicyBindingmetadata:name: "policy-binding"spec:policyName: "policy-name.example.com"validationActions: [Deny] # or [Audit] for testingmatchResources:namespaceSelector:matchLabels:environment: production
Step 5: Test Before Deploying
- Use dry-run mode:
``bash kubectl apply --dry-run=server -f test-resource.yaml ``
- Start with Audit mode:
``yaml validationActions: [Audit] # Log violations, don't block ``
- Check events for violations:
``bash kubectl get events --field-selector reason=PolicyAudit ``
Common Policy Patterns
Security Policies
Require non-root containers:
validations:- expression: |has(object.spec.template.spec.securityContext) &&has(object.spec.template.spec.securityContext.runAsNonRoot) &&object.spec.template.spec.securityContext.runAsNonRoot == truemessage: "Pods must run as non-root user"
Disallow privileged containers:
validations:- expression: |!has(object.spec.template.spec.containers) ||!object.spec.template.spec.containers.exists(c,has(c.securityContext) &&has(c.securityContext.privileged) &&c.securityContext.privileged == true)message: "Privileged containers are not allowed"
Drop all capabilities:
validations:- expression: |object.spec.template.spec.containers.all(c,has(c.securityContext) &&has(c.securityContext.capabilities) &&has(c.securityContext.capabilities.drop) &&c.securityContext.capabilities.drop.exists(cap, cap == 'ALL'))message: "All containers must drop ALL capabilities"
Restrict to approved registries:
validations:- expression: |object.spec.template.spec.containers.all(c,c.image.startsWith('myregistry.io/') ||c.image.startsWith('gcr.io/myproject/'))message: "Container images must come from approved registries"
Disallow latest tag:
validations:- expression: |object.spec.template.spec.containers.all(c,c.image.contains(':') && !c.image.endsWith(':latest'))message: "Container images must not use 'latest' tag"
Resource Validation
Require resource limits:
validations:- expression: |object.spec.template.spec.containers.all(c,has(c.resources) &&has(c.resources.limits) &&has(c.resources.limits.memory) &&has(c.resources.limits.cpu) &&has(c.resources.requests) &&has(c.resources.requests.memory) &&has(c.resources.requests.cpu))message: "All containers must define CPU and memory limits and requests"
Enforce resource quotas:
validations:- expression: |object.spec.template.spec.containers.all(c,!has(c.resources.requests.memory) ||quantity(c.resources.requests.memory) <= quantity('2Gi'))message: "Memory requests cannot exceed 2Gi per container"
Label and Annotation Validation
Require specific labels:
validations:- expression: |has(object.metadata.labels) &&'app' in object.metadata.labels &&'environment' in object.metadata.labels &&'team' in object.metadata.labelsmessage: "Resources must have 'app', 'environment', and 'team' labels"
Validate label values:
validations:- expression: |!has(object.metadata.labels) ||!('environment' in object.metadata.labels) ||object.metadata.labels['environment'] in ['dev', 'staging', 'prod']message: "environment label must be one of: dev, staging, prod"
Validate naming conventions:
validations:- expression: |object.metadata.name.matches('^[a-z][a-z0-9-]*[a-z0-9]$') &&object.metadata.name.size() <= 63message: "Resource name must be lowercase alphanumeric with hyphens, max 63 chars"
Network Policies
Disallow hostNetwork:
validations:- expression: |!has(object.spec.template.spec.hostNetwork) ||object.spec.template.spec.hostNetwork == falsemessage: "hostNetwork is not allowed"
Disallow hostPath volumes:
validations:- expression: |!has(object.spec.template.spec.volumes) ||object.spec.template.spec.volumes.all(v, !has(v.hostPath))message: "hostPath volumes are not allowed"
CRD Validation Rules
For CustomResourceDefinitions, use x-kubernetes-validations:
apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata:name: myresources.example.comspec:group: example.comversions:- name: v1schema:openAPIV3Schema:type: objectproperties:spec:type: objectproperties:replicas:type: integerminimum: 1maximum: 100x-kubernetes-validations:- rule: "self >= 1 && self <= 100"message: "Replicas must be between 1 and 100"schedule:type: stringx-kubernetes-validations:- rule: "self.matches('^(\\\\d+|\\\\*)(/\\\\d+)?(\\\\s+(\\\\d+|\\\\*)(/\\\\d+)?){4}$')"message: "Must be a valid cron expression"x-kubernetes-validations:- rule: "has(self.replicas) || has(self.schedule)"message: "Either replicas or schedule must be specified"
Performance Best Practices
Set Schema Constraints
Help the cost estimator by bounding collections:
properties:containers:type: arraymaxItems: 20 # Bound array iterationslabels:type: objectmaxProperties: 50 # Bound map operationsname:type: stringmaxLength: 253 # Bound string operations
Avoid O(n^2) Patterns
# BAD - O(n^2): Nested iteration over same collection- expression: |object.spec.containers.all(c1,object.spec.containers.all(c2,c1.name != c2.name || c1 == c2))# GOOD - O(n): Use unique check- expression: |object.spec.containers.map(c, c.name).size() ==object.spec.containers.size()message: "Container names must be unique"
Use Targeted Match Rules
Limit policy scope to reduce evaluations:
matchConstraints:resourceRules:- apiGroups: ["apps"] # Specific groupapiVersions: ["v1"] # Specific versionoperations: ["CREATE"] # Only CREATE, not every operationresources: ["deployments"] # Specific resourcenamespaceSelector: # Target specific namespacesmatchLabels:enforce-policies: "true"
Debugging CEL Expressions
Common Errors and Fixes
Error: "no such key"
// Problem: Accessing map key that doesn't existobject.metadata.labels['app']// Fix: Check key existencehas(object.metadata.labels) && 'app' in object.metadata.labels &&object.metadata.labels['app']
Error: "type mismatch"
// Problem: Comparing wrong typesobject.spec.replicas == "5"// Fix: Use correct typeobject.spec.replicas == 5
Error: "no such field"
// Problem: Accessing field on null objectobject.spec.securityContext.runAsNonRoot// Fix: Check parent existencehas(object.spec.securityContext) &&object.spec.securityContext.runAsNonRoot == true
Testing Commands
# Test with dry-runkubectl apply --dry-run=server -f resource.yaml# Check policy statuskubectl get validatingadmissionpolicykubectl describe validatingadmissionpolicy <name># View audit eventskubectl get events --field-selector reason=PolicyAudit# Check type checking warningskubectl get validatingadmissionpolicy <name> -o yaml | grep -A 20 typeChecking
Output Format
When generating CEL policies, always provide:
- Complete ValidatingAdmissionPolicy YAML
- Corresponding ValidatingAdmissionPolicyBinding YAML
- Test resources (both passing and failing examples)
- Explanation of each validation rule
- Deployment instructions
Reference Files
- [CEL Syntax Reference](./references/cel_syntax.md) - Complete language reference
- [Common Patterns](./references/common_patterns.md) - Reusable validation patterns
- [Templates](./templates/) - Production-ready policy templates
Kubernetes Version Compatibility
- CEL in ValidatingAdmissionPolicy: GA in Kubernetes 1.30+
- CEL in CRD validation: GA in Kubernetes 1.29+
- Alpha/Beta: Available in earlier versions with feature gates
Always verify target cluster version before generating policies.