OpenShift 4 build your First Admission Controller

Oren Oichman
8 min readSep 8, 2020

About this Tutorial

Being a PAAS admin one of the thing that kept me must interested is the ability to create automation for my customers depending on there needs without me having to do all the heavy lifting for each request.

Ever sense Admission Controller came along it was clear to me that this is the way I want to go in case I want to enforce policies (validate) or add complex requirements while my customers need to do next to none (mutate).

I am basing this tutorial on my git repository which you can find here

NOTE

You need to be Cluster Admin in order to create the admission controller.

How does it works ?

The way that it works is once the Admission controller is setup and an API request is being sent to the Kubernetes API the Cluster check if it’s conditions are matched and sends a JSON request to the web server (which we will build).
To complete the process he cluster expects a JSON response with the changes or with a validation replay depending on the admission type.

More information about admission controller can be found at Kubernetes Official Site

There are 2 types of admission controller

  1. Mutating
  2. Validation

Mutating

Mutating admission webhooks are invoked first, and can modify objects sent to the API server to enforce custom defaults. After all object modifications are complete, and after the incoming object is validated by the API server.

Validation

validating admission webhooks are invoked and can reject requests to enforce custom policies.

Where to start ?

The communication between the API server and the webhook (our web server) is force by using SSL.

In order to create the SSL/TLS certificate we will use OpenSSL to generate the CA , the Key and then Sign the certificate (More information about OpenSSL can be found in my Tutorial about “Working with OpenSSL and DNS alternative names” )

To Begin let’s first create an answer file with the name of the service we are going to use :

First let’s clone the repository :

# git clone https://github.com/ooichman/acpodnaming

Now let’s Navigate to utils , and you will see 2 files in the directory

# cd acpodnaming/utils/
# ls -la
drwxr-xr-x. 2 user group 4096 Aug 9 21:41 .
drwxr-xr-x. 9 user group 4096 Aug 9 21:39 ..
-rwxr-xr-x. 1 user group 3046 Aug 4 13:26 generate_crt.sh
-rwxr-xr-x. 1 user group 1352 Aug 5 10:43 generate_secret.sh

