Build Your Own Admission Controllers in Kubernetes Using Go

Admission Control vs RBAC

With role-based access control (RBAC) you can limit access to K8s resources and impose different types of restrictions such as allowing or denying those with a particular role to create, list, update and delete any kind of objects or also restrict access within a namespace itself. But what if you want more than that? for example if the deployment is created and you want to validate its name should has prefix name or its image name should be allowed only from private registry? … The RBAC cannot validate and ensure this! So, that is where admission controller comes in.

What is Admission Controller?

As mentioned in Kubernetes docs: “An admission controller is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authorized.” So, basically it helps to define rules or security measures to enforce how a cluster is used.

There are two admission controllers to use:

  1. MutatingAdmissionWebhook : allow for the use of mutating webhooks to modify the content of the resource before it is persisted.
  2. ValidatingAdmissionWebhooks : allow for the use of validating webhooks to enforce custom admission policies.

We can configure these webhooks to reach the webhook server that contains our logic of admission controller service . In this post, I develop a simple webhook server that includes mutating and validating the deployment’s start with the specific prefix prod.

Let’s get started!

What we need to develop our own admission controller?

Before jump into the implementation, let’s take an overview of the process. First, you need to write and deploy the webhook server and make it accessible. Next, configure the webhooks on K8s to reach your webhook server. After that, webhooks are sent POST requests with JSON payload as AdmissionReview API object which has all details about the request. Then, webhooks should get a JSON payload AdmissionReview response that contains the decision on whether the request is allowed or not.

Now for implementation:

  1. Deploy Webhook Server

In the above code, it handles requests coming in at /validate and /mutate from two calls: validate and mutate . The mutate call gets the deployment’s name and response with a JSON patch operation of adding deployment’s prefix name to the deployment ✅. Then, the validate receives a validation webhook request and in the code’s example validates the deployment’s prefix name if it prod or not and accepts or rejects the request.

I deployed in the Kubernetes cluster as deployment object and created a service as the front-end of the webhook server (you can also deploy your webhook server outside of the cluster):

Since the API server only communicates over HTTPS with the admission webhook servers, it needs TLS cert’s CA information. Therefore, I created self-signed certificate and then created kubernetes.io/tls secret for storing a certificate and its associated key:

> kubectl create secret tls webhook-server-tls \
--cert "certs/tls.crt" \
--key "certs/tls.key" -n production

then apply webhook_server.yml in K8s:

> kubectl create -f webhook_server.yml

Note: in my GitHub repo you will find the create_k8s_objects.sh script that will create a self-signed certificates, tls secret, webhook server and webhooks.

2. Configure Admission Webhook

Note: To verify admissionregistration.k8s.io/v1 or admissionregistration.k8s.io/v1beta1 is enabled: kubectl api-versions | grep -i admissionregistration.k8s.io. Please check prerequisites to make sure the admission controllers are enabled.

In order to register admission webhooks, create MutatingWebhookConfiguration and ValidatingWebhookConfiguration API objects:

If your webhook server deployed outside the K8s, you need to replace service block under clientConfig by url in both webhooks like this:

Apply in K8s:

> kubectl create -f webhooks.yml

3. Test 💅

Now that our controller is written, to see all the created resources:

> kubectl get deployments.apps,svc,secret -n production
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/webhook-server 1/1 1 1 36m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/webhook-server ClusterIP 10.245.197.198 <none> 443/TCP 76m
NAME TYPE DATA AGE
secret/webhook-server-tls kubernetes.io/tls 2 59m
> kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io
NAME WEBHOOKS AGE
deployment-mutation 1 37m
> kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io
NAME WEBHOOKS AGE
deployment-validation 1 38m

Let’s create a simple Nginx deployment to test our webhook server:

To see the logs of webhook-server when creating the Nginx deployment:

kubectl logs -n production webhook-server-5999c6d74d-4k4pg --follow

and now apply in K8s:

> kubectl create -f test_deployment.yml

You’ll see:

{"level":"info","time":"2022-05-04T03:18:36Z","message":"Server started ..."}
{"level":"info","time":"2022-05-04T03:19:03Z","message":"handling request: {\"kind\":\"AdmissionReview\",\"apiVersion\":\"admission.k8s.io/v1\",\"request\":{\"uid\":\"8cde5fab-
...{"level":"info","time":"2022-05-04T03:19:03Z","message":"mutating deployments"}
{"level":"info","time":"2022-05-04T03:19:03Z","message":"sending response: &AdmissionReview{Request:nil,Response:&AdmissionResponse{UID:8cde5fab-8dd5-449d-8f92-455c005b1f3b,Allowed:true,Result:nil,Patch:*[91 123 32 34 111 112 34 58 32 34 97 100 100 34 44 32 34 112 97 116 104 34 58 32 34 47 109 101 116 97 100 97 116 97 47 110 97 109 101 34 44 32 34 118 97 108 117 101 34 58 32 34 112 114 111 100 45 110 103 105 110 120 45 100 101 112 108 111 121 109 101 110 116 34 32 125 93],PatchType:*JSONPatch,AuditAnnotations:map[string]string{},Warnings:[],},}"}
...{"level":"info","time":"2022-05-04T03:19:03Z","message":"validating deployments"}
{"level":"info","time":"2022-05-04T03:19:03Z","message":"sending response: &AdmissionReview{Request:nil,Response:&AdmissionResponse{UID:60773af9-ae64-41de-ade3-45f3424b59bf,Allowed:true,Result:nil,Patch:nil,PatchType:nil,AuditAnnotations:map[string]string{},Warnings:[],},}"}

If we get the Nginx deployment’s name, we can see it’s changed from nginx-deployment to prod-nginx-deployment :

> kubectl get deployments.apps -n production
NAME READY UP-TO-DATE AVAILABLE AGE
prod-nginx-deployment 1/1 1 1 5m24s

The End

Here, we’ve covered how to create a simple Kubernetes admission controller in Golang and deploy it to an K8s cluster. It’s quite easy and you can think about many different cases used as admission controller which make your cluster more secure by applying some polices.

I hope this was helpful!

References:

Bye :)

--

--

--

Hi, I’m Cloud Engineer, Golang & Python Enthusiast! A bit of random things...🥑🎶 🎯🌻

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

[Codility] Lesson-03.2: PermMissingElem

Making Airtable Exotic Again

KissPi. Kismet-on-the-Go! (Part 1/n for KissPi)

Free Text To MP3 File With Slovak Voices

Good, Better, Best: Zig Zag Conversion

The journey of deploying Apache Airflow at Grab

Stack Overflow Developer Survey Exploration Results:

Spring Batch and doing ETL between systems

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Bashayr Alabdullah

Bashayr Alabdullah

Hi, I’m Cloud Engineer, Golang & Python Enthusiast! A bit of random things...🥑🎶 🎯🌻

More from Medium

Get your custom logs of your operator

Vault using Kubernetes auth

Scalable self-hosted runner system for GitHub actions

Port of Kubernetes v1.24.0 for illumos (and solaris)