OPA Gatekeeper
OPA Gatekeeper is the Kubernetes-native packaging of the Open Policy Agent (OPA) — a general-purpose, CNCF-hosted policy engine — as a cluster admission controller (Gatekeeper docs). OPA evaluates policies written in Rego, a declarative query language; Gatekeeper exposes OPA to the cluster as a ValidatingAdmissionWebhook (and optionally a MutatingAdmissionWebhook) so that policy decisions gate object creation and modification at stage 6 of the API Server Request Flow. Crucially, Gatekeeper splits policy into two CRD layers — a ConstraintTemplate (which defines a new policy kind plus its Rego logic) and a Constraint (an instance of that kind, scoped and parameterized) — so non-Rego-literate platform users can apply governance by writing only YAML. Gatekeeper also runs an audit loop that periodically re-scans already-admitted objects, catching violations that predate a policy.
CNCF maturity (as of 2026-05). The parent OPA project is a CNCF Graduated project: per the CNCF project page, OPA was accepted to the CNCF on 2018-03-29, moved to Incubating on 2019-04-02, and graduated on 2021-01-29 (CNCF — OPA). Gatekeeper is a sub-project living under that OPA umbrella, not a separately graded CNCF project — the CNCF project page does not list Gatekeeper as its own entry. Third-party write-ups that label “Gatekeeper” as incubating conflate it with OPA’s own earlier incubation tier; the accurate statement is that the parent OPA project is Graduated and Gatekeeper rides on that status.
Mental Model
The dispatch’s framing — “OPA is the engine, Gatekeeper is the K8s integration” — is exactly right and worth internalizing. OPA is a domain-agnostic decision engine: feed it a JSON document plus a Rego policy, get back a decision. It has no idea what a Pod is. Gatekeeper is the glue that (a) receives K8s AdmissionReview requests, (b) hands the candidate object to embedded OPA as the input document, (c) collects Rego’s violation results, and (d) translates them into an admission allow/deny.
The two-CRD design is the load-bearing idea. A ConstraintTemplate is authored once by someone who knows Rego — it declares a new Kubernetes kind (e.g. K8sRequiredLabels), an OpenAPI schema for that kind’s parameters, and the Rego that implements the check. A Constraint is then an instance of that kind — ordinary YAML like kind: K8sRequiredLabels with a match block and a parameters block. Platform users apply Constraints without ever seeing Rego. This is policy-as-a-product: Rego experts ship templates, everyone else consumes them.
flowchart TD subgraph authoring["Authoring (Rego expert)"] CT["ConstraintTemplate<br/>kind: K8sRequiredLabels<br/>+ Rego logic<br/>+ parameter schema"] end subgraph consuming["Consuming (platform user, YAML only)"] C1["Constraint<br/>kind: K8sRequiredLabels<br/>match: Namespaces<br/>params: must have 'owner'"] end CT -. "registers a new CRD" .-> C1 API["kube-apiserver<br/>admission stage"] -->|"AdmissionReview"| GK["Gatekeeper webhook pod"] GK -->|"input = candidate object"| OPA["embedded OPA / Rego"] OPA -->|"violation[] results"| GK GK -->|"allow / deny + msg"| API C1 -. "loaded into" .-> GK AUDIT["Audit loop<br/>(every 60s)"] -->|"re-scan existing objects"| OPA AUDIT -->|"write violations"| C1
The two-layer CRD model plus two evaluation paths. Insight to extract: a ConstraintTemplate defines a CRD — applying one literally extends the Kubernetes API with a new kind. The admission path (top) gates new/changed objects; the audit path (bottom) catches objects that already exist. Both run the same Rego.
Mechanical Walk-through
Installation and webhook registration
Gatekeeper installs as a Deployment (the controller-manager / webhook pods) plus an audit Deployment, both in the gatekeeper-system namespace. It registers a ValidatingWebhookConfiguration (gatekeeper-validating-webhook-configuration) and — if mutation is enabled — a MutatingWebhookConfiguration. From that point every create/update of a matching resource triggers an HTTPS callout from the apiserver to the Gatekeeper webhook.
ConstraintTemplate → a new CRD
When you kubectl apply a ConstraintTemplate, Gatekeeper’s controller does two things: it compiles the embedded Rego and it generates a CustomResourceDefinition for the declared kind, with the parameter schema you supplied as the CRD’s OpenAPI validation. After this, kubectl get k8srequiredlabels works — the cluster genuinely has a new API kind.
Constraint → an active policy
Applying a Constraint of that kind tells Gatekeeper “enforce this template, scoped here, with these parameters.” The match block filters by kinds (apiGroup + kind), namespaces / excludedNamespaces (glob patterns like kube-*), labelSelector, namespaceSelector, and scope (Cluster vs Namespaced). The parameters block is whatever the template’s schema declared. The enforcementAction controls what a violation does: deny (default — reject the request), warn (admit, return a warning to the client), or dryrun (admit silently, record the violation in audit only). warn and dryrun are how you roll a policy out without breaking workloads on day one.
The Rego contract
A Gatekeeper-targeted Rego policy lives under package k8srequiredlabels (matching the template) and produces results in a violation rule. The candidate object is at input.review.object; the constraint’s parameters are at input.parameters. Each violation yields {"msg": "...", "details": {...}}. Gatekeeper aggregates all violation results across all matching Constraints; a non-empty set with enforcementAction: deny fails admission.
Gatekeeper 3.19+ can use OPA Rego v1 syntax in templates — relevant because Rego v1 tightened the grammar (it makes if and contains mandatory keywords and removes several v0 ambiguities). It is opt-in: by default only Rego v0 is accepted, and a template requests v1 by setting spec.targets[].code[].source.version: "v1" under the Rego engine (no import rego.v1 line is needed) (Gatekeeper — Constraint Templates).
Audit mode
Admission only sees objects as they are created or changed. Anything already in the cluster when a policy is applied is invisible to the webhook. Gatekeeper’s audit controller closes this gap: every --audit-interval seconds (default 60, settable to 0 to disable) it lists existing objects and re-runs every Constraint’s Rego against them (Gatekeeper — Audit). Violations are written back into each Constraint’s status.violations field (capped by --constraint-violations-limit, default 20, because etcd objects have a size ceiling), exposed as the gatekeeper_violations Prometheus metric, and emitted as structured audit logs. There is also an export feature that pushes violations to an external sink and is not subject to the per-constraint reporting limit (audit docs). Audit is how you answer “what in my cluster is already non-compliant?”
A consequence worth calling out: ConstraintTemplates whose Rego reads input.review.userInfo (the requesting user’s username, UID, and groups) are not auditable — the Gatekeeper audit docs state explicitly that Kubernetes cannot populate user information during audit reviews, so template authors must handle the case where userInfo is empty (audit docs). Such policies are effectively admission-only: the webhook path sees the real requesting user, the audit path sees an empty userInfo.
Mutation
Beyond validation, Gatekeeper supports mutation via three dedicated CRDs (not ConstraintTemplates — mutation has its own resource kinds): Assign (set a value on a non-metadata field, e.g. force spec.securityContext.runAsNonRoot: true), AssignMetadata (add labels/annotations — restricted to metadata to keep mutation predictable), and ModifySet (add/remove entries from a list, e.g. a capability drop list). Mutation runs in the mutating webhook before validation, so a mutate-then-validate pair can both fix and enforce.
Configuration / API Surface
A complete ConstraintTemplate + Constraint pair enforcing “every namespace must carry an owner label”:
apiVersion: templates.gatekeeper.sh/v1 # the template API group
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels # lowercase; becomes the CRD plural
spec:
crd:
spec:
names:
kind: K8sRequiredLabels # the NEW kind this template creates
validation:
openAPIV3Schema: # schema for the Constraint's parameters
type: object
properties:
labels:
type: array
items: { type: string }
targets:
- target: admission.k8s.gatekeeper.sh # the K8s admission target
rego: |
package k8srequiredlabels # package MUST match (convention)
violation[{"msg": msg}] { # 'violation' is the contract rule
required := input.parameters.labels # params come from the Constraint
provided := {label | input.review.object.metadata.labels[label]}
missing := required[_] # iterate required labels
not provided[missing] # the actual check
msg := sprintf("missing required label: %v", [missing])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1 # the constraint API group
kind: K8sRequiredLabels # the kind the template just created
metadata:
name: ns-must-have-owner
spec:
enforcementAction: deny # deny | warn | dryrun
match:
kinds:
- apiGroups: [""] # core API group
kinds: ["Namespace"]
parameters:
labels: ["owner"] # validated against the schema aboveLine-by-line highlights: the ConstraintTemplate’s crd.spec.names.kind is the new kind — after apply, that kind exists cluster-wide. targets[].rego is the policy body; the package name conventionally matches the template name. violation is the magic rule name Gatekeeper collects. In the Constraint, enforcementAction is the deny/warn/dryrun knob, match.kinds scopes it, and parameters is checked against the template’s openAPIV3Schema before the Constraint is even accepted.
Failure Modes
- Webhook unreachability outage. Gatekeeper is a
ValidatingAdmissionWebhook; if its pods are down and thefailurePolicyisFail, the apiserver cannot create the affected resources. The classic spiral: Gatekeeper gatespodscluster-wide, Gatekeeper’s own pods get evicted, and now nothing can schedule. Mitigation: excludegatekeeper-systemandkube-systemfrom the webhook’snamespaceSelector, run multiple replicas, and considerfailurePolicy: Ignorefor non-critical templates. See the same failure analysis in API Server Request Flow. - Rego compilation error. A ConstraintTemplate with broken Rego is rejected at apply time, or worse, a subtle logic bug silently admits everything (an empty
violationset is “no violation”). Test templates against known-bad fixtures. - Audit lag and staleness. With a 60-second
--audit-intervaland large clusters, a Constraint’sstatus.violationscan be a minute (or more) stale. The 20-violation cap also means a Constraint flagging hundreds of objects shows only the first 20 — use the export feature or Prometheus counts for the true total. --audit-from-cachedivergence. Auditing from the informer cache is faster but can miss freshly created objects; auditing via live API calls is accurate but heavier.- Rego learning curve. Rego is a genuinely unusual language (set comprehensions, partial rules, no imperative control flow). The dispatch’s “powerful but a learning curve” is the honest characterization — bad Rego produces wrong policy that looks authoritative.
Alternatives and When to Choose Them
- Kyverno — the dominant alternative. Policies are plain Kubernetes YAML (plus CEL), so there is no second language to learn. Kyverno also does
generateand native image verification. The trade-off is expressiveness: Rego can express arbitrary logic that Kyverno’s structured matchers cannot. Choose Gatekeeper when you need complex cross-object logic or already run OPA elsewhere (API authz, Terraform); choose Kyverno when team velocity and the no-Rego barrier dominate. Kyverno is CNCF Graduated (moved to Graduated on 2026-03-16, per the CNCF project page) — a maturity signal worth noting. - ValidatingAdmissionPolicy — the in-process, Common Expression Language (CEL)-based built-in, GA in Kubernetes 1.30. No webhook to operate, no extra pods, no fail-open risk. For policies expressible in CEL, it is increasingly the right default and cannibalizes the simple end of both Gatekeeper and Kyverno. Gatekeeper itself can now generate
ValidatingAdmissionPolicyobjects from Constraints (beta and on-by-default since Gatekeeper v3.20, controlled by--default-create-vap-for-templatesand--default-create-vap-binding-for-constraints, requiring Kubernetes ≥ 1.30), offloading enforcement to the apiserver while authoring stays in the Constraint model (Gatekeeper — VAP integration). - Pod Security Admission — the built-in for the specific case of Pod hardening (Privileged/Baseline/Restricted). If your only need is the standard Pod profiles, PSA needs no extra tooling at all.
Production Notes
- The gatekeeper-library (open-policy-agent/gatekeeper-library) ships dozens of ready-made ConstraintTemplates (required labels, allowed repos, disallowed capabilities, replica limits, the full Pod Security Standards re-implemented as templates) — most production users start here rather than writing Rego from scratch.
- The standard rollout discipline is
enforcementAction: dryrun→ inspectstatus.violationsand Prometheus →warn→deny. Going straight todenyon an existing cluster reliably breaks something. - Latest release line is v3.22.x (v3.22.0 released 2026-03-09); the actively-supported lines are 3.22 and 3.21 (releases). Beginning in v3.22 the
--sync-vap-enforcement-scopeflag defaults totrue(and is slated for removal in a later release), so generated ValidatingAdmissionPolicies now fully honor each Constraint’smatchcriteria and namespace exclusions rather than enforcing more broadly — a notable upgrade-skew item (VAP integration docs). - Gatekeeper needs an explicit sync config (
Configresource) telling it which resource kinds to cache when policies do cross-object lookups (e.g. “no duplicate Ingress hosts” must see all Ingresses). Forgetting the sync config silently breaks such policies.
See Also
- Kyverno — the YAML-native alternative policy engine
- ValidatingAdmissionWebhook — the admission mechanism Gatekeeper rides on
- MutatingAdmissionWebhook — the mechanism behind Gatekeeper mutation
- ValidatingAdmissionPolicy — the in-process CEL alternative
- Admission Controllers — the apiserver gate Gatekeeper plugs into
- API Server Request Flow — where admission sits in the request pipeline
- Pod Security Standards — the built-in Pod-hardening policy set
- Image Signing with Sigstore — provenance verification, often paired with policy engines
- Kubernetes MOC — parent map, §12 Security