You've rolled out admission control — Gatekeeper for the org-wide guardrails, Kyverno for the team-specific ones — and the cluster is quietly enforcing dozens of rules. Then a question arrives that the tooling makes surprisingly hard: which policies actually apply to this Deployment, and is anything it owns violating one right now?
This guide works backward from that question. We'll look at why listing policies is easy but mapping their coverage is not, and how KubeAtlas v1.4 turns "which rules govern this resource" into a single query — on the graph, in the API, or in an offline report.
Why this is hard with native tooling
Both engines store their rules as their own custom resources: Gatekeeper as ConstraintTemplates plus the generated Constraint CRDs, Kyverno as ClusterPolicy and Policy objects. Listing them is trivial:
kubectl get constraints -A # Gatekeeper
kubectl get clusterpolicies,policies -A # Kyverno
But that list is the easy 20%. The questions that actually matter in an audit or an incident are harder:
- Coverage is implicit. A Constraint's
matchblock (kinds, namespaces, label selectors) and a Kyverno rule'smatch/excludedescribe which resources are in scope — but to know the answer you have to evaluate those selectors against the live inventory yourself. - Two engines, two schemas. Gatekeeper and Kyverno express scope and report violations in completely different shapes, so "show me everything governing this Pod" means querying two systems and reconciling them by hand.
- Violation state is scattered. It lives in Constraint
status, in KyvernoPolicyReports, and in admission webhook logs — never in one place keyed by the resource you care about.
The hidden factor most teams overlook: the dangerous gap isn't a missing policy, it's a policy you assume covers something that it doesn't. A Constraint scoped to the wrong namespace selector looks healthy — zero violations — precisely because it governs nothing.
Working backward: what a real answer looks like
The end goal is one relationship, queryable in both directions: policy → governs → resource, carrying the current violation state. That requires three things:
- Discover every policy across both engines — including Constraints created from a
ConstraintTemplatelong after the tool started. - Model the relationship as a typed edge, so "what governs this resource" is as cheap as "what does this policy govern".
- Attach live violation status to each edge, in one normalized shape, regardless of which engine produced it.
This is exactly what KubeAtlas v1.4 adds to its dependency graph.
How KubeAtlas models it: ENFORCES edges
v1.4 introduces a new edge type — ENFORCES — drawn from each policy to the resources it governs, tagged with current violation status. Gatekeeper Constraints and Kyverno ClusterPolicies/Policies are watched continuously and projected onto the same graph as your workloads, config, and RBAC, so a policy is just another node with edges you can traverse.
The discovery is the interesting part. A naïve watcher would miss any Constraint whose CRD didn't exist at startup. KubeAtlas runs an informer-of-informers: it watches ConstraintTemplates and, whenever one generates a new Constraint CRD, registers an informer for it at runtime. Policies that appear after the tool is running are picked up automatically — no restart, no redeploy.
Good to know: the ENFORCES edges are purely additive — they sit alongside the existing dependency edges and change nothing about how the graph is built. KubeAtlas only renders edges for the engines you actually run; if a cluster has Gatekeeper but not Kyverno, you simply see the Gatekeeper half.
Step 1 — Upgrade to v1.4.0
Policy visibility ships in v1.4.0. If you're already running KubeAtlas, upgrade the chart; if not, the in-memory install is the fastest start:
helm upgrade --install kubeatlas oci://ghcr.io/lithastra/charts/kubeatlas \
--version 1.4.0 \
--namespace kubeatlas --create-namespace
kubectl -n kubeatlas rollout status deploy/kubeatlas
kubectl -n kubeatlas port-forward svc/kubeatlas 8080:80
KubeAtlas needs read access to the policy CRDs (Constraints, ConstraintTemplates, ClusterPolicies, Policies); the bundled ClusterRole already includes them.
Step 2 — Explore in the Policy view
Open http://localhost:8080 and switch to the new Policy view. It lists every Constraint and Kyverno policy in force and styles ENFORCES edges distinctly on the canvas, so you can:
- Select a resource and see which policies govern it — and which are currently failing.
- Select a policy and see its real coverage — the actual set of resources it matches, not the selector you hoped it matched.
- Spot policies that govern nothing: a Constraint with no
ENFORCESedges is a misconfigured scope hiding behind a clean violation count.
Step 3 — Query it from the API
The same data is available through engine-aware endpoints, which is what you'd wire into a dashboard or a periodic check:
# every policy in force, both engines
curl -s localhost:8080/api/v1/policy/constraints | jq '.[].name'
# narrow to one engine
curl -s 'localhost:8080/api/v1/policy/constraints?engine=gatekeeper'
curl -s 'localhost:8080/api/v1/policy/constraints?engine=kyverno'
# the resources a given policy governs, with per-resource violation state
curl -s localhost:8080/api/v1/policy/constraints/require-run-as-nonroot/affected
The /affected response is the one that pays off in practice: it returns the resolved set of governed resources — the same edges you see on the canvas — each annotated with whether it currently passes or violates, so you can answer "is anything under this policy actually broken?" without touching two engines' status formats.
Step 4 — Air-gapped audits and CI: the diagnostic report
The Policy view is for exploring interactively; auditors and pipelines want an artifact. v1.4 adds a self-contained offline diagnostic report that runs against your current KUBECONFIG and needs no running server:
# generate a report from the current cluster context
kubeatlas diagnose
# or pull it from a running instance as JSON
curl -s localhost:8080/api/v1/diagnose | jq '.policyViolations'
The report snapshots the whole dependency graph — orphans, cycles, the top blast-radius resources — and renders as either HTML (deliberately air-gapped: no external CDN or font references, so it opens on an isolated network) or JSON. Crucially, the JSON carries a normalized policyViolations array distilled from the ENFORCES edges, so a Gatekeeper failure and a Kyverno failure arrive in one engine-agnostic shape your automation can consume without special-casing either engine.
Beyond the core: the same release adds a Headlamp Policy view and a policy-report option for the kubeatlas-action GitHub Action, which appends a policy-violation summary to the pull-request comment — so a change that would trip a Constraint is flagged in review, before it merges. Exact flags and output formats are in the documentation.
The owl's-eye summary
| Question | Native tooling | KubeAtlas v1.4 |
|---|---|---|
| Which policies exist? | kubectl get per engine | One Policy view, both engines |
| Which policies govern resource X? | Evaluate selectors by hand | Reverse ENFORCES edges, one query |
| What does policy Y actually cover? | Hope the selector is right | Resolved affected set on the graph |
| Is anything violating, across engines? | Reconcile two status formats | Normalized policyViolations |
| Hand an auditor a snapshot? | Screenshots | Self-contained offline report |
Admission policies are only as good as their coverage, and coverage is invisible until something is on the graph next to the resources it's supposed to protect. Put the policies on the map, and "which rules govern this — and is it passing?" becomes a question with a one-query answer.