Skip to content
Back to blog OPA Gatekeeper: Policy as Code for Kubernetes

OPA Gatekeeper: Policy as Code for Kubernetes

SecurityK8s

OPA Gatekeeper: Policy as Code for Kubernetes

Kubernetes lets you deploy anything. That’s powerful - and dangerous. OPA Gatekeeper acts as a policy checkpoint, validating resources before they’re admitted to the cluster.

This guide covers Gatekeeper installation, constraint templates, and practical policies for production clusters.

TL;DR

  • OPA = Open Policy Agent, general-purpose policy engine
  • Gatekeeper = OPA integration for Kubernetes admission control
  • Constraint Templates = reusable policy definitions (Rego)
  • Constraints = instantiated policies applied to resources
  • Full examples for security, compliance, and best practices

What is OPA Gatekeeper?

Gatekeeper runs as a validating admission webhook. Every create, update, or delete request passes through it for policy evaluation.

┌──────────┐     ┌──────────────┐     ┌────────────────┐     ┌──────────┐
│  kubectl │────▶│  API Server  │────▶│   Gatekeeper   │────▶│   etcd   │
│  apply   │     │              │     │  (Admission)   │     │          │
└──────────┘     └──────────────┘     └────────────────┘     └──────────┘


                                      ┌──────────────┐
                                      │  Constraint  │
                                      │  Evaluation  │
                                      │    (Rego)    │
                                      └──────────────┘

                                        ┌─────┴─────┐
                                        ▼           ▼
                                    ALLOWED     DENIED
                                              (with reason)

OPA vs Gatekeeper

COMPONENT       PURPOSE                         LANGUAGE
=========       =======                         ========
OPA             General policy engine           Rego
Gatekeeper      K8s admission controller        Rego (via templates)
Conftest        CLI policy testing              Rego

Install Gatekeeper

# Using Helm
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm upgrade --install gatekeeper gatekeeper/gatekeeper \
  --namespace gatekeeper-system --create-namespace \
  --set replicas=3 \
  --set audit.replicas=1

# Verify
kubectl get pods -n gatekeeper-system

Helm Values for Production

# gatekeeper-values.yaml
replicas: 3

audit:
  replicas: 1
  # How often to audit existing resources
  auditInterval: 60
  # Maximum violations to report per constraint
  constraintViolationsLimit: 20

# Resource limits
resources:
  requests:
    cpu: 100m
    memory: 256Mi
  limits:
    cpu: 1000m
    memory: 512Mi

# Exempt namespaces from all policies
exemptNamespaces:
  - kube-system
  - gatekeeper-system

# Emit events for violations
emitAdmissionEvents: true
emitAuditEvents: true

# Mutation support (optional)
mutatingWebhook: enabled

Constraint Templates and Constraints

Gatekeeper uses two resources:

  1. ConstraintTemplate: Defines the policy logic (Rego)
  2. Constraint: Applies the template with specific parameters

Example: Required Labels

# template-required-labels.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, "details": {"missing_labels": missing}}] {
          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-required-labels.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-team-label
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
      - apiGroups: ["apps"]
        kinds: ["Deployment", "StatefulSet", "DaemonSet"]
    namespaceSelector:
      matchExpressions:
        - key: gatekeeper.sh/exempt
          operator: DoesNotExist
  parameters:
    labels:
      - "team"
      - "app"

Security Policies

Block Privileged Containers

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

        violation[{"msg": msg}] {
          c := input_containers[_]
          c.securityContext.privileged == true
          msg := sprintf("Privileged container not allowed: %v", [c.name])
        }

        input_containers[c] {
          c := input.review.object.spec.containers[_]
        }

        input_containers[c] {
          c := input.review.object.spec.initContainers[_]
        }

        input_containers[c] {
          c := input.review.object.spec.ephemeralContainers[_]
        }

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

Block Host Namespace

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

        violation[{"msg": msg}] {
          input.review.object.spec.hostNetwork == true
          msg := "hostNetwork is not allowed"
        }

        violation[{"msg": msg}] {
          input.review.object.spec.hostPID == true
          msg := "hostPID is not allowed"
        }

        violation[{"msg": msg}] {
          input.review.object.spec.hostIPC == true
          msg := "hostIPC is not allowed"
        }

---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPHostNamespace
metadata:
  name: block-host-namespace
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces:
      - kube-system

Read-Only Root Filesystem

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

        violation[{"msg": msg}] {
          c := input_containers[_]
          not c.securityContext.readOnlyRootFilesystem == true
          msg := sprintf("Container %v must have readOnlyRootFilesystem: true", [c.name])
        }

        input_containers[c] {
          c := input.review.object.spec.containers[_]
        }

        input_containers[c] {
          c := input.review.object.spec.initContainers[_]
        }

---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sReadOnlyRootFilesystem
metadata:
  name: require-readonly-root
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces:
      - kube-system

Resource Policies

