Kyverno Certified Associate study materials
Kyverno Certified Associate study resources

Background scan skips admission-only preconditions

Kyverno Certified Associate · Put your knowledge to work in context.

All case studies

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.

  1. Wrong report kind. Deployments are namespaced resources. Kyverno writes their results to a PolicyReport in each namespace, not to the cluster-scoped ClusterPolicyReport. Querying only ClusterPolicyReport will not show the 140 Deployments. Use kubectl get policyreport -A and use kubectl get clusterpolicyreport only for truly cluster-scoped resources such as Namespaces or ClusterRoles.

  2. 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.operation is not CREATE or UPDATE, and request.userInfo.username is 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.