Safeguard
Container Security

How to set up OPA Gatekeeper for Kubernetes admission con...

A step-by-step guide to OPA Gatekeeper Kubernetes admission control: install, write constraint templates, roll out safely, and verify enforcement.

Karan Patel
Cloud Security Engineer
7 min read

Admission control is the last checkpoint before a workload lands in your cluster, and for most teams it's wide open. A developer applies a manifest with a privileged container, a latest tag, or no resource limits, and Kubernetes happily creates it. Retrofitting security after the fact means chasing down misconfigurations that should never have been scheduled in the first place. This is the gap OPA Gatekeeper Kubernetes admission control closes: it intercepts every create and update request at the API server and rejects (or audits) anything that violates your organization's policy.

In this guide you'll install Gatekeeper, write and deploy your first constraint template, enforce a real-world policy (blocking privileged containers and requiring resource limits), verify it's working, and understand how to roll changes out safely with audit mode before you flip to hard enforcement. By the end you'll have a working policy-as-code Kubernetes pipeline you can extend to cover image provenance, registry allowlists, and label standards.

Step 1: Understand How OPA Gatekeeper Kubernetes Admission Control Works

Gatekeeper is a Kubernetes-native policy controller built on top of Open Policy Agent (OPA), the general-purpose policy engine used across cloud-native infrastructure. Rather than embedding raw Rego policy directly into your cluster, Gatekeeper wraps OPA in Kubernetes CRDs — ConstraintTemplate and Constraint — and registers a ValidatingAdmissionWebhook (and optionally a MutatingAdmissionWebhook) that the API server calls on every relevant request.

The flow looks like this:

  1. A user or CI pipeline runs kubectl apply.
  2. The API server sends the object to Gatekeeper's webhook before persisting it to etcd.
  3. Gatekeeper evaluates the object against every active Constraint.
  4. If any Constraint's Rego logic returns a violation, the request is denied (or logged, depending on enforcement mode).

This is the core mental model for anyone working through an open policy agent kubernetes tutorial: OPA provides the policy evaluation engine, Gatekeeper provides the Kubernetes integration, CRDs, and audit tooling on top of it.

Step 2: Install Gatekeeper in Your Cluster

Install the latest stable release directly from the official manifest. Use Helm if you already manage cluster add-ons that way, since it makes upgrades and configuration overrides easier to track.

# Direct manifest install
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.16/deploy/gatekeeper.yaml

# Or via Helm
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper/gatekeeper --name-template=gatekeeper \
  --namespace gatekeeper-system --create-namespace

Confirm the controller and audit pods are running before moving on:

kubectl get pods -n gatekeeper-system

You should see gatekeeper-controller-manager and gatekeeper-audit pods in a Running state. The controller pods handle the admission webhook calls; the audit pod periodically scans existing cluster objects against your constraints so you can catch violations that predate a policy, not just new ones.

Step 3: Write a Gatekeeper Constraint Template

Gatekeeper constraint templates define reusable policy logic in Rego, then expose a schema so the policy can be parameterized without touching code again. Here's a template that blocks 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}] {
          c := input.review.object.spec.containers[_]
          c.securityContext.privileged
          msg := sprintf("Privileged container is not allowed: %v", [c.name])
        }

Apply it:

kubectl apply -f block-privileged-template.yaml

This step is the heart of policy as code kubernetes practice: the Rego logic lives in version control, gets reviewed like any other code change, and is deployed the same way as the rest of your infrastructure.

Step 4: Create a Constraint from the Template

The ConstraintTemplate defines the logic; a Constraint activates it against a specific scope. This separation is what lets platform teams write one template and let different teams apply it with different parameters or namespace scopes.

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockPrivileged
metadata:
  name: block-privileged-containers
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces:
      - "kube-system"
      - "gatekeeper-system"
  enforcementAction: dryrun

Apply it with kubectl apply -f block-privileged-constraint.yaml. Note enforcementAction: dryrun — this is deliberate and covered next.

Step 5: Roll Out in Dry-Run / Audit Mode First

Never flip a new constraint straight to deny in a live cluster. Start with dryrun (or omit enforcementAction, which defaults to deny — so set it explicitly) so Gatekeeper logs violations without blocking anything. Check what would have been rejected:

kubectl get k8sblockprivileged block-privileged-containers -o yaml

Look at status.violations in the output. This shows every existing object in scope that fails the policy, which is exactly what you want to see before enforcement goes live — it tells you how much remediation work is ahead of you and prevents an outage caused by a policy you just wrote.

Step 6: Enforce and Add a Resource-Limits Policy

Once the violation list is empty (or accepted and remediated), switch enforcement on:

kubectl patch k8sblockprivileged block-privileged-containers \
  --type=merge -p '{"spec":{"enforcementAction":"deny"}}'

Layer in a second, equally common policy — requiring CPU/memory limits — using the same template/constraint pattern:

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

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

Most teams end up maintaining a small library of these templates for privileged mode, host networking, image registries, runAsNonRoot, and required labels — this is where community template libraries like gatekeeper-library save significant time over writing every policy from scratch.

Step 7: Verify and Test Your Policies

Confirm enforcement is actually working by attempting to deploy a violating pod:

kubectl run test-privileged --image=nginx --overrides='
{
  "spec": {
    "containers": [{
      "name": "test-privileged",
      "image": "nginx",
      "securityContext": { "privileged": true }
    }]
  }
}' --dry-run=server

With enforcementAction: deny, this should fail with an admission webhook error referencing your constraint's message. If it doesn't:

  • Check the webhook is registered: kubectl get validatingwebhookconfigurations gatekeeper-validating-webhook-configuration.
  • Check namespace exclusions: Gatekeeper skips kube-system and its own namespace by default; confirm your test namespace isn't accidentally excluded.
  • Check the ConstraintTemplate compiled: run kubectl describe constrainttemplate k8sblockprivileged and look for Created: true in the status; Rego syntax errors surface here.
  • Check constraint status for stale sync: kubectl get constraints across all kinds shows enforcementAction and whether the constraint status is populated — an empty status usually means the audit controller hasn't run yet.
  • Webhook timeouts: if the API server logs show admission timeouts under load, increase failurePolicy handling and webhook timeout settings in the Gatekeeper deployment, and check controller pod resource limits.

How Safeguard Helps

Gatekeeper gives you the enforcement mechanism, but writing, testing, and maintaining a comprehensive constraint library across every cluster is an ongoing engineering effort — and gaps in coverage are exactly where supply chain risk slips through admission control. Safeguard extends this foundation by continuously scanning your Kubernetes manifests, Helm charts, and CI/CD pipelines for the misconfigurations Gatekeeper policies are meant to catch, so you can validate policy-as-code Kubernetes coverage before it ever reaches a live admission webhook.

Safeguard maps findings back to the specific constraint templates that should be catching them, flags clusters where enforcement is still stuck in dry-run, and correlates admission control gaps with the artifacts and images moving through your software supply chain — from build to registry to runtime. Instead of treating Gatekeeper as a standalone control, Safeguard gives security and platform teams a single view of policy drift, unenforced constraints, and the upstream risks (vulnerable base images, unsigned artifacts, risky registries) that admission control alone won't stop. That combination turns Gatekeeper from a point solution into part of a verifiable, end-to-end supply chain security posture.

Never miss an update

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