Kyverno and Gatekeeper: policy
KubeBolt reads wgpolicyk8s.io PolicyReports, so Kyverno and Gatekeeper both work through one integration. Install, verify, and the two traps that look like bugs.
Kyverno enforces the rules you wrote: no hostNetwork, images from an
approved registry, every workload declares limits. Failed checks land in the
Configuration lens of Security & Compliance as
policy-violation findings.
KubeBolt reads PolicyReports, not Kyverno’s own API. That is deliberate,
and it has a useful consequence: wgpolicyk8s.io is a shared standard, so
Gatekeeper works through this same integration with no extra configuration.
If it writes PolicyReports, KubeBolt reads them.
Nothing is pushed. KubeBolt lists the reports on its sweep — no token, no webhook, no endpoint to expose.
Install
helm repo add kyverno https://kyverno.github.io/kyverno
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
Kyverno on its own reports nothing: it needs policies. The community set is the usual starting point:
helm install kyverno-policies kyverno/kyverno-policies -n kyverno \
--set podSecurityStandard=baseline \
--set validationFailureAction=Audit
Start with Audit, not Enforce. In Audit mode Kyverno writes the
report and lets the resource through; in Enforce it rejects the admission.
Turning on a policy set you have never seen the output of, in Enforce, is
how a deploy starts failing on a Friday. KubeBolt reads both modes
identically — the findings are the same, so there is nothing to gain by
rushing.
How KubeBolt finds it
By the label app.kubernetes.io/part-of=kyverno, falling back to pods named
kyverno* in the kyverno or kyverno-system namespaces. A hand-rolled
manifest without the standard labels is still found.
Verify
# Reports exist — one per resource on Kyverno 1.9 and later
kubectl get policyreports -A
kubectl get clusterpolicyreports
# A failing result in one of them
kubectl get policyreports -A -o json |
jq '.items[].results[] | select(.result=="fail") | {policy, rule, message}' | head
Those failures appear in the Configuration lens after the next sweep (every 10 minutes).
| Symptom | Cause |
|---|---|
| Card says “not installed” | No pods matching the label or the fallback namespaces |
| Card says “degraded” | Kyverno pods present but not Ready |
| Reports exist, KubeBolt shows nothing | Every result is pass/warn/skip — only fail becomes a finding |
| No reports at all | Kyverno is installed but has no policies |
What becomes a finding
Only result: fail. pass and skip are not problems, warn is audit
noise by design, and error means the policy engine itself hiccupped —
reporting that as a resource violation would blame the workload for the
engine’s bad day.
Severity defaults to medium when the policy does not declare one. This is
the opposite of the CVE rule, where an unknown severity is discarded, and the
reason is that a policy is not a fact of nature: someone wrote it on
purpose, so a failure matters even when Kyverno does not label how much. Set
policies.kyverno.io/severity on the policy to control it.
Two traps worth knowing
Both come from Kyverno’s shape, and both look like KubeBolt bugs when they bite.
One workload, two rule names
Kyverno validates a workload twice: the Pod against your rule, and the
controller’s template against an auto-generated twin named autogen-<rule>.
Same policy, same defect, two names.
KubeBolt strips the autogen- prefix so both collapse into a single row.
Without that, one problem would read as two findings on the same workload,
forever un-closable — fixing the Deployment clears one and leaves the other.
The title is the policy, not the message
Kyverno writes the JSON path into the message, so the same violation reads differently depending on where it was caught:
Pod: …rule host-namespaces failed at path /spec/hostNetwork/
DaemonSet: …rule autogen-host-namespaces failed at path /spec/template/spec/hostNetwork/
The title is part of a finding’s fingerprint. Carrying the message in it turned
one problem into two findings, and any reword by Kyverno resolved the old
finding and minted a new one with the age clock back at zero — which
quietly destroys the “oldest unresolved” signal. So the title is policy/rule
and nothing more. The message keeps all its value as the remediation text,
where it is read rather than compared.
Where the resource comes from
Worth knowing if a finding ever shows up without one.
The wgpolicyk8s.io standard puts the subject in results[].resources, and
that is where KubeBolt looks first — Gatekeeper and older Kyverno fill it in.
Kyverno 1.9 and later leave it empty. It writes one report per resource and
names the subject in the report’s top-level scope. KubeBolt falls back to
scope, and then to ownerReferences. Without that fallback every violation
would arrive with no resource attached: counted on the dashboard, invisible in
the list, and impossible to act on.