Skip to content

[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

  1. Start Kyverno v1.15.2 (with crdWatcher enabled).

  2. 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
  3. Wait for the policy to become ready

  4. 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
  5. Wait for the CRD to be established

  6. Create the Namespace and Widget CR:

    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
  7. Attempt to delete the Widget CR (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.