Plan2026/04-decision-logs.md

23 KiB

Decision: Logs centralizados (punto 1.4)

Por que es critico

En este incidente, el backdoor capturo credenciales durante 4 dias en /var/log/ssh_auth.log. Ese fichero estaba solo en el servidor comprometido. Si el atacante lo hubiera borrado (trivial con acceso root), no habria quedado evidencia de que contraseñas fueron robadas ni desde cuando.

Los logs locales en un servidor comprometido no son fiables. Necesitamos que los logs salgan del servidor en tiempo real a un sitio que el atacante no pueda tocar.


Que logs centralizar (minimo viable)

Prioridad 1 - Seguridad (dia 1)

Log Que contiene Por que es critico
auth.log Logins SSH, sudo, fallos de autenticacion, PAM events Detecta accesos no autorizados, brute force, uso de credenciales robadas
syslog Eventos de sistema, servicios que arrancan/paran, errores kernel Detecta cambios de configuracion, servicios sospechosos, fallos
Firewall PVE (pvefw-logger) Conexiones bloqueadas y permitidas por regla Detecta intentos de conexion al C2, escaneos, trafico anormal

Prioridad 2 - Operaciones (semana 1-2)

Log Que contiene Por que es critico
PBS task logs Resultado de GC, verify, sync jobs Detecta jobs fallidos antes de que el exporter reporte
PBS API log Peticiones a la API de PBS Detecta accesos sospechosos a datastores, intentos de borrado
ZFS events (zed) Scrub results, errores de disco, pool events Detecta degradacion de hardware antes de que falle

Prioridad 3 - Infraestructura (mes 1)

Log Que contiene Por que es critico
Docker logs (VMs) Stdout/stderr de contenedores Errores de PowerDNS, Traefik, MariaDB
dpkg.log Paquetes instalados/actualizados/eliminados Detecta instalacion de software no autorizado
cron logs Ejecucion de cron jobs Detecta cron jobs añadidos o modificados
Proxmox cluster logs Eventos del cluster PVE Problemas de quorum, migraciones, fallos

Opciones evaluadas

Opcion A: Grafana Loki + Alloy (RECOMENDADA)

Que es

Loki es un sistema de logs diseñado por Grafana Labs. No indexa el contenido de los logs (como Elasticsearch), sino solo las etiquetas (labels). Esto lo hace mucho mas ligero en CPU, RAM y disco.

Alloy (antes Promtail + Grafana Agent) es el recolector que envia logs a Loki.

Por que encaja

Ya hemos elegido VictoriaMetrics + Grafana para metricas. Añadir Loki significa:

  • Mismo Grafana para metricas Y logs (una sola UI)
  • Correlacion directa: ver un pico de CPU en Grafana y saltar a los logs de ese servidor en el mismo momento
  • LogQL (lenguaje de consulta) es similar a PromQL
  • Misma filosofia de labels que Prometheus/VictoriaMetrics

Arquitectura

Servidores PBS/PVE                     VM Monitoring
┌──────────────────┐                ┌─────────────────────┐
│  Grafana Alloy   │──push logs──> │  Loki               │
│  (en cada server) │               │  (almacen de logs)   │
│                   │               │                      │
│  Lee:             │               │  Grafana             │
│  - /var/log/auth  │               │  (consulta Loki +    │
│  - /var/log/syslog│               │   VictoriaMetrics)   │
│  - journald       │               │                      │
│  - PBS logs       │               │  VictoriaMetrics     │
│  - PVE firewall   │               │  (metricas)          │
└──────────────────┘                └─────────────────────┘

Alloy vs Promtail

Promtail era el recolector original de Loki. Grafana lo ha reemplazado por Alloy (un agente unificado que hace log shipping, metricas, y traces):

Aspecto Promtail (legacy) Alloy (actual)
Estado En mantenimiento, no nuevas features Desarrollo activo
Funcion Solo logs Logs + metricas + traces
Config YAML propio River (nuevo formato, mas potente)
Puede reemplazar a Solo Promtail Promtail + node_exporter + vmagent

Recomendacion: Si desplegais Alloy para logs, podeis usarlo tambien como recolector de metricas (reemplaza node_exporter + vmagent en cada servidor). Un solo agente para todo. Pero esto añade complejidad inicial; se puede empezar con Alloy solo para logs y expandir despues.

Dimensionamiento Loki

Para 100-500 servidores:

Parametro 100 servers 500 servers
Ingestion estimada ~5GB/dia ~25GB/dia
Almacenamiento (30 dias) ~50GB comprimido ~250GB comprimido
RAM Loki 2-4 GB 4-8 GB
CPU Loki 2 vCPU 4 vCPU
RAM Alloy por servidor 30-50 MB 30-50 MB

