Análisis de Vulnerabilidades usando Clair
Desarrollo del tema

Clair es un proyecto de open source que proporciona una herramienta para monitorear la seguridad de sus contenedores a través del análisis estático de vulnerabilidades en contenedores appc y docker. Clair es un motor de análisis basado en API que inspecciona los contenedores capa por capa en busca de fallas de seguridad conocidas. Con Clair, puede crear fácilmente servicios que proporcionen un monitoreo continuo de las vulnerabilidades de los contenedores.
Los datos de vulnerabilidad se importan continuamente desde un conjunto conocido de fuentes y se correlacionan con el contenido indexado de las imágenes del contenedor para producir listas de vulnerabilidades que amenazan un contenedor. Cuando los datos de vulnerabilidad cambian en sentido ascendente, el estado anterior y el nuevo estado de la vulnerabilidad junto con las imágenes a las que afectan se pueden enviar a través de un webhook a un punto final configurado. Todos los componentes principales se pueden personalizar mediante programación en tiempo de compilación sin bifurcar el proyecto. Clair apoya la seguridad de los contenedores mediante:
-
Actualizar datos de vulnerabilidad de un conjunto de fuentes que usted define y almacenar estos datos en su base de datos.
-
Permitir a los clientes consultar esta base de datos en busca de vulnerabilidades dentro de imágenes específicas mediante el uso de una API.
-
Indexación de imágenes de contenedores con una lista de características presentes en la imagen mediante el uso de una API
Laboratorio: Análisis usando Clair
Descripción
En la presente guía se creara una imagen de contenedor y se procederá con el respectivo análisis usando la herramienta Clair.
Objetivos
- Instalar Clair y usarlo para analizar una imagen de contenedor.
Antes de comenzar
- Contar con el acceso al ambiente del laboratorio.
Inicio de laboratorio
-
Crearemos un archivo Docker Compose y Config siguiente manera:
mkdir -p example-clair/docker-compose-data/clair-config; touch example-clair/docker-compose.yaml; touch example-clair/docker-compose-data/clair-config/config.yml -
Con el editor de texto añadiremos el contenido según corresponda:
- Se agrega el contenido del archivo Docker Compose:
vim example-clair/docker-compose.yamlservices: postgres: image: postgres:14 restart: unless-stopped volumes: - ./docker-compose-data/postgres-data/:/var/lib/postgresql/data:rw environment: - POSTGRES_PASSWORD=ChangeMe - POSTGRES_USER=clair - POSTGRES_DB=clair clair: image: quay.io/projectquay/clair:4.7.4 restart: unless-stopped volumes: - ./docker-compose-data/clair-config/config.yaml:/etc/clair/config.yaml:ro environment: - CLAIR_CONF=/etc/clair/config.yaml - CLAIR_MODE=combo depends_on: postgres: condition: service_started ports: - "6060:6060" - "8089:8089"- Se agrega el contenido del archivo Config:
vim example-clair/docker-compose-data/clair-config/config.yamlhttp_listen_addr: 0.0.0.0:6060 introspection_addr: 0.0.0.0:8089 log_level: debug indexer: connstring: host=postgres port=5432 dbname=clair user=clair password=ChangeMe sslmode=disable scanlock_retry: 10 layer_scan_concurrency: 5 migrations: true matcher: connstring: host=postgres port=5432 dbname=clair user=clair password=ChangeMe sslmode=disable max_conn_pool: 100 migrations: true notifier: connstring: host=postgres port=5432 dbname=clair user=clair password=ChangeMe sslmode=disable migrations: true updaters: sets: - alpine - debian - ubuntu - aws - oracle -
Iniciamos el Docker Compose:
cd example-clair; sudo docker compose up -d -
Cambiamos de manera temporal los permisos del docker.sock:
sudo chmod 666 /var/run/docker.sock -
Descargamos una imagen de prueba:
sudo docker pull infoslack/dvwa -
Le damos permisos a la siguiente carpeta
sudo mkdir -p ./docker-compose-data/clairctl-reports sudo chmod 777 ./docker-compose-data/clairctl-reports -
Creamos el siguiente script para escanear la imagen.
#!/bin/bash set -e IMAGE=$1 CLAIR_URL="http://localhost:6060" WORK_DIR="/tmp/clair-scan-$$" HTTP_PORT=8088 HOST_IP="172.17.0.1" if [ -z "$IMAGE" ]; then echo "Usage: $0 <image:tag>" exit 1 fi echo "[*] Exporting image: $IMAGE" mkdir -p "$WORK_DIR" docker save "$IMAGE" -o "$WORK_DIR/image.tar" echo "[*] Extracting layers..." mkdir -p "$WORK_DIR/layers" tar -xf "$WORK_DIR/image.tar" -C "$WORK_DIR/layers" # Start HTTP server to serve layers echo "[*] Starting layer HTTP server on port $HTTP_PORT..." cd "$WORK_DIR/layers" python3 -m http.server $HTTP_PORT & HTTP_PID=$! cd - > /dev/null sleep 1 # Parse manifest to get layers and image digest MANIFEST="$WORK_DIR/layers/manifest.json" IMAGE_DIGEST=$(sha256sum "$WORK_DIR/image.tar" | awk '{print $1}') echo "[*] Building Clair manifest payload..." # Extract layer paths from manifest.json LAYERS=$(python3 - <<EOF import json, sys with open("$MANIFEST") as f: manifest = json.load(f) layers = manifest[0].get("Layers", []) result = [] for layer in layers: import hashlib with open("$WORK_DIR/layers/" + layer, "rb") as lf: digest = hashlib.sha256(lf.read()).hexdigest() result.append({ "hash": "sha256:" + digest, "uri": "http://$HOST_IP:$HTTP_PORT/" + layer, "headers": {} }) print(json.dumps(result)) EOF ) PAYLOAD=$(python3 -c " import json layers = $LAYERS payload = { 'hash': 'sha256:$IMAGE_DIGEST', 'layers': layers } print(json.dumps(payload, indent=2)) ") echo "[*] Submitting manifest to Clair indexer..." curl -s -X POST "$CLAIR_URL/indexer/api/v1/index_report" \ -H "Content-Type: application/json" \ -d "$PAYLOAD" | python3 -m json.tool echo "" echo "[*] Waiting for indexing to complete..." for i in $(seq 1 30); do STATE=$(curl -s "$CLAIR_URL/indexer/api/v1/index_report/sha256:$IMAGE_DIGEST" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('state',''))" 2>/dev/null) echo " State: $STATE" if [ "$STATE" = "IndexFinished" ] || [ "$STATE" = "IndexError" ]; then break fi sleep 5 done echo "" echo "[*] Fetching vulnerability report..." curl -s "$CLAIR_URL/matcher/api/v1/vulnerability_report/sha256:$IMAGE_DIGEST" | python3 -m json.tool > "./docker-compose-data/clairctl-reports/${IMAGE//\//_}_report.json" echo "" echo "[+] Report saved to: docker-compose-data/clairctl-reports/${IMAGE//\//_}_report.json" echo "[+] Vulnerability summary:" python3 -c " import json with open('./docker-compose-data/clairctl-reports/${IMAGE//\//_}_report.json') as f: report = json.load(f) vulns = report.get('vulnerabilities', {}) print(f' Total vulnerabilities found: {len(vulns)}') from collections import Counter severities = Counter(v.get('normalized_severity','Unknown') for v in vulns.values()) for sev, count in sorted(severities.items()): print(f' {sev}: {count}') " # Cleanup echo "" echo "[*] Cleaning up..." kill $HTTP_PID 2>/dev/null rm -rf "$WORK_DIR" echo "[+] Done." -
Observamos el reporte generado:
cat docker-compose-data/clairctl-reports/infoslack_dvwa_report.json -
Limpiamos el ambiente:
sudo docker compose down -v; cd ~ sudo chmod 660 /var/run/docker.sock