En el post anterior dejé el laboratorio de Wazuh con agents reportando, reglas custom disparando y una lista de próximos pasos. El primero de esa lista era integrar TheHive para que las alertas no se quedaran mirándose el ombligo en un dashboard, sino que se convirtieran en casos con contexto, con enriquecimiento automático y con un flujo de trabajo real de SOC. Esta semana terminé esa integración, y de paso me llevé puesto un buen susto con secretos que no deberían haber estado donde estaban.
Este post es la continuación directa del laboratorio de Wazuh: cómo armé el stack de TheHive y Cortex, cómo lo conecté al manager, y las dos lecciones más caras que me dejó el proceso. Una tiene que ver con seguridad de verdad, no de laboratorio. La otra con algo mucho más tonto pero que me hizo perder horas: mi entorno de agente y el host Docker real no comparten filesystem aunque los paths sean idénticos.
Por qué TheHive y no otra cosa
Wazuh genera alertas, y genera muchas. Sin un lugar donde esas alertas se conviertan en casos de trabajo, con un analista asignado, un timeline y observables enriquecidos, el SIEM termina siendo una pantalla que uno mira de vez en cuando. Necesitaba la pieza de case management y automatización que le faltaba al lab.
TheHive fue la elección obvia por varias razones:
- Integración nativa con Wazuh: el ecosistema ya tiene ejemplos de integración documentados, así que no partía de cero.
- Cortex como motor de análisis: los analizadores de Cortex corren automáticamente sobre los observables de un caso (IPs, hashes, dominios) sin que yo tenga que ir a mano a VirusTotal o a un feed de MISP por cada indicador.
- Es open source y corre en Docker: mismo criterio que usé para elegir Wazuh. Nada de licencias, todo reproducible con docker compose.
El stack: TheHive, Cortex, Cassandra, Elasticsearch y nginx
A diferencia de Wazuh, que es un solo ecosistema pensado para levantarse junto, TheHive necesita armar su propia base de datos y su propio motor de búsqueda por separado. El stack que terminé levantando fue:
- TheHive 5.7.1: la plataforma de case management en sí. Acá se crean los casos, se asignan analistas, se agregan observables y tareas.
- Cortex 4.0.1: el motor de análisis. Corre los analizadores (VirusTotal, MISP, entre otros) sobre cada observable de un caso, automáticamente o a demanda.
- Cassandra: la base de datos donde TheHive persiste casos, tareas y observables.
- Elasticsearch: el motor de búsqueda e indexado que usa TheHive para las consultas rápidas sobre casos.
- nginx: como reverse proxy delante de todo, para no exponer cada servicio con su puerto propio.
Documenté todo el stack en un repo hermano al de Wazuh: thehive-homelab. Van juntos a propósito — uno sin el otro es la mitad de la historia.
El flujo: de la alerta de Wazuh al caso enriquecido
La pieza que cierra el círculo es una integración custom en Python que corre del lado del manager de Wazuh. Cuando una alerta llega con nivel 3 o superior, el script arma un payload con el contexto de la alerta (host, regla, descripción, timestamp) y lo manda a la API de TheHive, que crea un caso nuevo automáticamente.
Una vez que el caso existe, agrego los observables relevantes — la IP de origen, el hash de un archivo, el dominio involucrado, lo que corresponda según el tipo de alerta. Ahí es donde entra Cortex: apenas se crea un observable, dispara los analizadores configurados sin que yo tenga que hacer nada más. En un par de minutos el caso ya tiene el veredicto de VirusTotal y el cruce contra los indicadores que tengo cargados en MISP, todo dentro del mismo caso, sin saltar entre pestañas.
El nivel mínimo de 3 lo elegí después de ver cuánto ruido generaba un umbral más bajo. Con nivel 3 capturo lo que realmente amerita revisión sin inundar TheHive de casos por cada login exitoso o evento rutinario.
Lo que salió mal: secretos en texto plano
Acá viene la parte menos cómoda de contar, pero la más útil. Mientras armaba la documentación del repo de Wazuh para dejarlo público, encontré API keys y contraseñas en texto plano committeadas: la key de VirusTotal, la de TheHive, la contraseña de OpenSearch y la de la API del manager de Wazuh. Todo en archivos trackeados, en un repo que ya estaba en GitHub.
Lo primero fue rotar lo que se pudo rotar de verdad. Las contraseñas de OpenSearch
(admin y kibanaserver) las regeneré con securityadmin.sh, la herramienta
que trae el propio Indexer para aplicar cambios de seguridad al cluster. La contraseña
de la API de Wazuh la roté a través de su propia REST API, sin tocar archivos de
configuración a mano. Las keys de VirusTotal y TheHive no las pude rotar de verdad
porque requerían una sesión externa que no tenía a mano en ese momento, pero sí las
saqué del repo.
Con las credenciales nuevas generadas, moví todo a un .env gitignoreado y
referencié las variables desde el docker-compose en vez de hardcodear valores. Pero eso
no alcanza: el historial de git todavía tenía los secretos viejos en cada commit anterior.
Ahí usé git-filter-repo para reescribir el historial completo y sacar esos
archivos de raíz, seguido de un force-push para que el remoto quedara limpio también.
Lección aprendida: rotar la credencial no es el final del trabajo si el valor viejo
sigue viviendo en el historial de un repo público. git-filter-repo reescribe
el historial, pero el force-push rompe cualquier clon existente — para un repo personal
de laboratorio es aceptable, para un repo con colaboradores hay que avisar antes.
Lo que salió mal, segunda parte: el bind mount fantasma
La segunda sorpresa fue más rara. Durante la rotación necesitaba editar un archivo de configuración montado como bind mount en uno de los contenedores. Edité el archivo desde mi entorno de agente, en la misma ruta que el docker-compose declaraba, y el contenedor seguía leyendo el valor viejo después de reiniciar.
Resulta que mi entorno de agente y el host Docker real de ese laboratorio no comparten
filesystem para esas rutas bind-mounteadas, aunque el path fuera idéntico en ambos
lugares. Escribir en /ruta/al/archivo desde mi lado no tocaba el archivo
físico que el contenedor tenía montado — eran dos archivos distintos que solo coincidían
en el nombre de la ruta.
La solución fue escribir directamente sobre el contenedor, pasando el contenido por stdin en vez de depender del path compartido:
# Escribir el contenido directamente en el archivo físico
# del contenedor, sin depender de que el bind mount
# apunte a lo mismo desde ambos lados
docker exec -i <container> sh -c 'cat > /ruta/al/archivo' < archivo_local
Ese comando fue el único que efectivamente modificó el archivo que el contenedor estaba usando. Fue un buen recordatorio de que "mismo path" no siempre significa "mismo archivo", sobre todo cuando hay capas de virtualización o entornos aislados de por medio. Antes de asumir que una edición se propagó, conviene verificar desde adentro del contenedor.
Cómo quedó el laboratorio hoy
Con todo esto resuelto, el flujo completo ya funciona de punta a punta: un evento en un agente dispara una regla en Wazuh, la alerta cruza el umbral de nivel 3, el script Python crea el caso en TheHive, los observables se cargan y Cortex corre sus analizadores sin intervención manual. Lo que antes era mirar un dashboard y copiar datos a mano ahora es un caso armado esperando revisión, con el enriquecimiento ya hecho.
Ambos repos quedaron documentados y públicos: wazuh-homelab para el SIEM y los agents, y thehive-homelab para el stack de case management. Van de la mano, y el README de cada uno referencia al otro.
Cierre
Lo que más me queda de este tramo del proyecto no es la integración en sí — que funciona bien y hace lo que tiene que hacer — sino los dos tropiezos. Ninguno de los dos lo iba a encontrar leyendo documentación de antemano. El de los secretos lo encontré porque me tomé el trabajo de revisar el repo completo antes de dejarlo público, no porque una herramienta me avisara. El del bind mount lo encontré porque el contenedor se negaba tercamente a leer lo que yo creía que le estaba escribiendo.
Ese es el valor real de un laboratorio: no es que todo salga bien, es que cuando algo sale mal es en un entorno donde el costo de equivocarse es aprender, no un incidente en producción. El próximo paso es sumar Active Response y terminar de conectar el Vulnerability Detector de Wazuh con casos automáticos en TheHive también. Va a quedar documentado acá.