Loki comprime los logs agresivamente (5-10x). 30 dias de 500 servidores caben en ~250GB.

Despliegue: single-node mode (monolitico). Suficiente hasta ~1000 servidores. Mas alla, se escala a microservices mode.

Almacenamiento backend

Loki puede guardar los logs en:

  • Filesystem local (simple, para empezar)
  • S3/Minio (recomendado para produccion, separacion compute/storage)
  • GCS/Azure Blob (si usais cloud)

Recomendacion para empezar: filesystem local en la VM de monitoring. Migrar a Minio/S3 cuando necesiteis mas retencion o redundancia.

Configuracion Alloy en cada servidor

// /etc/alloy/config.alloy
// Recoleccion de logs para envio a Loki

// === FUENTE: Journal (systemd) ===
loki.source.journal "system" {
  forward_to = [loki.write.default.receiver]
  labels = {
    job       = "systemd",
    host      = env("HOSTNAME"),
    component = "journal",
  }
  // Solo unidades relevantes
  matches = "_TRANSPORT=syslog OR _TRANSPORT=kernel OR _SYSTEMD_UNIT=sshd.service OR _SYSTEMD_UNIT=proxmox-backup-proxy.service OR _SYSTEMD_UNIT=proxmox-backup.service OR _SYSTEMD_UNIT=pvedaemon.service OR _SYSTEMD_UNIT=pveproxy.service OR _SYSTEMD_UNIT=pve-firewall.service"
}

// === FUENTE: auth.log (critico para seguridad) ===
local.file_match "auth" {
  path_targets = [{"__path__" = "/var/log/auth.log"}]
}
loki.source.file "auth" {
  targets    = local.file_match.auth.targets
  forward_to = [loki.write.default.receiver]
  labels = {
    job  = "auth",
    host = env("HOSTNAME"),
  }
}

// === FUENTE: syslog ===
local.file_match "syslog" {
  path_targets = [{"__path__" = "/var/log/syslog"}]
}
loki.source.file "syslog" {
  targets    = local.file_match.syslog.targets
  forward_to = [loki.write.default.receiver]
  labels = {
    job  = "syslog",
    host = env("HOSTNAME"),
  }
}

// === FUENTE: PVE firewall ===
local.file_match "pvefw" {
  path_targets = [{"__path__" = "/var/log/pve-firewall.log"}]
}
loki.source.file "pvefw" {
  targets    = local.file_match.pvefw.targets
  forward_to = [loki.write.default.receiver]
  labels = {
    job  = "pve-firewall",
    host = env("HOSTNAME"),
  }
}

// === FUENTE: dpkg (instalacion de paquetes) ===
local.file_match "dpkg" {
  path_targets = [{"__path__" = "/var/log/dpkg.log"}]
}
loki.source.file "dpkg" {
  targets    = local.file_match.dpkg.targets
  forward_to = [loki.write.default.receiver]
  labels = {
    job  = "dpkg",
    host = env("HOSTNAME"),
  }
}

// === DESTINO: Loki central ===
loki.write "default" {
  endpoint {
    url = "http://loki.monitoring.internal:3100/loki/api/v1/push"
  }
}

Configuracion Loki (single-node)

# /etc/loki/loki.yaml
auth_enabled: false

server:
  http_listen_port: 3100
  log_level: warn

common:
  path_prefix: /data/loki
  storage:
    filesystem:
      chunks_directory: /data/loki/chunks
      rules_directory: /data/loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2026-01-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

limits_config:
  retention_period: 30d              # 30 dias de retencion
  max_query_series: 1000
  ingestion_rate_mb: 20              # 20MB/s ingestion (sobra para 500 servers)
  ingestion_burst_size_mb: 40

compactor:
  working_directory: /data/loki/compactor
  retention_enabled: true
  delete_request_cancel_period: 24h

Consultas utiles en LogQL (desde Grafana)

# Logins SSH exitosos (quien entro y desde donde)
{job="auth", host="ns31787946"} |= "Accepted"

# Intentos de login fallidos
{job="auth"} |= "Failed password" | pattern `Failed password for <user> from <ip>`

# Alguien instalo un paquete
{job="dpkg"} |= "install" | pattern `<_> install <package> <_> <version>`

# Firewall: conexiones bloqueadas al C2
{job="pve-firewall"} |= "DROP" |= "91.208.162"

# Errores en PBS
{job="systemd", host=~"pbs.*"} |= "error" | logfmt

