Skip to content

Migrating to CEL Policies

This guide helps you migrate from ClusterPolicy to the new CEL-based policy types.

ClusterPolicy Structure

A ClusterPolicy contains an orderered list of rules and common settings.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
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/v1
kind: ValidatingPolicy
metadata:
name: require-app-version-labels
spec:
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'

Here is a mapping of each ClusterPolicy field to the CEL-based equivalent:

Common Fields

ClusterPolicyCEL-based policies
spec.admissionspec.evaluation.admission.enabled
applyRulesnot applicable; convert each rule to a policy and use matchConditions.
spec.backgroundspec.evaluation.background.enabled
spec.emitWarningspec.validationActions: Warn
spec.useServerSideApplySee: #14853
spec.webhookConfiguration.failurePolicyspec.failurePolicy
spec.webhookConfiguration.matchConditionsspec.matchConditions
spec.webhookConfiguration.timeoutSecondsspec.webhookConfiguration.timeoutSeconds

Rule Fields

ClusterPolicyCEL-based policies
spec.rules.matchspec.matchExpressions and spec.matchConditions
spec.rules.excludespec.matchConstraints.excludeResourceRules
spec.rules.contextspec.variables
spec.rules.preconditionsspec.matchConditions
spec.rules.celPreconditionsspec.matchConditions
spec.rules.skipBackgroundRequestsSee: #14852
spec.rules.reportPropertiesspec.auditAnnotations
spec.rules.validateValidatingPolicy
spec.rules.mutateMutatingPolicy
spec.rules.generateGeneratingPolicy
spec.rules.imageExtactorsvariables.expressions
spec.rules.verifyImagesImageValidatingPolicy
ClusterPolicyCEL-based policies
spec.rules.validate.allowExistingViolationsnot supported; use policy exceptions instead
spec.rules.validate.anyPatternspec.validations
spec.rules.validate.assertspec.validations
spec.rules.validate.celspec.validations
spec.rules.validate.denyspec.validations and invert the logic
spec.rules.validate.failureActionspec.validationActions
spec.rules.validate.failureActionOverridesnot supported; use policy exceptions instead
spec.rules.validate.foreachspec.validations with CEL comprehensions like all(), exists(), exists_one()
spec.rules.validate.manifestsNot supported
spec.rules.validate.messagespec.validations.message or spec.validations.messageExpression
spec.rules.validate.patternspec.validations
spec.rules.validate.podSecurityNot 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.

ClusterPolicyCEL-based policies
spec.rules.mutate.foreachCEL comprehensions e.g., all(), exists(), exists_one()
spec.rules.mutate.mutateExistingspec.evaluation.mutateExisting.enabled
spec.rules.mutate.patchStrategicMergenot supported; convert to patchType: ApplyConfiguration or patchType: JSONPatch
spec.rules.mutate.patchesJson6902patchType: JSONPatch
spec.rules.mutate.targetsspec.matchConstraints or spec.targetMatchConstraints

Refer to the MutatingPolicy documentation for details and examples.

ClusterPolicyCEL-based policies
spec.rules.generate.apiVersionCEL expressions
spec.rules.generate.cloneCEL expressions
spec.rules.generate.cloneListCEL expressions
spec.rules.generate.dataCEL expressions
spec.rules.generate.foreachCEL expressions
spec.rules.generate.generateExistingspec.evaluation.generateExisting
spec.rules.generate.kindCEL expressions
spec.rules.generate.nameCEL expressions
spec.rules.generate.namespaceCEL expressions
spec.rules.generate.orphanDownstreamOnPolicyDeletespec.evaluation.orphanDownstreamOnPolicyDelete
spec.rules.generate.synchronizespec.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.

CleanupPolicyCEL-based policies
spec.conditionsspec.matchConstraints and spec.matchConditions
spec.contextspec.variables
spec.deletionPropagationPolicyspec.deletionPropagationPolicy
spec.excludespec.matchConstraints.excludeResourceRules
spec.matchspec.matchConstraints and spec.matchConditions
spec.schedulespec.schedule

Refer to the DeletingPolicy documentation for details and examples.

Traditional KyvernoCEL EquivalentDescription
{{ request.object }}objectThe resource being validated
{{ request.oldObject }}oldObjectPrevious version (for updates)
{{ request.operation }}request.operationOperation type (CREATE, UPDATE, DELETE)
{{ serviceAccountName }}request.userInfo.usernameService account name
{{ request.namespace }}object.metadata.namespaceResource namespace

You can use the Kyverno CLI to validate the policy syntax:

Terminal window
# Test with a sample resource
kyverno apply validating-policy.yaml --resource test-pod.yaml

Alternatively, 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.

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([])"