Wazuh SIEM con Docker: despliegue completo con SSL y reglas de cumplimiento

Qué es el ENS y por qué importa

El Esquema Nacional de Seguridad (ENS) es el marco normativo de ciberseguridad obligatorio para las Administraciones Públicas españolas y para las empresas privadas que prestan servicios a estas. Está regulado por el Real Decreto 311/2022 y establece los principios, requisitos y medidas de seguridad que deben aplicar los sistemas de información que manejan datos o servicios públicos.

En la práctica, el ENS clasifica los sistemas según el impacto que tendría un incidente de seguridad sobre la organización y los ciudadanos. De esa clasificación depende el nivel de medidas que hay que implementar.

Las tres categorías del ENS

El ENS define tres niveles de categorización:

Categoría Básica
Para sistemas cuyo compromiso tendría un impacto limitado. Suele aplicar a webs informativas, servicios internos de bajo riesgo o sistemas con datos no sensibles. Las medidas requeridas son las mínimas del marco.

Categoría Media
Para sistemas cuyo compromiso causaría un perjuicio considerable a la organización o a terceros. Es la categoría más habitual en entornos de gestión interna: ERP, portales de empleados, sistemas de RRHH, plataformas de gestión documental. Requiere controles activos de monitorización, gestión de incidentes y control de acceso.

Categoría Alta
Para sistemas críticos cuyo compromiso podría causar un daño grave o muy grave. Aplica a sistemas que gestionan infraestructuras críticas, datos de salud, sistemas judiciales o de defensa. Exige las medidas más estrictas del esquema.

La categoría media en la práctica

Un sistema clasificado como categoría media necesita, entre otros controles:

  • Monitorización continua de eventos de seguridad
  • Detección y gestión activa de incidentes
  • Control de acceso privilegiado y trazabilidad de acciones
  • Integridad de los ficheros del sistema
  • Registro y análisis de logs centralizado

Es exactamente aquí donde entra Wazuh.


Wazuh como plataforma de cumplimiento

Wazuh es una plataforma SIEM (Security Information and Event Management) open source que cubre de forma nativa la mayoría de controles de monitorización que exige la categoría media: análisis de logs en tiempo real, detección de intrusiones, monitorización de integridad de ficheros, inventario de software y respuesta activa ante incidentes.

En este artículo despliego un nodo único con Docker, genero los certificados TLS necesarios y añado reglas personalizadas orientadas a los controles de seguridad más habituales en entornos de categoría media.


Arquitectura del stack

El stack de Wazuh single-node tiene tres componentes:

  • wazuh.manager — motor de análisis y correlación de eventos
  • wazuh.indexer — almacenamiento basado en OpenSearch
  • wazuh.dashboard — interfaz web (OpenSearch Dashboards)

La comunicación entre componentes usa TLS mutuo con certificados propios, que generamos antes del primer arranque.

Estructura del proyecto

wazuh/
├── docker-compose.yml
├── generate-certs.yml
├── gen-certs.sh
├── deploy.sh
├── .env
└── config/
    ├── certs.yml
    ├── wazuh_manager/
    │   ├── wazuh_manager.conf
    │   └── local_rules.xml
    ├── wazuh_indexer/
    │   ├── wazuh.indexer.yml
    │   └── internal_users.yml
    └── wazuh_dashboard/
        ├── opensearch_dashboards.yml
        └── wazuh.yml

Variables de entorno

Crea el fichero .env a partir del ejemplo:

cp .env.example .env

Contenido mínimo:

INDEXER_ADMIN_PASSWORD=TU_PASSWORD_SEGURO
KIBANA_PASSWORD=TU_PASSWORD_KIBANA
API_PASSWORD=TU_PASSWORD_API

SMTP_HOST=mail.tudominio.com
SMTP_FROM=alertas@tudominio.com
SMTP_TO=admin@tudominio.com

Usa contraseñas de al menos 12 caracteres con mayúsculas, números y símbolos. El indexer las valida en el arranque.

Generar certificados TLS

Wazuh requiere certificados TLS para la comunicación interna entre manager, indexer y dashboard. El stack incluye un contenedor generador:

docker compose -f generate-certs.yml run --rm generator

Esto crea config/wazuh_indexer_ssl_certs/ con:
root-ca.pem — CA raíz autofirmada
wazuh.manager.pem / wazuh.manager-key.pem
wazuh.indexer.pem / wazuh.indexer-key.pem
wazuh.dashboard.pem / wazuh.dashboard-key.pem

docker-compose.yml

