ImagePolicy Webhook in Kubernetes Part 2

Pramodh Kumar M
3 min readApr 28, 2024

--

In the previous article, I provided an overview of the admission controller, covering its general functionality and offering a high-level explanation of its implementation.

In this step-by-step guide, we will walk through the process of implementing an admission controller. The specific Linux operating system you use is not crucial, as long as you have Python 3 and Docker installed on your system.

Below are the commands to set up the project

#check python version
python3 -V

#ensure you have pip and venv installed, below is an example for ubuntu
sudo apt install -y python3-pip
sudo apt install -y build-essential libssl-dev libffi-dev python3-dev
sudo apt install -y python3-venv

mkdir environments
cd environments

python3 -m venv k8s
source k8s/bin/activate
cd k8s

pip install flask

Create image_policy_webhook.py file

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/validate', methods=['POST'])
def validate_image():
allowed_registries = ['registry.example.com', 'docker.io']

admission_review = request.get_json()
pod = admission_review['request']['object']

for container in pod['spec']['containers']:
image = container['image']
registry = image.split('/')[0]

if registry not in allowed_registries:
return jsonify({
'apiVersion': 'admission.k8s.io/v1',
'kind': 'AdmissionReview',
'response': {
'uid': admission_review['request']['uid'],
'allowed': False,
'status': {
'message': f"Image {image} is not allowed. Allowed registries: {', '.join(allowed_registries)}"
}
}
})

return jsonify({
'apiVersion': 'admission.k8s.io/v1',
'kind': 'AdmissionReview',
'response': {
'uid': admission_review['request']['uid'],
'allowed': True
}
})

if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, ssl_context=('tls.crt', 'tls.key'))

Create requirements.txt file

pip freeze > requirements.txt

Create Dockerfile

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY image_policy_webhook.py .

CMD ["python", "image_policy_webhook.py"]
docker build -t image-policy-webhook .

Generate the certificate with the configuration file webhook-server.conf

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
CN = image-policy-webhook.default.svc

[v3_req]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:image-policy-webhook.default.svc

The command below shows that I have generated the certificate in the master node where K8s is set up. The path for the CA certs is the default (/etc/kubernetes/pki/ca.key & /etc/kubernetes/pki/ca.key)

openssl genrsa -out webhook-server-key.pem 2048

openssl req -new -key webhook-server-key.pem -out webhook-server.csr -config webhook-server.conf

sudo openssl x509 -req -in webhook-server.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out webhook-server-cert.pem -days 365 -extfile webhook-server.conf -extensions v3_req

sudo openssl x509 -in webhook-server-cert.pem -text -noout

mv webhook-server-key.pem tls.key

mv webhook-server-cert.pem tls.crt

Create Secret

kubectl create secret tls tls-secret --cert=tls.crt --key=tls.key

Create a new file named webhook-deployment.yaml and add the following content

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: image-policy-webhook
spec:
replicas: 1
selector:
matchLabels:
app: image-policy-webhook
template:
metadata:
labels:
app: image-policy-webhook
spec:
containers:
- name: webhook
image: image-policy-webhook
ports:
- containerPort: 8080
volumeMounts:
- name: tls-certs
mountPath: /app/certs
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: tls-secret
---
apiVersion: v1
kind: Service
metadata:
name: image-policy-webhook
spec:
selector:
app: image-policy-webhook
ports:
- port: 443
targetPort: 8080

Apply the deployment YAML file

kubectl apply -f webhook-deployment.yaml

Create a new file named validating-webhook-configuration.yaml and add the following content

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: image-policy-webhook
webhooks:
- name: image-policy.example.com
clientConfig:
service:
name: image-policy-webhook
namespace: default
path: "/validate"
port: 443
caBundle: "<base64-encoded-ca-certificate>"
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None

Replace <base64-encoded-certificate> with the base64-encoded certificate obtained using the below command

cat tls.crt | base64 | tr -d '\n'

Apply the ValidatingWebhookConfiguration YAML file

kubectl apply -f validating-webhook-configuration.yaml

Test the Image Policy Webhook

Create a pod with an allowed image:

apiVersion: v1
kind: Pod
metadata:
name: allowed-pod
spec:
containers:
- name: test-container
image: docker.io/nginx:latest

The pod should be created successfully.

Create a pod with a disallowed image:

apiVersion: v1
kind: Pod
metadata:
name: disallowed-pod
spec:
containers:
- name: test-container
image: gcr.io/google-containers/nginx:latest

The pod creation should be rejected with the reason “Image not allowed”.

That’s it! You have now implemented the Image Policy Webhook in your Kubernetes cluster. The webhook server will intercept pod creation and update requests and allow or reject them based on the specified image policy.

Note: Make sure to replace the registry with your actual container registry URL and adjust the allowed image name in the webhook server code according to your requirements.

--

--

No responses yet