# Todo lo que paso en un servidor en los ultimos 30 minutos
{host="ns31787946"} | line_format "{{.job}}: {{__line__}}"

# Top 10 IPs con mas intentos fallidos de SSH
{job="auth"} |= "Failed password" | pattern `from <ip> port` | count by (ip) | topk(10, count)

Alertas en Loki (via Grafana alerting o Loki ruler)

# Alerta: mas de 10 intentos fallidos de SSH en 5 minutos
- alert: SSHBruteForce
  expr: |
    count_over_time({job="auth"} |= "Failed password" [5m]) > 10    
  for: 1m
  labels:
    severity: warning

# Alerta: login SSH desde IP no conocida
- alert: SSHUnknownIP
  expr: |
    count_over_time({job="auth"} |= "Accepted" |~ "(?!83\\.50\\.21|213\\.32\\.37|178\\.33\\.167|37\\.133\\.200)" [5m]) > 0    
  for: 0m
  labels:
    severity: critical

# Alerta: paquete instalado fuera de ventana de mantenimiento
- alert: UnexpectedPackageInstall
  expr: |
    count_over_time({job="dpkg"} |= "install" [5m]) > 0    
  for: 0m
  labels:
    severity: warning

# Alerta: alguien modifico PAM (habria pillado el backdoor)
- alert: PAMModification
  expr: |
    count_over_time({job="syslog"} |= "pam_exec" [5m]) > 0    
  for: 0m
  labels:
    severity: critical

Opcion B: Graylog

Que es

Plataforma de gestion de logs con busqueda, alertas, dashboards, y pipelines de procesamiento. Usa Elasticsearch/OpenSearch como backend.

Ventajas sobre Loki

  • Busqueda full-text indexada: buscar en el contenido de los logs es instantaneo (Loki es mas lento en busquedas sin label)
  • Pipelines de procesamiento: parsear, enriquecer, transformar logs antes de almacenar
  • UI de logs dedicada: pensada exclusivamente para analisis de logs (Grafana es mas generica)
  • Alertas de log nativas: sin necesitar Grafana
  • Extractor de campos: parsea syslog, JSON, etc. automaticamente

Desventajas

  • Requiere Elasticsearch/OpenSearch + MongoDB: 3 componentes pesados
  • Consumo de recursos: minimo 4GB RAM solo para Elasticsearch, 2GB para Graylog, 1GB para MongoDB = 7GB minimo
  • Otro UI: metricas en Grafana, logs en Graylog. Dos sitios diferentes para investigar un problema
  • Mas complejo de operar: Elasticsearch necesita cuidado (shards, indices, GC de Java)
  • No correlacion directa metricas-logs en una sola vista

Veredicto

Potente pero pesado. Si el equipo ya conoce Graylog/ELK, es buena opcion. Si empezais de cero, el overhead operacional y de recursos no se justifica cuando Loki + Grafana cubre el 90% de las necesidades con un tercio de los recursos.


Opcion C: ELK Stack (Elasticsearch + Logstash + Kibana)

Veredicto rapido

Descartado para vuestro caso. ELK es el estandar de la industria para logs a gran escala, pero:

  • Elasticsearch consume minimo 8-16GB RAM para ser usable
  • Kibana es otra UI separada de Grafana
  • Logstash es Java y consume mucho
  • A 100-500 servidores, es matar moscas a cañonazos
  • El coste operacional de mantener un cluster Elasticsearch no compensa

Si algun dia superais los 5000 servidores o necesitais cumplir regulaciones que exigen SIEM certificado, evaluarlo. Para ahora, no.


Comparativa final

Aspecto A: Loki + Alloy (RECOMENDADA) B: Graylog C: ELK
RAM backend 2-4 GB (100 servers) 7+ GB 16+ GB
Disco (30 dias, 100 servers) ~50 GB ~200 GB ~300 GB
Complejidad operacional Baja Media-alta Alta
UI Grafana (ya la tenemos) Propia (separada) Kibana (separada)
Correlacion metricas-logs Nativa en Grafana No directa No directa
Busqueda full-text Lenta sin labels Rapida (indexada) Rapida (indexada)
Parseo de logs Basico (LogQL patterns) Potente (pipelines) Muy potente (Logstash)
Alertas sobre logs Grafana alerting / Loki ruler Nativas ElastAlert / Kibana
Agente Alloy (30-50MB RAM) Sidecar/Filebeat Filebeat (similar)
Ecosistema Grafana Nativo Plugin Plugin

Recomendacion: Opcion A (Loki + Alloy)

Razon principal

