Seguridad de imágenes de contenedor con Wazuh y Trivy

Docker + Trivy + Wazuh Command module + decoders + reglas personalizadas

Documentación técnica para integrar Trivy con Wazuh siguiendo el flujo de instalación, configuración y validación de la práctica, manteniendo un ruleset personalizado con identificadores propios.

1. Introducción

Esta práctica integra Trivy con Wazuh para analizar vulnerabilidades en imágenes Docker. El endpoint Ubuntu ejecuta un script mediante el módulo command, genera una salida estructurada con el prefijo Trivy: y envía el resultado al manager para su decodificación y clasificación por severidad.

La secuencia sigue el mismo orden operativo de la documentación técnica: primero el endpoint, después el manager y, finalmente, la validación con wazuh-logtest y con eventos reales.

2. Instalar Docker Engine y descargar imágenes

Los pasos siguientes instalan Docker Engine y despliegan un conjunto de imágenes base en el endpoint Ubuntu.

Se requieren privilegios de root para ejecutar los comandos de esta sección.

2.1 Actualizar paquetes e instalar cURL

Actualizar paquetes
apt update && apt install curl -y

2.2 Descargar y ejecutar el instalador de Docker

Instalar Docker Engine
curl -sSL https://get.docker.com/ | sh

2.3 Crear el directorio del entorno de contenedores

Crear y acceder al directorio /docker
mkdir -p /docker && cd /docker

2.4 Crear el archivo docker-compose.yml

Editar docker-compose.yml
nano /docker/docker-compose.yml
Contenido de docker-compose.yml
services:
  api:
    image: python:3.9-alpine
  db:
    image: postgres:alpine
  redis:
    image: redis:alpine
La configuración define un entorno de prueba con tres servicios: una imagen base de Python, PostgreSQL y Redis. El objetivo es analizar las imágenes base python:3.9-alpine, postgres:alpine y redis:alpine.

2.5 Descargar las imágenes con Docker Compose

Descargar imágenes
cd /docker
docker compose pull

2.6 Verificar que las imágenes están presentes

Listar imágenes Docker
docker image ls
Salida esperada de ejemplo
REPOSITORY   TAG           IMAGE ID        CREATED          SIZE
postgres     alpine        2bf60600670f    8 days ago       278MB
redis        alpine        ee33180a8437    5 weeks ago      41.4MB
python       3.9-alpine    8831b2c8c07c    2 months ago     49.1MB

3. Instalar Trivy

Los pasos siguientes instalan Trivy en el endpoint Ubuntu.

3.1 Instalar dependencias requeridas

Instalar dependencias
apt-get install -y wget apt-transport-https gnupg lsb-release

3.2 Importar la clave del repositorio de Trivy

Importar clave GPG
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | tee /usr/share/keyrings/trivy.gpg > /dev/null

3.3 Añadir el repositorio APT de Trivy

Añadir repositorio
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | tee -a /etc/apt/sources.list.d/trivy.list

3.4 Actualizar índices de paquetes

Actualizar repositorios
apt-get update

3.5 Instalar Trivy

Instalar paquete Trivy
apt-get install trivy

3.6 Verificar la instalación

Comprobar versión de Trivy
trivy --version
Salida esperada de ejemplo
Version: 0.59.1

4. Crear el script personalizado y configurar el módulo Command

El script detecta las imágenes existentes en el endpoint y ejecuta un análisis de vulnerabilidades con Trivy sobre cada una. El módulo command ejecuta el script periódicamente y Wazuh trata su salida como contenido de log para su análisis posterior.

4.1 Crear el directorio y editar el script

Crear directorio y editar trivy_scan.sh
mkdir -p /var/ossec/custom-script/ && nano /var/ossec/custom-script/trivy_scan.sh

4.2 Contenido de /var/ossec/custom-script/trivy_scan.sh

Script completo
#!/bin/bash
# Copyright (C) 2015-2025, Wazuh Inc.

# Directory to save the custom output template
TEMPLATE_DIR="/tmp"
TEMPLATE_FILE="$TEMPLATE_DIR/trivy-custom.tmpl"

# Create the custom output template
cat <<'EOL' > "$TEMPLATE_FILE"
"Package","Version Installed","Vulnerability ID","Severity"
{{- range $ri, $r := . }}
{{- range $vi, $v := .Vulnerabilities }}
"{{ $v.PkgName }}","{{$v.InstalledVersion }}","{{ $v.VulnerabilityID }}","{{$v.Severity }}","{{$v.Title }}"
{{- end}}
{{- end }}
EOL

# Retrieve list of container images (including both repository and tag)
images=$(docker images --format "{{.Repository}}:{{.Tag}}")
if [ -z "$images" ]; then
  echo "No images found. Exiting..."
  exit 1
fi

