ImagePolicy Webhook in Kubernetes Part 2
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.