- 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.xhtmlcon 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
| Ejercicio | Concepto | Componente del proyecto |
|---|---|---|
| Ej 2 (hecho) | Parser serial, línea base | Módulo de parsing base |
| Ej 3 | multiprocessing en un nodo | Detectores Tier 1 corriendo en paralelo por regla |
| Ej 4 | Job arrays entre nodos | Ingesta multi-servidor (un job = un servidor de origen) |
| Ej 5 | Dependencias | Job de consolidación que espera a todos los ingest |
| Ej 6 | Contenedores | Empaquetar todo en un .sif reproducible |
| Extra | ML con scikit-learn | Modelos 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:
- 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.»
- Detecciones reales: «en la primera semana el sistema detectó Z eventos accionables que fail2ban solo no vio.»
- Baneos preventivos: «aportamos N IPs a la blocklist por día con precisión >95% (bajos falsos positivos).»
- 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.»
- Costo evitado: «un SIEM comercial equivalente cuesta $$$/año, este corre sobre infraestructura que ya tienen.»