# Loop through each container image and run Trivy scan
for image in $images; do
  # Run Trivy scan on the current image using the custom output template
  trivy_output=$(trivy --scanners vuln i -q --format template --template "@/tmp/trivy-custom.tmpl" "$image")

  # Check if the scan was successful
  if [ $? -ne 0 ]; then
    echo "Error running Trivy scan on image $image. Skipping..."
    continue
  fi

  # Process Trivy output for the current image
  while IFS= read -r line; do
    # Prepend image name with "Trivy:", followed by image name and a comma
    formatted_line="Trivy:\"$image\",$line"
    # Print the formatted line with quoted image name
    echo "$formatted_line"
  done <<< "$trivy_output"
done

# Clean up the custom output template
rm -f "$TEMPLATE_FILE"
El script crea una plantilla temporal de salida para Trivy, obtiene la lista de imágenes, ejecuta un escaneo sobre cada una, añade el prefijo Trivy: al inicio de cada línea y elimina la plantilla temporal al finalizar.

4.3 Asignar permisos de ejecución

Permisos del script
chmod 750 /var/ossec/custom-script/trivy_scan.sh

4.4 Ajustar propietario del script y su directorio

Propietario del directorio y script
chown root:wazuh /var/ossec/custom-script/ -R

4.5 Probar el script manualmente en el agente

Ejecución manual del script
/var/ossec/custom-script/trivy_scan.sh | head -n 20
Una salida válida debe comenzar con líneas en formato Trivy:"imagen","paquete","version","CVE","severity","titulo".

4.6 Editar la configuración del agente

Editar ossec.conf del agente
nano /var/ossec/etc/ossec.conf

4.7 Añadir el bloque del módulo Command

Bloque wodle command
<!-- Trivy container vulnerability scanner script -->
<wodle name="command">
  <disabled>no</disabled>
  <command>/var/ossec/custom-script/trivy_scan.sh</command>
  <interval>3d</interval>
  <ignore_output>no</ignore_output>
  <run_on_start>yes</run_on_start>
  <timeout>0</timeout>
</wodle>

4.8 Significado de los parámetros del módulo Command

ParámetroFunción
disabledIndica si la configuración del módulo está activa. El valor no mantiene habilitada la ejecución.
commandDefine la ruta del script trivy_scan.sh que se ejecutará en el endpoint.
intervalEstablece el intervalo de ejecución del script. En esta configuración se ejecuta cada 3 días.
ignore_outputDetermina si la salida del comando se ignora o se procesa. El valor no permite que Wazuh la procese y la registre.
run_on_startEjecuta la configuración del módulo inmediatamente después del arranque del servicio del agente.
timeoutDefine el tiempo máximo de ejecución. El valor 0 permite la ejecución sin restricciones de tiempo impuestas por el módulo.

5. Reiniciar el agente

El reinicio del agente aplica la configuración del módulo command y permite la ejecución inmediata del script si run_on_start está habilitado.

Reiniciar wazuh-agent
systemctl restart wazuh-agent

6. Crear decoders y reglas en el manager

Las acciones de esta sección se realizan en el Wazuh server. Se requieren privilegios de root.

6.1 Crear el archivo de decoders

Editar trivy_decoders.xml
nano /var/ossec/etc/decoders/trivy_decoders.xml

6.2 Añadir los decoders personalizados

Contenido de trivy_decoders.xml
<decoder name="trivy-decoder">
  <prematch>^Trivy:</prematch>
</decoder>

<decoder name="trivy-decoder-fields">
  <parent>trivy-decoder</parent>
  <regex offset="after_parent">"(\.+)","(\.+)","(\.+)","(\.+)","(\.+)","(\.+)"</regex>
  <order>image, package, version, vulnerability_id, severity, description</order>
</decoder>

6.3 Crear el archivo de reglas

Editar trivy_rules.xml
nano /var/ossec/etc/rules/trivy_rules.xml

6.4 Añadir las reglas personalizadas

Contenido de trivy_rules.xml
<group name="trivy,">
  <rule id="100250" level="0">
    <decoded_as>trivy-decoder</decoded_as>
    <description>Trivy alert detected.</description>
  </rule>

  <rule id="100251" level="14">
    <if_sid>100250</if_sid>
    <field name="severity">CRITICAL</field>
    <description>Trivy alert [CRITICAL]: Vulnerabilty '$(vulnerability_id)' detected in package '$(package)' version '$(version)' on container image '$(image)'.</description>
  </rule>

  <rule id="100252" level="12">
    <if_sid>100250</if_sid>
    <field name="severity">HIGH</field>
    <description>Trivy alert [HIGH]: Vulnerabilty '$(vulnerability_id)' detected in package '$(package)' version '$(version)' on container image '$(image)'.</description>
  </rule>

  <rule id="100253" level="7">
    <if_sid>100250</if_sid>
    <field name="severity">MEDIUM</field>
    <description>Trivy alert [MEDIUM]: Vulnerabilty '$(vulnerability_id)' detected in package '$(package)' version '$(version)' on container image '$(image)'.</description>
  </rule>

  <rule id="100254" level="4">
    <if_sid>100250</if_sid>
    <field name="severity">LOW</field>
    <description>Trivy alert [LOW]: Vulnerabilty '$(vulnerability_id)' detected in package '$(package)' version '$(version)' on container image '$(image)'.</description>
  </rule>

  <rule id="100255" level="7">
    <if_sid>100250</if_sid>
    <field name="severity">UNKNOWN</field>
    <description>Trivy alert [UNKNOWN]: Vulnerabilty '$(vulnerability_id)' detected in package '$(package)' version '$(version)' on container image '$(image)'.</description>
  </rule>
