Background scan skips admission-only preconditions
Kyverno 1.12 runs in a cluster with 600 existing Deployments across 40 namespaces. A ClusterPolicy named require-team-label matches Pods and Deployments, validates that metadata.labels.team exists, and sets background: true. The rule also has a precondition requiring request.operation to be CREATE or UPDATE and request.userInfo.username not to start with system:serviceaccount:ci. After rollout, the security team queries ClusterPolicyReport and finds zero failures, concluding the policy is working. However, a new Deployment created by the CI service account is admitted without a team label, and kubectl get deploy -A shows 140 Deployments with no team label. The platform team insists Kyverno's background scan is broken and wants to disable reports. The compliance team wants evidence for all 140 existing workloads without blocking CI's break-glass workflow. The reports controller is healthy and background scans complete without errors.
1. Why does ClusterPolicyReport show zero failures, and why are the 140 existing Deployments skipped even though background is true?
Read the worked answer
Two separate things are happening.
Wrong report kind. Deployments are namespaced resources. Kyverno writes their results to a
PolicyReportin each namespace, not to the cluster-scopedClusterPolicyReport. Querying onlyClusterPolicyReportwill not show the 140 Deployments. Usekubectl get policyreport -Aand usekubectl get clusterpolicyreportonly for truly cluster-scoped resources such as Namespaces or ClusterRoles.Admission-only data in a background rule. A background scan is not an admission request. It is performed by Kyverno's background controller under its own ServiceAccount.
request.operationis notCREATEorUPDATE, andrequest.userInfo.usernameis the Kyverno ServiceAccount, not the original CI identity. The precondition therefore evaluates false, so the rule is skipped rather than failed. A skip is not evidence of compliance.
The new CI-created Deployment was admitted for the same reason: the userInfo branch of the precondition excluded the CI ServiceAccount, so the rule never evaluated the label. The policy is not broken; it is testing data that only exists during admission and then looking for that data in a background scan.
2. Propose a policy structure that reports the 140 existing workloads while preserving the CI break-glass exemption. State which parts cannot work in background.
Read the worked answer
Split the policy into an admission rule and a background-safe rule.
rules:
- name: check-team-label-admission
match:
any:
- resources:
kinds: [Pod, Deployment]
preconditions:
any:
- key: '{{ request.userInfo.username }}'
operator: NotEquals
value: 'system:serviceaccount:ci:deployer'
validate:
failureAction: Enforce
message: 'team label required'
pattern:
metadata:
labels:
team: '?*'
background: false
- name: check-team-label-background
match:
any:
- resources:
kinds: [Pod, Deployment]
validate:
failureAction: Audit
message: 'team label required'
pattern:
metadata:
labels:
team: '?*'
The admission rule can use request.userInfo because it only runs during admission. The background rule must not use request.userInfo or request.operation; those values are not the original request values during a background scan. The background rule produces PolicyReport entries for existing workloads. It will also evaluate CI-created resources in Audit mode, which is useful because identity-based exemptions cannot be reconstructed after the fact. If CI must be allowed to create temporarily unlabeled resources, scope the Enforce rule to a dedicated namespace or use a namespace-based exception, not a historical userInfo check. Query kubectl get policyreport -A to evidence the 140 violations.