3. HPC: ejemplo analisis de log

  • para este ejemplo vamos a analizar un log real de tomcat del sistema sigesa
  • Nos conectamos al servidor prod.sigesa-n01 y vamos al log de user tomcat
  • Vemos los log vamos a trabajar con localhost_access como podemos ver pesa 15M
  • este archivo tiene muchos chequeos del F5 verificando si el servicio esta arriba contestando 200 (si)
head -50 localhost_access_log.2026-08-19.txt
  • Entonces vamos a separar el archivo para ver el ruido y datos reales
# Tráfico "ruido" (health checks y monitoreo)
grep -E 'monitorf5\.xhtml|/health|/ping|/status' localhost_access_log.2026-08-19.txt > tomcat_ruido.log

# Tráfico real (todo lo demás)
grep -vE 'monitorf5\.xhtml|/health|/ping|/status' localhost_access_log.2026-08-19.txt > tomcat_real.log

# Ver la proporción
wc -l localhost_access_log.2026-08-19.txt tomcat_ruido.log tomcat_real.log
  • ahora por seguridad ya que vamos a subir el archivo a una infra que no es nuestra vamos a eliminar datos sensibles si es que existen
  • Creamos el archivo
nano hpc_anonimizar_log_prueba_tavo.py
  • le pegamos
# anonimizar.py
import hashlib
import re
import sys

IP_RE = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')

def es_interna(ip):
    p = ip.split('.')
    return (
        p[0] == '10'
        or (p[0] == '192' and p[1] == '168')
        or (p[0] == '172' and 16 <= int(p[1]) <= 31)
        or p[0] == '127'
    )

def anon(match):
    ip = match.group(0)
    prefijo = 'INT' if es_interna(ip) else 'EXT'
    h = hashlib.sha256(ip.encode()).hexdigest()[:8]
    return f'{prefijo}_{h}'

with open(sys.argv[1]) as f_in, open(sys.argv[2], 'w') as f_out:
    for linea in f_in:
        f_out.write(IP_RE.sub(anon, linea))
  • lo ejecutamos
# Anonimizar solo tomcat_real.log
python3 hpc_anonimizar_log_prueba_tavo.py tomcat_real.log tomcat_real_anon.log
  • Verificar cuánto pesa y cuántas líneas quedan
du -h tomcat_real_anon.log
wc -l tomcat_real_anon.log
  • Como es tan pequeño lo vamos a inflar
  • Inflar hasta ~500 MB (ajusta el multiplicador según el tamaño real)
for i in {1..100}; do cat tomcat_real_anon.log; done > tomcat_big.log
du -h tomcat_big.log
  • Queda de un 1GB
  • Ingresamos a Helix al home de mis ejemplos
/home/gustavo.matamoros.gonzalez/curso_hpc/mios
  • creamos nueva carpeta
mkdir -p ej2_logs_reales
  • ingresamos
cd ej2_logs_reales
  • ahora de sigesa subimos el archivo
scp tomcat_big.log gustavo.matamoros.gonzalez@10.10.132.5:~/curso_hpc/mios/ej2_logs_reales
  • Subimos los otros
scp tomcat_real_anon.log  gustavo.matamoros.gonzalez@10.10.132.5:~/curso_hpc/mios/ej2_logs_reales

scp tomcat_ruido.log   gustavo.matamoros.gonzalez@10.10.132.5:~/curso_hpc/mios/ej2_logs_reales
  • Primero verificamos si tenemos el modulo de python
module avail
  • Pero no esta
