Module 4.2: OPA & Gatekeeper
Toolkit Track | Complexity:
[COMPLEX]| Time: 45-50 minutes
Overview
Section titled “Overview”“Trust but verify” doesn’t work in Kubernetes—you need “deny by default, allow explicitly.” This module covers Open Policy Agent (OPA) and Gatekeeper for policy-as-code admission control, helping you enforce security and compliance requirements on the Kubernetes resources your policies match.
What You’ll Learn:
- OPA and Rego policy language basics
- Gatekeeper architecture and constraints
- Writing effective admission policies
- Policy testing and CI/CD integration
Prerequisites:
- Security Principles Foundations
- Kubernetes admission controllers concept
- Basic programming logic
What You’ll Be Able to Do
Section titled “What You’ll Be Able to Do”After completing this module, you will be able to:
- Deploy OPA Gatekeeper and configure ConstraintTemplates for Kubernetes admission policy enforcement
- Implement Rego policies for pod security, resource limits, label requirements, and image restrictions
- Configure Gatekeeper audit mode for policy violation reporting without blocking existing workloads
- Evaluate OPA Gatekeeper against Kyverno for policy-as-code enforcement complexity and flexibility trade-offs
Why This Module Matters
Section titled “Why This Module Matters”Without admission control, anyone with kubectl create permissions can deploy anything—privileged containers, images from untrusted registries, pods without resource limits. Gatekeeper acts as your cluster’s bouncer, checking every resource against your policies before allowing it in.
💡 Did You Know? Open Policy Agent was created by Styra and donated to CNCF in 2018. It’s now used beyond Kubernetes—in Terraform, Envoy, Kafka, and even CI/CD pipelines. Learning OPA/Rego is an investment that pays off across the entire cloud-native stack.
The Admission Control Problem
Section titled “The Admission Control Problem”WITHOUT POLICY ENFORCEMENT════════════════════════════════════════════════════════════════════
Developer kubectl apply ──▶ API Server ──▶ etcd ──▶ 😱 Running │ │ No checks! │ - Privileged container? ✓ │ - No resource limits? ✓ │ - Image from docker.io? ✓ │ - Root filesystem writable? ✓
═══════════════════════════════════════════════════════════════════
WITH GATEKEEPER════════════════════════════════════════════════════════════════════
Developer kubectl apply ──▶ API Server ──▶ Gatekeeper ──▶ etcd │ │ │ │ Check policies: │ │ - No privileged? ✓ │ │ - Has limits? ✓ │ │ - Allowed registry? ✓ │ │ - Read-only root? ✓ │ │ │ ▼ │ DENY or ALLOW │ │ If denied: └──▶ "Error: container must not be privileged"Open Policy Agent (OPA)
Section titled “Open Policy Agent (OPA)”What is OPA?
Section titled “What is OPA?”OPA is a general-purpose policy engine. You give it:
- Data - JSON representing current state
- Query - What you want to know
- Policy - Rules written in Rego
OPA DECISION FLOW════════════════════════════════════════════════════════════════════
┌───────────────┐│ Policy │ # Written in Rego│ (rules.rego) │└───────┬───────┘ │ ▼┌───────────────┐ ┌───────────────┐│ OPA │◀────│ Input │ # JSON data to evaluate│ Engine │ │ (request) │└───────┬───────┘ └───────────────┘ │ ▼┌───────────────┐│ Decision │ # allow: true/false│ (output) │ # violations: [...]└───────────────┘Rego Language Basics
Section titled “Rego Language Basics”# Rego 101 - The Policy Language
# Package declaration (namespace for rules)package kubernetes.admission
# Import statementsimport future.keywords.inimport future.keywords.containsimport future.keywords.if
# Constantsallowed_registries := ["gcr.io", "registry.example.com"]
# Rules - evaluate to true/false or a set of values
# Simple boolean ruleis_privileged if { input.request.object.spec.containers[_].securityContext.privileged == true}
# Rule with iteration (for each container)violation contains msg if { container := input.request.object.spec.containers[_] not container.resources.limits.cpu msg := sprintf("Container '%v' has no CPU limit", [container.name])}
# Rule with comprehensionall_container_images := [image | container := input.request.object.spec.containers[_] image := container.image]
# Helper functionstarts_with_allowed_registry(image) if { some registry in allowed_registries startswith(image, registry)}Key Rego Concepts
Section titled “Key Rego Concepts”| Concept | Example | Description |
|---|---|---|
| Unification | x := input.name | Assignment with pattern matching |
| Iteration | containers[_] | Iterate over array elements |
| Comprehension | [x | x := arr[_]] | Build arrays/sets from iteration |
| some | some i; arr[i] | Explicit iteration variable |
| contains | set contains msg if {...} | Add to set when condition true |
| default | default allow := false | Default value if rule undefined |
💡 Did You Know? Rego is OPA’s declarative policy language, designed for policy evaluation over structured data.
Gatekeeper
Section titled “Gatekeeper”Architecture
Section titled “Architecture”GATEKEEPER ARCHITECTURE════════════════════════════════════════════════════════════════════
┌─────────────────────────────────────────────────────────────────┐│ Kubernetes Cluster ││ ││ kubectl apply ──▶ API Server ││ │ ││ │ ValidatingAdmissionWebhook ││ ▼ ││ ┌────────────────────────────────────────────────────────────┐ ││ │ GATEKEEPER │ ││ │ │ ││ │ ┌─────────────────┐ ┌─────────────────┐ │ ││ │ │ Constraint │ │ OPA Engine │ │ ││ │ │ Templates │────▶│ │ │ ││ │ │ (Rego policies) │ │ Evaluate │ │ ││ │ └─────────────────┘ │ Request │ │ ││ │ └────────┬────────┘ │ ││ │ ┌─────────────────┐ │ │ ││ │ │ Constraints │──────────────┘ │ ││ │ │ (policy params) │ │ ││ │ └─────────────────┘ │ ││ │ │ ││ └────────────────────────────────────────────────────────────┘ ││ │ ││ ▼ ││ Allow or Deny + Message ││ │└─────────────────────────────────────────────────────────────────┘Key Components
Section titled “Key Components”| Component | Purpose | Example |
|---|---|---|
| ConstraintTemplate | Defines reusable policy logic (Rego) | “Container must have resource limits” |
| Constraint | Instance of template with parameters | ”Apply to namespace ‘prod’, require CPU/memory” |
| Config | Gatekeeper settings | Exempt namespaces, audit interval |
Installation
Section titled “Installation”# Install Gatekeeperkubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.14.0/deploy/gatekeeper.yaml
# Verify installationkubectl get pods -n gatekeeper-system
# Check webhook is registeredkubectl get validatingwebhookconfigurationsWriting Policies
Section titled “Writing Policies”ConstraintTemplate Structure
Section titled “ConstraintTemplate Structure”apiVersion: templates.gatekeeper.sh/v1kind: ConstraintTemplatemetadata: name: k8srequiredlabels # lowercase, no spacesspec: crd: spec: names: kind: K8sRequiredLabels # CamelCase validation: openAPIV3Schema: type: object properties: labels: type: array items: type: string targets: - target: admission.k8s.gatekeeper.sh rego: | package k8srequiredlabels
violation[{"msg": msg}] { # Get provided labels provided := {label | input.review.object.metadata.labels[label]}
# Get required labels required := {label | label := input.parameters.labels[_]}
# Find missing missing := required - provided count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing]) }Constraint Usage
Section titled “Constraint Usage”apiVersion: constraints.gatekeeper.sh/v1beta1kind: K8sRequiredLabelsmetadata: name: require-team-labelspec: match: kinds: - apiGroups: [""] kinds: ["Pod"] - apiGroups: ["apps"] kinds: ["Deployment", "StatefulSet"] namespaces: - "production" excludedNamespaces: - "kube-system" parameters: labels: - "team" - "app"Common Policy Patterns
Section titled “Common Policy Patterns”# 1. Require Container Resource LimitsapiVersion: templates.gatekeeper.sh/v1kind: ConstraintTemplatemetadata: name: k8scontainerlimitsspec: crd: spec: names: kind: K8sContainerLimits targets: - target: admission.k8s.gatekeeper.sh rego: | package k8scontainerlimits
violation[{"msg": msg}] { container := input.review.object.spec.containers[_] not container.resources.limits.cpu msg := sprintf("Container '%v' has no CPU limit", [container.name]) }
violation[{"msg": msg}] { container := input.review.object.spec.containers[_] not container.resources.limits.memory msg := sprintf("Container '%v' has no memory limit", [container.name]) }---# 2. Allowed Container RegistriesapiVersion: templates.gatekeeper.sh/v1kind: ConstraintTemplatemetadata: name: k8sallowedreposspec: 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 image_allowed(container.image) msg := sprintf("Container '%v' uses image '%v' from disallowed registry", [container.name, container.image]) }
image_allowed(image) { repo := input.parameters.repos[_] startswith(image, repo) }---# 3. Block Privileged ContainersapiVersion: templates.gatekeeper.sh/v1kind: ConstraintTemplatemetadata: name: k8sblockprivilegedspec: 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 == true msg := sprintf("Container '%v' must not run as privileged", [container.name]) }
violation[{"msg": msg}] { container := input.review.object.spec.initContainers[_] container.securityContext.privileged == true msg := sprintf("Init container '%v' must not run as privileged", [container.name]) }💡 Did You Know? Gatekeeper includes the Gatekeeper Library, a collection of reusable policies and examples for common Kubernetes security and policy patterns.
Policy Testing
Section titled “Policy Testing”Testing Rego Locally
Section titled “Testing Rego Locally”# Install OPA CLIbrew install opa # or download from https://www.openpolicyagent.org/
# Create test filecat > policy_test.rego << 'EOF'package k8sallowedrepos
test_allowed_registry { image_allowed("gcr.io/my-project/app:v1") with input.parameters.repos as ["gcr.io/"]}
test_disallowed_registry { not image_allowed("docker.io/nginx:latest") with input.parameters.repos as ["gcr.io/"]}EOF
# Run testsopa test . -vGatekeeper Dry-Run Mode
Section titled “Gatekeeper Dry-Run Mode”# Test constraint without enforcingapiVersion: constraints.gatekeeper.sh/v1beta1kind: K8sBlockPrivilegedmetadata: name: block-privileged-dry-runspec: enforcementAction: dryrun # warn or deny for enforcement match: kinds: - apiGroups: [""] kinds: ["Pod"]# Check violations in auditkubectl get k8sblockprivileged block-privileged-dry-run -o yaml
# Look at status.violationsCI/CD Integration
Section titled “CI/CD Integration”name: Test OPA Policieson: [push, pull_request]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Setup OPA uses: open-policy-agent/setup-opa@v2 with: version: latest
- name: Run Rego tests run: opa test policies/ -v
- name: Validate ConstraintTemplates run: | for file in policies/*.yaml; do opa fmt --check "$file" || exit 1 doneAdvanced Patterns
Section titled “Advanced Patterns”External Data
Section titled “External Data”# Sync data from cluster into OPAapiVersion: config.gatekeeper.sh/v1alpha1kind: Configmetadata: name: config namespace: gatekeeper-systemspec: sync: syncOnly: - group: "" version: "v1" kind: "Namespace" - group: "networking.k8s.io" version: "v1" kind: "Ingress"# Use synced data in policypackage k8suniqueingress
violation[{"msg": msg}] { input.review.kind.kind == "Ingress" host := input.review.object.spec.rules[_].host
# Check against all existing ingresses other := data.inventory.namespace[ns][otherapiversion]["Ingress"][name] other.spec.rules[_].host == host
# Not the same ingress other.metadata.name != input.review.object.metadata.name
msg := sprintf("Ingress host '%v' already in use by '%v/%v'", [host, ns, name])}Mutation Policies
Section titled “Mutation Policies”# Gatekeeper also supports mutationapiVersion: mutations.gatekeeper.sh/v1kind: Assignmetadata: name: add-default-securitycontextspec: applyTo: - groups: [""] kinds: ["Pod"] versions: ["v1"] match: scope: Namespaced kinds: - apiGroups: [""] kinds: ["Pod"] location: "spec.securityContext.runAsNonRoot" parameters: assign: value: true💡 Did You Know? Gatekeeper supports mutation alongside validation, so you can enforce settings like
runAsNonRootand also assign defaults when matching resources omit them.
Debugging Policies
Section titled “Debugging Policies”# Check constraint statuskubectl describe k8srequiredlabels require-team-label
# View audit resultskubectl get constraint -o json | jq '.items[].status'
# Check Gatekeeper logskubectl logs -n gatekeeper-system -l control-plane=controller-manager
# Test policy with specific inputopa eval --data policy.rego --input input.json "data.k8srequiredlabels.violation"Common Mistakes
Section titled “Common Mistakes”| Mistake | Problem | Solution |
|---|---|---|
| Policy blocks kube-system | Core components can’t deploy | Exclude kube-system in constraints |
| No dry-run testing | Breaking changes hit production | Use enforcementAction: dryrun first |
| Overly strict policies | Developers can’t work | Start permissive, tighten gradually |
| Complex Rego with no tests | Policy bugs in production | Write unit tests for every policy |
| Forgetting init containers | Security holes in init phase | Always check both containers and initContainers |
| Blocking all during rollout | Existing pods fail validation | Gatekeeper only checks new/updated resources |
War Story: The Policy That Cried Wolf
Section titled “War Story: The Policy That Cried Wolf”A platform team that deploys strict container registry policies without auditing current image usage can quickly trigger a large wave of exception requests.
What went wrong: They blocked everything except gcr.io/company-project/, but:
- Some Helm charts may pull images from registries outside your initial allowlist.
- Operational add-ons may depend on images from registries outside your initial allowlist.
- Monitoring components may also depend on registries you did not initially account for.
The fix:
- Audit existing images BEFORE deploying policies
- Start with
dryrunto identify violations - Build allowlist from actual usage, not assumptions
- Communicate changes with 2-week notice
# Audit existing imageskubectl get pods -A -o jsonpath='{.items[*].spec.containers[*].image}' | tr ' ' '\n' | sort -uQuestion 1
Section titled “Question 1”What’s the difference between a ConstraintTemplate and a Constraint?
Show Answer
ConstraintTemplate: Defines the policy LOGIC in Rego. It’s like a class definition or function.
Constraint: Instance of a template with specific PARAMETERS. It’s like calling a function with arguments.
Example:
- ConstraintTemplate: “Check if labels exist”
- Constraint: “Require labels
teamandenvon Pods in namespaceproduction”
One ConstraintTemplate can have many Constraints with different parameters.
Question 2
Section titled “Question 2”How do you test a policy without blocking deployments?
Show Answer
Use enforcementAction: dryrun in the Constraint:
spec: enforcementAction: dryrun # Options: deny, dryrun, warndeny: Block violations (default)dryrun: Record violations but don’t blockwarn: Show warning but allow
Check violations via: kubectl get constraint <name> -o yaml
Question 3
Section titled “Question 3”Why might a policy work for Pods but miss deployments?
Show Answer
Gatekeeper evaluates the exact resource submitted. When you kubectl apply a Deployment, Gatekeeper sees the Deployment—not the Pods it will create.
Solutions:
- Match on
Pod- catches pods when created by controllers - Match on
DeploymentAND check.spec.template.spec.containers - Use the pod-specific path in Rego:
# For Deploymentscontainers := input.review.object.spec.template.spec.containers# For Podscontainers := input.review.object.spec.containers
Hands-On Exercise
Section titled “Hands-On Exercise”Objective
Section titled “Objective”Create and deploy policies to enforce container security standards.
Environment Setup
Section titled “Environment Setup”# Install Gatekeeperkubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.14.0/deploy/gatekeeper.yaml
# Wait for itkubectl wait --for=condition=ready pod -l control-plane=controller-manager -n gatekeeper-system --timeout=90s-
Create ConstraintTemplate for allowed registries (provided above)
-
Deploy Constraint allowing only
gcr.io/andghcr.io/:apiVersion: constraints.gatekeeper.sh/v1beta1kind: K8sAllowedReposmetadata:name: allowed-reposspec:match:kinds:- apiGroups: [""]kinds: ["Pod"]parameters:repos:- "gcr.io/"- "ghcr.io/" -
Test policy by deploying a violating pod:
Terminal window kubectl run nginx --image=nginx:latest# Should be denied -
Test compliance with allowed registry:
Terminal window kubectl run allowed --image=gcr.io/google-containers/pause:3.2# Should succeed -
Check audit results:
Terminal window kubectl get k8sallowedrepos allowed-repos -o yaml
Success Criteria
Section titled “Success Criteria”- Gatekeeper controller running
- ConstraintTemplate created successfully
- Constraint shows
0totalViolations initially -
nginx:latestdeployment blocked with clear error message -
gcr.io/image allowed - Audit shows violation details for blocked attempt
Bonus Challenge
Section titled “Bonus Challenge”Add a second constraint that requires all pods to have a team label.
Further Reading
Section titled “Further Reading”- OPA Documentation
- Gatekeeper Documentation
- Gatekeeper Policy Library
- Rego Playground - Test policies online
Next Module
Section titled “Next Module”Continue to Module 4.3: Falco to learn runtime security monitoring for detecting threats in running containers.
“Security is not a feature you add—it’s a constraint you enforce. Gatekeeper makes that constraint automatic.”
Sources
Section titled “Sources”- cncf.io: open policy agent opa — The CNCF project page gives the acceptance date for OPA directly.
- raw.githubusercontent.com: README.md — The upstream README states that OPA is a general-purpose policy engine and explains its query-based evaluation model.
- Kubernetes Admission Controllers — Explains where admission control fits in the API request path and why it matters for policy enforcement.
- Gatekeeper Repository — Provides the upstream overview of Gatekeeper features such as constraints, mutation support, audit, and policy library integration.
- OPA Repository — Provides the upstream overview of OPA as a general-purpose policy engine and links to its core integrations and language resources.