</group>

6.5 Significado de los Rule ID utilizados

Rule IDFunción
100250Detecta la salida del escaneo de Trivy. Esta regla no genera alertas visibles en el dashboard al tener nivel 0.
100251Detecta vulnerabilidades con severidad CRITICAL.
100252Detecta vulnerabilidades con severidad HIGH.
100253Detecta vulnerabilidades con severidad MEDIUM.
100254Detecta vulnerabilidades con severidad LOW.
100255Detecta vulnerabilidades con severidad UNKNOWN.

6.6 Ajustar propietario y permisos

Propietario y permisos de decoders y reglas
chown wazuh:wazuh /var/ossec/etc/rules/trivy_rules.xml /var/ossec/etc/decoders/trivy_decoders.xml
chmod 660 /var/ossec/etc/rules/trivy_rules.xml /var/ossec/etc/decoders/trivy_decoders.xml

7. Reiniciar el manager

El reinicio del manager aplica los nuevos decoders y reglas.

Reiniciar wazuh-manager
systemctl restart wazuh-manager

8. Validación con wazuh-logtest

La validación con wazuh-logtest permite comprobar que el decoder extrae los campos esperados y que la regla correspondiente se activará según la severidad del evento.

Ejecutar wazuh-logtest
/var/ossec/bin/wazuh-logtest
Evento de prueba
Trivy:"python:3.9-alpine","jaraco.context","5.3.0","CVE-2026-23949","HIGH","jaraco.context: jaraco.context: Path traversal via malicious tar archives"
Resultado esperado
Phase 2:
  name: 'trivy-decoder'
  image: 'python:3.9-alpine'
  package: 'jaraco.context'
  version: '5.3.0'
  vulnerability_id: 'CVE-2026-23949'
  severity: 'HIGH'
  description: 'jaraco.context: jaraco.context: Path traversal via malicious tar archives'

Phase 3:
  id: '100252'
  description: 'Trivy alert [HIGH]: Vulnerabilty ...'
  groups: '['trivy']'
  Alert to be generated.

9. Visualización en Wazuh dashboard

Tras una ejecución real del agente, las alertas pueden revisarse en los archivos del manager y en Discover o en los paneles del dashboard.

Comprobar alerts.json
grep 'Trivy alert' /var/ossec/logs/alerts/alerts.json | tail -n 20
grep '"id":"10025' /var/ossec/logs/alerts/alerts.json | tail -n 20
Comprobar archives.json
grep 'Trivy:' /var/ossec/logs/archives/archives.json | tail -n 20
En el dashboard pueden utilizarse búsquedas como rule.groups:trivy, rule.id:100252 o el nombre de la imagen analizada.

10. Validación con eventos reales

10.1 Reiniciar el agente para lanzar el wodle

Reiniciar el agente
systemctl restart wazuh-agent

10.2 Confirmar la presencia de eventos y alertas

Revisar archivos del manager
grep 'Trivy:' /var/ossec/logs/archives/archives.json | tail -n 10
grep 'Trivy alert' /var/ossec/logs/alerts/alerts.json | tail -n 10

10.3 Reiniciar Filebeat si las alertas no se visualizan

Reiniciar Filebeat
systemctl restart filebeat
systemctl status filebeat
tail -f /var/log/filebeat/filebeat

11. Errores comunes

ProblemaCausa probableSolución
El script no devuelve vulnerabilidadesTrivy no puede descargar la base de datos o no existen imágenes localesComprobar conectividad, validar la descarga de la base de datos y revisar docker image ls.
El evento aparece en archives.json pero no en alerts.jsonLas reglas no casan o el ruleset no está cargadoValidar con wazuh-logtest y reiniciar wazuh-manager.
El dashboard no muestra alertasLas alertas no se han indexado todavía o los filtros no coincidenComprobar primero alerts.json, reiniciar filebeat y revisar los filtros del dashboard.
El agente no puede ejecutar el scriptPermisos insuficientes sobre el script o sobre DockerRevisar permisos, propietario y acceso al motor Docker.