services:
  wazuh.manager:
    image: wazuh/wazuh-manager:4.9.2
    hostname: wazuh.manager
    restart: always
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 655360
        hard: 655360
    ports:
      - "1514:1514/tcp"   # agentes TCP
      - "1515:1515/tcp"   # enrollment
      - "5140:5140/udp"   # syslog externo
    environment:
      INDEXER_URL: https://wazuh.indexer:9200
      INDEXER_USERNAME: admin
      INDEXER_PASSWORD: ${INDEXER_ADMIN_PASSWORD}
      FILEBEAT_SSL_VERIFICATION_MODE: full
      SSL_CERTIFICATE_AUTHORITIES: /etc/ssl/root-ca.pem
      SSL_CERTIFICATE: /etc/ssl/filebeat.pem
      SSL_KEY: /etc/ssl/filebeat.key
      API_USERNAME: wazuh-wui
      API_PASSWORD: ${API_PASSWORD}
    volumes:
      - wazuh_api_configuration:/var/ossec/api/configuration
      - wazuh_etc:/var/ossec/etc
      - wazuh_logs:/var/ossec/logs
      - ./config/wazuh_indexer_ssl_certs/root-ca.pem:/etc/ssl/root-ca.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.manager.pem:/etc/ssl/filebeat.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key
      - ./config/wazuh_manager/local_rules.xml:/var/ossec/etc/rules/local_rules.xml
      - ./config/wazuh_manager/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf

  wazuh.indexer:
    image: wazuh/wazuh-indexer:4.9.2
    hostname: wazuh.indexer
    restart: always
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    environment:
      OPENSEARCH_JAVA_OPTS: "-Xms1g -Xmx1g"
    volumes:
      - wazuh-indexer-data:/var/lib/wazuh-indexer
      - ./config/wazuh_indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/opensearch.yml
      - ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/certs/root-ca.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/certs/wazuh.indexer.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/certs/wazuh.indexer-key.pem

  wazuh.dashboard:
    image: wazuh/wazuh-dashboard:4.9.2
    hostname: wazuh.dashboard
    restart: always
    ports:
      - "8443:5601"
    environment:
      INDEXER_USERNAME: admin
      INDEXER_PASSWORD: ${INDEXER_ADMIN_PASSWORD}
      WAZUH_API_URL: https://wazuh.manager
      DASHBOARD_USERNAME: kibanaserver
      DASHBOARD_PASSWORD: ${KIBANA_PASSWORD}
      API_USERNAME: wazuh-wui
      API_PASSWORD: ${API_PASSWORD}
    volumes:
      - ./config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml
      - ./config/wazuh_dashboard/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml
      - ./config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem
      - ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem
    depends_on:
      - wazuh.indexer

volumes:
  wazuh_api_configuration:
  wazuh_etc:
  wazuh_logs:
  wazuh_queue:
  wazuh_var_multigroups:
  wazuh_integrations:
  wazuh_active_response:
  wazuh_agentless:
  wazuh_wodles:
  filebeat_etc:
  filebeat_var:
  wazuh-indexer-data:

Script de despliegue

#!/usr/bin/env bash
set -euo pipefail

echo "=== [1/4] Generando certificados SSL ==="
docker compose -f generate-certs.yml run --rm generator

echo "=== [2/4] Abriendo puertos en UFW ==="
ufw allow 1514/tcp comment "Wazuh agentes TCP"
ufw allow 1515/tcp comment "Wazuh enrollment"
ufw allow 5140/udp comment "Wazuh syslog"

echo "=== [3/4] Iniciando stack ==="
docker compose up -d

echo "=== [4/4] Esperando indexer (2-3 min) ==="
for i in $(seq 1 30); do
  if docker compose exec wazuh.indexer \
      curl -ks https://localhost:9200/_cat/health 2>/dev/null | grep -qE 'green|yellow'; then
    echo "Indexer OK"
    break
  fi
  echo "Esperando... ($i/30)"
  sleep 10
done

docker compose ps
echo "Dashboard disponible en https://TU-IP:8443"

Configuración del manager

El fichero wazuh_manager.conf define el comportamiento global. Puntos clave:

<ossec_config>
  <global>
    <jsonout_output>yes</jsonout_output>
    <alerts_log>yes</alerts_log>
    <email_notification>no</email_notification>
    <smtp_server>mail.tudominio.com</smtp_server>
    <email_from>alertas@tudominio.com</email_from>
    <email_to>admin@tudominio.com</email_to>
    <email_maxperhour>12</email_maxperhour>
    <agents_disconnection_time>10m</agents_disconnection_time>
    <white_list>127.0.0.1</white_list>
    <white_list>192.168.0.0/16</white_list>
  </global>

  <alerts>
    <log_alert_level>3</log_alert_level>
    <email_alert_level>12</email_alert_level>
  </alerts>

  <!-- Agentes via TCP -->
  <remote>
    <connection>secure</connection>
    <port>1514</port>
    <protocol>tcp</protocol>
    <queue_size>131072</queue_size>
  </remote>

  <!-- Syslog UDP para firewalls y dispositivos de red -->
  <remote>
    <connection>syslog</connection>
    <port>5140</port>
    <protocol>udp</protocol>
    <allowed-ips>0.0.0.0/0</allowed-ips>
  </remote>

  <syscheck>
    <disabled>no</disabled>
    <frequency>43200</frequency>
    <scan_on_start>yes</scan_on_start>
    <alert_new_files>yes</alert_new_files>
    <directories check_all="yes" report_changes="yes">/etc,/usr/bin,/usr/sbin</directories>
    <directories check_all="yes">/bin,/sbin,/boot</directories>
  </syscheck>