module avail | grep pyth
      • Entonces vamos a utilizar Anaconda que es python + un montón de librerías científicas
      module load Anaconda
      which python
      python --version
      python -c "import re, collections, sys; print('todo ok')"
      • Creamos el archivo
      nano ~/curso_hpc/mios/ej2_logs_reales/parser.py
      • Agregamos
      #!/usr/bin/env python3
      """Parser serial de access log Tomcat. Reporta métricas útiles."""
      import re
      import sys
      import time
      from collections import Counter, defaultdict
      
      # Regex para el formato: IP1 IP2 - - [timestamp] "MÉTODO URL PROTO" código bytes
      # Regex para el formato: IP1 IP2 - - [timestamp] "MÉTODO URL PROTO" código bytes
      LINE_RE = re.compile(
          r'^(\S+)\s+'                              # IP front (proxy)
          r'((?:\S+,\s+)*\S+)\s+'                   # IP client (puede ser lista con comas)
          r'\S+\s+\S+\s+'                           # identd y user (siempre "-")
          r'\[([^\]]+)\]\s+'                        # timestamp
          r'"(\S+)\s+(\S+)(?:\s+\S+)?\s*"\s+'       # método + URL
          r'(\d+)\s+'                               # código HTTP
          r'(\S+)'                                  # bytes
      )
      
      
      def parse_log(path):
          ip_front  = Counter()
          ip_client = Counter()
          endpoints = Counter()
          metodos   = Counter()
          codigos   = Counter()
          por_hora  = Counter()
          bytes_por_endpoint = defaultdict(int)
      
          total = 0
          malformadas = 0
      
          with open(path, 'r', encoding='utf-8', errors='replace') as f:
              for linea in f:
                  total += 1
                  m = LINE_RE.match(linea)
                  if not m:
                      malformadas += 1
                      continue
      
                  ip1, ip2, ts, metodo, url, codigo, tam = m.groups()
      
                  ip_front[ip1] += 1
                  ip_client[ip2] += 1
                  endpoints[url] += 1
                  metodos[metodo] += 1
                  codigos[codigo] += 1
      
                  # Hora del día: extrae "HH" de "19/Aug/2026:03:28:07 -0600"
                  try:
                      hora = ts.split(':')[1]
                      por_hora[hora] += 1
                  except IndexError:
                      pass
      
                  if tam.isdigit():
                      bytes_por_endpoint[url] += int(tam)
      
          return {
              'total': total,
              'malformadas': malformadas,
              'ip_front': ip_front,
              'ip_client': ip_client,
              'endpoints': endpoints,
              'metodos': metodos,
              'codigos': codigos,
              'por_hora': por_hora,
              'bytes_por_endpoint': bytes_por_endpoint,
          }
      
      def imprimir_reporte(r):
          print(f"\n=== RESUMEN ===")
          print(f"Total líneas:       {r['total']:,}")
          print(f"Líneas malformadas: {r['malformadas']:,}")
          print(f"IPs front únicas:   {len(r['ip_front']):,}")
          print(f"IPs cliente únicas: {len(r['ip_client']):,}")
          print(f"Endpoints únicos:   {len(r['endpoints']):,}")
      
          print(f"\n=== TOP 10 ENDPOINTS ===")
          for url, n in r['endpoints'].most_common(10):
              print(f"  {n:>10,}  {url}")
      
          print(f"\n=== MÉTODOS HTTP ===")
          for m, n in r['metodos'].most_common():
              print(f"  {n:>10,}  {m}")
      
          print(f"\n=== CÓDIGOS HTTP ===")
          for c, n in sorted(r['codigos'].items()):
              print(f"  {n:>10,}  {c}")
      
          print(f"\n=== TOP 10 IPs CLIENTE ===")
          for ip, n in r['ip_client'].most_common(10):
              print(f"  {n:>10,}  {ip}")
      
          print(f"\n=== DISTRIBUCIÓN POR HORA ===")
          for h in sorted(r['por_hora']):
              n = r['por_hora'][h]
              barra = '█' * int(n * 40 / max(r['por_hora'].values()))
              print(f"  {h}:00  {n:>10,}  {barra}")
      
          print(f"\n=== TOP 10 ENDPOINTS POR VOLUMEN (bytes) ===")
          top_bytes = sorted(r['bytes_por_endpoint'].items(), key=lambda x: -x[1])[:10]
          for url, b in top_bytes:
              mb = b / (1024 * 1024)
              print(f"  {mb:>10,.1f} MB  {url}")
      
      if __name__ == '__main__':
          if len(sys.argv) != 2:
              print(f"Uso: {sys.argv[0]} <archivo.log>", file=sys.stderr)
              sys.exit(1)
      
          inicio = time.time()
          resultados = parse_log(sys.argv[1])
          duracion = time.time() - inicio
      
          imprimir_reporte(resultados)
      
          print(f"\n=== TIEMPO ===")
          print(f"Duración parsing:  {duracion:.2f} s")
          print(f"Líneas por segundo: {resultados['total']/duracion:,.0f}")
      • Dale permisos:
      chmod +x parser.py
      • creamos el archivo
      nano ej2_logs_reales.sbatch
      • contenido
      #!/bin/bash
      #SBATCH --job-name=ej2_logs_reales
      #SBATCH --partition=debug
      #SBATCH --nodes=1
      #SBATCH --ntasks=1
      #SBATCH --cpus-per-task=1
      #SBATCH --time=00:30:00
      #SBATCH --output=ej2_logs_reales_%j.out
      #SBATCH --error=ej2_logs_reales_%j.err
      
      echo "=== INFO DEL JOB ==="
      echo "Nodo:       $(hostname)"
      echo "Job ID:     $SLURM_JOB_ID"
      echo "CPUs:       $SLURM_CPUS_PER_TASK"
      echo "Directorio: $SLURM_SUBMIT_DIR"
      echo "Inicio:     $(date)"
      echo ""
      
      module load Anaconda
      which python
      python --version
      echo ""
      
      cd $SLURM_SUBMIT_DIR
      
      python parser.py tomcat_big.log
      
      echo ""
      echo "Fin: $(date)"
      • Lanza el job
      sbatch ej2_logs_reales.sbatch
      • Ver progreso
      squeue -u $USER
      • Podemos ver el contenido
      cat ej2_logs_reales_39063.out
      • RESULTADO
      === INFO DEL JOB ===
      Nodo:       n001.cluster.pssclabs.com
      Job ID:     39063
      CPUs:       1
      Directorio: /home/gustavo.matamoros.gonzalez/curso_hpc/mios/ej2_logs_reales
      Inicio:     Fri Aug 28 14:19:55 PDT 2026
      
      /opt/miniconda3/bin/python
      Python 3.9.18
      
      
      === RESUMEN ===
      Total líneas:       6,996,300
      Líneas malformadas: 116,400
      IPs front únicas:   3
      IPs cliente únicas: 268
      Endpoints únicos:   3,665
      
      === TOP 10 ENDPOINTS ===
           344,900  /sigesa-webapp-1.0.0-SNAPSHOT/pages/boletaVacacionEditForm.xhtml
           290,000  /sigesa-webapp-1.0.0-SNAPSHOT/pages/loteEditForm.xhtml
           275,800  /sigesa-webapp-1.0.0-SNAPSHOT/pages/main.xhtml
           216,400  /sigesa-webapp-1.0.0-SNAPSHOT/pages/nombramientoEditForm.xhtml
           199,400  /sigesa-webapp-1.0.0-SNAPSHOT/pages/reintegroLiquidacionFondoEditForm.xhtml
           156,000  /sigesa-webapp-1.0.0-SNAPSHOT/pages/recepcionPedidoEditForm.xhtml
           155,300  /sigesa-webapp-1.0.0-SNAPSHOT/pages/registroBancarioEditForm.xhtml
           144,900  /sigesa-webapp-1.0.0-SNAPSHOT/pages/solicitudBienServicioEditForm.xhtml
           129,400  /sigesa-webapp-1.0.0-SNAPSHOT/pages/contratoBienServicioEditForm.xhtml
           125,800  /sigesa-webapp-1.0.0-SNAPSHOT/pages/analisisFormalOferenteEditForm.xhtml
      
      === MÉTODOS HTTP ===
         5,434,700  POST
         1,442,800  GET
             2,400  OPTIONS
      
      === CÓDIGOS HTTP ===
         6,659,000  200
           129,700  302
            11,300  304
             1,200  400
               100  401
            28,700  403
            48,200  404
             1,700  500
      
      === TOP 10 IPs CLIENTE ===
           346,700  INT_1239bd83
           282,500  EXT_f8e7083b
           260,600  EXT_bdcfaa83
           234,100  INT_4e7dbf23
           193,900  INT_558e1d40
           176,700  EXT_35ca3320
           170,600  INT_47cf6f38
           156,900  INT_491fe2bc
           149,100  EXT_926fc875
           133,300  -
      
      === DISTRIBUCIÓN POR HORA ===
        03:00      32,500  █
        05:00       9,800  
        06:00      45,100  █
        07:00     157,800  ██████
        08:00     497,700  █████████████████████
        09:00     804,100  ██████████████████████████████████
        10:00     939,800  ████████████████████████████████████████
        11:00     586,800  ████████████████████████
        12:00     202,900  ████████
        13:00     902,700  ██████████████████████████████████████
        14:00     929,900  ███████████████████████████████████████
        15:00     601,900  █████████████████████████
        16:00     631,800  ██████████████████████████
        17:00     198,100  ████████
        18:00     151,000  ██████
        19:00      58,900  ██
        20:00      50,700  ██
        21:00      25,600  █
        22:00      39,500  █
        23:00      13,300  
      
      === TOP 10 ENDPOINTS POR VOLUMEN (bytes) ===
          16,241.3 MB  /sigesa-webapp-1.0.0-SNAPSHOT/pages/nombramientoEditForm.xhtml
          14,763.0 MB  /sigesa-webapp-1.0.0-SNAPSHOT/pages/reintegroLiquidacionFondoEditForm.xhtml
          14,144.5 MB  /sigesa-webapp-1.0.0-SNAPSHOT/pages/boletaVacacionEditForm.xhtml
          14,007.5 MB  /sigesa-webapp-1.0.0-SNAPSHOT/pages/loteEditForm.xhtml
          10,944.6 MB  /sigesa-webapp-1.0.0-SNAPSHOT/pages/solicitudBienServicioEditForm.xhtml
          10,014.1 MB  /sigesa-webapp-1.0.0-SNAPSHOT/pages/recepcionPedidoEditForm.xhtml
           9,976.3 MB  /sigesa-webapp-1.0.0-SNAPSHOT/pages/contratoBienServicioEditForm.xhtml
           9,415.4 MB  /sigesa-webapp-1.0.0-SNAPSHOT/pages/registroBancarioEditForm.xhtml
           7,508.6 MB  /sigesa-webapp-1.0.0-SNAPSHOT/pages/analisisFormalOferenteEditForm.xhtml
           7,439.3 MB  /sigesa-webapp-1.0.0-SNAPSHOT/pages/main.xhtml
      
      === TIEMPO ===
      Duración parsing:  29.60 s
      Líneas por segundo: 236,323
      
      Fin: Fri Aug 28 14:20:25 PDT 2026
      • Ver errores si hubo
      cat ej2_logs_reales_39063.err
      • Metricas de ejecucion
      sacct -j 39063 -o JobID,JobName,State,Elapsed,MaxRSS,AllocCPUS,NodeList
      sacct -j 39063 --format=JobID,JobName,State,Elapsed,TotalCPU,CPUTime,MaxRSS,ReqMem,AllocCPUS,NodeList

      Perfecto momento para cambiar de sombrero. Antes del catálogo de ideas, hay una corrección importante sobre la intuición «HPC alimenta fail2ban»:

      HPC no es para fail2ban en tiempo real, pero SÍ para hacer que fail2ban sea inteligente

      fail2ban reacciona en segundos, lee el log local, aplica reglas simples. Si intentaras «correr fail2ban en HPC», el job entra en cola, espera 30-60 segundos, arranca, procesa, y para cuando decide bloquear ya pasó el ataque completo. Mala arquitectura.

      Lo que sí es una arquitectura ganadora:

      [servidor Tomcat/Apache]                     [Helix HPC]
            │                                            │
            │ fail2ban local con reglas SIMPLES          │ análisis batch NOCTURNO
            │ (5 fallos de login en 60s → banea)         │ 7M líneas del día, correlaciones
            │                                            │ modelos de anomalías, ML
            │                                            │
            │◄──── blocklist enriquecida diaria ─────────┤
            │      (IPs con patrones sofisticados        │
            │       que fail2ban solo no ve)             │

      fail2ban local caza lo obvio y en tiempo real. HPC caza lo sutil, lo lento, lo distribuido, lo que solo se ve mirando el día completo — y le devuelve al firewall una lista de IPs para banear preventivamente. Es lo mejor de los dos mundos.

      Lo que YA descubrimos hoy es evidencia del proyecto

      Fijate qué cosas útiles salieron de un log de un solo día con parsing simple:

      Errores operacionales detectables:

      • 1,700 respuestas 500 → hay bugs o problemas de infra que están pasando ahora mismo.
      • Un balanceador que reporta la misma IP dos veces en X-Forwarded-For → misconfiguration real.

      Señales de comportamiento anómalo:

      • 48k respuestas 404 → o hay muchos links rotos (mala UX) o alguien está escaneando.
      • 28k respuestas 403 → o la UI muestra botones que no debería o hay probing de permisos.
      • Una IP interna con 347k requests → ¿es un servicio no catalogado? ¿es un usuario con macro?
      • Tráfico a las 3 AM cuando la app es de RRHH y compras → sospechoso por definición.

      Métricas de gestión:

      • Pico de 260 req/s a las 10 AM → dimensionamiento.
      • 16 GB/día en un solo endpoint → optimización pendiente.
      • Ventana de mantenimiento clara antes de las 6 AM.

      Todo esto salió gratis del Ejercicio 2. Con solo agregar heurísticas de detección encima, ya tienes un proyecto real.

      Catálogo de detecciones factibles sobre logs de acceso

      Divido en tres tiers según complejidad. Los tres caben en el proyecto; el punto es empezar por los de alto valor y baja complejidad.

      Tier 1 — Reglas deterministas (heurísticas explícitas)

      • Fuerza bruta a login: N POSTs a /login.xhtml con 302→login o 401 desde misma IP en X segundos.
      • Enumeración/scanning: >M respuestas 404 desde misma IP en Y minutos con paths distintos (patrón dirbuster/gobuster/nikto).
      • Session hijacking: mismo JSESSIONID (si lo hubiera loguado) usado desde IPs geográficamente distantes.
      • Métodos inusuales: OPTIONS/TRACE/PUT/DELETE contra endpoints donde no aplican.
      • Payloads sospechosos en URL: patrones ' OR 1=1, <script>, ../, %00, union select, ${jndi: (log4shell).
      • Scraping: mismo endpoint hit miles de veces por minuto sin variar parámetros.
      • Off-hours anómalo: actividad transaccional a las 3 AM en un sistema de horario administrativo.
      • User-agent scanning: si logras loguear el UA, patrones de sqlmap, nikto, burp, curl automatizado.

      Tier 2 — Estadística sobre baseline

      • Perfil horario por endpoint: aprendes el patrón normal de las últimas 4 semanas por endpoint y hora; alarma si hoy se desvía > 3σ. Esto detecta cosas que ninguna regla explícita ve (ej. «de repente el endpoint X está recibiendo 10x tráfico un martes por la tarde»).
      • Perfil por IP: cada IP habitual tiene un patrón (endpoints que toca, volumen, horas); nuevas IPs que emulan alto volumen son sospechosas.
      • Tasa de errores por endpoint: si un endpoint pasa de 0.1% de 500s a 5% de un día para otro, alarma antes de que los usuarios reporten.
      • Ratio POST/GET por sesión: bots suelen tener ratios raros; humanos son más equilibrados.
      • Distribución de tamaños de respuesta: exfiltración se ve como respuestas anómalamente grandes de endpoints normalmente pequeños.

      Tier 3 — Machine Learning

      • Isolation Forest sobre features horarias (requests, códigos, bytes, endpoints únicos por IP) para detectar días/horas/IPs anómalas sin decirle qué buscar.
      • Clustering (DBSCAN/K-means) para agrupar IPs por comportamiento y descubrir «grupos» de atacantes coordinados.
      • Modelo de secuencias (LSTM ligero o Markov): aprende secuencias normales de navegación de usuarios; secuencias raras (login → editar_admin → dump_datos) son alarma.
      • Detección de UA falsos: si logras loguear UAs, un clasificador simple distingue Chrome real de Chrome falseado.

      Arquitectura del proyecto (propuesta)

      ┌─ Servidores SIGESA (Tomcat) ─────────────────────┐
      │  Cada uno con fail2ban local (reglas Tier 1      │
      │  básicas: brute-force, log4shell obvio, etc.)    │
      │  Rsync/rsyslog envía logs a un buzón compartido   │
      └──────────────────┬───────────────────────────────┘
                         │
                         ▼
      ┌─ Helix HPC — job nocturno (03:00 AM) ────────────┐
      │                                                   │
      │  1. Ingesta paralela (job array):                 │
      │     un job por servidor de origen                 │
      │                                                   │
      │  2. Parseo + normalización + separación de ruido  │
      │     (health checks, assets estáticos)             │
      │                                                   │
      │  3. Detección Tier 1 (reglas)  ─┐                 │
      │     Detección Tier 2 (baseline) ├─ en paralelo   │
      │     Detección Tier 3 (modelo)   ─┘                │
      │                                                   │
      │  4. Consolidación (job con --dependency):         │
      │     - reporte diario (PDF/HTML)                   │
      │     - blocklist enriquecida (IPs a banear)        │
      │     - eventos para revisión manual (SIEM-lite)    │
      │                                                   │
      └──────────────────┬───────────────────────────────┘
                         │
              ┌──────────┴──────────┬──────────────┐
              ▼                     ▼              ▼
      ┌─ fail2ban / firewall ─┐  ┌─ Control IP ─┐  ┌─ Email/Slack ─┐
      │  Blocklist actualizada│  │ (NocoBase):  │  │  Alertas de   │
      │  automáticamente cada │  │ dashboard    │  │  eventos      │
      │  mañana antes de 6AM  │  │ operacional  │  │  críticos     │
      └───────────────────────┘  └──────────────┘  └───────────────┘

      Mapeo — cómo los ejercicios construyen el proyecto

      EjercicioConceptoComponente del proyecto
      Ej 2 (hecho)Parser serial, línea baseMódulo de parsing base
      Ej 3multiprocessing en un nodoDetectores Tier 1 corriendo en paralelo por regla
      Ej 4Job arrays entre nodosIngesta multi-servidor (un job = un servidor de origen)
      Ej 5DependenciasJob de consolidación que espera a todos los ingest
      Ej 6ContenedoresEmpaquetar todo en un .sif reproducible
      ExtraML con scikit-learnModelos Tier 3 sobre features del baseline

      Cuando termines los 6 ejercicios, tendrás el 70-80% del código del proyecto ya escrito. El proyecto final es integrarlos + agregar detectores + reporte + blocklist.

      Métricas de éxito (para tu presentación)

      Cosas concretas que podrías mostrar:

      1. Tiempo: «procesamos 24h de logs de 5 servidores (~35M líneas) en X minutos, vs Y horas si lo hiciéramos secuencialmente en un solo servidor.»
      2. Detecciones reales: «en la primera semana el sistema detectó Z eventos accionables que fail2ban solo no vio.»
      3. Baneos preventivos: «aportamos N IPs a la blocklist por día con precisión >95% (bajos falsos positivos).»
      4. Reducción de incidentes: «correlacionamos el aumento de 500s en el endpoint X con el pico de 403s del atacante, ahorrando W horas de investigación.»
      5. Costo evitado: «un SIEM comercial equivalente cuesta $$$/año, este corre sobre infraestructura que ya tienen.»