Kubernetes Admission Controllers Security: Modern Approaches and Implementation

Admission controllers sit between your users and the Kubernetes API. Every create or update request passes through them.

Get them wrong and you either lock out your own team or let a privileged pod slip through. When PodSecurityPolicy disappeared in Kubernetes 1.25, a lot of security playbooks broke overnight. The admission control landscape is different now, and teams take one of three paths: Pod Security Admission, OPA Gatekeeper, or Kyverno.

How Kubernetes Admission Controllers Actually Work for Security

An admission controller is a piece of code that intercepts requests to the API server after authentication and authorization but before the resource is persisted.

Think of it as a bouncer who checks your ID at the door. RBAC decides if you are on the list. The admission controller decides if what you are trying to bring inside complies with the house rules.

The admission process runs in two phases. First, mutating admission controllers run and can modify the request, adding labels or changing defaults. Then validating admission controllers run and can reject the request if it violates a policy.

If either phase rejects a request, the entire operation fails and the user gets an error back.

Security-relevant extension points include webhooks and built-in policies. MutatingAdmissionWebhook and ValidatingAdmissionWebhook call external HTTP servers, which is how Gatekeeper and Kyverno plug in. Webhooks offer more flexibility while built-in policies avoid network dependency.

PodSecurityPolicy Is Gone and What Replaced It

PodSecurityPolicy was the original way to enforce pod-level security settings. It was deprecated in Kubernetes 1.21 and removed entirely in 1.25.

Tutorials that reference PodSecurityPolicy or the pod-security-policy API are obsolete. Those commands fail on any current cluster.

Pod Security Admission is the built-in replacement. It is enabled by default in every Kubernetes 1.25 cluster and requires no additional components.

You configure it by adding labels to namespaces. Policy levels range from privileged (unrestricted, for infrastructure workloads) through baseline (prevents known privilege escalations) to restricted (follows current pod hardening best practices).

Baseline is the policy teams start with. It disallows host namespaces, privileged containers, hostPath volumes, and adding capabilities beyond a small allowlist.

The restricted policy is stricter and requires running as non-root, dropping all capabilities, and setting a seccomp profile.

Neither policy requires writing Rego or YAML policy definitions. The API server handles it natively.

Choosing Your Admission Control Approach

The bulk of use cases are covered by three approaches. Start with Pod Security Admission for baseline security with zero infrastructure overhead. It is always on, it costs nothing to run, and it enforces the Kubernetes project’s own recommended settings.

The tradeoff is that you can only express the three predefined levels. If your security team needs a policy like “every container image must come from an internal registry” or “no deployment can run without a specific label,” PSA alone will not do it.

OPA Gatekeeper is the policy-as-code option. You define policies in Rego, store them as ConstraintTemplates and Constraints in your cluster, and Gatekeeper enforces them through a validating webhook.

It handles complex logic, cross-resource validation, and audit reporting well.

Kyverno is the YAML-native alternative. You write policies as Kubernetes resources using YAML and CEL expressions. It can validate, mutate, generate, and cleanup resources.

Kyverno runs as an admission controller and a CLI scanner, and it can also verify container images for software supply chain security.

The advantage for platform teams is that there is no new language to learn. The disadvantage is that it is another component to install, upgrade, and monitor in your cluster.

ValidatingAdmissionPolicy sits between PSA and the webhook options. It is a built-in API that runs CEL expressions directly in the API server, no external webhook required.

Use it when you need custom validation logic but want to avoid the operational overhead of running a webhook server. CEL expressions are simpler than Rego, so complex policies may still need Gatekeeper.

Decision framework

  • If you need baseline pod security with no overhead: Pod Security Admission
  • If you need custom validation logic without a webhook: ValidatingAdmissionPolicy
  • If you need complex multi-resource policies and your team knows Rego: OPA Gatekeeper
  • If you want YAML-only policies with mutation and generation: Kyverno

Implementing a Security Policy with Pod Security Admission

PSA is the fastest path to enforcement. You add two labels to a namespace and the API server starts rejecting non-compliant pods.

Create a namespace with the baseline enforcement label:

kubectl create namespace production
kubectl label namespace production pod-security.kubernetes.io=enforce=baseline

Now try to create a privileged pod. The admission controller rejects it before the pod is persisted.

kubectl run privileged --image=nginx --privileged -n production

The error message names the violation explicitly:

