Migrating to CEL Policies
This guide helps you migrate from ClusterPolicy to the new CEL-based policy types.
Key Differences
Section titled “Key Differences”ClusterPolicy Structure
A ClusterPolicy contains an orderered list of rules and common settings.
apiVersion: kyverno.io/v1kind: ClusterPolicymetadata: name: require-labelsspec: validationFailureAction: Enforce rules: - name: check-labels match: any: - resources: kinds: - Pod validate: message: 'Required labels missing' pattern: metadata: labels: app: '?*' version: '?*'CEL-based Policy Structure
Kyverno CEL-based policies are defined as discrete types where each policy is focused on a single action e.g., validate, mutate, generate, or cleanup. Each CEL-based policy can contain an ordered list of actions of the same type, e.g. spec.validations.
apiVersion: policies.kyverno.io/v1kind: ValidatingPolicymetadata: name: require-app-version-labelsspec: matchConstraints: resourceRules: - apiGroups: - '' apiVersions: - v1 operations: - CREATE - UPDATE resources: - pods validationActions: - Deny validations: - expression: > ['app', 'version'].all(label, object.metadata.?labels[label].orValue('') != '' ) message: 'Required labels missing'Policy Settings
Section titled “Policy Settings”Here is a mapping of each ClusterPolicy field to the CEL-based equivalent:
Common Fields
| ClusterPolicy | CEL-based policies |
|---|---|
| spec.admission | spec.evaluation.admission.enabled |
| applyRules | not applicable; convert each rule to a policy and use matchConditions. |
| spec.background | spec.evaluation.background.enabled |
| spec.emitWarning | spec.validationActions: Warn |
| spec.useServerSideApply | See: #14853 |
| spec.webhookConfiguration.failurePolicy | spec.failurePolicy |
| spec.webhookConfiguration.matchConditions | spec.matchConditions |
| spec.webhookConfiguration.timeoutSeconds | spec.webhookConfiguration.timeoutSeconds |
Rule Fields
| ClusterPolicy | CEL-based policies |
|---|---|
| spec.rules.match | spec.matchExpressions and spec.matchConditions |
| spec.rules.exclude | spec.matchConstraints.excludeResourceRules |
| spec.rules.context | spec.variables |
| spec.rules.preconditions | spec.matchConditions |
| spec.rules.celPreconditions | spec.matchConditions |
| spec.rules.skipBackgroundRequests | See: #14852 |
| spec.rules.reportProperties | spec.auditAnnotations |
| spec.rules.validate | ValidatingPolicy |
| spec.rules.mutate | MutatingPolicy |
| spec.rules.generate | GeneratingPolicy |
| spec.rules.imageExtactors | variables.expressions |
| spec.rules.verifyImages | ImageValidatingPolicy |
Validate Rule
Section titled “Validate Rule”| ClusterPolicy | CEL-based policies |
|---|---|
| spec.rules.validate.allowExistingViolations | not supported; use policy exceptions instead |
| spec.rules.validate.anyPattern | spec.validations |
| spec.rules.validate.assert | spec.validations |
| spec.rules.validate.cel | spec.validations |
| spec.rules.validate.deny | spec.validations and invert the logic |
| spec.rules.validate.failureAction | spec.validationActions |
| spec.rules.validate.failureActionOverrides | not supported; use policy exceptions instead |
| spec.rules.validate.foreach | spec.validations with CEL comprehensions like all(), exists(), exists_one() |
| spec.rules.validate.manifests | Not supported |
| spec.rules.validate.message | spec.validations.message or spec.validations.messageExpression |
| spec.rules.validate.pattern | spec.validations |
| spec.rules.validate.podSecurity | Not supported; use spec.validations for each pod security control |
ClusterPolicy validate pattern:
validate: pattern: spec: containers: - name: '*' image: '!*:latest'ValidatingPolicy:
validations: - expression: > ['app', 'version'].all(label, object.metadata.?labels[label].orValue('') != '' ) message: "Pod must have an 'app' and 'version' labels"ClusterPolicy validate deny
validate: deny: conditions: all: - key: '{{ request.object.spec.replicas }}' operator: GreaterThan value: 10 message: 'Replica count cannot exceed 10'ValidatingPolicy:
Note the logic innversion when converting a deny rule to a CEL expression:
validate: cel: expressions: - expression: 'object.spec.replicas <= 10' message: 'Replica count cannot exceed 10'Refer to the ValidatingPolicy documentation for details and additional examples.
Mutate Rule
Section titled “Mutate Rule”| ClusterPolicy | CEL-based policies |
|---|---|
| spec.rules.mutate.foreach | CEL comprehensions e.g., all(), exists(), exists_one() |
| spec.rules.mutate.mutateExisting | spec.evaluation.mutateExisting.enabled |
| spec.rules.mutate.patchStrategicMerge | not supported; convert to patchType: ApplyConfiguration or patchType: JSONPatch |
| spec.rules.mutate.patchesJson6902 | patchType: JSONPatch |
| spec.rules.mutate.targets | spec.matchConstraints or spec.targetMatchConstraints |
Refer to the MutatingPolicy documentation for details and examples.
Generate Rule
Section titled “Generate Rule”| ClusterPolicy | CEL-based policies |
|---|---|
| spec.rules.generate.apiVersion | CEL expressions |
| spec.rules.generate.clone | CEL expressions |
| spec.rules.generate.cloneList | CEL expressions |
| spec.rules.generate.data | CEL expressions |
| spec.rules.generate.foreach | CEL expressions |
| spec.rules.generate.generateExisting | spec.evaluation.generateExisting |
| spec.rules.generate.kind | CEL expressions |
| spec.rules.generate.name | CEL expressions |
| spec.rules.generate.namespace | CEL expressions |
| spec.rules.generate.orphanDownstreamOnPolicyDelete | spec.evaluation.orphanDownstreamOnPolicyDelete |
| spec.rules.generate.synchronize | spec.evaluation.synchronize |
A number of the GeneratingPolicy fields, such as the generated resource properties, are now directly handled as CEL expressions. See GeneratingPolicy docs for details.
Verify Images Rule
Section titled “Verify Images Rule”| ClusterPolicy | CEL-based policies |
|---|---|
| spec.rules.verifyImages.imageReferences | spec.matchImageReferences |
| spec.rules.verifyImages.skipImageReferences | spec.matchImageReferences with CEL expressions |
| spec.rules.verifyImages.type | spec.attestors[].cosign or spec.attestors[].notary |
| spec.rules.verifyImages.attestors | spec.attestors |
| spec.rules.verifyImages.attestations | spec.attestations and spec.validations |
| spec.rules.verifyImages.imageRegistryCredentials | spec.credentials |
| spec.rules.verifyImages.mutateDigest | spec.validationConfigurations.mutateDigest |
| spec.rules.verifyImages.verifyDigest | spec.validationConfigurations.verifyDigest |
| spec.rules.verifyImages.required | spec.validationConfigurations.required |
| spec.rules.imageExtractors | spec.images |
Refer to the ImageValidatingPolicy documentation for details and examples.
CleanupPolicy
Section titled “CleanupPolicy”| CleanupPolicy | CEL-based policies |
|---|---|
| spec.conditions | spec.matchConstraints and spec.matchConditions |
| spec.context | spec.variables |
| spec.deletionPropagationPolicy | spec.deletionPropagationPolicy |
| spec.exclude | spec.matchConstraints.excludeResourceRules |
| spec.match | spec.matchConstraints and spec.matchConditions |
| spec.schedule | spec.schedule |
Refer to the DeletingPolicy documentation for details and examples.
PolicyException
Section titled “PolicyException”The legacy kyverno.io PolicyException must be migrated to the policies.kyverno.io PolicyException, which works with the CEL-based policy types:
Legacy PolicyException (kyverno.io) | PolicyException (policies.kyverno.io) |
|---|---|
| spec.exceptions.policyName | spec.policyRefs.name |
| spec.exceptions.ruleNames | not applicable; CEL policies do not contain named rules |
| spec.match | spec.matchConditions |
| spec.conditions | spec.matchConditions |
| spec.podSecurity | spec.matchConditions with CEL expressions |
apiVersion: policies.kyverno.io/v1kind: PolicyExceptionmetadata: name: allow-team-tool namespace: deltaspec: policyRefs: - name: disallow-host-namespaces kind: ValidatingPolicy matchConditions: - name: match-pod-name expression: object.metadata.name.startsWith('important-tool')Refer to the Policy Exceptions guide for details and examples.
Context Variables
Section titled “Context Variables”| Traditional Kyverno | CEL Equivalent | Description |
|---|---|---|
{{ request.object }} | object | The resource being validated |
{{ request.oldObject }} | oldObject | Previous version (for updates) |
{{ request.operation }} | request.operation | Operation type (CREATE, UPDATE, DELETE) |
{{ serviceAccountName }} | request.userInfo.username | Service account name |
{{ request.namespace }} | object.metadata.namespace | Resource namespace |
Testing
Section titled “Testing”You can use the Kyverno CLI to validate the policy syntax:
# Test with a sample resourcekyverno apply validating-policy.yaml --resource test-pod.yamlAlternatively, you can use the Kyverno Playground.
If you have existing Kyverno CLI tests, you can use them with the new policy with no changes, and validate it works as expected.
If you have existing Kyverno Chainsaw tests, any policy type and status checks will need to be converted. The rest of the test logic can be reused.
Detecting Legacy Policy Usage
Section titled “Detecting Legacy Policy Usage”Starting with Kyverno v1.19, the admission controller and the Kyverno CLI emit deprecation warnings to help you find remaining legacy policies before they are removed.
Admission Warnings
When a legacy kyverno.io policy type is created or updated, the response includes a warning identifying the replacement type:
Warning: kyverno.io/v1 ClusterPolicy is deprecated and will be removed in a future release; migrate to ValidatingPolicy, MutatingPolicy, GeneratingPolicy or ImageValidatingPolicy (policies.kyverno.io), see https://kyverno.io/docs/guides/migration-to-cel/Warnings are also returned for deprecated field values, such as the lowercase enforce/audit validation failure actions:
Warning: spec.validationFailureAction: Validation failure actions enforce/audit are deprecated, use Enforce/Audit instead.CLI Warnings
The kyverno apply and kyverno test commands print the same kind-level deprecation warnings when loading legacy policies or policy exceptions (field-level warnings are only emitted by the admission controller). To enforce migration in CI pipelines, add the --warnings-as-errors flag to make these commands fail when any deprecation warning is found:
kyverno apply /path/to/policy.yaml --resource /path/to/resource.yaml --warnings-as-errorskyverno test . --warnings-as-errorsTracking Usage with Metrics
The kyverno_deprecated_api_requests_total counter, labeled by group, version, kind, and field, tracks admission requests that use deprecated policy types or fields. Use it to confirm that nothing in the cluster still creates or updates legacy policies before upgrading. See the metrics reference for details and example queries.
Troubleshooting
Section titled “Troubleshooting”CEL Expression Errors
Error: unknown field 'foo'
Solution: Use has() or ? to check field existence:
expression: "has(object.metadata.labels) && has(object.metadata.labels.foo) && object.metadata.labels.foo == 'value'"expression: "object.metadata.?labels.foo.orValue('') == 'value'"Error: index out of bounds
Solution: Check array length before accessing:
expression: "size(object.spec.initContainers) > 0 && object.spec.initContainers[0].image != 'bad'"Error: expression of type 'any' cannot be range of a comprehension (must be list, map, or dynamic)
Solution: Convert JSON types to dynamic using orValue
"name": "deploymentList","expression": "resource.List('apps/v1', 'deployments', object.metadata.namespace).items.orValue([])"