Kubernetes Security

OPA Gatekeeper for Kubernetes: Writing Policies That Actually Work

Gatekeeper brings OPA's policy engine to Kubernetes. The learning curve is steep but the flexibility is unmatched. Here is how to write, test, and deploy Rego policies that enforce real security.

Michael
Application Security Engineer
6 min read

Open Policy Agent (OPA) is a general-purpose policy engine. Gatekeeper is the Kubernetes-native integration that turns OPA into an admission controller. Together, they provide the most flexible policy enforcement system available for Kubernetes.

That flexibility comes at a cost: Rego, OPA's policy language, is unlike anything most developers or security engineers have written before. Teams that adopt Gatekeeper without investing in Rego competency end up with policies that are either too permissive (missing edge cases) or too restrictive (blocking legitimate workloads and getting disabled).

This guide covers how to write Rego policies that are correct, testable, and maintainable.

Gatekeeper Architecture

Gatekeeper consists of three components:

  1. ConstraintTemplate: Defines the policy logic in Rego
  2. Constraint: Instantiates a template with specific parameters
  3. Webhook: Intercepts API requests and evaluates them against constraints
kubectl apply → API Server → Gatekeeper Webhook → Rego Evaluation → Allow/Deny

Installation

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.14.0/deploy/gatekeeper.yaml

Verify:

kubectl get pods -n gatekeeper-system
# NAME                                          READY   STATUS
# gatekeeper-audit-7c6f6d8b9c-xxxxx           1/1     Running
# gatekeeper-controller-manager-xxx-yyy        1/1     Running

Rego Fundamentals for Security Engineers

Rego is a declarative language optimized for querying structured data (JSON/YAML). The key concepts:

Rules Are Queries, Not If-Else

# This is NOT: if container.image starts with "docker.io" then violate
# This IS: find all containers where the image starts with "docker.io"
violation[{"msg": msg}] {
    container := input.review.object.spec.containers[_]
    startswith(container.image, "docker.io/")
    msg := sprintf("Container '%v' uses Docker Hub image '%v'", [container.name, container.image])
}

The [_] iterates over all containers. If any container matches the condition, a violation is generated.

Input Structure

Gatekeeper provides the Kubernetes admission request as input.review:

# The pod spec
input.review.object.spec.containers[_]

# The pod metadata
input.review.object.metadata.labels

# The namespace
input.review.object.metadata.namespace

# The request user
input.review.userInfo.username

# Parameters from the Constraint resource
input.parameters

Common Patterns

Check all containers (including init and ephemeral):

all_containers[container] {
    container := input.review.object.spec.containers[_]
}
all_containers[container] {
    container := input.review.object.spec.initContainers[_]
}
all_containers[container] {
    container := input.review.object.spec.ephemeralContainers[_]
}

Negate a condition (does NOT have a label):

violation[{"msg": msg}] {
    not input.review.object.metadata.labels["app.kubernetes.io/name"]
    msg := "Pod must have the 'app.kubernetes.io/name' label"
}

Essential Security Policies

1. Require Non-Root Containers

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
        msg := sprintf("Container '%v' must set securityContext.runAsNonRoot=true", [container.name])
      }

      violation[{"msg": msg}] {
        container := input.review.object.spec.containers[_]
        container.securityContext.runAsUser == 0
        msg := sprintf("Container '%v' must not run as UID 0", [container.name])
      }

      violation[{"msg": msg}] {
        container := input.review.object.spec.initContainers[_]
        not container.securityContext.runAsNonRoot
        msg := sprintf("Init container '%v' must set securityContext.runAsNonRoot=true", [container.name])
      }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireNonRoot
metadata:
  name: require-non-root
spec:
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    excludedNamespaces:
    - kube-system
    - gatekeeper-system
  enforcementAction: deny

2. Block Privileged Containers

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sblockprivileged
spec:
  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
        msg := sprintf("Privileged container '%v' is not allowed", [container.name])
      }

      violation[{"msg": msg}] {
        container := input.review.object.spec.containers[_]
        container.securityContext.allowPrivilegeEscalation
        msg := sprintf("Privilege escalation in container '%v' is not allowed", [container.name])
      }

