AIOps con Ollama: Detección de ataques en tiempo real
Este laboratorio simula un escenario real de AIOps (Inteligencia Artificial para Operaciones de TI), una máquina expuesta recibe un ataque de fuerza bruta SSH, los logs se monitorizan en tiempo real, la IA local analiza si el patrón es un ataque, y si lo confirma, lanza una alerta. Todo sin depender de la nube.
Para este ejemplo usaré mi equipo con las siguientes características (estoy pensando en comprar un Mac Mini u otro equipo para dedicarlo a IA, pero son malos tiempos por el coste actual de componentes):
- Nuc Extreme 13 con i5 y 64gb de RAM
- GPU 4060 Ti con 16GB
- Windows 11 Pro
- Docker Desktop
- Ollama instalado en local

El ataque SSH de fuerza bruta (brute force) es el ataque más común en internet. Cualquier servidor con puerto 22 abierto lo recibe. Aprenderemos a detectarlo con IA y trasladarlo a tu empresa con cambios mínimos.
Os dejo un esquema:
Requerimientos Laboratorio AIOps
Antes de empezar, os explico los diferentes componentes:
| Software | Versión mínima | Dónde conseguirlo |
|---|---|---|
| Ollama | 0.3+ | ollama.com — ya instalado |
| Docker Desktop | 4.x | docker.com — ya instalado |
| Python | 3.10+ | python.org o ya instalado |
| Hydra | cualquiera | kali linux o apt install hydra |
| nmap | 7.x | nmap.org o apt install nmap |
| Modelo llama3.2:3b | — | ollama pull llama3.2:3b |
¿Qué hace cada componente?
- Ollama : Ejecuta modelos de IA (LLMs) en tu propio hardware. Como tener ChatGPT en casa, sin internet ni suscripción.
- Docker Desktop : Crea “cajas virtuales” (contenedores) que simulan servidores Linux dentro de Windows. No afectan a tu sistema.
- Python : El lenguaje que une todas las piezas. El script vigila los logs y llama a la IA.
- Hydra : Herramienta de hacking ético que prueba contraseñas automáticamente. La usamos para simular el ataque.
- auth.log : El “libro de registro” del servidor SSH. Anota cada intento de conexión, exitoso o fallido.
- Bot de Telegram : Un bot que creamos nosotros y que recibirá los mensajes de alerta cuando detectemos un ataque.
Carpeta de trabajo
Vamos a crear una carpeta donde vivirán todos los archivos del laboratorio. Trabajar con orden es importante cuando tienes varios archivos, si los mezclas con otros proyectos, vas a perderte.
Desde la consola de Powershell lanzamos la creación del directorio:
|
1 |
New-Item -ItemType Directory -Path "D:\LABORATORIO\aiops-lab" |
En esta carpeta al final dispondremos de:
📁 aiops-lab/
├── 📄 Dockerfile -> Define el servidor víctima
├── 📄 passwords.txt -> Lista de contraseñas para el ataque
├── 📄 aiops_watcher.py -> Script principal de vigilancia
├── 📄 .env -> Credenciales (Telegram, Email)
├── 📄 entrypoint.sh -> Script arranque contenedor
└── 📄 test_alertas.py -> Para probar Telegram y email antes
Verificación Ollama
Ollama ya está instalado en mi NUC. Pero necesito comprobar que el servidor está activo y descargar el modelo que usará para analizar los logs. Lanzamos:
|
1 |
Invoke-RestMethod http://localhost:11434/api/tags |
Debería devolver un OK:
Descargamos el modelo llama3.2:3b . Para los que empezáis, Ollama se instala en “c:\Users\USUARIO\AppData\Local\Programs\Ollama“:
|
1 |
.\ollama.exe pull llama3.2:3b |
Validamos:
|
1 2 3 4 |
.\ollama.exe list NAME ID SIZE MODIFIED llama3.2:3b a80c4f17acd5 2.0 GB 18 minutes ago llama3.1:8b 46e0c10c039e 4.9 GB 3 hours ago |
Hacemos un pequeño test o simulación de envío de logs para ver si responde. Lanzar en la consola de Powershell:
|
1 |
$body = '{"model":"llama3.2:3b","prompt":"Estos son logs SSH: Failed password for victima from 192.168.1.100 (5 intentos en 30 segundos). Es un ataque de fuerza bruta? Responde: veredicto, nivel de riesgo y accion recomendada.","stream":false}' |
Posteriormente el siguiente comando, que quedará pensando unos segundos:
|
1 |
Invoke-RestMethod -Uri http://localhost:11434/api/generate ` -Method POST ` -ContentType "application/json" ` -Body $body | Select-Object -ExpandProperty response |
Nos debería mostrar una respuesta en texto, si todo es correcto:
Verificación Docker Desktop
Ahora vamos a validar que tenemos Docker Desktop bien instalado y funcionando. Lanzamos el siguiente comando para saber en qué versión nos encontramos:
|
1 2 |
docker --version Docker version 29.3.1, build c2be9c |
Y que el motor esté activo:
|
1 2 3 |
docker info | Select-String "Server Version" Server Version: 29.3.1 |
Podéis lanzar un contenedor para verificación:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 4f55086f7dd0: Pull complete Digest: sha256:452a468a4bf985040037cb6d5392410206e47db9bf5b7278d281f94d1c2d0931 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ |
Crear Dockerfile para contenedor víctima ataque SSH
Con todo preparado, vamos a generar un Dockerfile en la ruta que hemos acordado para trabajar. Nos desplazamos a la ruta:
|
1 |
cd D:\LABORATORIO\aiops-lab\ |
Y lanzamos vía comando:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
$dockerfile = @' FROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y \ openssh-server \ rsyslog \ sudo \ coreutils \ && rm -rf /var/lib/apt/lists/* # Deshabilitar lectura del kernel log (no disponible en Docker) RUN sed -i '/imkmsg/s/^/#/' /etc/rsyslog.conf RUN mkdir -p /var/run/sshd /var/log RUN useradd -m -s /bin/bash victima && echo "victima:password123" | chpasswd RUN sed -i 's/#LogLevel INFO/LogLevel VERBOSE/' /etc/ssh/sshd_config && \ sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \ echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \ sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin no/' /etc/ssh/sshd_config RUN ssh-keygen -A RUN touch /var/log/auth.log && chmod 666 /var/log/auth.log COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 22 CMD ["/entrypoint.sh"] '@ |
Ahora exportamos la variable:
|
1 |
$dockerfile | Out-File -FilePath "Dockerfile" -Encoding UTF8 |
Ahora necesitamos crear el script de arranque del contenedor (el entrypoint.sh que referencia el Dockerfile):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$entrypoint = @' #!/bin/bash # Limpiar archivos de PID previos si existen rm -f /var/run/rsyslogd.pid # Arrancar rsyslog en segundo plano /usr/sbin/rsyslogd # Darle un segundo para que cree el socket /dev/log sleep 1 # Arrancar SSH en primer plano echo "Servidor listo para recibir ataques..." /usr/sbin/sshd -D '@ |
Y generamos el fichero:
|
1 |
PS D:\LABORATORIO\aiops-lab> $entrypoint = $entrypoint -replace "`r`n", "`n"; [System.IO.File]::WriteAllText("D:\LABORATORIO\aiops-lab\entrypoint.sh", $entrypoint) |
Construir la imagen y lanzar contenedor Docker
Con todo preparado generamos el contenedor:
|
1 |
docker build -t victima-ssh . |
Lanzamos:
|
1 |
docker run -d -p 2222:22 --name ssh-victima victima-ssh |
Y comprobamos:
|
1 |
docker ps |
Verifica la conexión (la contraseña es “password123”):
|
1 |
ssh -p 2222 victima@localhost |
Y revisamos que los logs se recogen sin problema. Nos equivocamos:
|
1 |
docker exec ssh-victima tail -f /var/log/auth.log |
En las pruebas podéis provocar un fallo de contraseña. Aparecerá un mensaje tipo “Failed password…”:
Verificación Python
Revisamos la versión instala:
|
1 2 |
PS D:\LABORATORIO\aiops-lab> python --version Python 3.14.2 |
Creamos un entorno virtual donde trabajar:
|
1 2 3 4 |
PS D:\LABORATORIO\aiops-lab> cd D:\LABORATORIO\aiops-lab PS D:\LABORATORIO\aiops-lab> python -m venv venv PS D:\LABORATORIO\aiops-lab> .\venv\Scripts\Activate.ps1 (venv) PS D:\LABORATORIO\aiops-lab> |
Instalamos las dependencias y verificamos:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
(venv) PS D:\LABORATORIO\aiops-lab> pip install requests python-dotenv Collecting requests Using cached requests-2.33.1-py3-none-any.whl.metadata (4.8 kB) Collecting python-dotenv Using cached python_dotenv-1.2.2-py3-none-any.whl.metadata (27 kB) Collecting charset_normalizer<4,>=2 (from requests) Using cached charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl.metadata (41 kB) Collecting idna<4,>=2.5 (from requests) Using cached idna-3.11-py3-none-any.whl.metadata (8.4 kB) Collecting urllib3<3,>=1.26 (from requests) Using cached urllib3-2.6.3-py3-none-any.whl.metadata (6.9 kB) Collecting certifi>=2023.5.7 (from requests) Using cached certifi-2026.2.25-py3-none-any.whl.metadata (2.5 kB) Using cached requests-2.33.1-py3-none-any.whl (64 kB) Using cached charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl (159 kB) Using cached idna-3.11-py3-none-any.whl (71 kB) Using cached urllib3-2.6.3-py3-none-any.whl (131 kB) Using cached python_dotenv-1.2.2-py3-none-any.whl (22 kB) Using cached certifi-2026.2.25-py3-none-any.whl (153 kB) Installing collected packages: urllib3, python-dotenv, idna, charset_normalizer, certifi, requests Successfully installed certifi-2026.2.25 charset_normalizer-3.4.7 idna-3.11 python-dotenv-1.2.2 requests-2.33.1 urllib3-2.6.3 [notice] A new release of pip is available: 25.3 -> 26.0.1 [notice] To update, run: python.exe -m pip install --upgrade pip (venv) PS D:\LABORATORIO\aiops-lab> python.exe -m pip install --upgrade pip Requirement already satisfied: pip in d:\laboratorio\aiops-lab\venv\lib\site-packages (25.3) Collecting pip Using cached pip-26.0.1-py3-none-any.whl.metadata (4.7 kB) Using cached pip-26.0.1-py3-none-any.whl (1.8 MB) Installing collected packages: pip Attempting uninstall: pip Found existing installation: pip 25.3 Uninstalling pip-25.3: Successfully uninstalled pip-25.3 Successfully installed pip-26.0.1 (venv) PS D:\LABORATORIO\aiops-lab> |
Verificamos con “pip list”:
Crear el bot de Telegram para alertas
Para poder crear el bot podéis usar este manual. La validación sería de la siguiente forma:
|
1 |
https://api.telegram.org/botTU_TOKEN/sendMessage?chat_id=TU_CHAT_ID&text=Hola+desde+AIOps+Lab |
El script Python completo de vigilancia
Este es el núcleo de todo el sistema. El script hace cuatro cosas: lee logs en tiempo real, cuenta fallos por IP, llama a Ollama para analizar y envía alertas. Vamos a crearlo (recordar incluir los datos de Telegram):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
$script = @' #!/usr/bin/env python3 import subprocess import re import time import requests import os from collections import defaultdict from datetime import datetime from dotenv import load_dotenv # Cargar variables del archivo .env load_dotenv() # ══════════════════════════════════════════════════ # CONFIGURACIÓN # ══════════════════════════════════════════════════ CONTAINER_NAME = "ssh-victima" LOG_PATH = "/var/log/auth.log" OLLAMA_URL = "http://localhost:11434/api/generate" OLLAMA_MODEL = "llama3.2:3b" FAIL_THRESHOLD = 5 WINDOW_SECONDS = 60 TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", "") TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "") # ══════════════════════════════════════════════════ # ESTADO INTERNO # ══════════════════════════════════════════════════ ip_attempts = defaultdict(list) alerted_ips = set() # Regex para detectar fallos SSH en Ubuntu PATRON_FALLO = re.compile(r'Failed password for (?:invalid user )?(\S+) from (\d+\.\d+\.\d+\.\d+)') def analizar_con_ollama(ip, usuario, intentos): prompt = ( f"Analiza este evento de seguridad:\n" f"IP: {ip}, Usuario: {usuario}, Intentos: {intentos}\n" f"Responde brevemente en español:\n" f"VEREDICTO, RIESGO y una recomendación técnica rápida." ) try: r = requests.post(OLLAMA_URL, json={"model": OLLAMA_MODEL, "prompt": prompt, "stream": False}, timeout=60) return r.json().get("response", "Error en respuesta").strip() except Exception as e: return f"Error Ollama: {e}" def enviar_telegram(mensaje): if not TELEGRAM_TOKEN or not TELEGRAM_CHAT_ID: print(" → Telegram no configurado.") return url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage" try: requests.post(url, json={"chat_id": TELEGRAM_CHAT_ID, "text": mensaje, "parse_mode": "HTML"}, timeout=10) print(" → Alerta enviada a Telegram.") except Exception as e: print(f" → Error enviando a Telegram: {e}") def procesar_linea(linea): match = PATRON_FALLO.search(linea) if not match: return usuario, ip = match.group(1), match.group(2) ahora = time.time() ip_attempts[ip].append(ahora) ip_attempts[ip] = [t for t in ip_attempts[ip] if ahora - t <= WINDOW_SECONDS] intentos = len(ip_attempts[ip]) print(f"[{datetime.now().strftime('%H:%M:%S')}] Intento fallido desde {ip} ({intentos}/{FAIL_THRESHOLD})") if intentos >= FAIL_THRESHOLD and ip not in alerted_ips: alerted_ips.add(ip) print(f"⚠️ Umbral alcanzado. Consultando IA...") analisis = analizar_con_ollama(ip, usuario, intentos) msg = (f"🚨 <b>ATAQUE DETECTADO</b>\n\n" f"<b>IP:</b> <code>{ip}</code>\n" f"<b>Intentos:</b> {intentos}\n" f"<b>Usuario:</b> {usuario}\n\n" f"🤖 <b>Análisis IA:</b>\n{analisis}") enviar_telegram(msg) def monitorizar(): print(f"🚀 Watcher iniciado. Monitoreando: {CONTAINER_NAME}") cmd = ["docker", "exec", CONTAINER_NAME, "tail", "-f", LOG_PATH] try: with subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True, encoding="utf-8") as p: for linea in p.stdout: procesar_linea(linea.strip()) except KeyboardInterrupt: print("\nDetenido.") if __name__ == "__main__": monitorizar() '@ |
Lo exportamos:
|
1 2 |
# Guardar el archivo limpio $script | Out-File -FilePath "D:\LABORATORIO\aiops-lab\aiops_watcher.py" -Encoding utf8 |
Script pruebas de alertas
Antes de ejecutar el lab completo, conviene probar que Telegram funcionan. Lo haremos con el script para el test (también agregar el TOKEN y CHAT_ID al script):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
$test = @' from dotenv import load_dotenv import os, requests # Cargar variables del archivo .env load_dotenv() # Test Telegram token = os.getenv("TELEGRAM_TOKEN") chat_id = os.getenv("TELEGRAM_CHAT_ID") if token and chat_id: try: r = requests.post( f"https://api.telegram.org/bot{token}/sendMessage", json={"chat_id": chat_id, "text": "✅ Test AIOps Lab — Telegram funciona correctamente"}, timeout=10 ) if r.ok: print("Telegram: OK (Mensaje enviado)") else: print(f"Telegram: ERROR — Código {r.status_code}: {r.text}") except Exception as e: print(f"Telegram: ERROR — No se pudo conectar con la API: {e}") else: print("Telegram: ERROR — Credenciales no encontradas en el archivo .env") '@ |
Exportamos:
|
1 2 |
# Corregir saltos de línea y guardar el archivo $test = $test -replace "`r`n", "`n"; [System.IO.File]::WriteAllText("D:\LABORATORIO\aiops-lab\test_alertas.py", $test) |
Ejecutamos el test:
|
1 |
D:\LABORATORIO\aiops-lab\test_alertas.py |
Si todo es correcto nos llegará un mensaje:
Instalación atacante (Hydra en Docker)
Con todo preparado sólo falta pasar al ataque…
Hydra es la herramienta de hacking ético que prueba contraseñas automáticamente. En Windows lo más fácil es ejecutarlo dentro de otro contenedor Docker, así no instalamos nada extra en Windows y queda aislado.
Definimos un pequeño conjunto de contraseñas:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$passwords = @" admin 1234 qwerty letmein abc123 root secret 12345678 password welcome monkey dragon password123 "@ |
Exportamos:
|
1 |
$passwords | Out-File -FilePath "D:\LABORATORIO\aiops-lab\passwords.txt" -Encoding UTF8 |
Hydra necesita la IP del contenedor, no “localhost”. Los contenedores Docker tienen su propia red interna, así que tenemos que descubrirla:
|
1 2 |
PS D:\LABORATORIO\aiops-lab> docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ssh-victima 172.17.0.2 |
Ahora que sabemos como extraer la IP, la guardamos en una variable:
|
1 2 |
PS D:\LABORATORIO\aiops-lab> $ip_victima = docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ssh-victima; Write-Host "IP del contenedor víctima: $ip_victima" IP del contenedor víctima: 172.17.0.2 |
Generaremos un contenedor Kali Linux con Hydra ya instalado:
|
1 |
docker run --rm ` -v "${PWD}:/trabajo" ` kalilinux/kali-rolling ` bash -c "apt-get update -q && apt-get install -yq hydra && hydra -l victima -P /trabajo/passwords.txt -t 4 -V ssh://$ip_victima" |
Ejecución de Laboratorio completo sobre 3 Terminales
Como ya tenemos todas las piezas, ahora vamos a lanzar el ataque y revisamos la vista desde el que vigila, la víctima y el atacante:
Terminal 1 — El vigilante (ejecutar primero)
- Ir a la carpeta del lab cd “D:\LABORATORIO\aiops-lab”
- Activar el entorno virtual Python .\venv\Scripts\Activate.ps1
- Lanzar el script vigilante:
- Queda en espera, mostrando un log de actividad python aiops_watcher.py
Terminal 2 — Víctima (opcional para la prueba, revisar logs)
- Lanzamos: docker exec -it ssh-victima tail -f /var/log/auth.log
Terminal 3 — Atacante
- Lanzamos: cd D:\LABORATORIO\aiops-lab
- Obtenemos la IP: $ip_victima = docker inspect -f ‘{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}’ ssh-victima; Write-Host “IP del contenedor víctima: $ip_victima” -ForegroundColor Red

- Lanzamos el ataque: docker run –rm
-v "${PWD}:/trabajo"kalilinux/kali-rolling ` bash -c “apt-get update -q && apt-get install -yq hydra && hydra -l victima -P /trabajo/passwords.txt -t 4 -V ssh://$ip_victima”
¿Qué veremos en cada terminal?
Veremos que mientras el atacante está intentado la combinación de contraseñas que hemos preconfigurado, se escribe el log y el script de Python revisa esos intentos:
Mientras en Telegram nos entregará un análisis de Ollama:
Y con esto terminamos un laboratorio de ciberseguridad con múltiples componentes que podemos llevar a nuestra empresa, por ejemplo y al que podemos hacer varias mejoras, como agregar emails para las notificaciones.
¿Qué cambios tendríamos que hacer para llevarlo a Producción?
Este laboratorio es totalmente exportable a una empresa…os dejo algunas pautas que habría que cambiar:
| En el laboratorio | En producción | Cambio necesario |
|---|---|---|
| docker exec … tail -f | tail -f /var/log/auth.log | Quitar “docker exec”, si el script corre en un servidor real completo |
| Contenedor Ubuntu local | Servidor Linux real (on-prem o cloud) | Ninguno en el script Python |
| Ollama en el NUC local | Ollama en servidor centralizado | Cambiar OLLAMA_URL a la IP del servidor central |
| 1 servidor vigilado | 10 servidores vigilados | Lanzar el script con threading, uno por servidor |
| Solo SSH | SSH + Apache + Nginx + Windows Event Log | Añadir más patrones regex en PATRON_FALLO |
Si os ha gustado, compartir que es gratis ;P
¿Te ha gustado la entrada SÍGUENOS EN TWITTER O INVITANOS A UN CAFE?
Blog Virtualizacion Tu Blog de Virtualización en Español. Maquinas Virtuales (El Blog de Negu) en castellano. Blog informática vExpert Raul Unzue





