Start by creating the namespace and running the generate_crt.sh with the service and the namespace for the webhook pod (for our example we will call it “kubernetes-acpodnaming”

# oc create ns kube-acpodnaming

And Run :

# ./generate_crt.sh --service acpodnaming --namespace kube-acpodnaming | tee  ca_base64.txt

The output should look like :

Generating RSA private key, 4096 bit long modulus (2 primes)
......................................................................................++++
.....................++++
e is 65537 (0x010001)
Generating RSA private key, 4096 bit long modulus (2 primes)
.................................................++++
...........++++
e is 65537 (0x010001)
DNS:acpodnaming, DNS:acpodnaming.kube-acpodnaming, DNS:acpodnaming.kube-acpodnaming.svc
Signature ok
subject=C = US, ST = New York, L = New York, O = MyOrg, OU = MyOrgUnit, emailAddress = me@working.me, CN = acpodnaming
Getting CA Private Key
DNS:acpodnaming, DNS:acpodnaming.kube-acpodnaming, DNS:acpodnaming.kube-acpodnaming.svc
validate.crt: OK
your CA base64 is :
< CA BASE 64 >

You Noticed that by the end of the run an out put of the CA in Base 64 was givern , we saved the output to a file called “ca_base64.txt” so it will be available for us later on

# cat ca_base64.txt

And we can even generate a variable that will hold the ca base64 :

# CA_BASE64=`cat ca_base64.txt | grep "ca_base64" | awk '{print $2}'`
# echo $CA_BASE64
< CA BASE 64 >

Once the script is completed you will notice there are a lot of new files in your directory :

#ls -la
total 60
drwxr-xr-x. 3 user group 4096 Sep 3 11:52 .
drwxr-xr-x. 9 user group 4096 Aug 9 21:39 ..
-rw-r--r--. 1 user group 314 Sep 3 11:52 ca_answer.txt
-rw-r--r--. 1 user group 3094 Sep 3 11:52 ca_base64.txt
-rw-r--r--. 1 user group 2134 Sep 3 11:52 ca.crt
-rw-------. 1 user group 3247 Sep 3 11:52 ca.key
-rw-r--r--. 1 user group 41 Sep 3 11:52 ca.srl
drwxr-xr-x. 2 user group 4096 Sep 3 11:52 certs
-rw-r--r--. 1 user group 400 Sep 3 11:52 csr_answer.txt
-rwxr-xr-x. 1 user group 3046 Aug 4 13:26 generate_crt.sh
-rwxr-xr-x. 1 user group 1352 Aug 5 10:43 generate_secret.sh
-rw-r--r--. 1 user group 2159 Sep 3 11:52 validate-certonly.crt
-rw-r--r--. 1 user group 4293 Sep 3 11:52 validate.crt
-rw-r--r--. 1 user group 1907 Sep 3 11:52 validate.csr
-rw-------. 1 user group 3243 Sep 3 11:52 validate.key

Now we will switch to our new Namespace :

# oc project kube-acpodnaming
Now using project "kube-acpodnaming" on server ...

And now let’s create a secret from our new certificate :

# ./generate_secret.sh --service acpodnaming --namespace kube-acpodnaming 
secret/acpodnaming-tls created

Now that the secret is created we can go on and build our pod.
For our example I will use registry.example.local as my registry .
Run the following command :

As you can tell by the name it is a pretty KUKU web hook , the only thing that it does is to check if your pod has the name containing the word kuku and only then it will allow us to deploy it.

# cd ..
# buildah bud -f Dockerfile -t registry.example.local/acpodnaming .

Or run the build.sh in the project’s top directory:

#./build.sh

Once the build is complete you can push the container to your local registry and we can continue to our Deployment

# podman push registry.example.local/acpodnaming

Optional

OpenShift Internal Registry

if you want to work with your OpenShift internal registry you can push it with buildah but you will need to directed the image internally on the deployment

For Example …

Login to OpenShift internal registry :

# export CLUSTER="ocp4"
# export MY_DOMAIN="example.local"
# podman login --username $(oc whoami) --password $(oc whoami -t) default-route-openshift-image-registry.apps.${CLUSTER}.${MY_DOMAIN}

Now build :

# buildah bud -f Dockerfile -t default-route-openshift-image-registry.apps.${CLUSTER}.${MY_DOMAIN}/kube-acpodnaming/acpodnaming

And Push it to the registry :

# buildah push default-route-openshift-image-registry.apps.${CLUSTER}.${MY_DOMAIN}/kube-acpodnaming/acpodnaming

(Optional Section Ends)

Continue …

Now for the deployment all we need to do is to update the image section under the deployment directory

# sed -i "s|\"REPLACE_IMAGE\"|registry.example.local/acpodnaming:latest|g" deployment/deployment.yaml

NOTE !!!

For the internal registry you need to direct to pull internally :

# sed -i "s|\"REPLACE_IMAGE\"|image-registry.openshift-image-registry.svc:5000/kube-acpodnaming/acpodnaming:latest|g" deployment/deployment.yaml

And deploy it :

# oc create -f deployment/deployment.yaml

Once the Image is running we can go ahead and deploy the service :

# oc create -f deployment/service.yaml

Now let’s look at the Validating Webhook YAML file to see what is going to happened

# cat deployment/validatingwebhook.yaml 
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: "pod-kuku.example.local"
webhooks:
- name: "pod-kuku.example.local"
namespaceSelector:
matchExpressions:
- key: admission.example.local/podnaming
operator: In
values: ["True"]
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
scope: "Namespaced"
clientConfig:
service:
namespace: "kube-acpodnaming"
name: "acpodnaming"
path: /validate
port: 8080
caBundle: "CA_CERT_BASE64"
admissionReviewVersions: ["v1", "v1beta1"]
sideEffects: None
timeoutSeconds: 5

As you can see, this admission controller only effects namespace with the annotation of “admission.example.local/podnaming: True” or the admission controller will disregard the namespace

Continue on in the “rules” section we see that this AC (Admission controller) only been called when a pod in been created under the scope of a namespace.

And lastly the configuration tells the Kubernetes API to talk to our acpodnaming service which is located in the “kube-acpodnaming” namespace and needs to be addressed with the path of “/validate” on port 8080 …

The last part is the interesting one … here we are going to replace this content with our CA_BASE64 content we generated in the beginning.

The reason we update our admission controller with the CA that was generated is to enable a clean SSL/TLS connection (PASS the TLS verification step).

After we ran the update on the file we can go ahead and deploy it :

# sed -i "s|\"CA_CERT_BASE64\"|$CA_BASE64|g" deployment/validatingwebhook.yaml

And apply the file :

# oc create -f deployment/validatingwebhook.yaml

Congratulations , you have just configured your admission controller , now it is time to test it …

First create a new name space :

# oc new-project podnaming-test
(oc create -f tests/namespace.yaml)

And Add annotation to it (if you created it from the tests directory you do not need to patch) :

# oc patch ns podnaming-test -p '{ "metadata": { "annotations": { "admission.example.local/podnaming": "True" } } }'

And Now we can test our AC.

Open A new terminal and run the events with a “wait” flag :

# oc get events -n podnaming-test

Now deploy the “not working” pod :

# oc create -f tests/deployment-not-working.yaml

Look at the description of the pod and the events on the name space to see what’s going on and what is the feedback we are getting from the AC :

0s          Warning   FailedCreate        replicaset/ubuntu-notworking-5c89549bbd   Error creating: admission webhook "pod-kuku.il.redhat.io" denied the request without explanation
0s Warning FailedCreate replicaset/ubuntu-notworking-5c89549bbd Error creating: admission webhook "pod-kuku.il.redhat.io" denied the request without explanation
0s Warning FailedCreate replicaset/ubuntu-notworking-5c89549bbd Error creating: admission webhook "pod-kuku.il.redhat.io" denied the request without explanation
0s Warning FailedCreate replicaset/ubuntu-notworking-5c89549bbd Error creating: admission webhook "pod-kuku.il.redhat.io" denied the request without explanation
0s Warning FailedCreate replicaset/ubuntu-notworking-5c89549bbd Error creating: admission webhook "pod-kuku.il.redhat.io" denied the request without explanation
0s Warning FailedCreate replicaset/ubuntu-notworking-5c89549bbd Error creating: admission webhook "pod-kuku.il.redhat.io" denied the request without explanation
0s Warning FailedCreate replicaset/ubuntu-notworking-5c89549bbd Error creating: admission webhook "pod-kuku.il.redhat.io" denied the request without explanation

as you can see our admission controller denied our request…

Now delete it :

# oc delete -f tests/deployment-not-working.yaml

And deploy the working pod :

# oc create -f tests/deployment-working.yaml

Now let’s look at our events :

0s          Normal    ScalingReplicaSet   deployment/ubuntu-kuku                    Scaled up replica set ubuntu-kuku-5f8596b4b7 to 1
0s Normal SuccessfulCreate replicaset/ubuntu-kuku-5f8596b4b7 Created pod: ubuntu-kuku-5f8596b4b7-zlqdv
0s Normal Scheduled pod/ubuntu-kuku-5f8596b4b7-zlqdv Successfully assigned podnaming-test/ubuntu-kuku-5f8596b4b7-zlqdv to ocp-worker-03.c.oren-212107.internal
0s Normal AddedInterface pod/ubuntu-kuku-5f8596b4b7-zlqdv Add eth0 [10.131.0.46/23]
0s Normal Pulling pod/ubuntu-kuku-5f8596b4b7-zlqdv Pulling image "quay.io/ooichman/ubuntu-minimal"
0s Normal Pulled pod/ubuntu-kuku-5f8596b4b7-zlqdv Successfully pulled image "quay.io/ooichman/ubuntu-minimal"
0s Normal Created pod/ubuntu-kuku-5f8596b4b7-zlqdv Created container ubuntu-minimal
0s Normal Started pod/ubuntu-kuku-5f8596b4b7-zlqdv Started container ubuntu-minimal

GREAT !!!!!!

As you can see , all the pods are working and running :

# oc get pods
NAME READY STATUS RESTARTS AGE
ubuntu-kuku-5f8596b4b7-69pss 1/1 Running 0 13s

A real life scenario that can help us get a better use case of admission controller such as :

  1. making sure labels exists
  2. forcing the image pull policy on the pods
  3. validation mutation
  4. running external tasks and validating (creating a new git repository when a new name space is been created for example)

we can use this tools to enforce any kind of policy we want. A Tutorial about the code (GO LANG) will be out shortly.

That is it !!!
Have fun …

--

--