Ya tenemos Grafana y VictoriaMetrics. Loki se integra nativamente. Un solo panel para metricas + logs + alertas. El equipo aprende una sola herramienta.

Despliegue en fases

Fase 1 (dia 1-2): Seguridad minima

  1. Desplegar Loki single-node en la VM de monitoring (junto a VictoriaMetrics y Grafana)
  2. Desplegar Alloy en todos los PBS/PVE via Ansible
  3. Recolectar solo: auth.log + syslog
  4. Configurar alerta de "login desde IP no conocida"
  5. Verificar que los logs llegan consultando en Grafana

Fase 2 (semana 1): Operaciones

  1. Añadir fuentes: firewall PVE, dpkg.log, PBS logs (journald)
  2. Alertas: brute force SSH, instalacion de paquetes, errores PBS
  3. Dashboard de logs de seguridad en Grafana

Fase 3 (semana 2-3): Visibilidad completa

  1. Añadir Docker logs desde VMs (PowerDNS, Traefik, etc.)
  2. Labels enriquecidos: tipo de servidor (pbs/pve/dns), cliente, datacenter
  3. Dashboard correlacionado: metricas + logs en la misma vista temporal

Fase 4 (mes 2): Retencion y backup

  1. Evaluar migracion de filesystem a Minio/S3 para retencion larga
  2. Backup de Loki data
  3. Retencion diferenciada: auth.log 90 dias, syslog 30 dias, dpkg 180 dias

Diagrama del stack completo (Capa 1: Visibilidad + Capa 2: Seguridad)

                    ┌─────────────────────────────────────┐
                    │            AUTHENTIK                  │
                    │  SSO + MFA centralizado               │
                    │  (todos los paneles web se            │
                    │   autentican contra Authentik)        │
                    └──────────────────┬──────────────────┘
                                       │ SSO
          ┌────────────────────────────┼────────────────────────────┐
          │                            │                            │
┌─────────▼─────────┐   ┌─────────────▼──────────┐   ┌────────────▼───────────┐
│     GRAFANA       │   │   Wazuh Dashboard      │   │  Forgejo / Semaphore   │
│  (UI unificada)   │   │   (seguridad)          │   │  Netbox / Vaultwarden  │
│  Metricas + Logs  │   │   FIM, rootkits, vulns │   │  (todos con SSO)       │
│  Alertas→Telegram │   │   Alertas→Telegram     │   │                        │
└───┬──────────┬────┘   └────────────┬───────────┘   └────────────────────────┘
    │          │                     │
┌───▼──────┐ ┌▼───────────┐  ┌──────▼───────────┐
│Victoria  │ │   Loki     │  │  Wazuh Manager   │
│Metrics   │ │  (logs)    │  │  (analisis)      │
│(metricas)│ │ ret. 30d   │  │  + vuln detect   │
│ ret. 1a  │ │            │  │                  │
└───┬──────┘ └─┬──────────┘  └──────┬───────────┘
    │          │                     │
┌───▼──────┐   │                     │
│ vmagent  │   │                     │
│ (scrape) │   │                     │
└───┬──────┘   │                     │
    │          │                     │
╔═══╪══════════╪═════════════════════╪════════════════════════════════╗
║   │          │                     │                                ║
║   │  Cada servidor de produccion (PBS/PVE/Linux gestionado):       ║
║   │                                                                 ║
║   │  ┌──────────────┐ ┌────────┐ ┌───────────┐ ┌───────────────┐  ║
║   │  │node_exporter │ │ Alloy  │ │Wazuh Agent│ │CrowdSec       │  ║
║   │  │(metricas)    │ │(logs)  │ │(FIM+HIDS) │ │+ Fail2ban     │  ║
║   │  │  5MB RAM     │ │ 30-50MB│ │ 50-100MB  │ │  ~80MB        │  ║
║   │  │+textfile jobs│ │→ Loki  │ │→ Wazuh Mgr│ │               │  ║
║   │  └──────────────┘ └────────┘ └───────────┘ └───────────────┘  ║
║   │  + auditd (ya instalado, 0 extra)                              ║
║   │  + SSH solo desde Bastion (iptables)                           ║
║   │                                                                 ║
║   │  Total overhead por servidor: ~200-250 MB RAM                   ║
╚═══╪═════════════════════════════════════════════════════════════════╝
    │
    │  Centralizados (VM monitoring):
    │  ┌─────────────────────────────────┐
    ├──│  pbs-exporter (natrontech)      │
    │  │  - multi-target: todos los PBS  │
    │  │  - pve_exporter: clusters PVE   │
    │  └─────────────────────────────────┘
    │
    │  Complementarios:
    │  ┌──────────────────┐  ┌──────────────────┐
    │  │  Uptime Kuma     │  │  Bastion SSH     │
    │  │  checks binarios │  │  grabacion sesion│
    │  │  SSL, DNS, HTTP  │  │  ProxyJump (-J)  │
    │  └──────────────────┘  └──────────────────┘

    Inventario + Secretos:
    ┌──────────────────┐  ┌──────────────────┐
    │  Netbox          │  │  Vaultwarden     │
    │  SSOT ← ICSMgr  │  │  secretos equipo │
    │  → Ansible inv.  │  │  (SSO Authentik) │
    │  → Prometheus SD │  │                  │
    └──────────────────┘  └──────────────────┘

