Kyverno is used in CI clusters and was upgraded to 1.17.1
Release Notes: https://kyverno.io/blog/2026/02/02/announcing-kyverno-release-1.17/
Migration Guide: https://kyverno.io/docs/guides/migration-to-cel/
Kyverno 1.17 officially marks ClusterPolicy and CleanupPolicy as Deprecated. While they remain functional in this release, the clock has started on their removal to make way for the more performant, standardized CEL-based engines.
Summary
Kyverno 1.17 deprecated ClusterPolicy and CleanupPolicy in favor of new CEL-based policy engines. These deprecated resources will receive only critical fixes in v1.18 (Apr 2026) and v1.19 (Jul 2026), and are planned for removal in v1.20 (Oct 2026).
We currently use Kyverno v1.17.1 (see hack/deployer/runner/kyverno/install/README.md) with 5 ClusterPolicy resources to enforce security in our E2E CI pipelines. All of these need to be migrated to the new ValidatingPolicy kind under policies.kyverno.io/v1.
Affected Files
| File |
Description |
hack/deployer/runner/kyverno/install/policies.yaml |
4 ClusterPolicy resources (privileged containers, run-as-non-root, hostPath, SA token automount) |
hack/deployer/runner/kyverno/install/gke-policies.yaml |
1 ClusterPolicy resource (disallow GKE built-in StorageClasses) |
hack/deployer/runner/kyverno/kyverno.go |
Go code that embeds and applies the policy manifests |
hack/deployer/runner/kyverno/install/README.md |
Documentation |
Policies to Migrate
1. disallow-privileged-containers (policies.yaml)
- Type: Pattern-based validation
- What it does: Ensures
securityContext.privileged is unset or false on all container types.
- Excludes:
default, *-system, local-path-storage namespaces; Pods with labels common.k8s.elastic.co/type in [beat, agent] or app.kubernetes.io/name in [max-map-count-setter].
- CEL equivalent: Use
object.spec.containers.all(c, !has(c.securityContext) || !has(c.securityContext.privileged) || c.securityContext.privileged == false) (and similar for initContainers/ephemeralContainers).
2. require-run-as-non-root-user (policies.yaml)
- Type: Pattern-based validation
- What it does: Ensures
runAsUser is unset or > 0 on pod securityContext and all container types.
- Excludes:
*-system, local-path-storage namespaces; Pods named manage-agent-hostpath-permissions-*; same label selectors as above.
- CEL equivalent: Use
!has(object.spec.securityContext) || !has(object.spec.securityContext.runAsUser) || object.spec.securityContext.runAsUser > 0 (and per-container).
3. disallow-host-path (policies.yaml)
- Type: Pattern-based validation
- What it does: Ensures no
hostPath volumes exist.
- Excludes:
default, *-system, local-path-storage namespaces; Pods named manage-agent-hostpath-permissions-*; Pods with label common.k8s.elastic.co/type in [beat, agent].
- CEL equivalent: Use
!has(object.spec.volumes) || object.spec.volumes.all(v, !has(v.hostPath)).
4. restrict-automount-sa-token (policies.yaml)
- Type: Pattern-based validation
- What it does: Ensures
automountServiceAccountToken is false.
- Excludes: Same as disallow-host-path plus label exclusions for
max-map-count-setter.
- CEL equivalent: Use
object.spec.automountServiceAccountToken == false.
5. disallow-gke-storageclasses (gke-policies.yaml)
- Type: Deny with conditions (uses Kyverno JMESPath variables)
- What it does: Blocks PVCs and StatefulSets from using built-in GKE StorageClasses (
premium-rwo, standard, standard-rwo).
- Migration note: This policy uses
deny with conditions and Kyverno variable substitution ({{request.object.spec.storageClassName}}). When converting to CEL, the deny logic must be inverted into a positive assertion.
- CEL equivalent (PVC rule):
!['premium-rwo', 'standard', 'standard-rwo'].exists(sc, object.spec.?storageClassName.orValue('') == sc)
- CEL equivalent (StatefulSet rule):
object.spec.volumeClaimTemplates.all(vct, !['premium-rwo', 'standard', 'standard-rwo'].exists(sc, vct.spec.?storageClassName.orValue('') == sc))
Migration Steps
Step 1: Convert policies.yaml to CEL-based ValidatingPolicy resources
For each ClusterPolicy, create a corresponding ValidatingPolicy:
Key field mappings:
| Old (ClusterPolicy) |
New (ValidatingPolicy) |
apiVersion: kyverno.io/v1 |
apiVersion: policies.kyverno.io/v1 |
kind: ClusterPolicy |
kind: ValidatingPolicy |
spec.validationFailureAction: enforce |
spec.validationActions: [Deny] |
spec.rules[].match.any[].resources.kinds |
spec.matchConstraints.resourceRules[].resources + apiGroups/apiVersions/operations |
spec.rules[].exclude |
spec.matchConstraints.excludeResourceRules and/or spec.matchConditions |
spec.rules[].validate.pattern |
spec.validations[].expression (CEL) |
spec.rules[].validate.deny.conditions |
spec.validations[].expression (CEL, inverted logic) |
spec.rules[].validate.message |
spec.validations[].message |
Handling label-selector exclusions: The current policies use matchExpressions with NotIn to exclude certain label values. In the CEL model, these should become matchConditions using CEL expressions, e.g.:
matchConditions:
- name: exclude-beats-and-agents
expression: >
!('common.k8s.elastic.co/type' in object.metadata.labels) ||
!(['beat', 'agent'].exists(t, object.metadata.labels['common.k8s.elastic.co/type'] == t))
Handling namespace exclusions: Wildcard namespace patterns like *-system need to be converted to CEL string functions:
matchConditions:
- name: exclude-system-namespaces
expression: "!object.metadata.namespace.endsWith('-system')"
Handling name-based exclusions: Pod name patterns like manage-agent-hostpath-permissions-* become:
matchConditions:
- name: exclude-hostpath-permissions-pods
expression: "!object.metadata.name.startsWith('manage-agent-hostpath-permissions-')"
Step 2: Convert gke-policies.yaml
This policy uses deny conditions with Kyverno variable interpolation. The deny logic must be inverted into positive CEL assertions. The policy has two rules targeting different resource kinds (PVC and StatefulSet) -- these should become two separate ValidatingPolicy resources since each ValidatingPolicy targets a single resource type via matchConstraints.
Step 3: Update kyverno.go
The Go deployment code (hack/deployer/runner/kyverno/kyverno.go) should not need structural changes since it uses kubectl apply -f against the embedded manifests. However:
- Verify the embedded YAML filenames in the
//go:embed directives still match.
- Ensure Kyverno 1.17+ CRDs for
ValidatingPolicy are included in the kyverno.yaml installer manifest (they should be, since we're already on v1.17.1).
Step 4: Update documentation
Update hack/deployer/runner/kyverno/install/README.md to reference the new policy types and link to the CEL migration guide.
Step 5: Test
- Run the E2E test pipeline with the new policies to verify enforcement behavior is identical.
- Verify that excluded workloads (beats, agents, max-map-count-setter, system namespaces) are still correctly excluded.
- Verify that violating workloads are still correctly denied.
- Use Kyverno CLI to validate policy syntax:
kyverno apply validating-policy.yaml --resource test-pod.yaml
Example: Full Conversion of disallow-privileged-containers
Before (ClusterPolicy):
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged-containers
spec:
validationFailureAction: enforce
background: true
rules:
- name: privileged-containers
exclude:
any:
- resources:
namespaces:
- 'default'
- "*-system"
- "local-path-storage"
match:
any:
- resources:
kinds:
- Pod
selector:
matchExpressions:
- {key: common.k8s.elastic.co/type, operator: NotIn, values: [beat, agent]}
- {key: app.kubernetes.io/name, operator: NotIn, values: [max-map-count-setter]}
validate:
message: >-
Privileged mode is disallowed. The fields spec.containers[*].securityContext.privileged
and spec.initContainers[*].securityContext.privileged must be unset or set to `false`.
pattern:
spec:
=(ephemeralContainers):
- =(securityContext):
=(privileged): "false"
=(initContainers):
- =(securityContext):
=(privileged): "false"
containers:
- =(securityContext):
=(privileged): "false"
After (ValidatingPolicy):
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: disallow-privileged-containers
annotations:
policies.kyverno.io/title: Disallow Privileged Containers
policies.kyverno.io/category: Pod Security Standards (Baseline)
policies.kyverno.io/severity: medium
policies.kyverno.io/description: >-
Privileged mode disables most security mechanisms and must not be allowed.
spec:
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
resources: ['pods']
operations: [CREATE, UPDATE]
excludeResourceRules:
- apiGroups: ['']
apiVersions: ['v1']
resources: ['pods']
resourceNames: []
namespaces: ['default', 'local-path-storage']
matchConditions:
- name: exclude-system-namespaces
expression: "!object.metadata.namespace.endsWith('-system')"
- name: exclude-beats-and-agents
expression: >
!('common.k8s.elastic.co/type' in object.metadata.?labels.orValue({})) ||
!(['beat', 'agent'].exists(t, object.metadata.labels['common.k8s.elastic.co/type'] == t))
- name: exclude-max-map-count-setter
expression: >
!('app.kubernetes.io/name' in object.metadata.?labels.orValue({})) ||
object.metadata.labels['app.kubernetes.io/name'] != 'max-map-count-setter'
validationActions: [Deny]
validations:
- expression: >
object.spec.containers.all(c,
!has(c.securityContext) || !has(c.securityContext.privileged) || c.securityContext.privileged == false
)
&&
(!has(object.spec.initContainers) || object.spec.initContainers.all(c,
!has(c.securityContext) || !has(c.securityContext.privileged) || c.securityContext.privileged == false
))
&&
(!has(object.spec.ephemeralContainers) || object.spec.ephemeralContainers.all(c,
!has(c.securityContext) || !has(c.securityContext.privileged) || c.securityContext.privileged == false
))
message: >-
Privileged mode is disallowed. The fields spec.containers[*].securityContext.privileged
and spec.initContainers[*].securityContext.privileged must be unset or set to `false`.
References
Kyverno is used in CI clusters and was upgraded to 1.17.1
Release Notes: https://kyverno.io/blog/2026/02/02/announcing-kyverno-release-1.17/
Migration Guide: https://kyverno.io/docs/guides/migration-to-cel/
Summary
Kyverno 1.17 deprecated
ClusterPolicyandCleanupPolicyin favor of new CEL-based policy engines. These deprecated resources will receive only critical fixes in v1.18 (Apr 2026) and v1.19 (Jul 2026), and are planned for removal in v1.20 (Oct 2026).We currently use Kyverno v1.17.1 (see
hack/deployer/runner/kyverno/install/README.md) with 5ClusterPolicyresources to enforce security in our E2E CI pipelines. All of these need to be migrated to the newValidatingPolicykind underpolicies.kyverno.io/v1.Affected Files
hack/deployer/runner/kyverno/install/policies.yamlhack/deployer/runner/kyverno/install/gke-policies.yamlhack/deployer/runner/kyverno/kyverno.gohack/deployer/runner/kyverno/install/README.mdPolicies to Migrate
1.
disallow-privileged-containers(policies.yaml)securityContext.privilegedis unset orfalseon all container types.default,*-system,local-path-storagenamespaces; Pods with labelscommon.k8s.elastic.co/typein[beat, agent]orapp.kubernetes.io/namein[max-map-count-setter].object.spec.containers.all(c, !has(c.securityContext) || !has(c.securityContext.privileged) || c.securityContext.privileged == false)(and similar for initContainers/ephemeralContainers).2.
require-run-as-non-root-user(policies.yaml)runAsUseris unset or > 0 on pod securityContext and all container types.*-system,local-path-storagenamespaces; Pods namedmanage-agent-hostpath-permissions-*; same label selectors as above.!has(object.spec.securityContext) || !has(object.spec.securityContext.runAsUser) || object.spec.securityContext.runAsUser > 0(and per-container).3.
disallow-host-path(policies.yaml)hostPathvolumes exist.default,*-system,local-path-storagenamespaces; Pods namedmanage-agent-hostpath-permissions-*; Pods with labelcommon.k8s.elastic.co/typein[beat, agent].!has(object.spec.volumes) || object.spec.volumes.all(v, !has(v.hostPath)).4.
restrict-automount-sa-token(policies.yaml)automountServiceAccountTokenisfalse.max-map-count-setter.object.spec.automountServiceAccountToken == false.5.
disallow-gke-storageclasses(gke-policies.yaml)premium-rwo,standard,standard-rwo).denywithconditionsand Kyverno variable substitution ({{request.object.spec.storageClassName}}). When converting to CEL, the deny logic must be inverted into a positive assertion.!['premium-rwo', 'standard', 'standard-rwo'].exists(sc, object.spec.?storageClassName.orValue('') == sc)object.spec.volumeClaimTemplates.all(vct, !['premium-rwo', 'standard', 'standard-rwo'].exists(sc, vct.spec.?storageClassName.orValue('') == sc))Migration Steps
Step 1: Convert
policies.yamlto CEL-based ValidatingPolicy resourcesFor each ClusterPolicy, create a corresponding
ValidatingPolicy:Key field mappings:
apiVersion: kyverno.io/v1apiVersion: policies.kyverno.io/v1kind: ClusterPolicykind: ValidatingPolicyspec.validationFailureAction: enforcespec.validationActions: [Deny]spec.rules[].match.any[].resources.kindsspec.matchConstraints.resourceRules[].resources+apiGroups/apiVersions/operationsspec.rules[].excludespec.matchConstraints.excludeResourceRulesand/orspec.matchConditionsspec.rules[].validate.patternspec.validations[].expression(CEL)spec.rules[].validate.deny.conditionsspec.validations[].expression(CEL, inverted logic)spec.rules[].validate.messagespec.validations[].messageHandling label-selector exclusions: The current policies use
matchExpressionswithNotInto exclude certain label values. In the CEL model, these should becomematchConditionsusing CEL expressions, e.g.:Handling namespace exclusions: Wildcard namespace patterns like
*-systemneed to be converted to CEL string functions:Handling name-based exclusions: Pod name patterns like
manage-agent-hostpath-permissions-*become:Step 2: Convert
gke-policies.yamlThis policy uses deny conditions with Kyverno variable interpolation. The deny logic must be inverted into positive CEL assertions. The policy has two rules targeting different resource kinds (PVC and StatefulSet) -- these should become two separate
ValidatingPolicyresources since each ValidatingPolicy targets a single resource type viamatchConstraints.Step 3: Update
kyverno.goThe Go deployment code (
hack/deployer/runner/kyverno/kyverno.go) should not need structural changes since it useskubectl apply -fagainst the embedded manifests. However://go:embeddirectives still match.ValidatingPolicyare included in thekyverno.yamlinstaller manifest (they should be, since we're already on v1.17.1).Step 4: Update documentation
Update
hack/deployer/runner/kyverno/install/README.mdto reference the new policy types and link to the CEL migration guide.Step 5: Test
kyverno apply validating-policy.yaml --resource test-pod.yamlExample: Full Conversion of
disallow-privileged-containersBefore (ClusterPolicy):
After (ValidatingPolicy):
References
hack/deployer/runner/kyverno/install/README.md)