Kubernetes admission policies are the last gate before a workload reaches your cluster. Get them right and you have a cryptographic enforcement layer that blocks unsigned images, privileged containers, and unattested artifacts at deploy time — before the first syscall executes. Get them wrong and you have a false sense of coverage that collapses the moment an edge case hits production.
Three mature options exist for implementing admission control in Kubernetes: OPA Gatekeeper, Kyverno, and the natively-shipped Validating Admission Policy (VAP) introduced as stable in Kubernetes 1.30. Each has a distinct programming model, failure behavior, and operational footprint. Here's an honest look at the tradeoffs, informed by running all three in production environments.
OPA Gatekeeper: Rego Expressiveness at Operational Cost
OPA Gatekeeper is the most established option and the one with the deepest expressiveness. Policies are written in Rego, OPA's purpose-built policy language. A Gatekeeper policy for enforcing cosign attestation looks roughly like this:
package imageverify
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not has_valid_attestation(container.image)
msg := sprintf("image %v has no valid cosign attestation", [container.image])
}
Rego is expressive enough to handle complex, multi-condition policies — checking SLSA provenance level, validating in-toto attestation predicates, cross-referencing admission context with external data via OPA's data store. Teams that need nuanced policy logic (e.g., "allow images without attestation only in the staging namespace, but require SLSA Level 2 in all prod-* namespaces") have the full power of a Turing-complete language.
The operational cost is real. Rego has a steep learning curve for platform teams that haven't worked with it before. Policy debugging is non-obvious — opa eval is powerful but the tooling feedback loop is slower than most engineers expect. Gatekeeper's audit mode (where it evaluates existing resources against new policies without blocking) is excellent for rollout safety, but the CRD surface area is large: ConstraintTemplates, Constraints, Configs, SyncSets. Upgrading Gatekeeper across minor versions has historically required migration steps that block naively in-place upgrades.
Gatekeeper Failure Mode: Dry-Run Confusion
A pattern we've seen cause real incidents: a policy is correctly authored and passes local opa eval tests, but the ConstraintTemplate's enforcementAction is left as dryrun from initial testing and never promoted to deny. Audit logs show violations, deployments go through. This isn't a Gatekeeper bug — it's an operational gap. But in a fast-moving platform team, it's easy to miss. Treating policy enforcement status as a first-class metric (e.g., "what fraction of our constraint templates are in enforce mode?") requires deliberate instrumentation.
Kyverno: YAML-Native Policies with Better Developer Experience
Kyverno uses Kubernetes-native YAML resources for policy definition. No new language to learn — policies look like Kubernetes objects with familiar spec structures. The same cosign attestation check in Kyverno:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-image-attestation
spec:
validationFailureAction: Enforce
rules:
- name: check-cosign-attestation
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences: ["*"]
attestations:
- type: https://slsa.dev/provenance/v0.2
attestors:
- entries:
- keyless:
subject: "https://github.com/your-org/*"
issuer: "https://token.actions.githubusercontent.com"
The verifyImages API has first-class support for cosign keyless signing and SLSA provenance checks — Kyverno understands the Sigstore ecosystem without requiring policy authors to implement the verification logic themselves. This is a meaningful advantage for teams adopting supply chain controls for the first time: the happy path is well-documented and the common case is handled out of the box.
Kyverno also ships a policy testing framework (kyverno test) that runs unit tests against policy resources using synthetic admission inputs. The feedback loop for policy authors is faster than Gatekeeper's, and the YAML-native surface is familiar to anyone who writes Kubernetes manifests.
Where Kyverno Reaches Its Limits
Complex multi-step conditional logic is harder to express in Kyverno's YAML DSL than in Rego. Policies that need to join multiple pieces of admission context — for example, validating that a Pod's service account has a specific annotation AND the image has a valid provenance attestation AND the namespace is in the approved list — require Kyverno's JMESPath-based conditions, which become verbose quickly. For simple-to-medium complexity supply chain policies, Kyverno is the better operational choice. For policies approaching the complexity of a mini authorization engine, Gatekeeper and Rego is worth the learning curve.
We're not saying Kyverno is less capable overall — in many production environments it handles 90% of real supply chain policy needs with less operational overhead. The boundary is specifically at policy logic complexity.
Validating Admission Policy: Kubernetes-Native CEL
Kubernetes 1.30 (stable) introduced Validating Admission Policy as a first-class API — no webhook required, no external controller to manage. Policies are written in CEL (Common Expression Language) and embedded directly in Kubernetes API objects:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-nonroot
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE","UPDATE"]
resources: ["pods"]
validations:
- expression: >
object.spec.securityContext.runAsNonRoot == true ||
object.spec.containers.all(c,
c.securityContext.runAsNonRoot == true)
The operational simplicity is compelling: no webhook latency, no controller availability dependency, no CRD management. VAP is the right choice for teams that need basic security guardrails (no privileged containers, runAsNonRoot, no hostPath mounts, dropping dangerous capabilities like CAP_NET_ADMIN and CAP_SYS_ADMIN) without the complexity of a full policy engine.
The Gap: No Supply Chain Attestation Primitives
CEL can validate fields on Kubernetes objects. It cannot verify cosign signatures against a Rekor log, evaluate SLSA provenance attestations, or call external services. VAP is not a replacement for Gatekeeper or Kyverno in supply chain enforcement contexts — it's a complement. Use VAP for pod security standard enforcement (no privileged, no hostPath, RBAC-appropriate service accounts). Use Kyverno or Gatekeeper for cosign attestation and SLSA provenance verification. Running both is not redundancy — they operate at different abstraction layers and catch different violation classes.
Namespace Isolation and RBAC as the Foundation Layer
Admission policies operate on pod creation and update events, but they can't fully substitute for correct RBAC and namespace isolation. A policy that enforces SLSA Level 2 attestation on pods is only useful if attackers can't reach the API server with credentials that have pods/create in the target namespace — or bypass the admission webhook entirely via a misconfigured namespace selector.
Kyverno and Gatekeeper both support namespace selectors that apply policies cluster-wide, including system namespaces where the kubernetes.io/metadata.name label prevents policy bypass via namespace creation. The recommended practice is to set namespaceSelector to match all namespaces by default, and explicitly exclude namespaces that ship before policy enforcement (e.g., kube-system during bootstrap) rather than the reverse. Starting permissive and tightening is a ratchet that tends not to get tightened.
Choosing Based on Threat Model, Not Ecosystem Loyalty
A practical decision matrix based on the scenarios we've observed in growing container platform teams:
- Cosign keyless signing + SLSA provenance verification: Kyverno's
verifyImagesis the path of least resistance. The integration with the Sigstore ecosystem is first-class and the policy surface is well-documented. - Complex conditional policy logic: OPA Gatekeeper with Rego. Accept the learning curve investment.
- Pod security standards enforcement (privileged, runAsNonRoot, hostPath, capabilities): Kubernetes native VAP or Kyverno's mutation + validation rules. No need for Rego here.
- Audit before enforce: Both Gatekeeper (dryrun mode) and Kyverno (Audit) support this. Use it. Enforce mode on a cluster with legacy workloads will cause incidents.
The framing of "OPA vs Kyverno" is less useful than "what do I need to enforce, and which tool makes that enforcement reliable?" Teams that have already invested in OPA for other policy use cases (network policy, IAM, general authorization) should lean Gatekeeper to avoid tool sprawl. Teams starting fresh on supply chain enforcement with a clear cosign + SLSA requirement should reach for Kyverno first.
Whatever the choice, the enforcement action matters as much as the policy logic. A correctly authored policy in audit mode protects nothing. The metric worth tracking is not "do we have admission policies?" but "what percentage of our production namespace pod creates are blocked when attestation is absent?" If the answer isn't close to 100%, the control doesn't exist yet.