Install Gatekeeper
Gatekeeper runs as a set of Pods in the gatekeeper-system namespace. It registers a ValidatingAdmissionWebhook so the API server sends every CREATE/UPDATE request to Gatekeeper before persisting it to etcd.
Helm Installation
# Add the Gatekeeper Helm chart repository
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
# Install Gatekeeper into its own namespace
helm install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--create-namespace \
--set replicas=3 \
--set auditInterval=60
# Verify Pods are running
kubectl get pods -n gatekeeper-system
# NAME READY STATUS RESTARTS AGE
# gatekeeper-audit-6b9f8d7c5b-xk2lm 1/1 Running 0 45s
# gatekeeper-controller-manager-7f8c4d9b6-abc12 1/1 Running 0 45s
# gatekeeper-controller-manager-7f8c4d9b6-def34 1/1 Running 0 45s
# gatekeeper-controller-manager-7f8c4d9b6-ghi56 1/1 Running 0 45s
Verify the Webhook
# Confirm the ValidatingWebhookConfiguration exists
kubectl get validatingwebhookconfiguration
# NAME WEBHOOKS AGE
# gatekeeper-validating-webhook-configuration 2 1m
# Inspect the webhook — it targets CREATE and UPDATE operations
kubectl get validatingwebhookconfiguration \
gatekeeper-validating-webhook-configuration -o yaml | \
grep -A5 "rules:"
# - apiGroups: ["*"]
# apiVersions: ["*"]
# operations: ["CREATE", "UPDATE"]
# resources: ["*"]
kube-system and gatekeeper-system from policy enforcement to prevent lockout. You can configure additional exemptions via the Helm chart's exemptNamespaces value.
ConstraintTemplates
A ConstraintTemplate is a CRD that defines:
- The schema of parameters that Constraints can provide (via OpenAPI v3 schema)
- The Rego policy that evaluates admission requests using those parameters
Think of a ConstraintTemplate as a "policy class" and a Constraint as an "instance" of that class with specific configuration.
Template: Require Labels
This template enforces that specified labels exist on resources:
# constraint-template-require-labels.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
# Schema for the 'parameters' field in Constraint instances
openAPIV3Schema:
type: object
properties:
labels:
type: array
description: "List of required label keys"
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
# Violation is populated when a required label is missing
violation[{"msg": msg}] {
# Get the set of provided label keys
provided := {label | input.review.object.metadata.labels[label]}
# Get the set of required label keys from parameters
required := {label | label := input.parameters.labels[_]}
# Find missing labels
missing := required - provided
# If any are missing, generate a violation message
count(missing) > 0
msg := sprintf("Resource %v is missing required labels: %v", [
input.review.object.metadata.name,
missing
])
}
violation rule (not deny like standalone OPA). Each element in the violation set must be an object with a "msg" key. Gatekeeper collects all violation messages and returns them to the user.
Template: Allowed Registries
This template restricts which container image registries are permitted:
# constraint-template-allowed-registries.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedregistries
spec:
crd:
spec:
names:
kind: K8sAllowedRegistries
validation:
openAPIV3Schema:
type: object
properties:
registries:
type: array
description: "List of allowed container registry prefixes"
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedregistries
violation[{"msg": msg}] {
# Iterate over all containers (regular + init)
container := input_containers[_]
# Check if the image starts with any allowed registry
not registry_allowed(container.image)
msg := sprintf("Container '%v' uses image '%v' which is not from an allowed registry. Allowed: %v", [
container.name,
container.image,
input.parameters.registries
])
}
# Helper: collect all containers (containers + initContainers)
input_containers[c] {
c := input.review.object.spec.containers[_]
}
input_containers[c] {
c := input.review.object.spec.initContainers[_]
}
# Helper: check if image starts with an allowed prefix
registry_allowed(image) {
allowed := input.parameters.registries[_]
startswith(image, allowed)
}
# Apply the ConstraintTemplates
kubectl apply -f constraint-template-require-labels.yaml
kubectl apply -f constraint-template-allowed-registries.yaml
# Verify they are installed (STATUS should be "created: true")
kubectl get constrainttemplates
# NAME AGE
# k8sallowedregistries 5s
# k8srequiredlabels 10s
# Check template status for errors
kubectl get constrainttemplate k8srequiredlabels -o jsonpath='{.status}'
Constraints
A Constraint is an instance of a ConstraintTemplate. It specifies:
- Parameters — values passed to the Rego policy (e.g., which labels are required)
- Match — which resources and namespaces the policy applies to
- EnforcementAction —
deny(block),dryrun(audit only), orwarn(allow but warn)
Parameters & Match
# constraint-require-team-label.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet"]
parameters:
labels:
- "team"
- "cost-center"
# constraint-allowed-registries.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
name: prod-allowed-registries
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
- apiGroups: ["apps"]
kinds: ["Deployment", "StatefulSet", "DaemonSet"]
parameters:
registries:
- "gcr.io/my-org/"
- "docker.io/library/"
- "registry.k8s.io/"
# Apply the Constraints
kubectl apply -f constraint-require-team-label.yaml
kubectl apply -f constraint-allowed-registries.yaml
# Test: create a Deployment missing the "team" label
kubectl create deployment nginx-test --image=nginx -n default
# Error from server (Forbidden): admission webhook "validation.gatekeeper.sh"
# denied the request: [require-team-label] Resource nginx-test is missing
# required labels: {"cost-center", "team"}
# Test: create a Pod from an unauthorized registry
cat <
Namespace Scoping
The match block supports fine-grained scoping with namespaces, excludedNamespaces, and label selectors:
# constraint-registries-prod-only.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
name: registries-prod-namespaces
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
# Only enforce in these namespaces
namespaces:
- "production"
- "staging"
# Never enforce in these namespaces (takes precedence)
excludedNamespaces:
- "kube-system"
- "gatekeeper-system"
# Optional: only match resources with specific labels
labelSelector:
matchExpressions:
- key: "environment"
operator: "In"
values: ["prod", "staging"]
parameters:
registries:
- "gcr.io/my-org/"
- "registry.k8s.io/"
# Use enforcementAction: dryrun for testing without blocking
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-labels-dryrun
spec:
enforcementAction: dryrun # Audit only — does NOT block requests
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
parameters:
labels:
- "team"
# Check audit results:
# kubectl get k8srequiredlabels require-labels-dryrun -o yaml
# Look at status.violations[] for non-compliant resources
deny— Rejects the request (default). The resource is NOT created.dryrun— Allows the request but records violations in the Constraint'sstatus.violationsfield. Perfect for rollout testing.warn— Allows the request but returns a warning message to the client (shown in kubectl output).
Policy Library
The Gatekeeper Library (open-policy-agent/gatekeeper-library) provides a curated set of production-ready ConstraintTemplates:
- General — Required labels, allowed repos, unique ingress hosts, block NodePort services
- Pod Security — Disallow privilege escalation, restrict host namespaces, require read-only root filesystem, drop capabilities
- PSP Migration — Templates that replicate PodSecurityPolicy controls for clusters migrating from PSP
# Clone and explore the library
git clone https://github.com/open-policy-agent/gatekeeper-library.git
ls gatekeeper-library/library/
# general/
# pod-security-policy/
# Install a library template (e.g., block privileged containers)
kubectl apply -f gatekeeper-library/library/pod-security-policy/privileged-containers/template.yaml
# Create a constraint using the library template
cat <
enforcementAction: dryrun when deploying new policies. Monitor status.violations for a few days to identify existing non-compliant resources. Once clean, switch to deny. This prevents breaking existing workloads.
Exercises
K8sDisallowLatestTag that denies any Pod or Deployment where a container image uses the :latest tag (or no tag at all). Hint: use Rego's endswith(image, ":latest") and check for images with no : character.
K8sAllowedRegistries template from this article, create two Constraints: one for production namespace (only gcr.io/prod-org/) and another for development namespace (allows gcr.io/prod-org/ plus docker.io/library/). Verify each works by deploying test Pods.
require-team-label Constraint with enforcementAction: dryrun. Create several Deployments — some with the label, some without. Inspect kubectl get k8srequiredlabels require-team-label -o yaml and confirm violations appear in status.violations[] without blocking creation.
Key Takeaways
- Gatekeeper installs as a ValidatingAdmissionWebhook — every API request passes through it
- ConstraintTemplates define reusable policy logic (Rego) with a parameter schema
- Constraints instantiate templates with specific parameters and resource/namespace scoping
- Use
match.kinds,match.namespaces, andmatch.excludedNamespacesfor precise targeting enforcementAction: dryrunlets you test policies without blocking — always start here- The gatekeeper-library provides production-ready templates for common security policies
- Rego in Gatekeeper uses
violation[{"msg": msg}](notdeny) as the rule head
Next in This Track
In Part 3: Audit, Mutation & Testing, we cover Gatekeeper's audit controller for detecting existing violations, mutation webhooks for automatically modifying resources, and unit-testing Rego policies with opa test and Gatekeeper's test framework.