Scan images using admission controller
We can use Trivy as an admission controller to scan images before they are deployed in a Kubernetes cluster. This helps ensure that only images that meet your security policies are allowed to run in your environment.
With this approach, it might delay the deployment process because the admission controller will scan the image everytime before it is deployed and if the image is not compliant, it will block the deployment. So, the alternative way is to have your own internal registry with all pre-scanned images and use that registry in your deployment process. This way, the admission controller will only scan the images that are not in your internal registry.
Steps to scan images using Trivy with an admission controller
Step 1: Create TLS Certificates
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = trivy-service.default.svc
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = trivy-service.default.svc
DNS.2 = trivy-service
DNS.3 = trivy-service.default
DNS.4 = localhost
DNS.5 = trivy-service.default.svc.cluster.local
# Step 1: Generate a Certificate Authority (CA)
# Generate the CA private key
openssl genrsa -out ca.key 2048
# Generate the CA certificate
openssl req -x509 -new -nodes -key ca.key -sha256 -days 365 -out ca.crt -subj "/CN=trivy-ca"
# Step 2: Generate the Server Key and Certificate Signing Request (CSR)
# Generate the server private key
openssl genrsa -out server-key.pem 2048
# Generate the CSR using the openssl.cnf file
openssl req -new -key server-key.pem -out server.csr -config openssl.cnf
# Step 3: Sign the Server Certificate with the CA
# Sign the server certificate
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server-cert.pem -days 365 -extensions v3_req -extfile openssl.cnf
# Step 4: Base64 Encode the CA Certificate for the caBundle
# Base64 encode the CA certificate
cat ca.crt | base64 -w 0 # copy and replace <base64-encoded-CA-cert> in your validating-webhook.yaml file.
Step 2: Create a Webhook Service
Create a simple Flask application that will act as a webhook server. This server will receive admission review requests from the Kubernetes API server and respond with whether the image is allowed or not.
import subprocess
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/validate', methods=['POST'])
def validate():
admission_review = request.get_json()
pod_spec = admission_review['request']['object']['spec']['containers']
for container in pod_spec:
image = container['image']
# Scan the image using Trivy
result = subprocess.run(
["trivy", "image", "--quiet", "--severity", "CRITICAL", image],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
if result.returncode != 0:
return jsonify({
"response": {
"allowed": False,
"status": {
"message": f"Image {image} has critical vulnerabilities."
}
}
})
return jsonify({"response": {"allowed": True}})
if __name__ == '__main__':
context = ('/etc/webhook/certs/server-cert.pem', '/etc/webhook/certs/server-key.pem')
app.run(host='0.0.0.0', port=443, ssl_context=context)
Then, create a Dockerfile
for the webhook server.
# Use Python as the base image
FROM python:3.9-slim
# Install Trivy
RUN apt-get update && apt-get install -y wget \
&& wget -qO- https://github.com/aquasecurity/trivy/releases/latest/download/trivy_0.45.0_Linux-64bit.tar.gz | tar zxv \
&& mv trivy /usr/local/bin/ \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Set the working directory
WORKDIR /app
# Copy the webhook service code
COPY webhook.py /app/webhook.py
# Install Python dependencies
RUN pip install flask
# Expose the webhook service port
EXPOSE 443
# Run the webhook service
CMD ["python", "webhook.py"]
Build the Docker image.
docker build -t trivy-webhook .
Step 3: Deploy the Webhook Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: trivy-webhook
spec:
replicas: 1
selector:
matchLabels:
app: trivy-webhook
template:
metadata:
labels:
app: trivy-webhook
spec:
containers:
- name: trivy-webhook
image: trivy-webhook:latest
ports:
- containerPort: 443
volumeMounts:
- name: webhook-certs
mountPath: /etc/webhook/certs
volumes:
- name: webhook-certs
secret:
secretName: trivy-webhook-certs
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
Step 4: Create validating webhook configuration
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: trivy-image-scan-webhook
webhooks:
- name: trivy-scan.example.com
clientConfig:
service:
name: trivy-service
namespace: default
path: /validate
caBundle: <base64-encoded-CA-cert>
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
kubectl apply -f validating-webhook.yaml
Step 5: Test the Webhook
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: test-container
image: alpine:3.10
kubectl apply -f sample-pod.yaml
If the webhook is working correctly, the pod creation should be blocked, and you should see an error message similar to:
Error from server (Image alpine:3.10 has critical vulnerabilities.): admission webhook "trivy-scan.example.com" denied the request