[Bug] Wildcard policy does not match newly created CRs until Kyverno restarts
Kyverno Version
1.15.0
Kubernetes Version
1.32.x
Kubernetes Platform
KinD
Kyverno Rule Type
Validate
Description
A ClusterPolicy using kinds: ["*/*/*"] fails to match admission requests for Custom Resources (CRs) if the CRD for that resource was created after the policy was applied.
If the Kyverno admission controller pod is restarted, the policy matching works correctly, and the admission request is properly intercepted.
Steps to reproduce
-
Start Kyverno v1.15.2 (with
crdWatcherenabled). -
Apply the
ClusterPolicy:kubectl apply -f - <<EOF apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: protect-critical-crds-and-crs annotations: policies.kyverno.io/title: Deny CR Deletion when critical label is set policies.kyverno.io/category: Protection policies.kyverno.io/severity: critical policies.kyverno.io/subject: CustomResource policies.kyverno.io/minversion: 1.6.0 policies.kyverno.io/description: >- This policy prevents the deletion of Custom Resources that have the 'critical: true' label set, or whose parent CRD has the critical label set. spec: background: false rules: - name: cr-is-critical match: all: - resources: kinds: - "*/*" operations: - DELETE selector: matchLabels: critical: "true" exclude: any: - resources: kinds: - "/v1/*" - "admissionregistration.k8s.io/*/*" - "apiextensions.k8s.io/v1/*" - "apiregistration.k8s.io/*/*" - "apps/*/*" - authentication.k8s.io/*/* - authorization.k8s.io/*/* - autoscaling/*/* - batch/*/* - certificates.k8s.io/*/* - coordination.k8s.io/*/* - crd.projectcalico.org/*/* - discovery.k8s.io/*/* - events.k8s.io/*/* - externaldns.k8s.io/*/* - flowcontrol.apiserver.k8s.io/*/* - gateway.networking.k8s.io/*/* - gateway.networking.x-k8s.io/*/* - metrics.k8s.io/*/* - networking.k8s.io/*/* - node.k8s.io/*/* - policy.networking.k8s.io/*/* - policy/*/* - rbac.authorization.k8s.io/*/* - scheduling.k8s.io/*/* - snapshot.storage.k8s.io/*/* - storage.k8s.io/*/* - wgpolicyk8s.io/*/* validate: failureAction: Enforce message: "Deleting {{ request.object.metadata.name }} CR is not allowed since it has critical label." deny: {} - name: parent-crd-is-critical match: all: - resources: kinds: - "*/*" operations: - DELETE exclude: any: - resources: kinds: - "*/*/*" operations: - DELETE selector: matchLabels: critical: "false" - resources: kinds: - "/v1/*" - "admissionregistration.k8s.io/*/*" - "apiextensions.k8s.io/v1/*" - "apiregistration.k8s.io/*/*" - "apps/*/*" - authentication.k8s.io/*/* - authorization.k8s.io/*/* - autoscaling/*/* - batch/*/* - certificates.k8s.io/*/* - coordination.k8s.io/*/* - crd.projectcalico.org/*/* - discovery.k8s.io/*/* - events.k8s.io/*/* - externaldns.k8s.io/*/* - flowcontrol.apiserver.k8s.io/*/* - gateway.networking.k8s.io/*/* - gateway.networking.x-k8s.io/*/* - metrics.k8s.io/*/* - networking.k8s.io/*/* - node.k8s.io/*/* - policy.networking.k8s.io/*/* - policy/*/* - rbac.authorization.k8s.io/*/* - scheduling.k8s.io/*/* - snapshot.storage.k8s.io/*/* - storage.k8s.io/*/* - wgpolicyk8s.io/*/* context: - name: criticalLabels apiCall: urlPath: "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/{{request.resource.resource}}.{{request.resource.group}}" jmesPath: "metadata.labels.\"critical\" || 'false'" validate: failureAction: Enforce message: "Deleting {{ request.object.metadata.name }} CR is not allowed as it parents crd has the critical label." deny: conditions: all: - key: "{{ criticalLabels }}" operator: Equals value: "true" - name: critical-label-on-crd-exists-and-is-true match: all: - resources: kinds: - CustomResourceDefinition operations: - DELETE selector: matchLabels: critical: "true" validate: failureAction: Enforce message: "Deleting {{ request.object.metadata.name }} CRD is not allowed as it has the critical label." deny: {} EOF -
Wait for the policy to become ready
-
Apply the new
CustomResourceDefinition:kubectl apply -f - <<EOF apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: widgets.example.com labels: critical: "true" spec: group: example.com versions: - name: v1alpha1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: replicas: type: integer minimum: 1 image: type: string configMapName: type: string required: - image scope: Namespaced names: plural: widgets singular: widget kind: Widget shortNames: - wdg EOF -
Wait for the CRD to be established
-
Create the
NamespaceandWidgetCR:kubectl apply -f - <<EOF --- apiVersion: v1 kind: Namespace metadata: name: a-protection-test --- apiVersion: example.com/v1alpha1 kind: Widget metadata: name: widget-critical-true labels: critical: "true" namespace: a-protection-test spec: replicas: 3 image: "nginx:1.21.0" configMapName: "my-app-config" EOF -
Attempt to delete the
WidgetCR (this should fail):kubectl delete widget widget-critical-true -n a-protection-test
Expected behavior
The deletion should be denied by the cr-is-critical rule in the ClusterPolicy, as the resource kind matches */*/*, the operation is DELETE, and it has the critical: "true" label.
Screenshots
No response
Kyverno logs
2025-11-11T14:01:19+01:00 TRC ../../pkg/controllers/generic/logging/controller.go:45 > resource added logger=setup/cluster-policy name=protect-critical-crds-and-crs type=ClusterPolicy v=2
2025-11-11T14:01:19+01:00 -3 ../../pkg/policycache/store.go:49 > policy is removed from cache key=protect-critical-crds-and-crs logger=policycache v=4
2025-11-11T14:01:19+01:00 TRC ../../pkg/controllers/admissionpolicygenerator/cpol.go:12 > policy created kind=ClusterPolicy logger=admissionpolicy-generator name=protect-critical-crds-and-crs uid=c4f557ee-ef67-48e9-baf1-c60ea6305f83 v=2
2025-11-11T14:01:19+01:00 -3 ../../pkg/utils/controller/run.go:103 > reconciling ... id=1 key=ClusterPolicy/protect-critical-crds-and-crs logger=admissionpolicy-generator/worker name=protect-critical-crds-and-crs namespace=ClusterPolicy v=4
2025-11-11T14:01:19+01:00 -3 ../../pkg/policycache/store.go:41 > policy is added to cache key=protect-critical-crds-and-crs logger=policycache v=4
2025-11-11T14:01:19+01:00 ERR ../../pkg/controllers/admissionpolicygenerator/controller.go:247 > failed to update cluster policy status error="Operation cannot be fulfilled on clusterpolicies.kyverno.io \"protect-critical-crds-and-crs\": the object has been modified; please apply your changes to the latest version and try again" protect-critical-crds-and-crs=status
2025-11-11T14:01:19+01:00 -2 ../../pkg/controllers/admissionpolicygenerator/controller.go:249 > updated cluster policy status name=protect-critical-crds-and-crs status={"autogen":{},"rulecount":{"generate":0,"mutate":0,"validate":0,"verifyimages":0},"validatingadmissionpolicy":{"generated":false,"message":""}} v=3
2025-11-11T14:01:19+01:00 -3 ../../pkg/utils/controller/run.go:105 > done duration=14.161125ms id=1 key=ClusterPolicy/protect-critical-crds-and-crs logger=admissionpolicy-generator/worker name=protect-critical-crds-and-crs namespace=ClusterPolicy v=4
2025-11-11T14:01:19+01:00 INF ../../pkg/clients/dclient/discovery.go:128 > CRD added logger=dynamic-client/crd-definition-watcher name=widgets.example.com namespace= v=0
2025-11-11T14:01:19+01:00 INF ../../pkg/clients/dclient/discovery.go:130 > Discovery cache invalidated after CRD add logger=dynamic-client/crd-definition-watcher v=0
2025-11-11T14:01:19+01:00 DBG ../../pkg/controllers/webhook/controller.go:480 > CRD change detected, re-evaluating all policies logger=webhook-controller/crd-change-handler v=1
2025-11-11T14:01:19+01:00 INF ../../pkg/clients/dclient/discovery.go:135 > CRD updated logger=dynamic-client/crd-definition-watcher name=widgets.example.com namespace= v=0
2025-11-11T14:01:19+01:00 INF ../../pkg/clients/dclient/discovery.go:137 > Discovery cache invalidated after CRD update logger=dynamic-client/crd-definition-watcher v=0
2025-11-11T14:01:19+01:00 DBG ../../pkg/controllers/webhook/controller.go:480 > CRD change detected, re-evaluating all policies logger=webhook-controller/crd-change-handler v=1
2025-11-11T14:01:19+01:00 INF ../../pkg/clients/dclient/discovery.go:135 > CRD updated logger=dynamic-client/crd-definition-watcher name=widgets.example.com namespace= v=0
2025-11-11T14:01:19+01:00 INF ../../pkg/clients/dclient/discovery.go:137 > Discovery cache invalidated after CRD update logger=dynamic-client/crd-definition-watcher v=0
2025-11-11T14:01:19+01:00 DBG ../../pkg/controllers/webhook/controller.go:480 > CRD change detected, re-evaluating all policies logger=webhook-controller/crd-change-handler v=1
2025-11-11T14:01:28+01:00 -3 ../../pkg/webhooks/resource/handlers.go:118 > received an admission request in validating webhook URLParams= clusterroles=["cluster-admin","system:basic-user","system:discovery","system:public-info-viewer"] gvk="example.com/v1alpha1, Kind=Widget" gvr="example.com/v1alpha1, Resource=widgets" kind=Widget logger=webhooks/resource/validate name=widget-critical-true namespace=a-protection-test operation=DELETE resource.gvk="example.com/v1alpha1, Kind=Widget" roles=[] uid=d41fd533-c543-44b2-81f9-3aa8b68c6db9 user={"extra":{"authentication.kubernetes.io/credential-id":["X509SHA256=c75a7d6aa2938c1989a2faab20d66c92cb704abd200dabb493da1ea2a3abf8b5"]},"groups":["system:masters","system:authenticated"],"username":"system:admin"} v=4
2025-11-11T14:01:28+01:00 -3 ../../pkg/webhooks/resource/handlers.go:126 > no policies matched admission request URLParams= clusterroles=["cluster-admin","system:basic-user","system:discovery","system:public-info-viewer"] gvk="example.com/v1alpha1, Kind=Widget" gvr="example.com/v1alpha1, Resource=widgets" kind=Widget logger=webhooks/resource/validate name=widget-critical-true namespace=a-protection-test operation=DELETE resource.gvk="example.com/v1alpha1, Kind=Widget" roles=[] uid=d41fd533-c543-44b2-81f9-3aa8b68c6db9 user={"extra":{"authentication.kubernetes.io/credential-id":["X509SHA256=c75a7d6aa2938c1989a2faab20d66c92cb704abd200dabb493da1ea2a3abf8b5"]},"groups":["system:masters","system:authenticated"],"username":"system:admin"} v=4
2025-11-11T14:01:28+01:00 -3 ../../pkg/webhooks/resource/handlers.go:129 > processing policies for validate admission request URLParams= clusterroles=["cluster-admin","system:basic-user","system:discovery","system:public-info-viewer"] generate=0 gvk="example.com/v1alpha1, Kind=Widget" gvr="example.com/v1alpha1, Resource=widgets" kind=Widget logger=webhooks/resource/validate mutate=0 name=widget-critical-true namespace=a-protection-test operation=DELETE resource.gvk="example.com/v1alpha1, Kind=Widget" roles=[] uid=d41fd533-c543-44b2-81f9-3aa8b68c6db9 user={"extra":{"authentication.kubernetes.io/credential-id":["X509SHA256=c75a7d6aa2938c1989a2faab20d66c92cb704abd200dabb493da1ea2a3abf8b5"]},"groups":["system:masters","system:authenticated"],"username":"system:admin"} v=4 validate=0
2025-11-11T14:01:28+01:00 -3 ../../pkg/engine/context/context.go:274 > Adding service account logger=context service account name= service account namespace= v=4
2025-11-11T14:01:28+01:00 -2 ../../pkg/event/controller.go:83 > generating events count=0 logger=EventGenerator v=3
Slack discussion
No response
Troubleshooting
-
I have read and followed the documentation AND the troubleshooting guide. -
I have searched other issues in this repository and mine is not recorded.