</ossec_config>

Reglas personalizadas de seguridad

Wazuh incluye miles de reglas predefinidas. Las propias van en local_rules.xml con IDs desde 100000. Este bloque implementa los controles de monitorización más habituales en entornos con requisitos de cumplimiento de categoría media: detección de ataques, control de acceso privilegiado, integridad de ficheros y disponibilidad de servicios.

<group name="cumplimiento_custom,">

  <!-- DETECCIÓN DE ATAQUES — autenticación -->

  <!-- Fuerza bruta SSH: 8 intentos fallidos en 120 segundos -->
  <rule id="100001" level="10" frequency="8" timeframe="120">
    <if_matched_sid>5760</if_matched_sid>
    <description>Posible ataque de fuerza bruta SSH — $(attempts) intentos fallidos</description>
    <group>authentication_failures,</group>
  </rule>

  <!-- Cuenta de usuario bloqueada -->
  <rule id="100002" level="10">
    <if_sid>5503</if_sid>
    <description>Cuenta bloqueada por múltiples intentos fallidos de autenticación</description>
    <group>authentication_failures,</group>
  </rule>

  <!-- CONTROL DE ACCESO PRIVILEGIADO -->

  <!-- Escalada de privilegios con sudo -->
  <rule id="100003" level="9">
    <if_sid>5402</if_sid>
    <description>Escalada de privilegios detectada mediante sudo</description>
    <group>priv_escalation,</group>
  </rule>

  <!-- Nuevo usuario creado en el sistema -->
  <rule id="100010" level="8">
    <if_sid>5902</if_sid>
    <description>Nuevo usuario creado en el sistema — revisión recomendada</description>
    <group>account_changes,</group>
  </rule>

  <!-- INTEGRIDAD DE FICHEROS -->

  <!-- Modificación de fichero crítico del sistema -->
  <rule id="100020" level="10">
    <if_sid>550</if_sid>
    <description>Fichero crítico del sistema modificado — $(file)</description>
    <group>syscheck,integrity_check_host,</group>
  </rule>

  <!-- DISPONIBILIDAD DE SERVICIOS -->

  <!-- Servicio detenido inesperadamente -->
  <rule id="100030" level="8">
    <if_sid>2904</if_sid>
    <description>Servicio detenido de forma inesperada — $(service)</description>
    <group>service_control,</group>
  </rule>

</group>

Qué cubre cada bloque

Reglas Área de control Descripción
100001–100002 Detección de ataques Fuerza bruta y bloqueo de cuentas
100003, 100010 Acceso privilegiado Sudo y creación de usuarios
100020 Integridad del sistema Cambios en ficheros críticos
100030 Disponibilidad Caída inesperada de servicios

Los niveles (8-10) determinan qué alertas generan notificación por email según el umbral configurado en email_alert_level.

Enrollar un agente Linux

Desde el servidor donde instalar el agente:

# Descargar e instalar (ajusta versión y arquitectura)
wget https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_4.9.2-1_amd64.deb
dpkg -i wazuh-agent_4.9.2-1_amd64.deb

# Apuntar al manager
sed -i 's/MANAGER_IP/192.168.X.X/' /var/ossec/etc/ossec.conf

# Registrar y arrancar
/var/ossec/bin/agent-auth -m 192.168.X.X
systemctl enable wazuh-agent && systemctl start wazuh-agent

Acceso al dashboard

Una vez el stack esté UP (el indexer tarda 2-3 minutos en inicializar):

https://TU-IP:8443
Usuario: admin
Contraseña: (la definida en INDEXER_ADMIN_PASSWORD)

Importante: cambia todas las contraseñas por defecto antes de exponer el servicio en red.

Conclusión

Con este stack tienes un SIEM completo en un solo servidor capaz de cubrir los controles de monitorización que exigen los marcos de cumplimiento más habituales. El siguiente paso natural es añadir más agentes, configurar alertas por email o webhook para los eventos de nivel alto, y revisar los dashboards de cumplimiento que Wazuh incluye de serie para PCI-DSS, GDPR, HIPAA y el propio ENS. Para verificar el nivel de adecuación de un sistema al ENS, el CCN-CERT publica las guías de seguridad (series CCN-STIC) de referencia.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio