
Honeypot de interacción media para SSH/Telnet construido con Cowrie, Loki, Promtail y Grafana - aprovisionado en DigitalOcean mediante Terraform con un pipeline de validación de GitLab CI.
Un stack de honeypot SSH/Telnet desplegado en un droplet de DigitalOcean usando Terraform. Los ataques son capturados por Cowrie, almacenados en Loki y visualizados en Grafana con un mapa mundial en vivo del origen de los ataques.
honeypot/
├── cowrie/
│ └── etc/
│ ├── cowrie.cfg # Cowrie honeypot configuration
│ └── userdb.txt # Accepted and rejected fake credentials
├── geoip/
│ └── .gitkeep # Placeholder - MMDB files are gitignored
├── grafana/
│ ├── provisioning/
│ │ ├── dashboards/
│ │ │ ├── dashboards.yml # Provisioning: dashboard provider
│ │ │ └── honeypot-dashboard.json # Pre-built Attack Monitor dashboard
│ │ └── datasources/
│ │ └── loki.yml # Auto-provisioned Loki datasource
│ └── grafana.ini # Grafana server settings
├── loki/
│ └── config.yml # Loki single-binary config and retention
├── promtail/
│ └── config.yml # Promtail scrape and GeoIP pipeline
├── scripts/
│ ├── geoip-update.sh # Download or refresh GeoIP database
│ └── setup-firewall.sh # Host UFW rules for honeypot ports
├── terraform/
│ ├── templates/
│ │ ├── cloud-init.yaml.tftpl # Droplet first-boot script (Terraform-templated)
│ │ └── env.tftpl # .env lines embedded via Terraform
│ ├── backend.tf # Terraform backend config
│ ├── main.tf # Droplet, SSH key, firewall, cloud-init
│ ├── outputs.tf # IPs and helpful post-apply values
│ ├── terraform.tfvars.example # Example variable values (copy to terraform.tfvars)
│ ├── variables.tf # Terraform input variables
│ └── versions.tf # Terraform and provider version constraints
├── .env.example # Example environment file for manual setup
├── .gitignore # Ignored paths and files
├── .gitlab-ci.yml # CI/CD pipeline: fmt, validate, Checkov
├── docker-compose.yml # Docker Compose file for the honeypot stack
├── LICENSE # GPLv2 license
├── manual-deployment.sh # Legacy VM bootstrap without Terraform
└── README.md # Project documentation
## Requisitos de Hardware
> Nota: Estos requisitos se basan en el plan Basic Droplet de DigitalOcean a partir de mayo de 2026 y son los requisitos mínimos para ejecutar el proyecto.
- Proveedor: DigitalOcean
- Plan: Basic Droplet - 1 vCPU Intel
- RAM: 1 GB (+2 GB de swap)
- Almacenamiento: 35 GB NVMe SSD
- SO: Ubuntu 24.04 LTS
## Instalación y Configuración
>Nota: Después de la instalación, se recomienda abrir una nueva terminal y verificar que aún puedes conectarte por SSH en `ADMIN_SSH_PORT` (Predeterminado: 2022) antes de cerrar tu sesión original.
### Despliegue con Terraform (Recomendado)
1. Crea un token de API de DigitalOcean con permisos de lectura/escritura:
- Inicia sesión en DigitalOcean y navega a Cuenta > API > Tokens > Generar nuevo token.
- Nombra tu token (por ejemplo, "Cowrie Honeypot") y selecciona permisos de "Acceso completo".
- Haz clic en "Generar token" y copia el valor del token en un lugar seguro (no podrás volver a verlo).
2. Crea un par de claves SSH en tu máquina local y copia la ruta de la clave pública.
3. Ve al directorio `terraform`, luego copia y edita el archivo de variables:
> Nota: `do_token`, `ssh_public_key_path` y `grafana_admin_password` deben configurarse al menos en `terraform.tfvars` antes de aplicar.```bash
cd terraform
cp terraform.tfvars.example terraform.tfvars
Nota: Terraform debe estar instalado en tu máquina local. Visita Guía de instalación de Terraform para obtener instrucciones.```bash terraform init # Initialize Terraform and download providers terraform apply
5. Si pruebas - Redesplegar con reemplazo:```bash
terraform apply -replace="digitalocean_droplet.honeypot"
### Despliegue Manual (Legado)
1. Crea un droplet de DigitalOcean con los requisitos de hardware mencionados y tu clave SSH.
2. Conéctate mediante SSH al droplet o usa la consola web de DigitalOcean y ejecuta los siguientes comandos:```bash
ssh root@<your-droplet-ip> # If using DigitalOcean web console, skip this command
git clone https://gitlab.com/Oseguera12/cowrie-honeypot-digitalocean.git /opt/honeypot
cd /opt/honeypot
cp .env.example .env
nano .env
bash manual-deployment.sh
Nota: Por defecto, Terraform escribe el estado en
terraform/terraform.tfstateen tu máquina local. Este archivo contiene valores de salida sensibles (IP del droplet, contraseña de Grafana, huella digital de la clave SSH) y nunca debe ser enviado al repositorio..gitignorecubre*.tfstatey*.tfstate.*.
Riesgos del estado local:
apply pueden corromper el archivoPara cualquier cosa más allá de un laboratorio personal, cambia a un backend remoto. terraform/backend.tf contiene una configuración comentada de DigitalOcean Spaces (compatible con S3).
Para habilitar el backend remoto:
terraform.tfvars): ```bash
export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=
backend "s3" en terraform/backend.tf y completa con el nombre de tu bucket y el endpoint de la región.terraform init -migrate-state para mover el estado local existente a Spaces..gitlab-ci.yml ejecuta tres trabajos en cada push en una sola etapa validate:
| Job | Herramienta | Propósito |
|---|---|---|
terraform:fmt | hashicorp/terraform:1.8 | Formato: falla si algún archivo necesita terraform fmt |
terraform:validate | hashicorp/terraform:1.8 | Validez de la configuración sin contactar a DigitalOcean |
checkov:scan | bridgecrew/checkov:latest | Configuraciones incorrectas de IaC en el código de Terraform |
Nota:
terraform:fmtyterraform:validatebloquean el pipeline en caso de fallo.checkov:scanestá configurado conallow_failure: trueporque algunos hallazgos son compromisos intencionales. Suprime hallazgos aceptables específicos con comentarios en línea# checkov:skip=CKXXXen lugar de deshabilitar el trabajo por completo.
CKV_DIO_4 ("Asegurar que la entrada del cortafuegos no esté completamente abierta") se dispara una vez contra todo el recurso digitalocean_firewall. Se suprime con un único comentario # checkov:skip=CKV_DIO_4: colocado dentro del bloque del recurso en terraform/main.tf. La tabla a continuación documenta por qué cada puerto abierto es intencional.
| Check ID | Recurso | Hallazgo | Decisión |
|---|---|---|---|
CKV_DIO_4 | digitalocean_firewall.honeypot — admin SSH inbound | Puerto SSH de administración abierto a 0.0.0.0/0 | Aceptado — restringir a una IP fija no es práctico para un laboratorio portátil; recomendado en producción |
CKV_DIO_4 | digitalocean_firewall.honeypot — port 22 inbound | Puerto 22 abierto a 0.0.0.0/0 | Intencional — esta es la superficie honeypot SSH; restringir la fuente anula el propósito |
CKV_DIO_4 | digitalocean_firewall.honeypot — port 23 inbound | Puerto 23 abierto a 0.0.0.0/0 | Intencional — superficie honeypot Telnet; misma razón que el puerto 22 |
CKV_DIO_4 | digitalocean_firewall.honeypot — port 3000 inbound | Grafana HTTP expuesto a 0.0.0.0/0 | Aceptado para accesibilidad del laboratorio — limitación conocida documentada en Consideraciones de Seguridad; los despliegues en producción deben restringir a una IP conocida o proxy a través de HTTPS en el puerto 443 |
flowchart TB internet((Internet))
subgraph tf["Terraform"] fw[DigitalOcean Cloud Firewall] droplet[Ubuntu 24.04 droplet] fw --> droplet end
subgraph compose["Docker Compose on droplet"] cowrie["Cowrie honeypot — SSH on port 22, Telnet on port 23"] promtail[Promtail with GeoIP labels] loki[Loki log store] grafana["Grafana — host :3000"] cowrie -->|JSON logs from ./data/cowrie-logs| promtail --> loki --> grafana end
internet -->|22/23 honeypot| fw internet -->|Admin SSH :2022| fw internet -->|Grafana :3000| fw droplet --> compose
## Observaciones
Datos capturados durante 5 días de despliegue en vivo (2026-05-05 a 2026-05-09):
| Métrica | Valor |
|---------------------------|--------|
| Conexiones totales | 41,700 |
| Intentos de inicio de sesión | 15,400 |
| Inicios de sesión exitosos | 868 |
| Comandos ejecutados | 836 |
| Archivos descargados | 6 |
| Países de origen únicos | 106 |
### Principales países atacantes
| País | Conexiones |
|---------------------|------------|
| Alemania | 9,768 |
| Países Bajos | 9,110 |
| Estados Unidos | 7,706 |
| Reino Unido | 3,858 |
| Singapur | 1,770 |
| Bélgica | 1,422 |
### Patrones de credenciales
El nombre de usuario más común fue `root` con 3,866 intentos, seguido por
`admin` (728) y `user` (494), lo que refleja escáneres automatizados que apuntan a
credenciales predeterminadas y cuentas de servicio conocidas. La contraseña más común fue
`123456` (1,480 intentos) seguida de `123` y `12345`, consistente con
herramientas de fuerza bruta basadas en diccionarios.
### Comportamiento del atacante dentro del shell falso
868 intentos de inicio de sesión tuvieron éxito contra el conjunto de credenciales falsas. De esas sesiones,
se ejecutaron 836 comandos. El comando más común fue `uname -s -v -n -r -m`
(360 ejecuciones), un comando estándar de identificación del sistema ejecutado por scripts
automatizados de post-explotación para identificar el SO y la arquitectura del objetivo antes de
implementar un payload. Otros comandos observados incluyen `export HISTFILE=/dev/null`
para deshabilitar el registro del historial del shell, y `export HISTSAVE=/dev/null`, lo que indica
que los atacantes intentan activamente ocultar sus huellas incluso dentro de lo que creían
era un sistema comprometido.
### Conclusión clave
El volumen de tráfico de escaneo automatizado — 41,700 conexiones en 5 días desde 106
países — confirma que cualquier servicio SSH accesible públicamente se enfrenta a constantes
intentos de fuerza bruta en cuestión de horas después de su exposición. El patrón de comportamiento de
ejecutar inmediatamente `uname` seguido de comandos de supresión de historial es consistente
con marcos de post-explotación automatizados que operan con mínima intervención humana.
## Uso
### Uso del tablero Grafana
> Nota: Las credenciales del tablero se establecen en terraform.tfvars o .env (para despliegue manual)
El tablero se puede acceder en:```
http://<your-droplet-ip>:3000
Iniciar sesión con admin / <GRAFANA_ADMIN_PASSWORD>.
| Panel | Descripción |
|---|---|
| Mapa mundial de ataques | Geomapa con capas de mapa de calor + marcadores que muestran el origen de cada conexión |
| Conexiones totales | Recuento de sesiones entrantes del honeypot en el rango de tiempo seleccionado |
| Intentos de inicio de sesión | Intentos totales de fuerza bruta de credenciales |
| Inicios de sesión exitosos | Atacantes que coincidieron con las credenciales de userdb.txt |
| Comandos ejecutados | Comandos de shell ejecutados dentro del shell falso |
| Archivos descargados | Malware/scripts capturados mediante wget/curl |
| Tasa de conexiones | Serie temporal: conexiones/s, inicios de sesión fallidos/s, éxitos/s |
| Nombres de usuario principales | Nombres de usuario SSH más probados |
| Contraseñas principales | Contraseñas más probadas |
| Comandos principales | Comandos de shell más ejecutados |
| Ataques por país | Tabla de conexiones a nivel de país |
| Eventos recientes | Transmisión en vivo de eventos recientes |
| Descargas de archivos | Tabla de cada archivo que un atacante intentó obtener |
cowrie/etc/userdb.txt - contiene las credenciales aceptadas para el honeypot.
Edite este archivo para ajustar las credenciales del honeypot
Los inicios de sesión aceptados colocan al atacante en un shell falso donde todos los comandos se registran
Las entradas rechazadas se registran como intentos fallidos
Esta sección detalla las decisiones tomadas y los problemas encontrados durante el desarrollo de este proyecto. Sirve como reflexión del proceso de desarrollo y las lecciones aprendidas. Pase a Consideraciones de seguridad si solo desea saber cómo usar el proyecto.
¿Por qué alojamiento en la nube?
¿Por qué DigitalOcean?
¿Por qué Terraform?
manual-deployment.sh, es para usuarios que no tienen experiencia con Terraform o prefieren no usarlo. Esa ruta requiere aprovisionar un droplet, conectarse a él, clonar el repositorio y ejecutar el script. Terraform permite una iteración más rápida: edite la base de código y vuelva a implementar sin repetir cada paso manual desde su máquina local. También facilita desmantelar la infraestructura y comenzar de nuevo. Si el proyecto crece de un solo honeypot a una honeynet, Terraform escala de manera más limpia que el aprovisionamiento manual o el script heredado por sí solo.¿Por qué Cowrie?
¿Por qué Loki?
¿Por qué Grafana?
¿Por qué Promtail?
¿Por qué GeoIP?
scripts/geoip-update.sh: sin cuenta ni clave API, lo que mantiene la implementación simple para cualquiera que clone el repositorio. La etapa GeoIP incorporada de Promtail lee el archivo MMDB local y agrega etiquetas para el panel Geomap de Grafana.Resumen paso a paso de lo que ocurre cuando se configura el honeypot usando Terraform o el script de implementación manual.
En su máquina:
terraform/terraform.tfvars: token API, ruta de la clave SSH pública, contraseña de Grafana, URL/rama del repositorio, manage_do_firewall opcionalterraform init: instala el proveedor de DigitalOcean localmenteterraform apply: Terraform crea un plan, luego crea o actualiza recursosEn DigitalOcean:
droplet_image, droplet_size, region, etc.user_data se establece en el cloud-init renderizado: en el primer arranque la VM obtiene el puerto SSH de administración, git repo_url / repo_branch, y una copia codificada en base64 del .env generado (contraseña de Grafana y ADMIN_SSH_PORT de env.tftpl)manage_do_firewall es verdadero):
terraform/main.tf)En el droplet, cloud-init se ejecuta automáticamente:
package_update / package_upgrade, luego instala los paquetes listados (curl, git, ufw, …). Al inicio del arranque, write_files crea /root/honeypot.env (el env.tftpl renderizado por Terraform como base64: contraseña de Grafana, ADMIN_SSH_PORT, etc.)runcmd (orden en terraform/templates/cloud-init.yaml.tftpl): establece los backends iptables / ip6tables a heredados/swapfile, fstab, swappiness)systemctl enable --now dockersshd en ADMIN_SSH_PORT; deshabilita ssh.socket para que el puerto 22 del host esté libre para Cowriegit clone desde repo_url / repo_branch en /opt/honeypot (debe incluir docker-compose.yml y configuraciones).env en disco: mueve /root/honeypot.env a /opt/honeypot/.env (modo 600)scripts/setup-firewall.sh (UFW para SSH de administración, 22, 23, 3000)scripts/geoip-update.sh (DB-IP City Lite en geoip/); añade una línea cron mensual para el mismo scriptdata/cowrie-logs y data/cowrie-dl con permisos que espera Compose/opt/honeypot, docker compose pull luego docker compose up -dDentro de Docker Compose:
/ready en el puerto 3100 dentro del contenedor; en el host solo 127.0.0.1:3100)depends_on en docker-compose.yml), luego se iniciandata/cowrie-logs para PromtailEl usuario crea la VM, se conecta por SSH y ejecuta el script desde el repositorio clonado.
manual-deployment.sh hace lo siguiente:
.envapt update y apt upgradeufw e iptablessshd en ADMIN_SSH_PORT y deshabilita ssh.socketscripts/setup-firewall.shscripts/geoip-update.sh y cron mensualdocker compose pull / up -d, luego el script espera las verificaciones de salud de Loki y Grafana e imprime un resumenSoluciones consideradas:
Resolución:
Compensaciones:
Lecciones aprendidas:
docker compose up fallaba porque el puerto 22 ya estaba en uso y no podía ser vinculado por Cowrie.Soluciones consideradas:
sshd estaba mal configurado o escuchando en el puerto incorrectoResolución:
sshd se mueve a ADMIN_SSH_PORT (por defecto 2022) y se reinicia primero, luego se detiene y deshabilita ssh.socket para liberar el puerto 22 para que Docker lo vincule para Cowrie. Las reglas de UFW se aplican después de ambos pasos para permitir el nuevo puerto de administración.Compensaciones:
Lecciones aprendidas:
ssh.socket es una unidad de socket de systemd que mantiene el puerto 22 reservado para activación SSH bajo demanda. Debe detenerse y deshabilitarse antes de que Docker pueda vincular el puerto 22 del host para Cowrie. Si sigue activo, docker compose up falla con "address already in use".Funcionalidad GeoIP
HTTPS de Grafana
Funcionalidad de servidor proxy
terraform.tfvars está en gitignore y nunca debe ser commiteadoGNU General Public License Versión 2.0 (GPLv2) Consulte el texto completo de la licencia en el archivo LICENSE.