Skip to content

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

  1. ConstraintTemplate -- Defines the policy logic using Rego and creates a new CRD
  2. Constraint -- An instance of the ConstraintTemplate CRD that specifies where and how the policy applies
  3. Gatekeeper Controller -- Evaluates incoming requests against matching Constraints
  4. Decision -- Allow or deny with violation messages

Installing Gatekeeper

bash
# 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-configuration

Exam Note

On the CKS exam, Gatekeeper may already be installed. Check with:

bash
kubectl get crd | grep gatekeeper
kubectl get pods -n gatekeeper-system

ConstraintTemplates

A ConstraintTemplate defines:

  1. The Rego policy logic
  2. The schema for parameters the Constraint can pass
  3. A new CRD that Constraints instantiate

Template Structure

yaml
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

txt
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

txt
# 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.repos

Common Rego Patterns

txt
# 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:

yaml
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:

yaml
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:

yaml
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:

yaml
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 namespace

Policy 3: Enforce Allowed Image Registries

ConstraintTemplate:

yaml
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:

yaml
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:

yaml
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:

yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireNonRoot
metadata:
  name: require-non-root
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    excludedNamespaces:
    - kube-system

Enforcement Actions: Deny vs Audit vs Warn

ModeBehaviorUse Case
denyBlocks non-compliant resources from being createdProduction enforcement
dryrunAllows resources but records violations in auditTesting new policies
warnAllows resources but returns a warning to the userGradual rollout

Setting Enforcement Action

yaml
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

bash
# 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: default

Exam 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:

yaml
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: enabled

Testing Policies

Test with a Compliant Resource

bash
# 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 succeed

Test with a Non-Compliant Resource

bash
# 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 repository

Check Constraint Status

bash
# 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 status

Debugging Gatekeeper

bash
# 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 matching

Common 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:

bash
kubectl get constrainttemplate <name> -o jsonpath='{.status.created}'
# Should be true

Quick Reference

bash
# 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

Released under the MIT License.