Ludovic Alarcon

Ludovic Alarcon .

Kubernetes and Cloud technologies enthusiast, DevOps believer - Golang and Dotnet Developer

Validating Admission Policies in Kubernetes 1.26

Kubernetes 1.26 introduces the Validating Admission Policies.

Validating Admission Policies, an in-process alternative to Validating Admission Webhooks using the Common Expression Language (CEL) to describe the policies.

Why not just stick with Admission Webhooks ?

Because Admission Webhooks need to be developed, maintain and operate over time.
You need to:

It’s a lot just for one so image you have several to handle…

That’s why Admission Policies come to the rescue!
It removes all this complexity by allowing you to define CEL expressions into the Kubernetes resources ValidatingAdmissionPolicy.
Then, you just need to bind the policy to the appropriate resources through ValidatingAdmissionPolicyBinding.

As of today, there is no Mutating Admission Policy.

Experiment with Validating Admission Policy

Prerequisites

Kubeadm enable feature gate

Ssh to the node(s) where core componants are running and edit the /etc/kubernetes/manifests/kube-apiserver.yaml manifest file.

spec:
  containers:
  - command:
    - kube-apiserver
    ...
    ...
    - --feature-gates=ValidatingAdmissionPolicy=true
    - --runtime-config=admissionregistration.k8s.io/v1alpha1=true

This enables the feature and the API on the api-server component.
Now we need to edit the kubelet service, add the following to the /etc/systemd/system/kubelet.service.d/10-kubeadm.conf service file.

--feature-gates=ValidatingAdmissionPolicy=true

So, it looks like

[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --feature-gates=ValidatingAdmissionPolicy=true"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
...
...

Finally, we need to reload the config and restart the kubelet service.

sudo systemctl daemon-reload
sudo systemctl restart kubelet

Policies creation

To create a policy, we previously saw that we need two resources:

Let’s start with a simple example where we want to create a policy that limits the number of replicas at 2 maximum in dev namespace.

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "max-2-replicas-in-dev"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments"]
  validations:
    - expression: "object.spec.replicas <= 2"
      message: "In dev deployment should use 2 replicas maximum"

The manifest above is articulate as follow:

According to the official documentation, CEL expressions have access to the contents of the Admission request/response through some variables

Now we have our policy, we need to create the ValidatingAdmissionPolicyBinding

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "max-2-replicas-in-dev-binding"
spec:
  policyName: "max-2-replicas-in-dev"
  matchResources:
    namespaceSelector:
      matchLabels:
        env: dev

So now, let’s apply our policy

# max-2-replicas-in-dev.yaml

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "max-2-replicas-in-dev"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments"]
  validations:
    - expression: "object.spec.replicas <= 2"
      message: "In dev deployment should use 2 replicas maximum"
---
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "max-2-replicas-in-dev-binding"
spec:
  policyName: "max-2-replicas-in-dev"
  matchResources:
    namespaceSelector:
      matchLabels:
        env: dev
> kubectl apply -f max-2-replicas-in-dev.yaml

We can test and validate our policy

# deploy.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: sandbox
  labels:
    env: dev
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: sandbox
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
> kubectl apply -f deploy.yaml
The deployments "nginx" is invalid: : ValidatingAdmissionPolicy 'max-2-replicas-in-dev' with binding 'max-2-replicas-in-dev-binding' denied request: In dev deployment should use 2 replicas maximum

We can see the policy denied the deployment because we try to set 3 replicas. The message used is also the one we defined in the policy.

If we don’t set a message, we get the following output

The deployments "nginx" is invalid: : ValidatingAdmissionPolicy 'max-2-replicas-in-dev' with binding 'max-2-replicas-in-dev-binding' denied request: failed expression: object.spec.replicas <= 2

Another one

Now let’s say we want a policy to deny a deployment if it does not use an image with a tag.
Our policy will look like that:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "force-tagged-image"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments"]
  validations:
    - expression: "object.spec.template.spec.containers.all(e, e.image.contains(':'))"
      message: "Image should use a tag"
---
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "force-tagged-image-binding"
spec:
  policyName: "force-tagged-image"
  matchResources:

You can see that we did not define a matchResources section like in our first example.
This allows us to define a cluster-wide scope, it will apply be applied to every namespace.

Let’s try our policy

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: sandbox
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.23.3
      - name: foo
        image: foo
> kubectl apply -f deploy.yaml
The deployments "nginx" is invalid: : ValidatingAdmissionPolicy 'force-tagged-image' with binding 'force-tagged-image-binding' denied request: Image should use a tag

It’s possible to chain multiple expressions to create more complex validations.
We might want to use exclusively a private registry and no latest tag for our prod.

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "force-tagged-image"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments"]
  validations:
    - expression: "object.spec.template.spec.containers.all(e, e.image.contains(':'))"
      message: "Image should use a tag"
	  - expression: "object.spec.template.spec.containers.all(e, e.image.startsWith('myregistry.com'))"
      message: "Image should come from myregistry.com"
---
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "force-tagged-image-binding"
spec:
  policyName: "force-tagged-image"
  matchResources:
    namespaceSelector:
      matchLabels:
        env: prod

To infinity and beyond …

You can find some examples of Validation expression
And of course go through the official documentation

With the new ValidatingAdmissionPolicy feature in k8s 1.26, we can now create custom powerful and/or complex policies to enforce security and compliance without the burdensome of managing AdmissionWebhooks.
Of course, there is powerfull solutions like Kyverno or OPA with a lot of features, but ValidatingAdmissionPolicy is more lightweight and easier.

Depending of your requirement you have now the choice between two approaches.

I hope you found that article helpful and enjoy while creating your own policies!