Skip to content

Open Policy Agent (OPA) is a general-purpose policy engine. Gatekeeper is its Kubernetes admission controller integration — it enforces policies as code using Rego, blocking non-compliant resources before they're created.

1. OPA vs Gatekeeper vs Kyverno

Policy enforcement options for Kubernetes
ToolScopePolicy LanguageBest for
OPA standaloneAny system (HTTP API)RegoMicroservice authz, API gateways, CI/CD gates
OPA GatekeeperKubernetes admissionRego + K8s CRDsEnterprise K8s with complex policies
KyvernoKubernetes admissionYAML (no Rego)Teams who prefer YAML over a new language
# Rule of thumb:
# Gatekeeper: you already use OPA elsewhere, or need Rego's expressiveness
# Kyverno: YAML-native, simpler policies, no Rego learning curve
# Both enforce on: CREATE/UPDATE/DELETE via K8s admission webhooks

# How Gatekeeper works:
# 1. You define ConstraintTemplate (the Rego policy logic)
# 2. You define Constraint (instances of the template with parameters)
# 3. When a resource is created/updated, Gatekeeper's webhook evaluates the policy
# 4. DENY = resource is rejected with your custom error message

2. OPA Rego Basics

Policy language fundamentals — rules, references, and data
# Rego is a declarative query language
# A policy DENIES when a 'deny' rule is true

package kubernetes.admission

# Import request data (the K8s resource being admitted):
# input.request.object = the resource being created/updated
# input.request.operation = CREATE / UPDATE / DELETE

# Simple rule: deny pods without resource limits
deny[msg] {
    input.request.kind.kind == "Pod"
    container := input.request.object.spec.containers[_]    # _ = any element
    not container.resources.limits                          # no limits defined
    msg := sprintf("Container '%s' has no resource limits", [container.name])
}

# Check a specific field:
deny[msg] {
    input.request.kind.kind == "Pod"
    container := input.request.object.spec.containers[_]
    container.image == "latest"   # exact match
    msg := sprintf("Container '%s' uses 'latest' tag — pin to a specific version", [container.name])
}

# Check with regex:
import future.keywords.if

deny[msg] if {
    input.request.kind.kind == "Pod"
    container := input.request.object.spec.containers[_]
    endswith(container.image, ":latest")
    msg := sprintf("Pin image tag for container '%s'", [container.name])
}

# Test Rego in the playground:
# https://play.openpolicyagent.org/

# Test locally with OPA CLI:
opa eval --data policy.rego --input input.json "data.kubernetes.admission.deny"
opa test ./policy_test.rego -v              # run Rego unit tests

3. Gatekeeper ConstraintTemplate

Define reusable policy templates with parameters
# ConstraintTemplate: defines the policy with Rego + the CRD schema
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: requiredlabels                  # lowercase, no underscores
spec:
  crd:
    spec:
      names:
        kind: RequiredLabels            # CRD kind — used in Constraint below
      validation:
        openAPIV3Schema:
          type: object
          properties:
            labels:
              type: array
              items: {type: string}     # parameter: list of required label keys
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package requiredlabels

        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])
        }

# Note: Gatekeeper uses 'violation' not 'deny', and 'input.review' not 'input.request'

4. Gatekeeper Constraints & Audit

Deploy policies, check violations, audit existing resources
# Install Gatekeeper:
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.15/deploy/gatekeeper.yaml

# Constraint: instance of a ConstraintTemplate with specific parameters
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: RequiredLabels                    # must match ConstraintTemplate spec.crd.spec.names.kind
metadata:
  name: require-team-label
spec:
  match:
    kinds:
      - apiGroups: ["*"]
        kinds: ["Namespace"]
    excludedNamespaces:
      - kube-system
      - gatekeeper-system
      - istio-system
  parameters:
    labels: ["team", "environment"]    # must have both labels

# Check for violations:
kubectl get constraint -A              # list all constraints
kubectl describe requiredlabels require-team-label
# Look at: Status.Violations — lists existing resources that violate the policy

# Audit mode (gatekeeper audits ALL existing resources on a schedule):
kubectl get constraint require-team-label -o jsonpath='{.status.violations}' | jq .
# Shows existing violations — useful before enforcing a new policy

# Dry-run mode (monitor without blocking):
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: RequiredLabels
spec:
  enforcementAction: dryrun            # warn in status.violations but don't block
  # enforcementAction: deny           # block the request (default)
  # enforcementAction: warn           # allow but add warning to API response

# Common built-in policies (Gatekeeper Policy Library):
# https://github.com/open-policy-agent/gatekeeper-library
# includes: no-privileged-containers, required-labels, allowed-repos, block-nodeport, etc.

5. OPA Standalone (HTTP API)

Policy decisions for microservices and API gateways
# OPA as an authorization sidecar:
# Your service calls OPA's HTTP API, OPA returns allow/deny

# Start OPA server:
opa run --server --addr=:8181 --bundle ./policies/

# Policy (policies/authz.rego):
package authz

default allow = false

allow {
    input.method == "GET"
    input.path == ["api", "public"]
}

allow {
    input.method == "GET"
    input.path[0] == "api"
    input.user.role == "admin"
}

# Query OPA from your service:
curl -X POST http://localhost:8181/v1/data/authz/allow   -H "Content-Type: application/json"   -d '{"input": {"method": "GET", "path": ["api", "users"], "user": {"role": "admin"}}}'
# Returns: {"result": true}

# Bundle (OPA loads from remote URL, polling for updates):
opa run --server   --bundle https://s3.amazonaws.com/my-policies/bundle.tar.gz
# OPA polls the bundle URL and automatically loads new policies

# OPA in Envoy (external authorization filter):
# Configure Envoy ExtAuthz to call OPA for every request
# OPA returns allow/deny at the proxy level — no app code changes needed

6. Testing & CI Integration

Rego unit tests and conftest for CI gates
# Rego unit tests (policy_test.rego):
package requiredlabels_test

import data.requiredlabels.violation

test_missing_labels {
    violation[{"msg": _}] with input as {
        "review": {"object": {
            "metadata": {"labels": {"team": "backend"}}
        }},
        "parameters": {"labels": ["team", "environment"]}
    }
}

test_all_labels_present {
    count(violation) == 0 with input as {
        "review": {"object": {
            "metadata": {"labels": {"team": "backend", "environment": "prod"}}
        }},
        "parameters": {"labels": ["team", "environment"]}
    }
}

# Run tests:
opa test ./policies/ -v

# conftest — OPA policy testing for CI (validates Kubernetes YAML, Terraform, Dockerfiles):
# Install: brew install conftest

# policies/deny_latest.rego:
package main
deny[msg] {
    input.spec.template.spec.containers[_].image == "latest"
    msg := "Do not use the 'latest' image tag"
}

# Run against your manifests in CI:
conftest test deployment.yaml                  # runs all policies in ./policy/
conftest test deployment.yaml --policy custom/ # specific policy dir
# exit code 1 if any deny rule matches — blocks CI pipeline

Track OPA, Gatekeeper, and Kubernetes policy tool releases. ReleaseRun monitors Kubernetes, Docker, and 13+ DevOps technologies.

Related: Kubernetes RBAC Reference | Kubernetes YAML Reference | Vault Reference

🔍 Free tool: K8s YAML Security Linter — check the K8s manifests OPA Gatekeeper constrains for 12 security misconfigurations before they reach the cluster.