Last updated on May 10th, 2024 at 07:42 am

In this tutorial I will walk you through 4 simple steps for creating a Validating webhook in Kubernetes cluster and an example of adding validation based on your use case.

Before we begin make sure that you have a running Kubernetes cluster, kubectl should be installed to manage that cluster. One other command line tool required is openssl.

An Admission controller can be validatingmutating, or both. Mutating controllers may modify objects related to the requests they admit; validating controllers may not.

In this example I will be creating a Validating controller. More details can be found in these documents

One of the cool feature of webhooks is that you can write your own logic on how an object for example a deployment needs to be created, whether it has to have a specific label or allow those deployments to be created on a given day of the week etc., . You can develop these logic in any programming language (python, php, go etc.,) as long as you can catch a post request and create a JSON response to allow or forbid the request

For example, below JSON response from your webhook allow the request to go through, since allowed key is having value true. You can capture the UID of a specific request from the POST payload.

  "apiVersion": "",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": true

A sample request POST payload looks like this, (truncated) – you can see the uid inside request block. This gets triggered when someone try to create a new object like Deployment / PODS – depending on the resources you provided as part of the rules (inside ValidatingWebhookConfiguration)


Without further delay lets jump on to the setup

Step 1 Create Self Signed Certificate

Using openssl we are going to create a self signed certificate. If you have requirement to use your own CA signed certificates, use them accordingly. Here I am going to create certificates for my service endpoint named “mywebhook.default.svc” (replace that with your own service name – if required)

$ openssl req -x509 -sha256 -newkey rsa:2048 -keyout server.key -out server.crt -days 1024 -nodes -addext "subjectAltName = DNS.1:mywebhook.default.svc"
Generating a 2048 bit RSA private key
writing new private key to 'server.key'
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letter code) []:US
State or Province Name (full name) []:
Locality Name (eg, city) []:
Organization Name (eg, company) []:
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:mywebhook.default.svc
Email Address []:

Now that the SSL certificates are created, lets verify the cert (truncated)

$ openssl x509 -in server.crt -text -noout
        Version: 3 (0x2)
        Serial Number:
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, CN=mywebhook.default.svc
            Not Before: Mar 21 17:02:38 2024 GMT
            Not After : Jan  9 17:02:38 2027 GMT
        Subject: C=US, CN=mywebhook.default.svc
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Alternative Name:
    Signature Algorithm: sha256WithRSAEncryption

We now have the certs ready.

Step 2 Deploy docker image

For the benefit of this guide I am using my own custom repo. You are free to use your own.

Let us create a PHP code to accept the POST request and process the data. We are extracting the uid and label from the payload then accordingly create a response_data. Write it to application log and also echo the json payload for the webhook to process it.

For example, in your deployment manifest if there is no label with key as environment and value as stage the webhook will reject the request. We will see more details with sample output in the step 4 section.

We are also parsing the POST request and writing that to a log file. There will be 2 log entries, first one will show the response along with the label extracted and the second is just a dump of post request from kind AdmissionReview. Adding logs will enable us to easily debug and trace the issue if there are any errors while triggering the webhook.

$raw_payload = file_get_contents('php://input', true);
$payload = json_decode($raw_payload, true);
$uid = $payload['request']['uid'];
$label = $payload['request']['object']['metadata']['labels'];
if ($label['environment'] == 'stage')
	$message="Webhook validation passed";
	$message="You cannot do this because the environment is not tagged as stage. Current value is ".$label['environment'];
