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.
Generate solid, high-quality, production-ready CEL (Common Expression Language) code for Kubernetes admission control, CRD validation, and security policy enforcement.
Use this skill when the user wants to:
// Comparison
== != < <= > >=
// Logical
&& || !
// Arithmetic
+ - * / %
// Membership
in // Check if element exists in collection
// Ternary
condition ? trueValue : falseValue
// Field existence (CRITICAL - always check before accessing optional fields)
has(object.spec.field)
// String functions
size(string) // Length
contains(string, substring) // Contains check
startsWith(string, prefix) // Prefix check
endsWith(string, suffix) // Suffix check
matches(string, regex) // Regex match
split(string, delimiter) // Split to list
lower(string) // Lowercase
upper(string) // Uppercase
trim(string) // Remove whitespace
// Collection functions
size(list) // List length
all(list, var, condition) // All elements satisfy
exists(list, var, condition) // Any element satisfies
exists_one(list, var, condition) // Exactly one satisfies
filter(list, var, condition) // Filter elements
map(list, var, transformation) // Transform elements
// Kubernetes-specific
quantity(string) // Parse K8s quantity (e.g., "2Gi", "500m")
isQuantity(string) // Validate quantity format
url(string) // Parse URL
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 resourceIn CRD Validation (x-kubernetes-validations):
self - The field being validatedoldSelf - Previous field value (UPDATE)Before writing any CEL:
Follow these principles:
1. Always use has() for optional fields:
// CORRECT - Safe field access
has(object.spec.template.spec.securityContext) &&
object.spec.template.spec.securityContext.runAsNonRoot == true
// WRONG - Will error if field doesn't exist
object.spec.template.spec.securityContext.runAsNonRoot == true
2. Handle null/missing values gracefully:
// Check for labels existence before accessing
has(object.metadata.labels) &&
'app' in object.metadata.labels &&
object.metadata.labels['app'] == 'myapp'
3. Use short-circuit evaluation:
// Fast checks first, expensive operations last
has(object.metadata.labels) && // Fast: field existence
'app' in object.metadata.labels && // Medium: map lookup
object.metadata.labels['app'].matches('^[a-z]+$') // Slow: regex
4. Prefer positive assertions:
// BETTER - Clear intent
object.spec.replicas >= 1 && object.spec.replicas <= 10
// AVOID - Double negatives
!(object.spec.replicas < 1 || object.spec.replicas > 10)
Use this structure:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "policy-name.example.com"
spec:
failurePolicy: Fail # or Ignore for non-critical policies
matchConstraints:
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"
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "policy-binding"
spec:
policyName: "policy-name.example.com"
validationActions: [Deny] # or [Audit] for testing
matchResources:
namespaceSelector:
matchLabels:
environment: production
Use dry-run mode:
kubectl apply --dry-run=server -f test-resource.yaml
Start with Audit mode:
validationActions: [Audit] # Log violations, don't block
Check events for violations:
kubectl get events --field-selector reason=PolicyAudit
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 == true
message: "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"
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"
Require specific labels:
validations:
- expression: |
has(object.metadata.labels) &&
'app' in object.metadata.labels &&
'environment' in object.metadata.labels &&
'team' in object.metadata.labels
message: "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() <= 63
message: "Resource name must be lowercase alphanumeric with hyphens, max 63 chars"
Disallow hostNetwork:
validations:
- expression: |
!has(object.spec.template.spec.hostNetwork) ||
object.spec.template.spec.hostNetwork == false
message: "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"
For CustomResourceDefinitions, use x-kubernetes-validations:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: myresources.example.com
spec:
group: example.com
versions:
- name: v1
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
replicas:
type: integer
minimum: 1
maximum: 100
x-kubernetes-validations:
- rule: "self >= 1 && self <= 100"
message: "Replicas must be between 1 and 100"
schedule:
type: string
x-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"
Help the cost estimator by bounding collections:
properties:
containers:
type: array
maxItems: 20 # Bound array iterations
labels:
type: object
maxProperties: 50 # Bound map operations
name:
type: string
maxLength: 253 # Bound string operations
# 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"
Limit policy scope to reduce evaluations:
matchConstraints:
resourceRules:
- apiGroups: ["apps"] # Specific group
apiVersions: ["v1"] # Specific version
operations: ["CREATE"] # Only CREATE, not every operation
resources: ["deployments"] # Specific resource
namespaceSelector: # Target specific namespaces
matchLabels:
enforce-policies: "true"
Error: "no such key"
// Problem: Accessing map key that doesn't exist
object.metadata.labels['app']
// Fix: Check key existence
has(object.metadata.labels) && 'app' in object.metadata.labels &&
object.metadata.labels['app']
Error: "type mismatch"
// Problem: Comparing wrong types
object.spec.replicas == "5"
// Fix: Use correct type
object.spec.replicas == 5
Error: "no such field"
// Problem: Accessing field on null object
object.spec.securityContext.runAsNonRoot
// Fix: Check parent existence
has(object.spec.securityContext) &&
object.spec.securityContext.runAsNonRoot == true
# Test with dry-run
kubectl apply --dry-run=server -f resource.yaml
# Check policy status
kubectl get validatingadmissionpolicy
kubectl describe validatingadmissionpolicy <name>
# View audit events
kubectl get events --field-selector reason=PolicyAudit
# Check type checking warnings
kubectl get validatingadmissionpolicy <name> -o yaml | grep -A 20 typeChecking
When generating CEL policies, always provide:
Always verify target cluster version before generating policies.
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer