OPA/Gatekeeper
What Is OPA (Open Policy Agent)?
Open Policy Agent (OPA) is a general-purpose policy engine that enables unified, context-aware policy enforcement across the entire stack. In the Kubernetes context, OPA evaluates policies written in the Rego language to make admission decisions about API requests.
Gatekeeper is the Kubernetes-native implementation of OPA. It runs as a validating admission webhook and provides Kubernetes-native CRDs for defining and managing policies.
CKS Exam Relevance
OPA/Gatekeeper is one of the most complex topics on the CKS exam. You may be asked to:
- Install or verify Gatekeeper is running
- Create ConstraintTemplates with Rego policies
- Create Constraints that apply templates to resources
- Debug why pods are being rejected
- Understand audit vs enforcement modes
Gatekeeper Architecture
How It Works
- ConstraintTemplate -- Defines the policy logic using Rego and creates a new CRD
- Constraint -- An instance of the ConstraintTemplate CRD that specifies where and how the policy applies
- Gatekeeper Controller -- Evaluates incoming requests against matching Constraints
- Decision -- Allow or deny with violation messages
Installing Gatekeeper
# Install Gatekeeper using the official manifest
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.14.0/deploy/gatekeeper.yaml
# Verify installation
kubectl get pods -n gatekeeper-system
# NAME READY STATUS
# gatekeeper-audit-... 1/1 Running
# gatekeeper-controller-manager-... 1/1 Running
# gatekeeper-controller-manager-... 1/1 Running
# gatekeeper-controller-manager-... 1/1 Running
# Verify webhook is registered
kubectl get validatingwebhookconfigurations
# gatekeeper-validating-webhook-configurationExam Note
On the CKS exam, Gatekeeper may already be installed. Check with:
kubectl get crd | grep gatekeeper
kubectl get pods -n gatekeeper-systemConstraintTemplates
A ConstraintTemplate defines:
- The Rego policy logic
- The schema for parameters the Constraint can pass
- A new CRD that Constraints instantiate
Template Structure
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels # Lowercase, becomes the CRD name
spec:
crd:
spec:
names:
kind: K8sRequiredLabels # CamelCase, used in Constraint
validation:
openAPIV3Schema:
type: object
properties:
labels: # Parameters the Constraint provides
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing])
}Rego Policy Language Basics
Rego is OPA's declarative query language. Here are the essential patterns for the CKS exam:
Key Concepts
package k8sexample
# The violation rule - Gatekeeper looks for this
# Each violation must return a msg
violation[{"msg": msg}] {
# Access the incoming Kubernetes object
container := input.review.object.spec.containers[_]
# Check a condition (if true, it's a violation)
container.securityContext.privileged == true
# Build the violation message
msg := sprintf("Container '%v' is privileged", [container.name])
}Accessing Kubernetes Objects in Rego
# The incoming object being reviewed
input.review.object
# Object metadata
input.review.object.metadata.name
input.review.object.metadata.namespace
input.review.object.metadata.labels
# Pod spec (for Pods)
input.review.object.spec.containers[_]
input.review.object.spec.containers[_].image
input.review.object.spec.containers[_].securityContext
# Parameters from the Constraint
input.parameters
input.parameters.labels
input.parameters.reposCommon Rego Patterns
# Iterate over containers (including init containers)
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
# ... check container ...
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
# ... check init container ...
}
# Check if a value is NOT in a list
violation[{"msg": msg}] {
image := input.review.object.spec.containers[_].image
not startswith(image, "gcr.io/my-company/")
msg := sprintf("Image '%v' is not from allowed registry", [image])
}
# Check string patterns
violation[{"msg": msg}] {
image := input.review.object.spec.containers[_].image
not contains(image, "@sha256:")
msg := sprintf("Image '%v' must use digest, not tag", [image])
}
# Set operations
violation[{"msg": msg}] {
provided := {l | input.review.object.metadata.labels[l]}
required := {l | l := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing labels: %v", [missing])
}Common Policies with Full Examples
Policy 1: Require Labels
ConstraintTemplate:
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
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing])
}Constraint:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
enforcementAction: deny # deny | dryrun | warn
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"] # Apply to Namespaces
parameters:
labels:
- "team"
- "environment"Policy 2: Deny Privileged Containers
ConstraintTemplate:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdenyprivileged
spec:
crd:
spec:
names:
kind: K8sDenyPrivileged
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdenyprivileged
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("Privileged container not allowed: %v", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
container.securityContext.privileged == true
msg := sprintf("Privileged init container not allowed: %v", [container.name])
}Constraint:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDenyPrivileged
metadata:
name: deny-privileged-containers
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system # Exclude system namespacePolicy 3: Enforce Allowed Image Registries
ConstraintTemplate:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not startswith(container.image, _repo)
msg := sprintf(
"Container '%v' image '%v' not from allowed repository. Allowed: %v",
[container.name, container.image, input.parameters.repos]
)
}
# Helper: check if image starts with any allowed repo
startswith(image, _repo) {
repo := input.parameters.repos[_]
startswith(image, repo)
}Constraint:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: allowed-repos
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- production
- staging
parameters:
repos:
- "gcr.io/my-company/"
- "docker.io/library/"Policy 4: Require Non-Root Containers
ConstraintTemplate:
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
not input.review.object.spec.securityContext.runAsNonRoot
msg := sprintf(
"Container '%v' must set runAsNonRoot to true",
[container.name]
)
}Constraint:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireNonRoot
metadata:
name: require-non-root
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-systemEnforcement Actions: Deny vs Audit vs Warn
| Mode | Behavior | Use Case |
|---|---|---|
deny | Blocks non-compliant resources from being created | Production enforcement |
dryrun | Allows resources but records violations in audit | Testing new policies |
warn | Allows resources but returns a warning to the user | Gradual rollout |
Setting Enforcement Action
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-labels-audit
spec:
enforcementAction: dryrun # Change to "deny" when ready
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
labels: ["team"]Checking Audit Results
# View violations found during audit
kubectl get k8srequiredlabels require-labels-audit -o yaml
# The violations appear in status.violations:
# status:
# violations:
# - enforcementAction: dryrun
# kind: Pod
# message: 'Missing required labels: {"team"}'
# name: nginx-pod
# namespace: defaultExam Strategy
If you are unsure about a Rego policy, first deploy it with enforcementAction: dryrun. Verify it catches the right violations using audit results, then switch to deny.
Match Configuration
The match field in Constraints controls which resources the policy applies to:
spec:
match:
kinds:
- apiGroups: [""] # Core API group
kinds: ["Pod", "Service"] # Resource kinds
- apiGroups: ["apps"]
kinds: ["Deployment"]
namespaces: # Only these namespaces
- production
- staging
excludedNamespaces: # Skip these namespaces
- kube-system
- gatekeeper-system
labelSelector: # Only resources with these labels
matchLabels:
environment: production
namespaceSelector: # Only namespaces with these labels
matchLabels:
policy-enforcement: enabledTesting Policies
Test with a Compliant Resource
# Create a pod that meets the policy
kubectl run compliant-pod --image=gcr.io/my-company/app:v1.0 \
--labels="team=backend,environment=prod" --dry-run=server -o yaml
# Should succeedTest with a Non-Compliant Resource
# Create a pod that violates the policy
kubectl run bad-pod --image=dockerhub.com/evil/app:latest --dry-run=server -o yaml
# Error from server (Forbidden): admission webhook "validation.gatekeeper.sh"
# denied the request: [allowed-repos] Container 'bad-pod' image
# 'dockerhub.com/evil/app:latest' not from allowed repositoryCheck Constraint Status
# List all constraints of a specific type
kubectl get k8sallowedrepos
# Check constraint details and violations
kubectl describe k8sallowedrepos allowed-repos
# View all ConstraintTemplates
kubectl get constrainttemplates
# Check if a template compiled successfully
kubectl get constrainttemplate k8sallowedrepos -o yaml | grep -A5 statusDebugging Gatekeeper
# Check Gatekeeper controller logs
kubectl logs -n gatekeeper-system -l control-plane=controller-manager -f
# Check audit controller logs
kubectl logs -n gatekeeper-system -l control-plane=audit-controller -f
# Verify webhook is registered
kubectl get validatingwebhookconfigurations gatekeeper-validating-webhook-configuration -o yaml
# Check if a ConstraintTemplate has errors
kubectl get constrainttemplate <name> -o jsonpath='{.status}'
# Common issues:
# - Rego syntax errors in ConstraintTemplate
# - Wrong CRD kind name in Constraint
# - Missing parameters that the template requires
# - Excluded namespace not matchingCommon Pitfall
If the ConstraintTemplate has a Rego syntax error, the CRD will not be created, and creating Constraints will fail with "no matches for kind" errors. Always check:
kubectl get constrainttemplate <name> -o jsonpath='{.status.created}'
# Should be trueQuick Reference
# List all ConstraintTemplates
kubectl get constrainttemplates
# List all Constraints (across all template types)
kubectl get constraints
# List Constraints of a specific type
kubectl get k8srequiredlabels
# View violations
kubectl get <constraint-kind> <name> -o jsonpath='{.status.violations}'
# Test a resource against policies (dry-run server-side)
kubectl apply -f resource.yaml --dry-run=server
# Gatekeeper system pods
kubectl get pods -n gatekeeper-system
# Gatekeeper webhook configuration
kubectl get validatingwebhookconfigurations | grep gatekeeper