Open Policy Agent (OPA) is a general-purpose policy engine. Gatekeeper is the Kubernetes-native integration that turns OPA into an admission controller. Together, they provide the most flexible policy enforcement system available for Kubernetes.
That flexibility comes at a cost: Rego, OPA's policy language, is unlike anything most developers or security engineers have written before. Teams that adopt Gatekeeper without investing in Rego competency end up with policies that are either too permissive (missing edge cases) or too restrictive (blocking legitimate workloads and getting disabled).
This guide covers how to write Rego policies that are correct, testable, and maintainable.
Gatekeeper Architecture
Gatekeeper consists of three components:
- ConstraintTemplate: Defines the policy logic in Rego
- Constraint: Instantiates a template with specific parameters
- Webhook: Intercepts API requests and evaluates them against constraints
kubectl apply → API Server → Gatekeeper Webhook → Rego Evaluation → Allow/Deny
Installation
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.14.0/deploy/gatekeeper.yaml
Verify:
kubectl get pods -n gatekeeper-system
# NAME READY STATUS
# gatekeeper-audit-7c6f6d8b9c-xxxxx 1/1 Running
# gatekeeper-controller-manager-xxx-yyy 1/1 Running
Rego Fundamentals for Security Engineers
Rego is a declarative language optimized for querying structured data (JSON/YAML). The key concepts:
Rules Are Queries, Not If-Else
# This is NOT: if container.image starts with "docker.io" then violate
# This IS: find all containers where the image starts with "docker.io"
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
startswith(container.image, "docker.io/")
msg := sprintf("Container '%v' uses Docker Hub image '%v'", [container.name, container.image])
}
The [_] iterates over all containers. If any container matches the condition, a violation is generated.
Input Structure
Gatekeeper provides the Kubernetes admission request as input.review:
# The pod spec
input.review.object.spec.containers[_]
# The pod metadata
input.review.object.metadata.labels
# The namespace
input.review.object.metadata.namespace
# The request user
input.review.userInfo.username
# Parameters from the Constraint resource
input.parameters
Common Patterns
Check all containers (including init and ephemeral):
all_containers[container] {
container := input.review.object.spec.containers[_]
}
all_containers[container] {
container := input.review.object.spec.initContainers[_]
}
all_containers[container] {
container := input.review.object.spec.ephemeralContainers[_]
}
Negate a condition (does NOT have a label):
violation[{"msg": msg}] {
not input.review.object.metadata.labels["app.kubernetes.io/name"]
msg := "Pod must have the 'app.kubernetes.io/name' label"
}
Essential Security Policies
1. Require Non-Root Containers
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequirenonroot
spec:
crd:
spec:
names:
kind: K8sRequireNonRoot
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequirenonroot
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Container '%v' must set securityContext.runAsNonRoot=true", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.runAsUser == 0
msg := sprintf("Container '%v' must not run as UID 0", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("Init container '%v' must set securityContext.runAsNonRoot=true", [container.name])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireNonRoot
metadata:
name: require-non-root
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system
- gatekeeper-system
enforcementAction: deny
2. Block Privileged Containers
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sblockprivileged
spec:
crd:
spec:
names:
kind: K8sBlockPrivileged
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sblockprivileged
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.privileged
msg := sprintf("Privileged container '%v' is not allowed", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.allowPrivilegeEscalation
msg := sprintf("Privilege escalation in container '%v' is not allowed", [container.name])
}
3. Enforce Resource Limits
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequireresources
spec:
crd:
spec:
names:
kind: K8sRequireResources
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequireresources
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.memory
msg := sprintf("Container '%v' must set memory limits", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.limits.cpu
msg := sprintf("Container '%v' must set CPU limits", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.resources.requests.memory
msg := sprintf("Container '%v' must set memory requests", [container.name])
}
4. Restrict Host Path Volumes
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srestricthostpath
violation[{"msg": msg}] {
volume := input.review.object.spec.volumes[_]
volume.hostPath
not allowed_path(volume.hostPath.path)
msg := sprintf("HostPath volume '%v' mounting '%v' is not allowed", [volume.name, volume.hostPath.path])
}
allowed_path(path) {
allowedPaths := input.parameters.allowedPaths
startswith(path, allowedPaths[_])
}
Testing Rego Policies
Untested policies break production. Rego has built-in testing support.
Unit Tests
# policy_test.rego
package k8srequirenonroot
test_deny_root_container {
input := {"review": {"object": {"spec": {"containers": [
{"name": "web", "securityContext": {"runAsUser": 0}}
]}}}}
count(violation) > 0
}
test_allow_nonroot_container {
input := {"review": {"object": {"spec": {"containers": [
{"name": "web", "securityContext": {"runAsNonRoot": true, "runAsUser": 1000}}
]}}}}
count(violation) == 0
}
test_deny_missing_security_context {
input := {"review": {"object": {"spec": {"containers": [
{"name": "web"}
]}}}}
count(violation) > 0
}
Run tests:
opa test . -v
# PASS: 3/3
Integration Testing with Gator
Gator is Gatekeeper's CLI testing tool:
# Test a constraint against a resource
gator test -f constraint.yaml -f template.yaml -f test-pod.yaml
Create test fixtures:
# test-pod-should-fail.yaml
apiVersion: v1
kind: Pod
metadata:
name: bad-pod
spec:
containers:
- name: web
image: nginx
securityContext:
runAsUser: 0
Gatekeeper Audit Mode
Before enforcing, audit existing resources:
spec:
enforcementAction: dryrun # Log violations without blocking
Check audit results:
kubectl get k8srequirenonroot require-non-root -o json | \
jq '.status.violations'
This shows every existing resource that would fail the policy. Fix them before switching to deny.
Performance Considerations
Gatekeeper adds latency to every API request. At scale:
- Keep Rego policies simple. Complex logic adds evaluation time.
- Use
matchselectors to limit which resources are evaluated. - Run the audit controller on a schedule, not continuously.
- Monitor webhook latency:
kubectl get --raw /metrics | grep gatekeeper
If webhook latency exceeds 5 seconds, Kubernetes may timeout the request and allow it through, bypassing your policies entirely. Configure failurePolicy: Fail on the webhook to deny requests when Gatekeeper is unavailable, but be aware this can block all deployments if Gatekeeper goes down.
Gatekeeper Policy Library
The OPA Gatekeeper Library provides pre-built policies for common requirements:
# Clone the library
git clone https://github.com/open-policy-agent/gatekeeper-library
# Apply a policy
kubectl apply -f gatekeeper-library/library/pod-security-policy/privileged-containers/
Available policies include:
- Container limits
- Allowed registries
- Required labels
- Host networking restrictions
- Read-only root filesystem
- External IPs
Start with the library. Customize only when your requirements diverge from the standard patterns.
How Safeguard.sh Helps
Safeguard.sh provides visibility into your Gatekeeper policy coverage and enforcement status. The platform identifies gaps where namespaces lack policy coverage, detects overly permissive constraints that allow too many exceptions, and monitors audit violations over time to track compliance trends. For teams adopting Gatekeeper, Safeguard.sh offers policy recommendations based on your cluster configuration and maps Gatekeeper policies to compliance frameworks like CIS Kubernetes Benchmark and NIST 800-190.