home / skills / tyrchen / claude-skills / cel

cel skill

/cel

This skill generates production-ready CEL code for Kubernetes admission policies, CRD validations, and security rules to enforce compliance.

npx playbooks add skill tyrchen/claude-skills --skill cel

Review the files below or copy the command above to add this skill to your agents.

Files (8)
SKILL.md
13.4 KB
---
name: 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.
---

# 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

```cel
// Comparison
==  !=  <  <=  >  >=

// Logical
&&  ||  !

// Arithmetic
+  -  *  /  %

// Membership
in  // Check if element exists in collection

// Ternary
condition ? trueValue : falseValue
```

### Essential Functions

```cel
// 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
```

### Available Context Variables

**In ValidatingAdmissionPolicy:**
- `object` - The incoming resource being validated
- `oldObject` - The existing resource (UPDATE operations)
- `request` - Admission request metadata (user, operation, namespace)
- `params` - Parameters from ValidatingAdmissionPolicyBinding
- `namespaceObject` - The namespace resource

**In CRD Validation (x-kubernetes-validations):**
- `self` - The field being validated
- `oldSelf` - Previous field value (UPDATE)

## Instructions for Writing CEL Policies

### Step 1: Understand the Requirement

Before writing any CEL:
1. What resource types need validation? (Deployments, Pods, Services, etc.)
2. What operations should trigger validation? (CREATE, UPDATE, DELETE)
3. What specific conditions must be enforced?
4. Should violations block the request or just audit?

### Step 2: Design the Expression

Follow these principles:

**1. Always use `has()` for optional fields:**
```cel
// 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:**
```cel
// 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:**
```cel
// 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:**
```cel
// BETTER - Clear intent
object.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:

```yaml
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"
```

### Step 4: Create the Binding

```yaml
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
```

### Step 5: Test Before Deploying

1. **Use dry-run mode:**
   ```bash
   kubectl apply --dry-run=server -f test-resource.yaml
   ```

2. **Start with Audit mode:**
   ```yaml
   validationActions: [Audit]  # Log violations, don't block
   ```

3. **Check events for violations:**
   ```bash
   kubectl get events --field-selector reason=PolicyAudit
   ```

## Common Policy Patterns

### Security Policies

**Require non-root containers:**
```yaml
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:**
```yaml
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:**
```yaml
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:**
```yaml
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:**
```yaml
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:**
```yaml
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:**
```yaml
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:**
```yaml
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:**
```yaml
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:**
```yaml
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"
```

### Network Policies

**Disallow hostNetwork:**
```yaml
validations:
- expression: |
    !has(object.spec.template.spec.hostNetwork) ||
    object.spec.template.spec.hostNetwork == false
  message: "hostNetwork is not allowed"
```

**Disallow hostPath volumes:**
```yaml
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`:

```yaml
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"
```

## Performance Best Practices

### Set Schema Constraints

Help the cost estimator by bounding collections:

```yaml
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
```

### Avoid O(n^2) Patterns

```yaml
# 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:

```yaml
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"
```

## Debugging CEL Expressions

### Common Errors and Fixes

**Error: "no such key"**
```cel
// 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"**
```cel
// Problem: Comparing wrong types
object.spec.replicas == "5"

// Fix: Use correct type
object.spec.replicas == 5
```

**Error: "no such field"**
```cel
// Problem: Accessing field on null object
object.spec.securityContext.runAsNonRoot

// Fix: Check parent existence
has(object.spec.securityContext) &&
object.spec.securityContext.runAsNonRoot == true
```

### Testing Commands

```bash
# 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
```

## Output Format

When generating CEL policies, always provide:

1. **Complete ValidatingAdmissionPolicy YAML**
2. **Corresponding ValidatingAdmissionPolicyBinding YAML**
3. **Test resources** (both passing and failing examples)
4. **Explanation** of each validation rule
5. **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.

Overview

This skill generates production-ready CEL (Common Expression Language) code for Kubernetes admission control, CRD validation rules, and security policies. It produces complete ValidatingAdmissionPolicy and binding YAML, CRD x-kubernetes-validations, test resources, and deployment instructions. Use it to replace webhooks or migrate from OPA/Gatekeeper/Kyverno to native CEL-based policies.

How this skill works

I inspect the target resource types, operations, and constraint requirements you provide, then generate safe, efficient CEL expressions with has() checks and short-circuit ordering to avoid runtime errors. Output includes full ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding YAML, optional CRD validation snippets, example passing/failing test resources, and a brief explanation and deployment steps. I also apply performance best practices like schema bounds and targeted match rules.

When to use it

  • Create ValidatingAdmissionPolicy resources with robust CEL expressions
  • Add x-kubernetes-validations to CRD schemas
  • Enforce security constraints (non-root, no privileged, approved registries)
  • Validate labels, annotations, names, and resource requests/limits
  • Migrate or simplify policy logic from OPA/Gatekeeper/Kyverno to native CEL
  • Debug or optimize existing CEL expressions for performance and safety

Best practices

  • Always check optional fields with has() before access to avoid runtime errors
  • Order checks: cheap existence checks first, expensive regex or map lookups last
  • Scope policies with matchConstraints and namespace selectors to minimize evaluations
  • Set schema bounds (maxItems, maxProperties, maxLength) to help the cost estimator
  • Start in Audit mode and use server dry-run before switching to Deny

Example use cases

  • Require containers to run as non-root and disallow privileged containers in production namespaces
  • Enforce image registry allowlist and forbid :latest tags across deployments
  • Ensure all containers declare CPU and memory requests and limits within allowed quotas
  • Add x-kubernetes-validations to a CRD to validate replicas and cron schedule formats
  • Generate a validating policy and binding plus pass/fail test manifests for CI validation

FAQ

What Kubernetes versions are required for CEL policies?

CEL in ValidatingAdmissionPolicy is GA in Kubernetes 1.30+; CEL in CRD validation is GA in 1.29+. Verify your cluster version before deploying.

How do I test a generated policy safely?

Use validationActions: [Audit] and kubectl apply --dry-run=server for server-side checks, and inspect PolicyAudit events before switching to Deny.