> ## Content Index
> Fetch the complete content index at: https://devopscube.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to Implement Native Admission Policies in Kubernetes
- URL: https://devopscube.com/kubernetes-admission-policies/
- Published: 2026-09-04T08:03:18.000Z
- Updated: 2026-09-04T10:51:16.000Z
- Author: Bibin Wilson
- Tags: #blog, #kubernetes, #syntax-highlight

Policy as code is a key best practice in production [Kubernetes clusters](https://devopscube.com/setup-kubernetes-cluster-kubeadm/). For example, you can enforce policies such as requiring every pod to have resource limits or ensuring images are pulled only from a specific private registry.

To enforce such policies in a Kubernetes cluster, teams usually use tools such as [Kyverno](https://kyverno.io/?ref=devopscube.com) or [OPA Gatekeeper.](https://open-policy-agent.github.io/gatekeeper/website/docs/?ref=devopscube.com)

These tools typically run as **custom** **dynamic admission controllers** and integrate with the [Kubernetes API server](https://devopscube.com/kubernetes-architecture-explained/) using **custom** **admission webhooks**.

Kubernetes also provides native admission policies that can validate and modify API requests without a separate webhook service.

In this guide, you will learn:

- How Kubernetes admission control works
- What native admission policies are
- How to create a `ValidatingAdmissionPolicy` using CEL
- How to create a `MutatingAdmissionPolicy`
- When to use native policies instead of Kyverno or Gatekeeper.

We will create two practical policies:

1. A validation policy that blocks [Deployments](https://devopscube.com/kubernetes-deployment-tutorial/) with more than five replicas.
2. A mutation policy that adds a default label to every new Namespace.

Let's get started.

## How Does Kubernetes Admission Control Work?

An admission controller is a small piece of code that validates or modifies Kubernetes objects before they are persisted. 

The following diagram illustrates how a dynamic admission controller, such as Kyverno, enforces policies via Kubernetes admission webhooks.

![How Kubernetes admission webhooks work with admission controllers to validate, modify, allow, or reject API requests](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/09/image-23.png)

Here is how it works.

When a user sends a request, such as creating a Pod using `kubectl`, the request first reaches the Kubernetes API server.

The API server first **authenticates** the request to verify the identity of the user or service account.

Next, the request goes through **authorization** to check whether that user has permission to perform the requested action. For example, whether the user is allowed to create Pods in a particular namespace.

If the request is authorized, it moves to the **admission control** stage. Admission control mainly applies to requests that create, modify, delete, or connect (proxy requests) resources. Read-only requests such as **GET, LIST, and WATCH bypass admission control.**

During the admission process, the API server checks whether the request matches any configured admission webhooks. For example, a Pod creation event. If it matches, the API server sends the request to the webhook as an AdmissionReview object.

There are two types of admission webhooks.

- **Validating webhooks** can allow or reject the request. For example, if a Deployment doesn’t have a specific label, the creation request gets rejected.
- **Mutating webhooks** can modify the resource before it is created or updated. A classic example of this is Istio, which uses a mutating webhook to automatically inject the sidecar container into the pod spec.

The dynamic admission controller then evaluates the request against the defined policies. For example, a Kyverno policy that requires the image URL to come from a specific private registry.

Once the policy check is complete, the admission controller sends the response back to the API server. If the request is allowed, Kubernetes proceeds with the requested operation.

💡

****Important Note:** This means tools such as Kyverno and OPA Gatekeeper require a custom external admission controller and webhook setup to enforce these policies.  
  
However, you can implement many of these policy use cases using Kubernetes native functionality without running an external admission webhook.  
  
This is where ****native Kubernetes Admission Policies** come into the picture.

First, let's understand what an admission policy is.

## What is an AdmissionPolicy**?**

Unlike custom admission controllers, Kubernetes Admission Policies provide a native way to validate or modify API requests during admission, without running a separate admission webhook server.

This means you **don’t need to deploy and manage custom admission controllers and webhooks** to validate or modify API objects based on your requirements.

There are two Native Admission Policy types:

- **ValidatingAdmissionPolicy** \- This checks the incoming API request and decides whether it should be allowed.
- **MutatingAdmissionPolicy** \- This changes the incoming resource before it is created.

💡

****Note:** ValidatingAdmissionPolicy became GA in version 1.30, and MutatingAdmissionPolicy became stable in 1.36.

### Common Expression Language (CEL).

The admission policies are written in [Common Expression Language (CEL).](https://cel.dev/?ref=devopscube.com) It is a lightweight expression language used by Kubernetes. It's the same language used for CRD validation rules.

A CEL expression evaluates `true` or `false`. If it returns `false`, the request is denied.

For example, `object.spec.replicas <= 5`.

This expression will deny the request if the replica count exceeds 5\. We will look at the same example in the hands-on part.

Let's look at ValidatingAdmissionPolicy first.

## What is ValidatingAdmissionPolicy?

ValidatingAdmissionPolicy is the native alternative to validating admission webhooks built directly into the API server.

Instead of running a separate HTTP server that the API server calls on every request, the validation logic lives directly inside the API server as a native object.

If a request violates a rule, the API server will reject it as shown in the image below.

![Kubernetes MutatingAdmissionPolicy example showing a policy adding a managed-by label to a Namespace](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/09/image-24.png)

Next, we will look at how to create a native ValidatingAdmissionPolicy.

## Creating ValidatingAdmissionPolicy

To understand the policy, we will **create a simple policy that blocks Deployments with more than 5 replicas.**

Here is the ValidatingAdmissionPolicy manifest with a CEL validation expression.

```yaml
kubectl apply -f - <<EOF
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: "limit-replicas-policy"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments"]
  validations:
  - expression: "object.spec.replicas <= 5"
    message: "Deployments in this namespace cannot have more than 5 replicas."
EOF
```

In the above manifest, one thing people often misunderstand is **`failurePolicy: Fail`**. It determines what happens if Kubernetes cannot evaluate the policy correctly due to issues such as a CEL runtime error or policy misconfiguration.

`Fail` means reject the request instead of letting it through, and the other option is `Ignore`, which accepts the request.

Now we have the rule, but **it doesn't do anything until we create a binding.** You can create a binding for this policy using the **ValidatingAdmissionPolicyBinding** object.

Let's create a binding for it.

The binding manifest below applies the policy in the default namespace.

```yaml
kubectl apply -f - <<EOF
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "limit-replicas-policy-binding"
spec:
  policyName: "limit-replicas-policy"
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: default
EOF
```

Here, `validationActions` is the field that specifies the action the admission controller should take if an API request violates the policy. We have used `Deny`, which **rejects requests** that fail the validation.

There are two more options `Warn` and `Audit`.

💡

****Tip:** If you are introducing a new policy into the cluster, start with the Warn action. This allows you to identify requests that violate the policy without blocking them. Once you have verified that the policy works as expected, you can change the action to Deny to enforce it.

Once both objects are created, check the policy by creating a deployment with 5 replicas in the default namespace using the following command. It should be created without any issues.

```bash
kubectl create deployment nginx-1 --image=nginx --replicas=5
```

Now, create a deployment with 6 replicas in the default namespace using the following command.

```bash
kubectl create deployment nginx-2 --image=nginx --replicas=6
```

This will be blocked because the **limit is 5 replicas for a deployment.** You will get the following error message.

```bash
error: failed to create deployment: deployments.apps "nginx-2" is forbidden: ValidatingAdmissionPolicy 'limit-replicas-policy' with binding 'limit-replicas-policy-binding' denied request: Deployments in this namespace cannot have more than 5 replicas.
```

This means the policy worked.

💡

****Why Do Admission Policies Use Separate Policies and Bindings?**  
  
The Policy defines the rule logic, and it's reusable. The Binding specifies where the rule applies, which resources it applies to, and what action to take upon violation.  
  
If they were one object, every time you wanted to apply the same rule to a different namespace or with a different action, you would have to duplicate the entire policy.  
  
It's the same reason Kubernetes separates `ClusterRole` from `ClusterRoleBinding`. One has the role, and the other binds it to different resources.

So far, we have only validated the incoming requests.

*But what if you want Kubernetes to automatically modify the resource?*

That's where **MutatingAdmissionPolicy** comes in.

## What is MutatingAdmissionPolicy?

MutatingAdmissionPolicy is the native alternative to a mutating admission webhook.

Instead of only validating the incoming object, it can modify the object during admission. For example, it **can add default labels or configuration.**

Just like validation policies, the mutation logic runs directly inside the API server and is written using CEL. It supports mutations using either an ApplyConfiguration or JSON Patch.

The following image illustrates how a Kubernetes MutatingAdmissionPolicy modifies a resource before it is created.

![How Kubernetes MutatingAdmissionPolicy modifies a Namespace by automatically adding a managed-by label](https://storage.ghost.io/c/5f/2f/5f2f4d20-2abf-4534-8d40-7aa233aedd43/content/images/2026/09/image-25.png)

When an admin creates the namespace, the API Server adds the label to it. 

Let’s put that use case into practice.

## Create a MutatingAdmissionPolicy

The following manifest creates a mutating policy that automatically adds a label to every newly created namespace.

```yaml
kubectl apply -f - <<EOF
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
  name: add-namespace-label
spec:
  failurePolicy: Fail
  reinvocationPolicy: IfNeeded
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE"]
      resources: ["namespaces"]
  mutations:
  - patchType: ApplyConfiguration
    applyConfiguration:
      expression: >
        Object{
          metadata: Object.metadata{
            labels: {"managed-by": "devops-team"}
          }
        }
EOF
```

Here you can see that under the **mutations** block, we tell Kubernetes to modify the incoming object using an ApplyConfiguration.

Now, let’s create a binding for the policy, as we did before.

```yaml
kubectl apply -f - <<EOF
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicyBinding
metadata:
  name: add-namespace-label-binding
spec: 
  policyName: add-namespace-label
EOF
```

Unlike a validating binding, there is **no separate action field** for options such as Deny, Warn, or Audit

Here, when a request matches the policy through a MutatingAdmissionPolicyBinding, the only action is to mutate the object.

Now let's test it by creating a new namespace.

```bash
kubectl create namespace demo
```

Then check the labels of the newly created namespace.

```bash
kubectl get namespace demo --show-labels
```

You should see the label added automatically.

```bash
NAME STATUS AGE   LABELS
demo Active 4s    kubernetes.io/metadata.name=demo,managed-by=devops-team
```

The namespace request came in without the label. The API server evaluated our policy, added the label, and then stored the modified Namespace.

## Do Native Policies Replace Kyverno or Gatekeeper?

Native admission policies can now handle both validation and mutation, which covers many common policy requirements. However, they're not a complete replacement for tools like Kyverno.

The following are the current limitations of native admission policies.

- Native policies don't cover every workflow available in dedicated policy engines, such as generating additional Kubernetes resources.
- Tools like Kyverno can automatically create a NetworkPolicy or a RoleBinding whenever a new namespace is created. Native policies cannot do this.
- CEL runs in the API server. It cannot call an external API, query a database, or check an OCI registry. If your validation logic needs to reach outside the cluster, you need a webhook.
- CEL is good for simple policies, but for complex policies, dedicated policy engines like Kyverno are recommended.

So, you can use **native admission policies for straightforward admission rules** and use tools such as Kyverno or Gatekeeper when your policy requirements go beyond what native admission policies can handle.

## Consclusion

In summary, with ValidatingAdmissionPolicy, the API server checks whether the request violates the policy and either allows or rejects it.

And with MutatingAdmissionPolicy, the API server modifies the specified object type.

Native policies can only validate or modify an object; they cannot create resources, verify image signatures, or access resources outside the cluster.

Tools like Kyverno and OPA/Gatekeeper cover those gaps. So, choose according to your needs.