OPA & Gatekeeper
A comprehensive guide to Open Policy Agent (OPA) and Gatekeeper covering policy as code, admission control, constraint templates, validation, and practical implementation strategies for Kubernetes policy enforcement.
Open Policy Agent (OPA) is an open-source, general-purpose policy engine that enables policy-as-code across the stack. Gatekeeper is a Kubernetes-specific implementation of OPA that acts as an admission controller, enforcing policies at the API server level.
OPA and Gatekeeper provide a powerful way to implement security, compliance, and operational policies in Kubernetes without modifying application code or relying on manual reviews.
- Policy as Code: Policies are written in Rego (OPA's declarative language)
- Admission Control: Policies are enforced during resource creation, update, or deletion
- Audit Mode: Policies can be audited without enforcement for testing
- Dry Run: Test policies before enforcing them in production
- Reusable Templates: Create reusable policy templates
- Enforce security best practices consistently across all clusters
- Prevent misconfigurations before they reach production
- Automate compliance with regulations (GDPR, HIPAA, SOC2)
- Reduce manual review overhead
- Enable self-service policies for development teams
OPA (Open Policy Agent)
Gatekeeper
ConstraintTemplate
Constraint
# Install Gatekeeper using kubectl
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.12/deploy/gatekeeper.yaml
# Install Gatekeeper using Helm
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--create-namespace
# Verify installation
kubectl get pods -n gatekeeper-system
kubectl get validatingwebhookconfigurations | grep gatekeeper
# Check Gatekeeper status
kubectl get crd | grep -E "constraint|gatekeeper"
# Enable audit mode (optional)
kubectl patch -n gatekeeper-system deployment/gatekeeper-controller \
-p '{"spec":{"template":{"spec":{"containers":[{"name":"controller","args":["--audit=true"]}]}}}}'
# Enable dry-run mode (test without enforcement)
# Add --dry-run flag to Gatekeeper deployment
- Gatekeeper requires Kubernetes 1.16+
- Install in a dedicated namespace (gatekeeper-system)
- Ensure cluster has sufficient resources (CPU/memory) for Gatekeeper pods
- Test in a non-production environment before enforcing policies in production
Rego is the declarative language used by OPA to write policies. It's designed to express policy decisions and is highly expressive yet readable.
# Basic Rego Policy Structure
package mypolicy
# Violation rule - returns violation message
violation[{"msg": msg, "details": details}] {
# Policy logic
# If condition matches, violation is triggered
not input.review.object.metadata.labels["app"]
msg := "Pod must have an 'app' label"
details := {"missing": "app label"}
}
# Multiple violation rules
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.allowPrivilegeEscalation == true
msg := sprintf("Container %v allows privilege escalation", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot == true
msg := sprintf("Container %v must run as non-root", [container.name])
}
# Helper functions
is_production(namespace) {
namespace == "production"
}
is_production(namespace) {
namespace == "prod"
}
# Policy with parameter
allowed_registries(registries) {
container := input.review.object.spec.containers[_]
image := container.image
# Check if image comes from allowed registries
allowed := {reg | reg := registries[_]}
starts_with(image, allowed)
}
# Example Rego playgound: https://play.openpolicyagent.org/
- Use clear package names to organize policies
- Write expressive violation messages for easier debugging
- Use helper functions to avoid code duplication
- Test policies with sample inputs before deploying
- Keep policies focused on single concerns
- Use comments to explain complex logic
ConstraintTemplates define reusable policy templates with a schema and Rego logic. They can be instantiated with different parameters across namespaces.
# ConstraintTemplate: Require Labels
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
message:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("%s: you must provide labels: %v", [input.parameters.message, missing])
}
# ConstraintTemplate: Require Security Context
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredsecuritycontext
spec:
crd:
spec:
names:
kind: K8sRequiredSecurityContext
validation:
openAPIV3Schema:
type: object
properties:
runAsNonRoot:
type: boolean
allowPrivilegeEscalation:
type: boolean
dropCapabilities:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredsecuritycontext
violation[{"msg": msg, "details": {"containers": containers}}] {
containers := [container | container := input.review.object.spec.containers[_]]
container := containers[_]
not container.securityContext.runAsNonRoot == input.parameters.runAsNonRoot
msg := sprintf("Container %v must run as non-root", [container.name])
}
# ConstraintTemplate: Restrict Image Registry
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedregistries
spec:
crd:
spec:
names:
kind: K8sAllowedRegistries
validation:
openAPIV3Schema:
type: object
properties:
registries:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedregistries
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
image := container.image
allowed := [reg | reg := input.parameters.registries[_]]
not any({reg | startswith(image, reg)})
msg := sprintf("Image %v must be from allowed registry: %v", [image, allowed])
}
- Use meaningful names for templates (k8s* prefix)
- Define clear schemas with OpenAPI validation
- Include descriptive violation messages
- Test templates with dry-run before enforcing
- Version control templates like any other code
- Create templates for common security and compliance requirements
Constraints are instances of ConstraintTemplates with specific parameters. They define which resources the policy applies to and the specific requirements.
# Constraint: Require Labels in Production
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-app-label-prod
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- "production"
- "prod"
- "staging"
labelSelector:
matchLabels:
environment: production
parameters:
labels:
- "app"
- "team"
- "environment"
message: "All production pods must have app, team, and environment labels"
# Constraint: Require Security Context
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredSecurityContext
metadata:
name: require-secure-context
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
runAsNonRoot: true
allowPrivilegeEscalation: false
dropCapabilities:
- "ALL"
# Constraint: Allow Only Trusted Registries
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
name: allow-trusted-registries
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
registries:
- "docker.io/"
- "gcr.io/"
- "mycompany.azurecr.io/"
- "ghcr.io/mycompany/"
# Constraint: Enforce Resource Limits
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResourceLimits
metadata:
name: require-limits
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- "production"
parameters:
memory: "512Mi"
cpu: "500m"
# Constraint: Restrict HostNetwork
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRestrictHostNetwork
metadata:
name: no-host-network
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
enabled: false
# Check constraints status
kubectl get constraints
kubectl describe constraint require-app-label-prod
# View violations
kubectl get constraint -o yaml | grep -A 5 violations
- Start with
enforcementAction: dryrunto test policies without blocking - Use
matchto target specific resources (kinds, namespaces, labels) - Monitor constraint violations with Gatekeeper audit logs
- Gradually enforce policies in production after testing in staging
- Document exceptions and use
excludedNamespacesfor exemptions
Gatekeeper provides audit and dry-run modes to test policies before enforcing them in production.
# Audit mode - Check existing resources for violations
# Enable audit by adding --audit flag to controller
kubectl patch -n gatekeeper-system deployment/gatekeeper-controller \
-p '{"spec":{"template":{"spec":{"containers":[{"name":"controller","args":["--audit=true"]}]}}}}'
# Check audit results
kubectl get constraint -o yaml | grep -A 10 status
# Example audit output
status:
auditTimestamp: "2025-01-15T10:00:00Z"
violations:
- enforcementAction: deny
kind: Pod
message: "Pod must have an 'app' label"
name: violating-pod
namespace: default
# Dry run mode (test without enforcement)
# Add enforcementAction: dryrun to constraint
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: test-labels
spec:
enforcementAction: dryrun # Don't block, just log violations
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
labels: ["app", "team"]
# Watch for dryrun violations
kubectl get constraint test-labels -o yaml | grep -A 5 violations
# Simulate a request with Gatekeeper
# Use the webhook test endpoint
kubectl run test-pod --image=nginx -o yaml --dry-run=client | \
kubectl apply -f - --server-side --dry-run=server
- Create ConstraintTemplate and test with Rego playground
- Deploy Template with
enforcementAction: dryrun - Create Constraint and monitor audit violations
- Review violations and fix non-compliant resources
- Enable
enforcementAction: denyfor enforcement - Monitor and refine policies based on feedback
# Policy: Prevent running as root
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8snonroot
spec:
crd:
spec:
names:
kind: K8sNonRoot
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8snonroot
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot == true
msg := sprintf("Container %v must run as non-root", [container.name])
}
# Policy: Disallow hostNetwork
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdisallowhostnetwork
spec:
crd:
spec:
names:
kind: K8sDisallowHostNetwork
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdisallowhostnetwork
violation[{"msg": msg}] {
input.review.object.spec.hostNetwork == true
msg := "hostNetwork is not allowed"
}
# Policy: Enforce image pull policy Always
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8simagepullpolicy
spec:
crd:
spec:
names:
kind: K8sImagePullPolicy
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8simagepullpolicy
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.imagePullPolicy != "Always"
msg := sprintf("Container %v must have imagePullPolicy Always", [container.name])
}
# Policy: Prevent latest tag
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8snolatesttag
spec:
crd:
spec:
names:
kind: K8sNoLatestTag
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8snolatesttag
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("Container %v uses 'latest' tag - specify a version", [container.name])
}
# Policy: Require Readiness and Liveness Probes
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredprobes
spec:
crd:
spec:
names:
kind: K8sRequiredProbes
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredprobes
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.readinessProbe
msg := sprintf("Container %v missing readinessProbe", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.livenessProbe
msg := sprintf("Container %v missing livenessProbe", [container.name])
}
| Feature | Gatekeeper (OPA) | Pod Security Standards | Kyverno | Validating Webhooks |
|---|---|---|---|---|
| Policy Language | Rego (rich) | Built-in profiles | YAML/JSON | Custom code |
| Custom Policies | Yes | Limited | Yes | Yes |
| Audit Mode | Yes | Yes | Yes | No |
| Dry Run | Yes | Yes | Yes | No |
| Constraint Templates | Yes | No | Yes (policies) | No |
| Complexity | High | Low | Medium | High |
| Learning Curve | Steep (Rego) | Low | Medium | High |
| GitOps Support | Yes | Yes | Yes | Yes |
- Gatekeeper: Best for complex, custom policies and enterprise compliance
- Pod Security Standards: Best for simple, built-in security profiles
- Kyverno: Best for teams familiar with YAML and simpler policy needs
- Validating Webhooks: Best for very specific, custom logic not covered by other tools
# Check Gatekeeper pods status
kubectl get pods -n gatekeeper-system
# View Gatekeeper logs
kubectl logs -n gatekeeper-system deployment/gatekeeper-controller
# Check for Rego syntax errors
# Use OPA playground: https://play.openpolicyagent.org/
# Validate ConstraintTemplate
kubectl get constrainttemplate -o yaml
# Check constraint status
kubectl get constraint -o yaml
# Debug admission request
# Add --log-level=debug to Gatekeeper controller
kubectl patch -n gatekeeper-system deployment/gatekeeper-controller \
-p '{"spec":{"template":{"spec":{"containers":[{"name":"controller","args":["--log-level=debug"]}]}}}}'
# Test policy with mock input
# Create test input file
cat > test-input.json < --overwrite \
"gatekeeper.sh/sync=true"
- Rego syntax errors: Use OPA playground to validate syntax
- Missing schemas: Ensure CRD validation matches parameters
- Admission timeouts: Increase webhook timeout if policies are complex
- Resource exhaustion: Monitor memory usage of Gatekeeper pods
- Policy conflicts: Ensure policies don't contradict each other
enforcementAction: dryrun in your Constraint. Gatekeeper will evaluate policies and report violations without blocking resource creation. Use audit mode to check existing resources.OPA and Gatekeeper provide powerful policy-as-code capabilities for Kubernetes. Start with simple policies, test thoroughly, and gradually expand your policy library to enforce security, compliance, and operational best practices.