Cloud Security Wire
AWS Azure GCP RSS
AWSAzureGCPMulti-Cloud Hardening Guide high

Kubernetes Admission Controllers: Enforcing Security Policy with OPA Gatekeeper and Kyverno

Admission controllers are Kubernetes's last line of defence before a workload reaches the cluster. This guide covers how OPA Gatekeeper and Kyverno work, the policies every production cluster needs, and how to audit for gaps.

By Cloud Security Wire · ·
#Kubernetes#admission controllers#OPA Gatekeeper#Kyverno#pod security#policy enforcement#K8s hardening#container security#RBAC
High Severity

This issue has been assessed as high severity. Review affected configurations immediately.

The most common Kubernetes misconfigurations — privileged containers, host network access, containers running as root, writable root filesystems — are not detection problems. They are policy enforcement problems. Each one is visible in the pod spec before the workload is admitted to the cluster. Admission controllers are the mechanism that evaluates that spec against policy and blocks or mutates it before any container runs.

Without admission control, security depends on developers not making mistakes and on post-admission scanning catching problems in running workloads. With admission control, violations are blocked at deployment time — the cheapest possible moment to catch them.

How Admission Controllers Work

Kubernetes processes admission webhooks during the admission control phase of the API server request lifecycle. When a resource (Pod, Deployment, Ingress, etc.) is created or updated, the API server serialises the request and sends it to configured webhook endpoints. The webhook evaluates the resource against its policy set and returns either an admission or a denial — optionally with mutation applied before admission.

Two types exist:

  • Validating admission webhooks — evaluate and accept or reject
  • Mutating admission webhooks — modify the resource (add labels, inject sidecars, set defaults) then admit

OPA Gatekeeper and Kyverno both operate as dynamic validating (and optionally mutating) admission webhooks deployed inside the cluster.

OPA Gatekeeper

Gatekeeper extends Kubernetes with two custom resources: ConstraintTemplate (defines the policy logic in Rego) and Constraint (instantiates a template against selected resources with parameters).

ConstraintTemplate — No Privileged Containers:

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

        violation[{"msg": msg}] {
          c := input_containers[_]
          c.securityContext.privileged == true
          msg := sprintf("Container <%v> must not run as privileged", [c.name])
        }

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

Constraint — Apply to All Namespaces Except System:

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

Gatekeeper also supports audit mode, which continuously evaluates existing resources against constraints and surfaces violations in the Constraint status. This allows brownfield adoption without immediately blocking existing workloads.

# Check current violations across all constraints
kubectl get constraints -A -o json | jq '.items[] | {name: .metadata.name, violations: .status.totalViolations}'

Kyverno

Kyverno uses a YAML-native policy language without Rego, making it more accessible for teams less comfortable with a separate policy language. Policies are Kubernetes resources with validate, mutate, generate, and verify images rules.

Kyverno Policy — Require Non-Root User:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-non-root-user
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: check-run-as-non-root
      match:
        any:
          - resources:
              kinds:
                - Pod
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
      validate:
        message: "Containers must run as non-root. Set securityContext.runAsNonRoot: true and runAsUser > 0."
        pattern:
          spec:
            =(initContainers):
              - =(securityContext):
                  runAsNonRoot: true
                  runAsUser: ">0"
            containers:
              - securityContext:
                  runAsNonRoot: true
                  runAsUser: ">0"

Kyverno Policy — Disallow Host Networking and Host PID:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-host-namespaces
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: host-namespaces
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Sharing the host network, PID, or IPC namespace is not allowed."
        pattern:
          spec:
            =(hostPID): "false"
            =(hostIPC): "false"
            =(hostNetwork): "false"

Mutating policy — Automatically add read-only root filesystem:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-readonly-rootfs
spec:
  rules:
    - name: add-readonly-rootfs
      match:
        any:
          - resources:
              kinds:
                - Pod
      mutate:
        patchStrategicMerge:
          spec:
            containers:
              - (name): "*"
                securityContext:
                  +(readOnlyRootFilesystem): true

Policies Every Production Cluster Should Have

The following covers the highest-impact attack surface for Kubernetes workloads:

PolicyPurposeRisk if missing
No privileged containersPrevents container escapeFull node compromise from any container
Require non-rootReduces blast radius of container escapeRoot in container → root on node
Read-only root filesystemLimits persistence and tooling installationAttacker can modify container files
No host network/PID/IPCPrevents namespace escapesAccess to host network stack and processes
Restrict volume typesPrevents hostPath mounts to sensitive pathsRead/write access to host filesystem
Require resource limitsPrevents DoSSingle workload can exhaust node resources
Disallow latest image tagForces explicit, auditable image versionsUnpredictable deployments, supply chain risk
Require image signaturesVerifies image provenance (Cosign/Notary)Unsigned/compromised images admitted

Auditing Existing Clusters

Before enforcing policies, audit what you already have:

# Find all privileged containers in the cluster
kubectl get pods -A -o json | jq -r '
  .items[] | 
  .metadata.namespace as $ns | 
  .metadata.name as $pod |
  .spec.containers[] | 
  select(.securityContext.privileged == true) |
  [$ns, $pod, .name] | join(" / ")
'

# Find pods running as root (runAsUser: 0 or no securityContext)
kubectl get pods -A -o json | jq -r '
  .items[] |
  .metadata.namespace as $ns |
  .metadata.name as $pod |
  .spec.containers[] |
  select(.securityContext == null or .securityContext.runAsNonRoot != true) |
  [$ns, $pod, .name] | join(" / ")
'

# Find pods with hostNetwork: true
kubectl get pods -A -o json | jq -r '
  .items[] | 
  select(.spec.hostNetwork == true) |
  [.metadata.namespace, .metadata.name] | join(" / ")
'

Gatekeeper vs Kyverno: Choosing Between Them

Both tools enforce policy effectively. The practical differentiators:

Choose Gatekeeper if:

  • Your team already knows Rego (OPA is used elsewhere — Styra, Conftest, Terraform)
  • You need the Gatekeeper constraint library’s extensive pre-built policy set
  • You want tight OPA ecosystem integration

Choose Kyverno if:

  • Your team prefers YAML-native policy without learning a new language
  • You want richer mutation and generation capabilities out of the box
  • You’re adopting admission control for the first time and want faster time-to-value

Running both is possible but operationally complex. Most teams pick one and build their policy library around it. Neither choice is wrong — enforcement is what matters.

CI/CD Integration

Admission controllers catch violations at deploy time. Shift-left further by running the same policies in CI before the manifest reaches the cluster.

# Kyverno CLI — validate manifests against policies in CI
kyverno apply ./policies/ --resource ./manifests/

# Conftest with OPA — for Gatekeeper Rego policies
conftest test manifests/ --policy policies/

# Checkov — built-in K8s security checks without custom policies
checkov -d ./manifests/ --framework kubernetes

Catching a privileged container in a pull request costs nothing. Catching it after it reaches production — and discovering why it was needed — costs a sprint.

← All Analysis Subscribe via RSS