Error from server (Forbidden): pods "privileged" is forbidden: violates PodSecurity "restricted:latest": privileged (container "privileged" must not set securityContext.privileged=true)

Audit and warn modes are safer for initial rollout. Run namespaces in audit mode for a week, review what would have been rejected, fix the offending deployments, then switch to enforce mode.

This avoids blocking production workloads while you discover which pods violate the policy.

kubectl label namespace staging pod-security.kubernetes.io=enforce=baseline pod-security.kubernetes.io=warn=baseline

The version label pins the policy to a specific Kubernetes release. Without it, the policy updates automatically when you upgrade your cluster, which might suddenly reject pods that were allowed before.

kubectl label namespace production pod-security.kubernetes.io=enforce-version=v1.30

When to Use OPA Gatekeeper or Kyverno Instead

PSA covers the common cases. You need a webhook-based engine when your policy involves relationships between resources. For example, “every deployment must have a corresponding HorizontalPodAutoscaler” or “no service can expose port 80 without an Ingress resource that terminates TLS.” These policies require looking at multiple resource types, and PSA cannot do that.

Gatekeeper is the established choice for teams that already use policy-as-code. It integrates with existing CI pipelines, generates audit reports, and has a large library of community constraints.

The learning curve is steep. Rego is a declarative language with its own idioms, and writing a correct ConstraintTemplate takes iteration.

Kyverno is the better fit when your team is already comfortable with YAML and wants to avoid learning Rego. Its mutation and generation capabilities are stronger than Gatekeeper’s.

You can write a policy that automatically adds a label to every pod in a namespace, or creates a default NetworkPolicy when a namespace is created. These operations require mutating and generating admission controllers, and Kyverno supports both natively.

Common Pitfalls and Failure Modes

Enabling enforce mode before checking what it breaks is the failure teams hit first.

Baseline policy rejects privileged containers, host namespaces, and hostPath volumes. A surprising number of standard workloads use one of these. Datadog and Prometheus agents, for example, often require hostPath access.

Run audit mode first.

Webhook timeouts are the next failure mode. If your Gatekeeper or Kyverno webhook does not respond within the timeout (usually 10 seconds), the API server either fails open or fails closed depending on your configuration.

Failing open means a policy violation gets through. Failing closed means every pod creation fails while the webhook is down. Set the failure policy to Fail and run multiple replicas of the webhook.

Namespace exemptions are a footgun. Both PSA and webhook engines let you exempt specific namespaces from enforcement. kube-system almost always needs exemptions because its workloads run with elevated privileges.

But if you exempt every namespace that complains, you end up with zero enforcement. Document every exemption and review it quarterly.

What to Enforce First in Your Cluster

Start with Pod Security Admission in audit mode across all non-system namespaces. Let it run for one week.

Then switch enforce mode to baseline while keeping audit and warn active.

This order avoids two mistakes. The first is deploying a complex policy engine before you understand your workloads. The second is enforcing a policy that breaks production because you did not measure what it would reject first.

PSA gives you that measurement with zero overhead.

Frequently Asked Questions

Here are the questions that come up when teams start working with admission controllers in practice.

What is an admission controller in Kubernetes?

An admission controller is a piece of code in the Kubernetes API server that intercepts requests after authentication and authorization but before the resource is persisted. It can modify the request (mutating) or reject it (validating). Admission controllers enforce security policies, set defaults, and ensure compliance across the cluster.

Can admission controllers block read requests?

No. Admission controllers only apply to requests that create, update, delete, or connect to objects. Read operations like get, watch, and list bypass the admission control layer entirely.

What replaced PodSecurityPolicy in Kubernetes 1.25?

Pod Security Admission replaced PodSecurityPolicy. It is a built-in admission controller enabled by default in Kubernetes 1.25 and later. You configure it with namespace labels (pod-security.kubernetes.io/enforce=baseline) instead of creating PodSecurityPolicy resources.

What is the difference between mutating and validating admission controllers?

Mutating admission controllers can modify the request before it is processed, such as adding default labels or changing container settings. Validating admission controllers can only accept or reject the request. Mutating controllers run first, then validating controllers run on the modified request.
Pankaj Kumar
Pankaj Kumar

Pankaj Kumar is the founder and CEO of CodeForGeek, with more than 14 years in IT. He is an open-source enthusiast who enjoys sharing what he learns through CodeForGeek and YouTube, with a focus on Python, data analytics, machine learning, Angular, Node.js, and Kafka.

Articles: 335