Use Kyverno for admission policy
Status: Accepted Date: 2026-08-21 Deciders: Smana (Platform Owner) Related Design: N/A — records a choice predating the design workflow
Context
The constitution’s security defaults — runAsNonRoot, a read-only root
filesystem, no privilege escalation, capabilities dropped to [ALL],
seccompProfile.type: RuntimeDefault — are written down in the Platform
Constitution’s security context
rule.
A rule that lives only in a document is a hope, not a gate: something
has to act on a manifest that violates it. ./scripts/validate-manifests.sh
already audits the rendered bundle with Polaris before merge, but that is
a pre-merge check against Git content — it says nothing about a kubectl apply run from a live session, or a Pod spec a controller generates at
runtime rather than one anyone hand-wrote and committed.
Two HelmReleases in security/base/kyverno/ close that gap: kyverno,
the admission-webhook controller, and kyverno-policies, the upstream
pack that implements the Kubernetes Pod Security Standards as
ClusterPolicy resources. Both are pinned to the same chart version,
sourced from a shared HelmRepository
(flux/sources/helmrepo-kyverno.yaml); the controller sets crds.install: false, and its CRDs instead come from a GitRepository pin
(flux/sources/gitrepo-kyverno.yaml) applied by
crds/base/kustomization-kyverno.yaml — one entry among a dozen siblings
in the crds Kustomization (clusters/mycluster-0/crds.yaml) that
installs every CRD-owning component’s CRDs ahead of the controller that
consumes them.
Kyverno is documented at Zero-Trust as one of the platform’s “enforced, not documented” gates — it “rejects non-compliant workloads at admission” — and that is a materially different guarantee from a CI check: CI only ever sees what is in Git before merge, while an admission webhook sees every write the API server accepts, regardless of where it originated.
Decision Drivers
- Enforcement at the API server, not only in CI.
./scripts/validate-manifests.shruns against rendered Git content; it has no visibility into a manifest applied directly or a Pod spec a controller generates at runtime. - Policy language reviewability. A policy written as a Kubernetes
object the same people already review
Deployments andCiliumNetworkPolicys in, rather than a second language only a subset of reviewers know. - Beyond validate-only. A validating-only gate can reject a noncompliant workload but not fix it, and cannot generate a companion resource a policy implies should exist.
- CRD management consistency. Every CRD-owning component in this
repository installs its CRDs from
crds/base/, ahead of the controller that consumes them — the same ordering this decision has to fit.
Considered Options
Option 1: Kyverno
kyverno (the controller) plus kyverno-policies (the upstream Pod
Security Standards pack), both HelmReleases in security/base/kyverno/.
Pros:
- Policies are Kubernetes-native YAML — a
ClusterPolicyresembles the objects it governs, reviewable by anyone who already reads aDeploymentorCiliumNetworkPolicymanifest. - Kyverno validates, mutates, and generates; Pod Security Admission only validates. A policy can rewrite a noncompliant field instead of only rejecting the request, or create a companion resource a rule implies should exist.
kyverno-policiesships the Pod Security Standards asClusterPolicyresources already, with no local authoring needed to get the baseline running.
Cons:
- An admission webhook sits in the request path for every matching API call; see Consequences.
kyverno-policiesinstalls withvalues: {}here, deferring policy selection to the chart; see Consequences.
Option 2: OPA Gatekeeper
The other general-purpose Kubernetes admission controller, policies
written in Rego and packaged as ConstraintTemplate/Constraint pairs.
Not present anywhere in this repository.
Pros:
- Also a mature, widely deployed CNCF admission controller with a large policy library (Gatekeeper library).
- OPA’s Rego engine is shared with other policy surfaces outside Kubernetes, which can be an advantage where the same team already writes Rego elsewhere.
Cons:
- Policies are Rego, a purpose-built logic language distinct from the YAML every other manifest in this repository is written in — a second language for whoever reviews a policy, on top of Kubernetes YAML and KCL (compositions) they already need.
- Validates only; no mutation, no resource generation — a noncompliant request is rejected, not corrected.
Option 3: Pod Security Admission alone
Kubernetes’ built-in, in-tree admission controller: a namespace label
selects privileged/baseline/restricted, no separate controller to
run.
Pros:
- No extra component: no
HelmRelease, no webhook pods, no chart to version. - No webhook in the request path — enforcement is done inside the API server process itself.
Cons:
- Covers only the Pod Security Standards’ fixed three profiles; nothing
outside
Pod-shape checks — no rule for, say, requiring anEKSPodIdentitylabel or restricting an image registry. - Validate-only, same as Gatekeeper: rejects, cannot mutate or generate.
- No custom policy authoring at all — the enforced ruleset is exactly the three built-in profiles, with no room to grow.
Option 4: Native ValidatingAdmissionPolicy (CEL, in-tree)
Kubernetes’ CEL-based, in-tree admission policy mechanism — no webhook,
no extra pods. Present in this repository exactly once, and not as the
platform’s policy engine: infrastructure/base/envoy-gateway/helmrelease.yaml
notes that the gateway-api chart’s own bundled CRDs ship a
safe-upgrades ValidatingAdmissionPolicy that refuses CRD downgrades,
which conflicts with this platform’s separately pinned Gateway API CRDs
and is why that chart’s crds: Skip install mode is set. That is a
CRD-protection policy shipped by an upstream chart, unrelated to workload
security policy, and envoy-gateway itself is an opt-in component wired
only into the LLM platform’s cluster directory
(clusters/mycluster-0-llm-platform/), not the default cluster.
Pros:
- No webhook in the request path at all — the check runs inside the API server via a compiled CEL expression, removing the failure mode where a webhook with no live endpoint blocks writes.
- No extra controller, no chart, no version to track.
Cons:
- CEL is its own expression language to write and review, with no mutation or resource-generation capability — closer to Option 3’s validate-only ceiling than to Kyverno’s.
- Was not viable at the time this platform’s admission story was built out; see Consequences and Implementation Notes for where this stands today.
Decision Outcome
Chosen option: “Option 1 — Kyverno”
Rationale: The deciding factor is not any single feature but the
combination the constitution’s security defaults need: something that
runs at the API server rather than only in CI, is authored in a language
the same reviewers who read the rest of this repository’s YAML already
know, and can do more than reject a bad request — the App composition
and other platform-generated objects benefit from mutation and generation,
not only validation. Gatekeeper matches the enforcement point but costs a
second policy language. Pod Security Admission and native
ValidatingAdmissionPolicy both avoid the webhook cost entirely, but
neither goes past validate-only, and PSA’s ruleset cannot grow beyond its
three built-in profiles. Kyverno is the only option that clears all four
drivers at once — at the accepted cost of a webhook in the request path,
recorded below rather than hidden.
Consequences
Positive
- Policies are Kubernetes-native YAML, reviewed the same way every other
manifest in this repository is reviewed — no Rego, no CEL, for anyone
auditing what a
ClusterPolicyactually does. - Kyverno can mutate and generate, not only validate — a capability
neither Gatekeeper, Pod Security Admission, nor native
ValidatingAdmissionPolicyoffers on its own. - The Pod Security Standards baseline ships from
kyverno-policiesasClusterPolicyresources with no local policy authoring required to get it running. - CRD lifecycle matches every other CRD-owning component:
crds.install: falseon the controller, the CRDs applied fromcrds/base/ahead of it.
Negative
kyverno-policiesinstalls withvalues: {}(security/base/kyverno/helmrelease-policies.yaml). No policy selection, no audit-versus-enforce override — the enforcedClusterPolicyset, and whether each one runs inauditorenforcemode, comes entirely from the chart’s own defaults. This is a deliberate deferral recorded here as an accepted trade-off, not an oversight: the platform gets the Pod Security Standards baseline running with zero local policy authoring, at the cost of not having chosen, or even enumerated, which policies are actually active or in which mode.- Mitigation: none today. Reviewing the chart’s default values for the pinned version and pinning the ones this platform actually wants is the natural next step, not yet taken.
- An admission webhook sits in the request path for every matching API
call, and a failed webhook can block writes. This is not
hypothetical here:
eks-prepare-destroy.shhas to explicitly disable “Kyverno’s and the Cilium operator’s blocking admission webhooks” before a teardown, because once their pods are evicted with the nodes, every subsequent delete would otherwise fail against a webhook with no live endpoint (teardown docs). The same class of failure — a webhook pod down, unreachable, or overloaded — can block ordinary writes outside a teardown too.- Mitigation: none automated beyond the teardown script’s explicit disable step; day-to-day operation relies on the webhook staying healthy.
- Native
ValidatingAdmissionPolicyhas since become a viable alternative for at least the validate-only slice of this problem, and would remove the webhook-in-the-path failure mode entirely for whatever it covers. It is not adopted here; see Implementation Notes for how that reconsideration is scoped.
Neutral
- Kyverno’s controller and its policy pack are two separate
HelmReleases sharing one chart family and oneHelmRepository, versioned together — a version bump touches both files, not one. - The CRD/controller split (
crds/base/kustomization-kyverno.yamlpulling from aGitRepositorypin, the controller itself from aHelmRepositorypin) means two independent version references have to be read to know what is actually installed — the chart version in theHelmReleaseand the tag influx/sources/gitrepo-kyverno.yaml.
Implementation Notes
security/base/kyverno/ holds both HelmReleases:
helmrelease-controller.yaml (crds.install: false,
fullnameOverride: kyverno) and helmrelease-policies.yaml
(values: {}). Both reference the kyverno HelmRepository
(flux/sources/helmrepo-kyverno.yaml). The controller’s CRDs come from
the kyverno GitRepository (flux/sources/gitrepo-kyverno.yaml),
applied by crds/base/kustomization-kyverno.yaml, itself one resource in
the crds Kustomization (clusters/mycluster-0/crds.yaml) that runs
ahead of security in the Flux dependency graph.
Turning kyverno-policies’s values: {} into a chosen, reviewed policy
set — rather than whatever the pinned chart version defaults to — is the
natural next step this record leaves open; it has not been done.
Revisiting native ValidatingAdmissionPolicy for the parts of this
policy set that are pure validation (no mutation, no generation) is a
future reconsideration, not a rejection: the option did not exist as a
mature, in-tree mechanism when this platform’s admission story was first
built out, and adopting it now would be a scoped follow-up — replacing
the validate-only subset of kyverno-policies’s coverage, not the whole
of Kyverno, since mutation and generation still need a controller.
References
- Policies — how the platform implements the constitution’s admission, network, and pod security rules
- Zero-Trust — Kyverno admission listed among the platform’s “enforced, not documented” gates
- Teardown — the concrete case of Kyverno’s blocking admission webhook needing an explicit disable step before cluster teardown
- Gateway API —
the
allow-gateway-l7-proxypolicy scoped by namespace specifically so it does not also open up unrelated pods like Kyverno’s admission webhook security/base/kyverno/helmrelease-controller.yaml— the controllerHelmRelease,crds.install: falsesecurity/base/kyverno/helmrelease-policies.yaml— thekyverno-policiesHelmRelease,values: {}crds/base/kustomization-kyverno.yaml— the CRD Kustomization sourced from thekyvernoGitRepositoryflux/sources/gitrepo-kyverno.yaml,flux/sources/helmrepo-kyverno.yaml— the Git and Helm source pinsinfrastructure/base/envoy-gateway/helmrelease.yaml— the one repository reference toValidatingAdmissionPolicy, a CRD-protection policy shipped by thegateway-apichart, unrelated to workload security policy- Platform Constitution §3.3 — the security-context baseline this admission layer enforces
- Kyverno documentation — validate, mutate, and generate policy types
- OPA Gatekeeper documentation — the Rego-based alternative
- Kubernetes ValidatingAdmissionPolicy — the native, in-tree CEL mechanism