Explore Validating and Mutating Admission Policies

Explore Validating and Mutating Admission Policies

Use declarative admission policies to validate or mutate resources at admission time using Common Expression Language (CEL).

This page lets you try out declarative admission policies, which allow you to use the Common Expression Language (CEL) to validate or mutate resources.

Kubernetes 1.36 supports two kinds of admission policy:

This tutorial covers both kinds of admission policy.

Before you begin

You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. It is recommended to run this tutorial on a cluster with at least two nodes that are not acting as control plane hosts. If you do not already have a cluster, you can create one by using minikube or you can use one of these Kubernetes playgrounds:

To define admission policies, you must be a cluster administrator. Make sure you have administrator access to the cluster where you are learning.

For ValidatingAdmissionPolicy, you need:

  • A cluster running version 1.30 or later.

For MutatingAdmissionPolicy, you need:

  • A cluster running version 1.36 or later.

To check the version, run kubectl version. If you are running an older version of Kubernetes, check the documentation for that version.

What are declarative admission policies?

Declarative admission policies offer a declarative, in-process alternative to admission webhooks.

By using the Common Expression Language (CEL) to declare policy rules, these policies are evaluated directly within the API server.

These policies are highly configurable, enabling policy authors to define logic that can be parameterized and scoped to resources as needed by cluster administrators.

API types for admission policies

The two types of policy have different purposes.

ValidatingAdmissionPolicy is for enforcing constraints.

MutatingAdmissionPolicy is for modifying resources during admission.

Policy elements

Each applied policy always has a policy object (ValidatingAdmissionPolicy or MutatingAdmissionPolicy) and a separate binding object (ValidatingAdmissionPolicyBinding or MutatingAdmissionPolicyBinding).

You can also use parameters, which are optional. To learn more, see parameter resources (ValidatingAdmissionPolicy) or parameter resources (MutatingAdmissionPolicy).

Policy objects describes the abstract logic of a policy using Common Expression Language (CEL). For example, a ValidatingAdmissionPolicy might enforce replica limits or ensure specific labels are present, while a MutatingAdmissionPolicy can modify resources such as adding a default label to a namespace.

Binding objects link the policy to your cluster and provides scoping. A ValidatingAdmissionPolicyBinding or MutatingAdmissionPolicyBinding connects the policy to specific resources. If you only want to enforce a policy for a specific subset of resources, the binding is where you narrow the scope of the policy (using matchResources).

Parameters allow separating configuration for the policy behavior from its definition. Parameter resources refer to Kubernetes resources available in the API. They can be built-in API types (such as ConfigMap), or they can be custom resources. A policy binding then uses spec.paramRef to reference an actual parameter resource.

If a policy does not require parameters, you leave spec.paramKind unspecified.

CEL expressions

Both kinds of policy rely on an expression language known as Common Expression Language (CEL). Read CEL in Kubernetes to learn more.

If you are new to CEL, practice writing a very simple expression, such as false || true. You can test CEL expressions in CEL Playground.

Policy actions

Each admission policy binding must specify one or more actions to declare how the policy is enforced.

ValidatingAdmissionPolicyBinding

For ValidatingAdmissionPolicyBinding, the supported validationActions are:

Audit
Validation failure is included in the audit event for the API request.
Warn
Validation failure is reported to the request client as a warning.
Deny
Validation failure results in a denied request.

A policy check that fails or an error that occurs is enforced according to these actions. Failures defined by the failurePolicy are enforced according to these actions only if the failurePolicy is set to Fail (or not specified).

See Audit Annotations: validation failures for more details about audit logging for policies.

You are not allowed to use Deny and Warn together, since this combination would duplicate the validation failure in both the API response body and the HTTP Warning: header.

MutatingAdmissionPolicyBinding

For MutatingAdmissionPolicyBinding, the the action is always to mutate the object.

You can use a JSON Patch or a Kubernetes apply configuration.

Enforcement through validation

Now, try defining a ValidatingAdmissionPolicy.

The following is an example of a ValidatingAdmissionPolicy that requires that any Deployment has multiple replicas.

---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: "enforce-multiple-replicas-deployments"
  annotations:
    kubernetes.io/description: "Require that matching Deployments have multiple replicas"
spec:
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      resources:   ["deployments"]      # the Deployment API
      operations:  ["CREATE", "UPDATE"] # the API verb, converted to upper case
  validations:
    - expression: "object.spec.replicas >= 2"