$response_data = [
   "apiVersion" => "",
   "kind" => "AdmissionReview",
   "response" => [
         "uid" => $uid,
         "allowed" => $validate,
		 "status" =>  [
		       "code" => $code,
		       "message" => $message
$response_data_from_app= json_encode($response_data);
$log = date("Y-m-d h:i:sa")." - ".$uid." - ".$response_data_from_app." - ".$label['environment']."\n";
$original = date("Y-m-d h:i:sa")." - ".$uid." - ".$json."\n";
file_put_contents('./log_'.date("j.n.Y").'.log', $log, FILE_APPEND);
file_put_contents('./log_'.date("j.n.Y").'.log', $original, FILE_APPEND);
$json= json_encode($response_data);
echo header("Content-Type: application/json; charset=utf-8");
echo stripslashes(trim($json));

My Dockerfile

# Derived from official PHP image (our base image)
FROM php:7.2-apache
COPY server.key /etc/ssl/private/
COPY server.crt /etc/ssl/certs/
COPY index.php /var/www/html
RUN sed -i s/ssl-cert-snakeoil.key/server.key/ /etc/apache2/sites-available/default-ssl.conf
RUN sed -i s/ssl-cert-snakeoil.pem/server.crt/ /etc/apache2/sites-available/default-ssl.conf
RUN a2enmod ssl
RUN a2ensite default-ssl

All I am doing here is enabling SSL for Apache and copying the certs we created in Step 1.

We are all set, build it and push it in your own repository. I am using Docker to save my image.

Step 3 Configure Kubernetes cluster with Webhook

Now that we have the image set up for the webhook. Since my image resides in private repo I have to create a secret for use with Docker registry (modify the values accordingly if you are using private repo)

$ kubectl create secret docker-registry my-registry-secret--docker-server= --docker-username=xxx--docker-password=xxx [email protected]

Now lets create a deployment using the below manifest, name it deployment.yaml

apiVersion: apps/v1
kind: Deployment
  name: validation-webhook
    app: validate
  replicas: 1
      app: validate
        app: validate
      - name: webhook
        image: xxx/mytestproject:apache-php-webhook-working
        - containerPort: 443
     - name: my-registry-secret

Lets create a service from the deployment above – service.yaml, all we are doing here is selecting the pod with the label app=validate. The port we are connecting is 443

$ cat service.yaml
apiVersion: v1
kind: Service
  name: mywebhook
    app: validate
  - port: 443

We now have a Webhook reachable at https://mywebhook.default.svc

Next step is to have the webhook.yaml deployed for ValidatingWebhookConfiguration kind.

kind: ValidatingWebhookConfiguration
  name: validating-webhook
  - name: mywebhook.default.svc
    failurePolicy: Fail
    sideEffects: None
    admissionReviewVersions: ["v1","v1beta1"]
      - apiGroups: ["apps", ""]
          - "deployments"
        apiVersions: [ "v1" ]
          - CREATE
        name: mywebhook
        namespace: default
        path: /

Execute this command to get the BASE64 ENCODED SERVER.CRT DATA encoded value of the server.crt file (truncated).

$ cat server.crt |base64  | tr -d "\n"

In the example above, we intercept DEPLOYMENT objects and CREATE operations – all new deployments that are going to be created in the Kubernetes cluster.

Once the above manifests are ready run these commands

$ kubectl create -f deployment.yaml
$ kubectl create -f webhook.yaml

Step 4 Write validating Webhook

For the validation test I have a deployment manifest file – lets call it test_nginx_deployment.yaml

apiVersion: apps/v1
kind: Deployment
  creationTimestamp: null
    app: nginx
    environment: dev
  name: nginx
  replicas: 1
      app: nginx
  strategy: {}
      creationTimestamp: null
        app: nginx
      - image: nginx
        name: nginx
        resources: {}
status: {}

In the example above environment is labeled as dev and not stage. So if someone deploy the above test_nginx_deployment.yaml, our validation webhook should prevent the creation of this deployment, since it fails the label check.

$ kubectl apply -f test_nginx_deployment.yaml
Error from server: error when creating "test_nginx_deployment.yaml": admission webhook "mywebhook.default.svc" denied the request: You cannot do this because the environment is not tagged as stage. Current value is dev

Update the label from dev to stage and try deployment again. If that goes through then our webhook validation is working as expected.

Sample application logs (truncated – I have added the validation fail and successful logs). As you can see it is correctly hitting the service endpoint.

#After changing the label from dev to stage the deployment was successful
$ kubectl apply -f test_nginx_deployment.yaml
deployment.apps/nginx created

#Lets check the logs for today
$ kubectl exec -it validation-webhook-68fc59f6f5-8txms -- cat /var/www/html/log_22.3.2024.log

#Validation failed due to wrong label
2024-03-22 06:40:41pm - d8fc22f8-18ba-454a-852b-01985d9c5857 - {"apiVersion":"\/v1","kind":"AdmissionReview","response":{"uid":"d8fc22f8-18ba-454a-852b-01985d9c5857","allowed":false,"status":{"code":403,"message":"You cannot do this because the environment is not tagged as stage. Current value is stage1"}}} - dev
2024-03-22 06:40:41pm - d8fc22f8-18ba-454a-852b-01985d9c5857 - {"kind":"AdmissionReview","apiVersion":"\/v1","request":{"uid":"d8fc22f8-18ba-454a-852b-01985d9c5857","kind":{"group":"apps","version":"v1","kind":"Deployment"},"resource":{"group":"apps","version":"v1","resource":"deployments"},"requestKind":{"group":"apps","version":"v1","kind":"Deployment"},"requestResource":{"group":"apps","version":"v1","resource":"deployments"},"name":"nginx","namespace":"default","operation":"CREATE","userInfo":{"username":"kubernetes-admin","uid":"aws-iam-authenticator:xxxx:AROLHCAOQ","creationTimestamp":"2024-03-22T18:40:41Z","labels":{"app":"nginx","environment":"dev"}

#Validation successful, label found
2024-03-22 07:12:32pm - 07ed1324-7c1c-4148-b4c0-860bf84b068c - {"apiVersion":"\/v1","kind":"AdmissionReview","response":{"uid":"07ed1324-7c1c-4148-b4c0-860bf84b068c","allowed":true,"status":{"code":200,"message":"Webhook validation passed"}}} - stage
2024-03-22 07:12:32pm - 07ed1324-7c1c-4148-b4c0-860bf84b068c - {"kind":"AdmissionReview","apiVersion":"\/v1","request":{"uid":"07ed1324-7c1c-4148-b4c0-860bf84b068c","kind":{"group":"apps","version":"v1","kind":"Deployment"},"resource":{"group":"apps","version":"v1","resource":"deployments"},"requestKind":{"group":"apps","version":"v1","kind":"Deployment"},"requestResource":{"group":"apps","version":"v1","resource":"deployments"},"name":"nginx","namespace":"default","operation":"CREATE","userInfo":{"username":"kubernetes-admin","uid":"aws-iam-authenticator:xxxx:AROLHCAOQ","creationTimestamp":"2024-03-22T19:12:32Z","labels":{"app":"nginx","environment":"stage"}

Thanks for your time. Please let me know if you have any questions.

Leave a Reply

Your email address will not be published. Required fields are marked *