Si tienes Wazuh solo como manager (sin Indexer ni Dashboard oficial), no hace falta montar todo el stack de Elastic/OpenSearch para tener una interfaz web. Se puede usar Grafana —por ejemplo la que ya corre en tu clúster Kubernetes— como “frontend”: consulta la API REST de Wazuh mediante el plugin JSON Datasource y muestra agentes, alertas de seguridad (SCA, MITRE ATT&CK) y control parental en dashboards. Esta guía está pensada para homelabs o entornos pequeños donde solo quieres ver agentes, alertas básicas y datos de control parental sin desplegar el stack completo de Indexer + Dashboard oficial de Wazuh. En este post explico el enfoque, por qué se toma cada decisión, los pasos de configuración y los problemas que suelen aparecer (JWT, selectores del Service, urlPath del plugin, integración con AdGuard y panel de alertas de seguridad).
Todo lo que se describe aquí está montado en un único host, raam: el manager de Wazuh, el clúster Kubernetes con Grafana, AdGuard Home y los scripts de configuración. La captura del dashboard que ves más abajo es de ese entorno en raam; en la imagen los nombres e IPs de agentes y clientes aparecen anonimizados para no exponer datos sensibles.

Captura del dashboard “Wazuh - Agentes y Sistema” con sección AdGuard Home, tal como se ve en Grafana en raam. Arriba: total de agentes, activos, nunca conectados, queries DNS y bloqueadas. Abajo: tablas de agentes (IDs y estado), estado en gráfico de dona, y sección DNS con top dominios bloqueados/consultados y top clientes.
Por qué lo monté: Quería ver agentes, alertas y control parental desde una pantalla decente, sin depender del móvil (JuiceSSH, consola del manager) cada vez que quiero comprobar si un agente está activo o qué está bloqueando AdGuard. Eso aterriza el problema real: una interfaz única, ligera y en el navegador.
Contexto: por qué Grafana y no el Dashboard oficial de Wazuh
El Dashboard oficial de Wazuh depende del Indexer (basado en Elasticsearch/OpenSearch): hay que desplegar índice, ingestas y un frontend que consulta ese índice. En un homelab o en un entorno donde solo quieres ver agentes, alertas y control parental sin montar un stack tan pesado, esa opción se vuelve incómoda.
La API REST del manager de Wazuh ya expone todo lo necesario: listado de agentes, estado, estadísticas, eventos. La idea es usar Grafana como interfaz única: si ya tienes Grafana en Kubernetes (por ejemplo para otras métricas), solo hace falta un datasource que hable con esa API. Grafana no tiene un datasource nativo para Wazuh, pero el plugin marcusolsson-json-datasource permite definir una URL base, autenticación (Basic Auth) y hacer peticiones GET con parámetros; la respuesta en JSON se puede mapear a paneles con JSONPath. Así evitas desplegar Indexer y Dashboard oficial. En resumen: esto es Wazuh sin Elasticsearch, sin Indexer y sin Dashboard oficial —un lightweight dashboard— usando únicamente la API REST de Wazuh y Grafana como frontend. Es una arquitectura deliberadamente minimalista: menos componentes, menos memoria, menos superficie de fallo, a cambio de renunciar a indexación y búsquedas complejas.
En mi caso, el manager de Wazuh corre en Docker en el mismo host raam; Grafana corre dentro del clúster Kubernetes que también está en raam (namespace monitoring), imagen 12.x y puerto interno 3030. Todo el montaje —Wazuh, Kubernetes, Grafana, AdGuard y los dashboards— está en raam, como se ve en la imagen del post.
Si ya usas Wazuh para auditar Kubernetes o control parental con AdGuard y Telegram, tener los mismos datos en Grafana unifica la vista en un solo sitio.
Arquitectura (vista general)
Antes de entrar en pasos concretos, así queda el flujo de datos. Todo corre en el mismo host (raam); Grafana es el único punto de entrada desde el navegador.
[Navegador] → http://<nodo>:30300 (NodePort)
|
v
┌─────────────────────────────────────────────────────────────┐
│ Grafana (pod, namespace monitoring) │
│ + Plugin marcusolsson-json-datasource (v1.3.24) │
└─────────────────────────────────────────────────────────────┘
| |
| datasource "Wazuh API" | datasource "AdGuard Home" | datasource "Wazuh-Alerts"
v v v
[API REST Wazuh] [Proxy AdGuard (pod)] [Proxy Alertas (pod)]
(Manager en Docker) | |
← opcional: proxy v v
si el pod no alcanza host [AdGuard Home API] [alerts.json vía hostPath
(Docker en host) desde volumen del manager]
Renovación JWT: CronJob (o cron/systemd en el host) cada 10 min
→ obtiene JWT nuevo → actualiza datasource Wazuh
Resumen: el navegador habla solo con Grafana. Grafana, vía el plugin JSON, llama a la API de Wazuh (directo o mediante un proxy si hay problemas de red), al proxy de AdGuard y al proxy de alertas (que lee alerts.json del manager). Un CronJob renueva el JWT de Wazuh para que el datasource no deje de funcionar a los 15 minutos.
Qué se hizo (explicado paso a paso)
Exponer Grafana por NodePort
Por defecto el Service de Grafana suele ser ClusterIP: solo es accesible desde dentro del clúster. Para abrir Grafana en el navegador desde la red local (por ejemplo desde tu portátil en casa), hay que exponer el puerto del nodo. La forma más directa sin ingresar por un Ingress es usar un Service tipo NodePort: Kubernetes asigna un puerto en el rango 30000–32767 en cada nodo y redirige el tráfico al pod de Grafana.
Se cambia el Service a NodePort y se fija un puerto concreto (por ejemplo 30300) para no depender del asignado al azar. Así, desde la red local se accede a Grafana en http://<IP_del_nodo>:30300. En raam, el nodo es el mismo host donde corre todo; la IP es la de ese host en la LAN (no la pongo aquí por no exponer datos del entorno).
Plugin JSON Datasource y datasource “Wazuh API”
La API de Wazuh devuelve JSON; Grafana no tiene un datasource específico para ella. El plugin marcusolsson-json-datasource permite configurar una URL base (la del manager), Basic Auth (usuario y contraseña del manager) y, en cada panel, una ruta y parámetros (por ejemplo /agents?limit=100). El plugin hace la petición HTTP, obtiene el JSON y con JSONPath se extraen los campos para tablas y gráficos. La versión probada es la 1.3.24; en versiones posteriores podrían cambiar nombres de campos (como path vs urlPath), así que si algo deja de funcionar tras actualizar el plugin, revisa el changelog.
- Instalación: dentro del pod de Grafana se ejecuta
grafana-cli plugins install marcusolsson-json-datasourcey se reinicia el despliegue para cargar el plugin. Ojo: en Kubernetes eso es efímero: si recreas el pod, el plugin desaparece. Para que persista, usa una imagen custom que incluya el plugin, un initContainer que ejecutegrafana-cli plugins install ...y escriba en un volumen compartido (por ejemploemptyDiro PVC) montado en/var/lib/grafana/plugins, o monta un volumen persistente en/var/lib/grafana/plugins. La documentación de Grafana para Docker describe el patrón con volumen de plugins; en k8s el equivalente es initContainer + volumen compartido. - Datasource en Grafana: se crea un datasource de tipo “JSON API” con la URL del manager (o de un proxy interno, ver más abajo), Basic Auth con el usuario que use tu manager (no pongo nombres de usuario concretos) y, muy importante, un UID fijo. Los dashboards que importas (JSON) referencian el datasource por ese UID; si no lo fijas al crearlo, Grafana asigna uno nuevo y todos los paneles quedan “sin datasource”. Por eso en la configuración del datasource se define un UID concreto y los JSON de los dashboards lo usan.
- Los dashboards de agentes y control parental usan ese mismo datasource; así no hay que reasignar panel por panel cada vez que reinstalas o recreas Grafana.
API Token de Grafana y script todo-en-uno
Crear el datasource e importar los dashboards a mano desde la UI es tedioso. Grafana tiene una API HTTP para crear datasources y subir dashboards (POST a /api/datasources y /api/dashboards/db). Para usarla hace falta autenticación: usuario/contraseña de admin o un token (por ejemplo de un Service Account). Se usa un token de larga duración guardado en un archivo local; los scripts lo leen y envían las peticiones. Un script todo-en-uno hace en orden: instalar el plugin y reiniciar Grafana, crear el token, crear o actualizar el datasource “Wazuh API” (con la URL y credenciales que correspondan a tu entorno) e importar los JSON de los dashboards. Así, en raam, con un solo comando queda Grafana listo para Wazuh sin tocar la UI.
Renovación automática del JWT de Wazuh
La API REST de Wazuh puede usar JWT o Basic Auth para el datasource. En mi caso el datasource usa JWT (el manager estaba ya configurado así), por eso hace falta el CronJob de renovación; si usas Basic Auth (usuario y contraseña del manager), no caduca y te puedes ahorrar la rotación. Por defecto el JWT caduca a los 15 minutos. Si el datasource de Grafana usa JWT y no se renueva, al poco rato los paneles empiezan a devolver “No data” sin que se vea un error claro en la UI. Opciones: alargar la caducidad del JWT en el manager (suele requerir reinicio del servicio) o renovar el JWT de forma periódica. La solución que se aplica en raam es un CronJob en Kubernetes que cada 10 minutos llama a la API de Wazuh para obtener un JWT nuevo y actualiza la configuración del datasource (o el secreto que use el datasource). Así el datasource sigue funcionando sin tocar el manager ni reiniciar Grafana. Si no usas Kubernetes, un cron en el host o un timer de systemd que ejecute el mismo script de renovación (obtener JWT, actualizar datasource vía API de Grafana) sirve igual.
Integración con AdGuard Home para DNS y control parental
AdGuard Home hace de filtro DNS y guarda estadísticas: consultas totales, bloqueadas, top dominios bloqueados/consultados, top clientes. Expone una API (por ejemplo GET /control/stats) que devuelve JSON. En principio se podría usar el mismo plugin JSON en Grafana para pintar paneles; el problema es que algunas respuestas tienen objetos cuya clave es dinámica (por ejemplo el dominio o la IP). Eso no se modela bien con JSONPath estándar en muchos paneles (tablas esperan columnas fijas). La solución que se aplica en raam es un micro-proxy en Python desplegado en el namespace monitoring: recibe la petición de Grafana, llama a la API de AdGuard, transforma el JSON y devuelve el resultado a Grafana. Así los paneles pueden usar campos fijos como name y count.
La transformación que hace el proxy es, en esencia, normalizar listas donde cada elemento es un objeto con una sola clave dinámica (el dominio o la IP) y el valor es el contador. Por ejemplo, de:
[{"global.telemetry.example.com": 29408}, {"one.one.one.one": 12870}]
a:
[{"name": "global.telemetry.example.com", "count": 29408}, {"name": "one.one.one.one", "count": 12870}]
Un snippet mínimo en Python (sin manejo de errores ni auth) sería:
import requests
def transform_top_list(items):
"""items: lista de dicts; cada uno tiene una sola clave (dominio o IP, por diseño de la API de AdGuard) y valor numérico."""
out = []
for item in items:
for key, value in item.items():
out.append({"name": key, "count": value})
break
return out
# En el endpoint que sirve a Grafana: GET a AdGuard /control/stats,
# extraer la lista que toque (top_blocked, top_queried, etc.) y devolver
# transform_top_list(lista) como JSON.
El dashboard unificado (wazuh-agentes-v3) incluye una sección Wazuh (total agentes, activos, nunca conectados, tabla de agentes, estado en dona), una sección AdGuard (queries totales, bloqueadas, tablas de top bloqueados, top consultados y top clientes) y, desde la última ampliación, una sección Alertas de seguridad (ver más abajo). La captura que abre el post es de ese dashboard en raam; los nombres e IPs sensibles están ocultos.
Agente Wazuh en el propio host (raam)
Para monitorizar el propio servidor donde corre el manager y Kubernetes (raam), se instaló un agente Wazuh en el host y se registró en el manager. Así el dashboard muestra también el estado y las alertas del servidor.
Versión del agente: Wazuh exige que el agente no sea más nuevo que el manager. Si en el repositorio oficial solo hay una versión más reciente (p. ej. 4.14 y tu manager es 4.9), el manager rechaza la conexión. Hay que instalar la misma versión que el manager, por ejemplo con apt-get install wazuh-agent=4.9.2-1 --allow-downgrades (sustituye por la versión de tu manager).
IP al registrar el agente: Cuando el manager corre en Docker y el agente en el host, las conexiones llegan al manager desde la IP del gateway de la red Docker (p. ej. 172.18.0.1), no desde 127.0.0.1. Si registras el agente con ip: 127.0.0.1, el manager no lo reconoce. Registrar siempre con ip: any para agentes que están en el mismo host que el contenedor del manager.
Campo jwt_expiration_timeout en api.yaml: En versiones antiguas del manager (p. ej. 4.9.2) ese campo no existe en la configuración del API. Si lo añades para alargar la vida del JWT y luego reinicias el contenedor (o ejecutas wazuh-control restart dentro del contenedor), el daemon wazuh-apid puede fallar al arrancar y dejar el manager inoperativo. La solución es eliminar esa línea de api.yaml y usar el CronJob de renovación del JWT cada 10 minutos, que es válido en todas las versiones.
No usar wazuh-control restart dentro del contenedor Docker: En muchos contenedores oficiales de Wazuh, los daemons del manager no están supervisados por el init del contenedor. Si ejecutas wazuh-control stop o restart dentro del contenedor, los procesos se paran y no se relanzan solos. Para reiniciar el manager, usar docker restart <contenedor>; si solo quieres arrancar daemons que hayan caído, wazuh-control start (sin hacer stop antes).
Panel de alertas de seguridad en Grafana
Las alertas de Wazuh se escriben en /var/ossec/logs/alerts/alerts.json dentro del contenedor del manager. Grafana corre en Kubernetes y no puede leer ese archivo directamente. Para llevar las alertas al dashboard hace falta un puente.
Paso 1 — Montar el directorio de logs del manager: Si el contenedor del manager no tiene montado un volumen para /var/ossec/logs, ese directorio vive solo en el overlay del contenedor y no es accesible desde fuera. Hay que recrear el contenedor añadiendo un volumen (por ejemplo -v <ruta_en_host>/logs:/var/ossec/logs:rw). Así alerts.json queda en el host y se puede exponer a un pod en Kubernetes vía hostPath (solo lectura). En el host, dar permisos de lectura al directorio para que el pod pueda leer (p. ej. chmod o+rx en directorios y o+r en ficheros, o confiar en el umask de Wazuh si ya crea los ficheros legibles).
Paso 2 — Proxy de alertas en Kubernetes: Se despliega un proxy en Python en el namespace monitoring que monta el directorio de logs del host (read-only), escucha en el puerto 9292 y expone una API HTTP sencilla. El proxy no indexa ni persiste; simplemente agrega en memoria bajo demanda leyendo alerts.json. No es un reemplazo de un pipeline de ingestión. En entornos con alto volumen de alertas el archivo puede crecer rápidamente; sería recomendable implementar rotación, caché o procesamiento incremental.
- GET /health — estado del proxy y comprobación de que existe
alerts.json. - GET /stats — totales agregados: total alertas, última hora, últimas 24 h, por severidad (crítico, alto, medio), por agente, por grupo/categoría, por tácticas MITRE ATT&CK. La respuesta está pensada para consumirla desde el plugin JSON de Grafana (campos fijos).
- GET /alerts?min_level=7&limit=30&agent=raam — lista de alertas filtradas, ordenadas de más reciente a más antigua (nivel, agente, regla, descripción, etc.).
En Grafana se crea un nuevo datasource de tipo JSON API (por ejemplo con UID wazuh-alerts) que apunta a la URL interna del proxy (p. ej. http://wazuh-alerts-proxy.monitoring.svc.cluster.local:9292). Los paneles usan ese datasource para pintar stats y tablas.
Paneles añadidos al dashboard: En la sección Alertas de seguridad se añadieron, entre otros: Total alertas, Última hora, Últimas 24 h, Nivel alto, Críticas, Nivel medio (stats con colores); tabla Alertas recientes (nivel ≥ 7, límite 30, con nivel coloreado por umbral); Alertas por agente; Top categorías (por grupo); MITRE ATT&CK — Tácticas. Todo sin indexer: los datos salen de alerts.json en tiempo casi real.
SCA (Security Configuration Assessment) vs. Vulnerability Detection (CVEs): El módulo SCA de Wazuh evalúa los agentes contra benchmarks CIS y genera alertas que sí se escriben en alerts.json, con mapeo a PCI-DSS, GDPR, HIPAA, NIST, ISO 27001 y MITRE ATT&CK. Eso funciona sin indexer y da una cobertura de seguridad muy útil (configuraciones inseguras, hardening pendiente). El módulo Vulnerability Detection (CVEs) en versiones sin indexer descarga las bases NVD pero intenta enviar resultados al indexer; sin indexer, los CVEs detectados no se persisten localmente ni aparecen en alerts.json. En la práctica, el bloque de alertas en Grafana se nutre sobre todo de SCA y de reglas de análisis de logs; para CVEs indexados haría falta el stack completo.
Problemas típicos y soluciones (explicados)
| Síntoma | Causa | Solución |
|---|---|---|
| No se puede abrir Grafana en el navegador | El Service es ClusterIP; solo accesible desde dentro del clúster | Cambiar el Service a NodePort (por ejemplo 30300) y aplicar el manifest. |
| Dashboards sin datos / “datasource not found” | El datasource se crea con otro tipo o sin UID fijo; los paneles referencian un UID que ya no existe | Crear el datasource como tipo “JSON API” (marcusolsson-json-datasource) y asignarle el mismo UID que usan los JSON de los dashboards. |
| “No data” cada cierto tiempo (p. ej. 15 min) | El JWT de la API de Wazuh ha caducado | Renovar el JWT periódicamente (p. ej. CronJob cada 10 min) o alargar la caducidad en el manager (suele requerir reinicio). |
| Grafana no accesible desde dentro del cluster (HTTP 000, timeouts) | El Service tiene varios labels en el selector; el pod solo tiene uno de ellos (app=grafana). El Service no encuentra ningún endpoint | Revisar kubectl get endpoints -n monitoring y que el selector del Service coincida con las labels del pod. Con kubectl patch dejar un único selector, por ejemplo app: grafana. |
| Paneles no hacen ninguna petición HTTP | En la versión 1.3.24 del plugin, las queries usan el campo urlPath; si en el JSON del dashboard pusiste path, el plugin lo ignora | En el JSON del dashboard, renombrar todos los path a urlPath. |
| Queries devuelven 400 Bad Request | El plugin añade ? antes de los query params; si en el dashboard ya pusiste ?limit=1&..., la URL queda con ?? y el servidor devuelve 400 | Quitar el ? inicial de los queryParams en el JSON del dashboard, o meter los parámetros directamente en la urlPath (p. ej. /agents?status=active&limit=1). |
| Tabla de agentes “No data” pero los stats sí tienen datos | Algunos agentes (p. ej. “nunca conectados”) no tienen campos como os, version, lastKeepAlive. Si la tabla usa JSONPath sobre esos campos, los arrays resultantes tienen longitudes distintas y el plugin no alinea bien las columnas | Usar en la tabla solo campos que existan en todos los agentes (p. ej. id, status, fecha de registro), o hacer paneles separados para “activos” y “sin conectar”. |
| 401 en todas las peticiones del dashboard desde el navegador | En Grafana 12 los tokens de sesión rotan; la sesión del usuario puede haber caducado | Cerrar sesión en Grafana y volver a iniciar sesión. |
| Exponer el datasource JSON a orígenes no confiables | El plugin hace peticiones HTTP a la URL configurada; si esa URL es accesible desde internet o apunta a servicios sensibles, aumenta la superficie de ataque. El plugin ha tenido CVEs en el pasado | Configurar el datasource solo para que hable con la API del manager (y con el proxy de AdGuard en red interna). Mantener el plugin actualizado y no exponer Grafana sin autenticación. |
| Manager rechaza agente: “Incompatible version” | El agente es más nuevo que el manager; Wazuh no permite agente más reciente que el manager | Instalar la misma versión (o inferior) que el manager, p. ej. apt-get install wazuh-agent=4.9.2-1 --allow-downgrades. |
| Manager no reconoce agente: “Cannot find the ID of the agent” | El agente está en el host y el manager en Docker; las conexiones llegan desde la IP del gateway Docker (p. ej. 172.18.0.1), no desde 127.0.0.1 | Registrar el agente con ip: any en lugar de 127.0.0.1. |
| wazuh-apid no arranca tras reiniciar; API inaccesible | En Wazuh 4.9.2 el campo jwt_expiration_timeout no existe en api.yaml; añadirlo rompe el daemon al arrancar | Quitar la línea jwt_expiration_timeout de api.yaml y usar CronJob de renovación del JWT. |
| Manager queda inoperativo tras “wazuh-control restart” dentro del contenedor | Los daemons de Wazuh en el contenedor no están supervisados por el init; al pararlos no se relanzan solos | Usar docker restart <contenedor> para reiniciar; o wazuh-control start (sin stop previo) si solo hay que arrancar daemons caídos. |
| No se pueden leer alertas en Grafana; alerts.json inaccesible | El directorio /var/ossec/logs no está montado como volumen; solo existe dentro del overlay del contenedor | Recrear el contenedor del manager con un volumen para logs (p. ej. -v <host>/logs:/var/ossec/logs); luego el proxy de alertas puede montar ese path vía hostPath en k8s. |
Cómo usarlo (pasos mínimos)
Aplicar el Service con NodePort (una vez):
kubectl apply -f grafana/grafana.yamlSustituye la ruta por la de tu repo o manifest donde tengas el Service de Grafana.
Dejar Grafana listo para Wazuh (una vez, o tras reinstalar Grafana):
cd wazuh-lite/scripts GRAFANA_ADMIN_PASSWORD='<tu_contraseña_admin_grafana>' WAZUH_PASSWORD='<contraseña_usuario_api_wazuh>' ./setup-wazuh-grafana-completo.shUsa las contraseñas reales de tu entorno (no las compartas ni las subas al blog).
Abrir la web: en el navegador,
http://<IP_del_nodo>:30300(o el puerto NodePort que hayas usado). Inicia sesión en Grafana y abre los dashboards “Control Parental” y “Wazuh - Agentes y Sistema”, o el unificado con AdGuard si lo has importado.Si cambias la contraseña del usuario de la API de Wazuh: vuelve a ejecutar solo el paso que actualiza el datasource (el script que hace PUT/POST al datasource con las nuevas credenciales), para que Grafana siga pudiendo llamar a la API.
Limitaciones de este enfoque
Esta aproximación no sustituye al stack completo de Wazuh (Indexer + Dashboard oficial). Es una solución ligera pensada para:
- Visualización básica de agentes y estados.
- Estadísticas simples (conteos, top N, estado).
- Entornos pequeños: homelab, laboratorio, testing.
No está pensada para:
- Búsquedas complejas sobre eventos históricos.
- Correlación avanzada entre fuentes.
- Retención larga de logs indexados.
- Análisis forense sobre grandes volúmenes.
Aquí no hay indexación ni pipeline de ingestión: Grafana consulta directamente la API del manager y, si montas el proxy de alertas, el archivo alerts.json. Las alertas que sí ves (SCA, reglas de análisis) son en tiempo casi real; Vulnerability Detection (CVEs) sin indexer no persiste resultados. Si el manager cae, no hay histórico persistido como sí lo habría en un Indexer. Conocer estas limitaciones te posiciona bien: esto no es un reemplazo total del stack oficial, sino una interfaz ligera para ver lo justo sin desplegar Elastic/OpenSearch.
Consideraciones de seguridad
- No expongas la API REST de Wazuh directamente a internet. El datasource debe hablar solo con el manager en red interna (o vía proxy dentro del cluster).
- Limita el acceso al NodePort de Grafana: firewall o red interna; no dejar el puerto abierto a todo el mundo.
- Usa usuarios específicos de API con permisos mínimos para lo que necesite el datasource; no reutilices credenciales de administración.
- Si usas Basic Auth entre Grafana y la API de Wazuh (o el proxy), considera TLS interno (certificado autofirmado en homelab es aceptable).
- Esta arquitectura reduce complejidad, pero no elimina las responsabilidades de hardening: actualizar Wazuh, Grafana y el plugin; no exponer Grafana sin autenticación.
Lecciones aprendidas
- Plugin marcusolsson-json-datasource (v1.3.24): usar siempre
urlPathen las queries, nopath; incluir los parámetros en la URL cuando sea posible; JSONPaths absolutas y evitarrootSelectorsi da problemas. - Grafana 12: los tokens de sesión rotan; si los paneles devuelven 401, hacer logout y login de nuevo.
- JWT de Wazuh: caduca a los 15 min por defecto; alargar mucho la caducidad suele requerir reinicio del manager; un CronJob de renovación es más práctico.
- API de AdGuard: el formato de las listas “top” (clave dinámica) no encaja bien con JSONPath en muchos paneles; un proxy que normalice a
name/count(o similar) simplifica los dashboards. - Service de Grafana: ante problemas de conectividad desde otros pods (p. ej. CronJob que llama a Grafana), comprobar
kubectl get endpointsy que el selector del Service coincida con las labels del pod. - Versión del agente Wazuh: debe coincidir exactamente con el manager (o ser inferior). El repositorio oficial puede ofrecer versiones más nuevas; instalar con versión fija y
--allow-downgradessi hace falta. - Agente en el mismo host que el manager (Docker): registrar siempre con
ip: any; las conexiones llegan por la red bridge del contenedor. - api.yaml en Wazuh 4.9.x: no añadir
jwt_expiration_timeout; no existe en esa rama y rompe wazuh-apid. Usar CronJob de renovación. - Reinicio del manager en Docker: no usar
wazuh-control restartdentro del contenedor; usardocker restarto, para solo arrancar daemons,wazuh-control start. - Alertas sin indexer: SCA (CIS Benchmark) sí escribe en
alerts.jsony da cobertura MITRE/PCI-DSS; Vulnerability Detection (CVEs) sin indexer no persiste resultados. El panel de alertas en Grafana se nutre de SCA y reglas de análisis.
Posibles mejoras futuras
La arquitectura actual es funcional; se puede evolucionar hacia un modelo más orientado a métricas y resiliente:
- Caché intermedia para evitar consultas repetidas a la API del manager (p. ej. un sidecar o servicio que agregue y cachee respuestas).
- Exportar métricas a Prometheus en lugar de (o además de) consultar JSON directo; luego Grafana usa Prometheus como datasource y se alinea con el resto del stack de monitorización.
- Panel de eventos críticos en tiempo real (últimas alertas, cambios de estado de agentes).
- Integrar alertas de Grafana hacia Telegram (o otro canal) cuando umbrales o reglas se disparen.
Así el montaje no queda en un experimento puntual, sino en una base sobre la que seguir construyendo.
Todo lo anterior está montado y probado en raam; la captura del dashboard que ilustra el post corresponde a ese entorno, con datos de agentes y DNS anonimizados.
Para profundizar en Wazuh en entornos ligeros o en Kubernetes, en el blog tienes más entradas: auditoría de Kubernetes con Wazuh y control parental con Wazuh, AdGuard y Telegram.
