Penguin-KarChunTKarChunT

Scan images using admission controller

Understand how to 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

openssl.cnf
[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.

trivy-webhook.py
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.

Dockerfile
# 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

deployment.yaml
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

validating-webhook.yaml
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

sample-pod.yaml
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

On this page