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.

ClusterPolicyCEL-based policies
spec.rules.verifyImages.imageReferencesspec.matchImageReferences
spec.rules.verifyImages.skipImageReferencesspec.matchImageReferences with CEL expressions
spec.rules.verifyImages.typespec.attestors[].cosign or spec.attestors[].notary
spec.rules.verifyImages.attestorsspec.attestors
spec.rules.verifyImages.attestationsspec.attestations and spec.validations
spec.rules.verifyImages.imageRegistryCredentialsspec.credentials
spec.rules.verifyImages.mutateDigestspec.validationConfigurations.mutateDigest
spec.rules.verifyImages.verifyDigestspec.validationConfigurations.verifyDigest
spec.rules.verifyImages.requiredspec.validationConfigurations.required
spec.rules.imageExtractorsspec.images

Refer to the ImageValidatingPolicy documentation for details and examples.

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.

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.policyNamespec.policyRefs.name
spec.exceptions.ruleNamesnot applicable; CEL policies do not contain named rules
spec.matchspec.matchConditions
spec.conditionsspec.matchConditions
spec.podSecurityspec.matchConditions with CEL expressions
apiVersion: policies.kyverno.io/v1
kind: PolicyException
metadata:
name: allow-team-tool
namespace: delta
spec:
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.

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.

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:

Terminal window
kyverno apply /path/to/policy.yaml --resource /path/to/resource.yaml --warnings-as-errors
kyverno test . --warnings-as-errors

Tracking 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.

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