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.