Recursos totales del stack completo (Capa 1 + Capa 2)

VMs centrales

VM Componentes CPU RAM Disco
Monitoring VictoriaMetrics + vmagent + Loki + Grafana + Alertmanager + exporters 4 vCPU 8-12 GB 200-500 GB
Wazuh Wazuh Manager + Dashboard (OpenSearch) 4 vCPU 12 GB 100 GB
Zammad Zammad + PostgreSQL + Redis + Elasticsearch 2-3 vCPU 4-6 GB 50 GB
Authentik Authentik + PostgreSQL + Redis 1-2 vCPU 2-3 GB 20 GB
Netbox Netbox + PostgreSQL + Redis 1-2 vCPU 2-3 GB 20 GB
ICSManager ICSManager + PostgreSQL (todo Docker) 1-2 vCPU 2-3 GB 20 GB
WildDuck WildDuck + MongoDB + Redis (todo Docker) 1-2 vCPU 2-3 GB 30-50 GB
Outline Outline + PostgreSQL + Redis 1 vCPU 1-2 GB 20 GB
Servicios Semaphore + Uptime Kuma + Technitium + Traefik (stateless) 1-2 vCPU 2-3 GB 20 GB
Bastion SSH jump host + grabacion sesiones 1 vCPU 1 GB 20 GB
WG Hub A WireGuard hub master + wgdashboard + nftables + NAT CGNAT 1 vCPU 512 MB 10 GB
WG Hub B WireGuard hub backup (keepalived HA) 1 vCPU 512 MB 10 GB
PDM Proxmox Datacenter Manager (gestion multi-cluster) 1 vCPU 1-2 GB 20 GB
PMG Proxmox Mail Gateway (SpamAssassin + ClamAV + quarantine) 1-2 vCPU 2-3 GB 30 GB
Forgejo Ya desplegado (repos desarrollo + nuevos repos infra) existente existente existente
Total VMs centrales ~24-32 vCPU ~44-58 GB 580-920 GB

Modelo: una VM = un servicio (caja negra). Cada servicio con BBDD tiene su propia VM dedicada. App + BBDD + Redis todo dentro. Se backupea, migra y diagnostica como unidad. PDM gestiona migracion cross-cluster. PMG filtra correo entrante/saliente delante de WildDuck.

Nota disco VM Monitoring: VictoriaMetrics (retencion 1 año, ~200GB) + Loki (retencion 30 dias, ~50-250GB segun servidores). A 500 servidores, considerar separar Loki en su propio disco/VM.

Infra de backup (Capa 6, ademas de las VMs centrales):

  • PBS1 (Alemania): ya existente
  • PBS2 (Polonia): servidor fisico nuevo por contratar (solo disco grande, CPU/RAM minimos)
  • PBS3 (opcional por cliente premium): otro proveedor/pais, coste repercutido al cliente
  • Replica oficina: servidor Xeon 96GB existente (coste 0)

Recomendacion servidor fisico: 128 GB RAM, 8-12c/16-24t, 2x2TB NVMe (ZFS mirror)

En cada servidor de produccion (PBS/PVE/Linux)

Componente CPU RAM
Grafana Alloy (logs) <0.1 30-50 MB
node_exporter (metricas) <0.1 5-10 MB
Wazuh Agent (FIM+HIDS) <0.1 50-100 MB
CrowdSec <0.1 ~50 MB
Fail2ban <0.1 ~30 MB
auditd ya instalado 0 extra
pbs-jobs-metrics.sh (cron) despreciable despreciable
Total por servidor ~0.2 vCPU ~200-250 MB

Frente a Checkmk/Zabbix agent que consume 200-500MB solo para monitoring, aqui tenemos monitoring + logs + HIDS + IPS por ~250MB. En servidores de 32-256GB, pasa completamente desapercibido.