Require Resource Limits

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sresourcelimits
spec:
  crd:
    spec:
      names:
        kind: K8sResourceLimits
      validation:
        openAPIV3Schema:
          type: object
          properties:
            cpu:
              type: string
            memory:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sresourcelimits

        violation[{"msg": msg}] {
          c := input_containers[_]
          not c.resources.limits.cpu
          msg := sprintf("Container %v must have CPU limits", [c.name])
        }

        violation[{"msg": msg}] {
          c := input_containers[_]
          not c.resources.limits.memory
          msg := sprintf("Container %v must have memory limits", [c.name])
        }

        violation[{"msg": msg}] {
          c := input_containers[_]
          not c.resources.requests.cpu
          msg := sprintf("Container %v must have CPU requests", [c.name])
        }

        violation[{"msg": msg}] {
          c := input_containers[_]
          not c.resources.requests.memory
          msg := sprintf("Container %v must have memory requests", [c.name])
        }

        input_containers[c] {
          c := input.review.object.spec.containers[_]
        }

---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sResourceLimits
metadata:
  name: require-limits
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces:
      - kube-system
      - monitoring

Block Latest Tag

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sdisallowedtags
spec:
  crd:
    spec:
      names:
        kind: K8sDisallowedTags
      validation:
        openAPIV3Schema:
          type: object
          properties:
            tags:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sdisallowedtags

        violation[{"msg": msg}] {
          c := input_containers[_]
          tag := get_tag(c.image)
          tag == input.parameters.tags[_]
          msg := sprintf("Container %v uses disallowed tag: %v", [c.name, tag])
        }

        violation[{"msg": msg}] {
          c := input_containers[_]
          not contains(c.image, ":")
          msg := sprintf("Container %v has no tag (implies latest)", [c.name])
        }

        get_tag(image) = tag {
          contains(image, ":")
          parts := split(image, ":")
          tag := parts[count(parts) - 1]
        }

        input_containers[c] {
          c := input.review.object.spec.containers[_]
        }

        input_containers[c] {
          c := input.review.object.spec.initContainers[_]
        }

---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDisallowedTags
metadata:
  name: block-latest-tag
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    tags:
      - "latest"

Registry Restrictions

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}] {
          c := input_containers[_]
          not image_allowed(c.image)
          msg := sprintf("Container %v uses non-approved registry: %v", [c.name, c.image])
        }

        image_allowed(image) {
          repo := input.parameters.repos[_]
          startswith(image, repo)
        }

        input_containers[c] {
          c := input.review.object.spec.containers[_]
        }

        input_containers[c] {
          c := input.review.object.spec.initContainers[_]
        }

---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
  name: approved-registries
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    repos:
      - "gcr.io/company-project/"
      - "ghcr.io/company/"
      - "docker.io/library/"

Audit Existing Resources

Gatekeeper audits existing resources and reports violations:

# List all violations
kubectl get constraints -o json | jq '.items[].status.violations'

# Get violations for specific constraint
kubectl get k8srequiredlabels require-team-label -o yaml

# Example output:
# status:
#   violations:
#     - enforcementAction: deny
#       kind: Deployment
#       name: my-app
#       namespace: default
#       message: "Missing required labels: {team}"

Dry Run Mode

Test policies before enforcing:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-team-label-dryrun
spec:
  enforcementAction: dryrun  # warn | deny | dryrun
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
  parameters:
    labels:
      - "team"

Mutation Policies

Gatekeeper can also mutate resources (add defaults):

apiVersion: mutations.gatekeeper.sh/v1
kind: Assign
metadata:
  name: add-default-securitycontext
spec:
  applyTo:
    - groups: [""]
      kinds: ["Pod"]
      versions: ["v1"]
  match:
    scope: Namespaced
    excludedNamespaces:
      - kube-system
  location: "spec.securityContext"
  parameters:
    assign:
      value:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
---
apiVersion: mutations.gatekeeper.sh/v1
kind: Assign
metadata:
  name: add-default-resource-requests
spec:
  applyTo:
    - groups: [""]
      kinds: ["Pod"]
      versions: ["v1"]
  match:
    scope: Namespaced
  location: "spec.containers[name:*].resources.requests.memory"
  parameters:
    assign:
      value: "64Mi"
    pathTests:
      - subPath: "spec.containers[name:*].resources.requests.memory"
        condition: MustNotExist

Testing Policies

Use Gator CLI or Conftest to test policies:

# Install gator
go install github.com/open-policy-agent/gatekeeper/cmd/gator@latest

# Test constraint template
gator test -f template.yaml -f constraint.yaml -f test-resources/

# Or use conftest
conftest test deployment.yaml -p policies/

Troubleshooting

Constraint not enforcing:

# Check constraint status
kubectl get constraint <name> -o yaml

# Check webhook config
kubectl get validatingwebhookconfiguration gatekeeper-validating-webhook-configuration

# Check gatekeeper logs
kubectl logs -n gatekeeper-system -l control-plane=controller-manager

Audit not running:

# Check audit controller
kubectl logs -n gatekeeper-system -l control-plane=audit-controller

# Verify audit interval
kubectl get deploy -n gatekeeper-system gatekeeper-audit -o yaml | grep -A5 audit

References

======================================== OPA Gatekeeper + Kubernetes

Policy as code. Enforced at admission.

Found this helpful?

Comments