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.
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:
For MutatingAdmissionPolicy, you need:
To check the version, run kubectl version.
If you are running an older version of Kubernetes, check the documentation for that version.
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.
The two types of policy have different purposes.
ValidatingAdmissionPolicy is for enforcing constraints.
MutatingAdmissionPolicy is for modifying resources during admission.
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.
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.
Each admission policy binding must specify one or more actions to declare how the policy is enforced.
For ValidatingAdmissionPolicyBinding, the supported validationActions are:
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.
For MutatingAdmissionPolicyBinding, the the action is always to mutate the object.
You can use a JSON Patch or a Kubernetes apply configuration.
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
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.
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?
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.
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.
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'}}}"
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
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.
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?
# 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?
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.