spec.validations contains CEL expressions which use the Common Expression Language (CEL) to validate the request. If an expression evaluates to false, the validation check is enforced according to the spec.failurePolicy field.

Write a policy like this and apply it.

Or, if you want to apply a ready-made manifest:

kubectl apply --server-side -f https://k8s.io/examples/access/manifest-admission-control/vap-min-replicas.yaml

On its own, this doesn't do anything.

You can try creating a Deployment with 0 or 1 replicas; it will work (unless some other policy prevents it).


To make it work, you define a ValidatingAdmissionPolicyBinding.

Pick a namespace where you'll enforce the new policy.

The following is an example ValidatingAdmissionPolicyBinding for the policy you made:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: enforce-multiple-replicas-deployments-binding
spec:
  policyName: "enforce-multiple-replicas-deployments"
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: default # change this to match the namespace you're using

Caution:

Anyone with full / admin access to a namespace can write to its labels. This includes deleting a label from the namespace.

The kubernetes.io/metadata.name label is protected, but if you use a different label, take care to make sure that only trusted users have a way to remove or edit that label you choose.

Write a manifest based on that example YAML (if you're using the default namespace, you can use it without any changes). Apply that manifest using kubectl apply.

Test the policy

Now, test the policy. Try creating a Deployment and then scale it to 0 replicas using kubectl scale. What happens?

You could change the ValidatingAdmissionPolicyBinding to have a different validation action, instead of Deny. If you choose the Warning validation action and try to scale a Deployment to 0 replicas, what happens?

Note:

If you did change the ValidatingAdmissionPolicyBinding to just warn people, there's a problem…

The name is wrong! If you change a ValidatingAdmissionPolicyBinding or the associated ValidatingAdmissionPolicy so that it only warns people, you should check if you also need to change the name of the policy. You would change the name to make sure that the naming doesn't mislead people.

Existing resources aren't affected

If you have a Deployment with 0 or 1 replicas, and you change the ValidatingAdmissionPolicyBinding back to Deny mode, it doesn't affect any existing resources.

(If you wanted to try to scale out Deployments to have at least 2 replicas, you could achieve that another way - for example, using a controller).

That's all for the ValidatingAdmissionPolicy. Now you'll learn about MutatingAdmissionPolicies.

Modifying resources when they are created or changed

For this example, imagine that you want to use Pod security admission to ensure that namespaces, other than system namespaces, enforce a Pod security standard.

Similar to validation, you can create a MutatingAdmissionPolicy that can modify resources during admission. The API type that you need to modify is Namespace.

Here's a MutatingAdmissionPolicy that does some of this:

---
# Caution: read the notes for this default-pod-security-baseline policy before
# you apply it, so that you understand the information security consequences.
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
  name: "default-pod-security-baseline"
  annotations:
    kubernetes.io/description: "Default new namespaces to enforce the Baseline pod security standard"
spec:
  reinvocationPolicy: IfNeeded
  matchConstraints:
    resourceRules:
    - apiGroups:   [""]
      apiVersions: ["v1"]
      operations:  ["CREATE"]
      resources:   ["namespaces"]
  matchConditions:
    - name: "exclude-system-namespaces"
      # If the name begins with "kube-", it's a system namespace.
      # Assume that the API server or cluster management tooling applies relevant defaults.
      expression: "!object.metadata.name.startsWith('kube-')"
    - name: "no-existing-pod-security-label"
      expression: "!('pod-security.kubernetes.io/enforce' in object.metadata.labels)"
  mutations:
    - patchType: "ApplyConfiguration"
      applyConfiguration:
        expression: "Object{metadata: Object.metadata{labels: {'pod-security.kubernetes.io/enforce': 'baseline'}}}"

Caution:

This policy sets a default. Someone with the ability to update a Namespace would be able to remove the pod-security.kubernetes.io/enforce label from a namespace.

If you are not sure what this means, read through the Security documentation or get external information security advice.

To apply that policy:

kubectl apply --server-side -f https://k8s.io/examples/access/manifest-admission-control/default-pod-security-baseline.yaml 

A MutatingAdmissionPolicyBinding is required to activate this policy; for example:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicyBinding
metadata:
  name: default-pod-security-baseline
spec:
  # the name of the MutatingAdmissionPolicy to apply
  policyName: default-pod-security-baseline

Test the policy

Try creating a new namespace named example:

kubectl create ns example

Examine its labels:

kubectl describe ns example

Even though you didn't specify a Pod security admission enforcement level, the label has been set.

Next, check whether you can find a way round the security settings. Create a YAML manifest for a different Namespace:

apiVersion: v1
kind: Namespace
metadata:
  name: another-example
  labels:
    pod-security.kubernetes.io/enforce: privileged

You can create that namespace from the local manifest using kubectl apply --server-side. Does it work?

Yes, and the new namespace allows running privileged Pods.

This admission policy was not set up to validate or restrict. It provides a default value, but you can set your own. However, you can combine mutating admission with a validating admission policy as a way to enforce something, but also make it easy to comply. (The tutorial doesn't explain this, but you can do it).

Providing a useful default means that when people don't set anything, they get a better outcome than just seeing an error message. Imagine if you did have a validation rule to make sure that all namespaces had to enforce at least the baseline standard. Anyone who didn't know about that rule might try to deploy something and immediately see an error message when they try making a namespace.

Use a parameter resource

Parameter resources allow a policy configuration to be separate from its definition. A policy can define paramKind, which outlines the group, version, and kind (also known as GVK) of the parameter resource. Then, a policy binding ties that policy to the scope where it is bound, as configured by a particular parameter resource.

---
# Caution: read the notes for this default-pod-security-configurable policy before
# you apply it, so that you understand the information security consequences.
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
  name: "default-pod-security-configurable"
  annotations:
    kubernetes.io/description: "Default new namespaces to enforce the chosen pod security standard"
spec:
  reinvocationPolicy: IfNeeded
  paramKind:
    apiVersion: v1
    kind: ConfigMap
  matchConstraints:
    resourceRules:
    - apiGroups:   [""]
      apiVersions: ["v1"]
      operations:  ["CREATE"]
      resources:   ["namespaces"]
  matchConditions:
    - name: "exclude-system-namespaces"
      # If the name begins with "kube-", it's a system namespace.
      # Assume that the API server or cluster management tooling applies relevant defaults.
      expression: "!object.metadata.name.startsWith('kube-')"
    - name: "no-existing-pod-security-label"
      expression: "!('pod-security.kubernetes.io/enforce' in object.metadata.labels)"
  mutations:
    - patchType: "ApplyConfiguration"
      applyConfiguration:
        expression: "Object{metadata: Object.metadata{labels: {'pod-security.kubernetes.io/enforce': params.data.default}}}"

Here is a sample MutatingAdmissionPolicyBinding:

---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicyBinding
metadata:
  name: default-pod-security-configurable
spec:
  # the name of the MutatingAdmissionPolicy to apply
  policyName: default-pod-security-configurable

  # parameters to use
  paramRef:
    # if the ConfigMap is missing or empty, don't set a default
    # (but do allow namespace creation)
    parameterNotFoundAction: Allow

    # where to find the parameter
    namespace: kube-system
    name: default-pod-security-standard

and here's a sample ConfigMap to put into the kube-system namespace:

---
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: kube-system
  name: default-pod-security-standard
data:
  default: baseline # could also be "restricted"

Define both of those. You should create the ConfigMap first; the binding expects that the parameter resource already exists (even if you plan to change it later).

Now, delete the previous MutatingAdmissionPolicyBinding:

kubectl delete mutatingadmissionpolicybindings/default-pod-security-baseline

and create a new namespace:

kubectl create ns yet-another-example
kubectl describe ns yet-another-example

Did the labels get defaulted?

Change the parameter

# This starts an editor that lets you change .data.default for the parameter
kubectl --namespace kube-system edit configmap default-pod-security-standard

After you change it, try creating one more namespace. What happens?

Clean up

To remove the resources created, run the following commands:

kubectl delete validatingadmissionpolices/enforce-multiple-replicas-deployments \
               validatingadmissionpolicybindings/enforce-multiple-replicas-deployments

kubectl delete mutatingadmissionpolicies/default-pod-security-baseline \
               mutatingadmissionpolicybindings/default-pod-security-baseline

kubectl delete mutatingadmissionpolicies/default-pod-security-configurable \
               mutatingadmissionpolicybindings/default-pod-security-configurable

kubectl --namespace kube-system delete configmaps/default-pod-security-standard

kubectl delete namespaces/example namespaces/another-example namespaces/yet-another-example

If you created any test Pods or test Namespaces, clear those up too.

Last modified July 06, 2026 at 9:52 PM PST: Improve admission policies tutorial (01c4b35816)