3. Enforce Resource Limits

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequireresources
spec:
  crd:
    spec:
      names:
        kind: K8sRequireResources
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package k8srequireresources

      violation[{"msg": msg}] {
        container := input.review.object.spec.containers[_]
        not container.resources.limits.memory
        msg := sprintf("Container '%v' must set memory limits", [container.name])
      }

      violation[{"msg": msg}] {
        container := input.review.object.spec.containers[_]
        not container.resources.limits.cpu
        msg := sprintf("Container '%v' must set CPU limits", [container.name])
      }

      violation[{"msg": msg}] {
        container := input.review.object.spec.containers[_]
        not container.resources.requests.memory
        msg := sprintf("Container '%v' must set memory requests", [container.name])
      }

4. Restrict Host Path Volumes

targets:
- target: admission.k8s.gatekeeper.sh
  rego: |
    package k8srestricthostpath

    violation[{"msg": msg}] {
      volume := input.review.object.spec.volumes[_]
      volume.hostPath
      not allowed_path(volume.hostPath.path)
      msg := sprintf("HostPath volume '%v' mounting '%v' is not allowed", [volume.name, volume.hostPath.path])
    }

    allowed_path(path) {
      allowedPaths := input.parameters.allowedPaths
      startswith(path, allowedPaths[_])
    }

Testing Rego Policies

Untested policies break production. Rego has built-in testing support.

Unit Tests

# policy_test.rego
package k8srequirenonroot

test_deny_root_container {
    input := {"review": {"object": {"spec": {"containers": [
        {"name": "web", "securityContext": {"runAsUser": 0}}
    ]}}}}
    count(violation) > 0
}

test_allow_nonroot_container {
    input := {"review": {"object": {"spec": {"containers": [
        {"name": "web", "securityContext": {"runAsNonRoot": true, "runAsUser": 1000}}
    ]}}}}
    count(violation) == 0
}

test_deny_missing_security_context {
    input := {"review": {"object": {"spec": {"containers": [
        {"name": "web"}
    ]}}}}
    count(violation) > 0
}

Run tests:

opa test . -v
# PASS: 3/3

Integration Testing with Gator

Gator is Gatekeeper's CLI testing tool:

# Test a constraint against a resource
gator test -f constraint.yaml -f template.yaml -f test-pod.yaml

Create test fixtures:

# test-pod-should-fail.yaml
apiVersion: v1
kind: Pod
metadata:
  name: bad-pod
spec:
  containers:
  - name: web
    image: nginx
    securityContext:
      runAsUser: 0

Gatekeeper Audit Mode

Before enforcing, audit existing resources:

spec:
  enforcementAction: dryrun  # Log violations without blocking

Check audit results:

kubectl get k8srequirenonroot require-non-root -o json | \
  jq '.status.violations'

This shows every existing resource that would fail the policy. Fix them before switching to deny.

Performance Considerations

Gatekeeper adds latency to every API request. At scale:

  • Keep Rego policies simple. Complex logic adds evaluation time.
  • Use match selectors to limit which resources are evaluated.
  • Run the audit controller on a schedule, not continuously.
  • Monitor webhook latency: kubectl get --raw /metrics | grep gatekeeper

If webhook latency exceeds 5 seconds, Kubernetes may timeout the request and allow it through, bypassing your policies entirely. Configure failurePolicy: Fail on the webhook to deny requests when Gatekeeper is unavailable, but be aware this can block all deployments if Gatekeeper goes down.

Gatekeeper Policy Library

The OPA Gatekeeper Library provides pre-built policies for common requirements:

# Clone the library
git clone https://github.com/open-policy-agent/gatekeeper-library

# Apply a policy
kubectl apply -f gatekeeper-library/library/pod-security-policy/privileged-containers/

Available policies include:

  • Container limits
  • Allowed registries
  • Required labels
  • Host networking restrictions
  • Read-only root filesystem
  • External IPs

Start with the library. Customize only when your requirements diverge from the standard patterns.

How Safeguard.sh Helps

Safeguard.sh provides visibility into your Gatekeeper policy coverage and enforcement status. The platform identifies gaps where namespaces lack policy coverage, detects overly permissive constraints that allow too many exceptions, and monitors audit violations over time to track compliance trends. For teams adopting Gatekeeper, Safeguard.sh offers policy recommendations based on your cluster configuration and maps Gatekeeper policies to compliance frameworks like CIS Kubernetes Benchmark and NIST 800-190.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.