13 minutes
Kubehive - Infraestructura de honeypots distribuida
Proyecto: Cluster de Kubernetes con nodos efímeros en VPS externos, honeypots corriendo en producción, logs centralizados en ELK y todo el tráfico cifrado por WireGuard. Desde un homelab.
Presentación
Por mucho que sepas utilizar una tecnología, sin un proyecto no sirve de nada. Llevo un tiempo trasteando con Kubernetes, Nodos, Proxmox, Honeypots, Servidores…. Los servicios se me acumulaban individualmente, aislados en VLANs, Redes Virtuales o Maquinas separadas.
Empezó a sonar en mi cabeza una vocecilla que decia "¡Juntalo todo!".
¡Manos a la obra!
Diseño
Antes de empezar a levantar cosas sin sentido hay que diseñar. Las tecnologías estaban claras, ahora quedaba combinarlas y darles forma.
El objetivo y la idea
Lo primero que me vino a la cabeza son los honeypot. Son faciles de detectar y una vez que sabes que estas en una trampa no vuelves a ella. Luego vino la comunicación, tengo que conseguir una red aislada que no entre en conflicto con IP’s publicas, por si surgiera cualquier imprevisto. Voy a sacar muchisima información, demasiada, tengo que ser capaz de gestionarla correctamente y darle un uso util. La gestión de el trafico tiene que ser impecable, no se puede colar ni escapar absolutamente NADA.
Ahora que tenemos la base solo me quedaba un problema. La IP se quema. En pocos días los scanners detectan el honeypot por sus firmas o por comportamiento y lo marcan.
El primer escaner que me vino a la cabeza fue Shodan, realizan escaneos diarios de practicamente todo lo que existe en internet.
Si tienes un honeypot durante más de una semana date por vencido, tendrás expuestos de manera global todos tus puertos, certificados, servicios e incluso vulnerabilidades con una etiqueta enorme que dice HONEYPOT.
¿Solucion? Ser más rápido
Si rotamos la dirección IP, usamos nodos efímeros que se destruyen antes de que se realize el escaneo, habremos recogido ataques durante un periodo de tiempo suficientemente largo como para que sea fructifero y no nos hayan fichado hasta el DNI.
Una IP limpia es una IP que todavía no sabe nadie que es un honeypot.
La arquitectura
Homelab (CPD)
├── hEX S → Router/Firewall/WireGuard server
├── Zimaboard (Proxmox) → Master k8s + ELK
VPS (worker k8s efímero)
└── Pod OpenCanary → SSH, HTTP, Telnet
└── Pod Mailoney → SMTP
└── Filebeat → logs → Elasticsearch
El master de Kubernetes vive en el CPD, en una VM dentro del Proxmox de la Zimaboard. Los workers son VPS en Vultr que se unen al cluster por WireGuard. Los honeypots corren en esos workers. Los logs van a Elasticsearch. Desde fuera solo se ve la IP del VPS y los puertos de los honeypots. El master nunca está expuesto.
Cuando un nodo lleva demasiado tiempo activo y puede estar quemado, se destruye y se crea uno nuevo. Terraform + cloud-init. Nueva IP, mismo cluster, mismo setup automático.

Decisiones de diseño
¿Por qué Kubernetes y no Docker Compose? K8s me da scheduling, health checks, rolling updates y la posibilidad de añadir nodos remotos de forma nativa. Un worker remoto en un VPS es un nodo más del cluster. No tengo que gestionar nada manualmente. Esta gestión de nodos facilita la creación y destrucción de honeypots de manera casi automática.
¿Por qué WireGuard y no una VPN clásica? WireGuard es simple, rápido y RouterOS v7 lo soporta de forma nativa en el MikroTik hEX. La clave pública del peer, la red del túnel y listo. El tráfico del cluster viaja cifrado sin que tenga que abrir puertos innecesarios.
¿Por qué Elasticsearch y Kibana? Elasticsearch es versatil, cómodo y eficiente, acompañado de Kibana se hace indestructible a la hora de administrar datos. Tiene la posibilidad de realizar consultas y de crear visualizaciones de datos muy definidas.
Preparación
Hardware
| Dispositivo | Rol |
|---|---|
| MikroTik hEX | Router, firewall, WireGuard Server |
| Zimaboard 2 (N150, 16GB RAM) | Proxmox producción + Master k8s + ELK |
| VPS (1 vCPU, 1GB, 6€/mes) | Worker k8s + honeypots |
Software base
- Proxmox VE en Zimaboard
- Ubuntu 22.04 en la VM del Master k8s
- Ubuntu 22.04 en el VPS worker
- RouterOS v7 en el hEX S
- WinBox gestion MikroTik
Configuración MikroTik
El Mikrotik hEX S es el cerebro de la red. Gestiona el tráfico, las subredes, el firewall y el servidor WireGuard.
Subredes
WAN → eth1 → DHCP desde el router del ISP (Salida Internet)
PROD → eth2 → 10.10.10.0/24 → Zimaboard (Proxmox)
Firewall
Las reglas críticas en IP → Firewall → Filter Rules:
ACCEPT established, related (tráfico ya establecido)
ACCEPT LAN → WAN (salida a internet)
DROP DMZ → PROD (el resto de maquinas no pueden hablar con Proxmox)
DROP WAN → PROD (Proxmox no accesible desde fuera)
ACCEPT forward in/out wireguard1 (tráfico entre peers WireGuard)
Con esto conseguimos aislar el resto de trafico en la red de nuestro mikrotik con la red de los Honeypots.
Rutas estáticas
El hEX S necesita saber que la red del túnel WireGuard (10.10.20.0/24) se alcanza por la interfaz WireGuard. RouterOS lo gestiona automáticamente cuando creamos la interfaz, pero los peers necesitan tener correctamente configurados sus AllowedIPs para que el routing funcione en ambas direcciones.
WireGuard
WireGuard crea una red segura que une el master con los workers en la VPS. Sin él, k8s no tendrá comunicación y no podrá orquestar los nodos. El API server del master tiene que ser alcanzable desde los workers para que el servicio kubelet pueda registrarse y recibir instrucciones.
Mapa de red del túnel
hEX → 10.10.20.1 (server)
Master k8s → 10.10.20.2 (peer)
Worker VPS → 10.10.20.3 (peer)
El Serivor Wireguard en Mikrotik hEX actúa como conector. Todos los peers se conectan a él para entrutar el tráfico entre los nodos.
Configuración en Mikrotik RouterOS 7
En WinBox: WireGuard → nueva interfaz KubeHive, puerto 51820. RouterOS genera las claves automáticamente. Se añade cada peer con su clave pública y sus AllowedIPs.
Peer del master:
Public Key: <clave del master>
Allowed IPs: 10.10.20.2/32, 10.10.10.0/24
Peer del worker VPS:
Public Key: <clave del VPS>
Allowed IPs: 10.10.20.3/32
El AllowedIPs del peer del master necesitaremos incluir la red 10.10.10.0/24, es la red en la que se encuentra el Master de k8s (10.10.10.10) donde escucha el API server de k8s. De lo contrario, el worker no podra enviar los datos recogidos al Master ni recibira instrucciones de kubelet.
Configuración en el Master
/etc/wireguard/KubeHive.conf:
[Interface]
PrivateKey = <Clave privada>
Address = 10.10.20.2/24
[Peer]
PublicKey = <Clave publica Mikrotik>
Endpoint = <IP Mikrotik>:51820
AllowedIPs = 10.10.20.0/24
PersistentKeepalive = 25
Configuración en el VPS worker
/etc/wireguard/KubeHive.conf:
[Interface]
PrivateKey = <Clave privada>
Address = 10.10.20.3/24
[Peer]
PublicKey = <Clave publica Mikrotik>
Endpoint = <IP Mikrotik>:51820
AllowedIPs = 10.10.10.0/24, 10.10.20.0/24 #Añadimos la red del Master
PersistentKeepalive = 25
Los AllowedIPs del worker incluyen la red del túnel, la red del master y las redes internas de k8s (pods y servicios). Sin esto el tráfico del cluster no entra en el túnel y el nodo nunca llega a ser Ready.
Comprobación
Con todo esto, un ping 10.10.10.10 desde la VPS y un ping 10.10.20.2 desde el master confirman que el túnel funciona en ambas direcciones antes de montar k8s.
Hay que tener presente que las reglas configuradas en el firewall mikrotik no tengan conflicto con la red WG.
Kubernetes
Con la red funcionando ahora toca montar el cluster. En este caso usaremos la versión vanilla, Kubernetes con kubeadm, sin distribuciones gestionadas, sin managed services. Esto nos permitirá analizar y gestiona cada pieza del cluster a nuestro gusto.
Configuracion Kubernetes Master
Prerequisitos antes del init:
- Desactivar la memoria swap
- Activar modulo br_netfliter
- Permitir ip_forward y modo bridge
Para esto utilizaremos los siguientes comandos.
swapoff -a
sed -i '/ swap / s/^/#/' /etc/fstab
modprobe br_netfilter
echo "br_netfilter" >> /etc/modules-load.d/k8s.conf
sysctl -w net.bridge.bridge-nf-call-iptables=1
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-iptables=1" >> /etc/sysctl.conf
Iniciamos el cluster con kubeadm, utilizaremos la dirección de nuestra red interna 10.10.10.10/24 como endpoint para el control plane, de esta manera cuando los workers vayan a buscar al master el servicio estará expuesto.
Ejecutamos:
kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--control-plane-endpoint=10.10.10.10 \
--node-ip=10.10.10.10
El --node-ip es crítico. Sin él, kubeadm puede coger la IP de WireGuard en lugar de la IP real del master, y el kubelet queda apuntando a una IP que no existe en sus interfaces. Esto rompe el cluster de forma silenciosa y es dificil de encontrar.
CNI: Calico
Se eligió Calico como CNI ya que permite implementar politicas de seguirdad avanzadas con eBPF y tiene integración nativa con Wireguard.
Calico nos proporciona la función hostPort, de cara a los honeypot es algo muy útil, sin embargo nosotros dispondremos del parametro hostNetwork: true ya que los honeypots necesitan escuchar en los puertos reales del sistema (22, 25, 23). Con hostNetwork: true el pod usa la red del host directamente. Sin NodePort, sin DNAT, sin reglas de iptables que mantener. Y la IP del pod no cambia cuando el pod se reinicia, porque la IP es la del host.
Aplicamos la siguiente configuración obtenida de la web del CNI Calico:
vim custom-resources.yaml
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
# Note: The ipPools section cannot be modified post-install.
ipPools:
- blockSize: 26
cidr: 10.244.0.0/16
encapsulation: VXLANCrossSubnet
natOutgoing: Enabled
nodeSelector: all()
---
apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
name: default
spec: {}
Y aplicamos el fichero de configuración creado:
kubectl apply -f custom-resources.yaml
Una vez que este aplicado comprobamos que todo el cluster está listo y todos los pods en Running:
kubectl get pods -n calico-system

Worker (VPS)
Para todos los nodos tendremos los mismos prerequisitos que el master. Una vez que tengamos configurado WireGuard y haya conexion entre Master - Worker procederemos a unirlo al cluster.
Usaremos el parametro join con el token generado por el init:
kubeadm join 10.10.10.10:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
Nota: En caso de que el token generado en el init del cluster no este disponible podremos generar uno nuevo con el siguiente comando
kubeadm token create --print-join-command
Una vez se haya unido, se crearán pods de Calico que se encargarán de la gestión de la red del Nodo.
Los honeypots
Los Honeypots van a ser desplegados únicamente en los Worker, la gestión de los nodos se realizará desde el Master, en esta instalación utilizaremos dos Honeypot (OpenCanary/Mailoney).
OpenCanary
OpenCanary simula SSH, HTTP y Telnet. Con hostNetwork: true escucha directamente en la IP pública del VPS. El pod tiene label role: honeypot y nodeSelector para asegurarse de que siempre va al worker, nunca al master.
Crearemos dos ficheros con la configuración.
opencanary-configmap.yaml -> Donde declaramos los puertos, servicios y formato de logs.
apiVersion: v1
kind: ConfigMap
metadata:
name: opencanary-config
namespace: default
data:
opencanary.conf: |
{
"device.node_id": "KubeHive-XXXX",
"ip.ignorelist": [],
"ftp.enabled": false,
"ftp.port": 21,
"ftp.banner": "FTP server ready",
"http.enabled": true,
"http.port": 8080,
"http.banner": "Apache/2.4.1 (Unix)",
"http.skin": "nasLogin",
"ssh.enabled": true,
"ssh.port": 22,
"ssh.version": "SSH-2.0-OpenSSH_5.9p1 Debian-5ubuntu1",
"telnet.enabled": true,
"telnet.port": 23,
"smtp.enabled": true,
"smtp.port": 25,
"smtp.banner": "Postfix SMTP",
"mysql.enabled": false,
"mysql.port": 3306,
"redis.enabled": false,
"redis.port": 6379,
"logger": {
"class": "PyLogger",
"kwargs": {
"formatters": {
"plain": {
"format": "%(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.FileHandler",
"filename": "/var/log/opencanary/opencanary.log"
}
}
}
}
}
opencanary-deployment.yaml -> Donde declaramos los contenedores y configuraciónes.
apiVersion: apps/v1
kind: Deployment
metadata:
name: opencanary
namespace: default
labels:
app: opencanary
spec:
replicas: 1
selector:
matchLabels:
app: opencanary
template:
metadata:
labels:
app: opencanary
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
nodeSelector:
role: honeypot
containers:
- name: opencanary
image: thinkst/opencanary:latest
imagePullPolicy: IfNotPresent
securityContext:
privileged: false
runAsUser: 0
allowPrivilegeEscalation: true
capabilities:
add:
- NET_BIND_SERVICE
- SETUID
- SETGID
drop:
- ALL
volumeMounts:
- name: config
mountPath: /etc/opencanaryd/opencanary.conf
subPath: opencanary.conf
- name: logs
mountPath: /var/log/opencanary
volumes:
- name: config
configMap:
name: opencanary-config
- name: logs
hostPath:
path: /var/log/opencanary
type: DirectoryOrCreate
La versión de SSH es intencionada: este honeypot imita un servidor Ubuntu 12.04 con una versión antigua de OpenSSH.
Mailoney
Mailoney simula SMTP. Banner de Exim 4.97, logs en JSON. La imagen que uilizaremos será dtagdevsec/mailoney:24.04.1.
Como variables de entorno usaremos LOGPATH=/var/log/mailoney/ para que los logs vayan al hostPath y persistan aunque el pod se reinicie.
Crearemos un fichero con la siguiente configuración:
mailoney.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailoney
namespace: default
labels:
app: mailoney
spec:
replicas: 1
selector:
matchLabels:
app: mailoney
template:
metadata:
labels:
app: mailoney
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
nodeSelector:
role: honeypot
containers:
- name: mailoney
image: dtagdevsec/mailoney:24.04.1
imagePullPolicy: IfNotPresent
env:
- name: LOGPATH
value: /var/log/mailoney/
securityContext:
capabilities:
add:
- NET_BIND_SERVICE
volumeMounts:
- name: logs
mountPath: /var/log/mailoney
volumes:
- name: logs
hostPath:
path: /var/log/mailoney
type: DirectoryOrCreate
Despliegue
Iniciamos los honeypot para que empiecen a recoger intentos de ataques.
kubectl apply -f opencanary-configmap.yaml
kubectl apply -f opencanary-deployment.yaml
kubectl apply -f mailoney.yaml
ELK Stack
Elasticsearch y Kibana corren directamente en el SO del master, fuera de k8s. No tiene sentido meterlos en el cluster cuando los logs vienen de ficheros en el host del VPS, no de los pods.
Filebeat corre en todos los VPS y mandan los logs a Elasticsearch por el túnel WireGuard:
Editando el archivo /etc/filbeat/filebeat.yml prepararemos la siguiente configuración:
ilebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/opencanary/opencanary.log
fields:
honeypot: opencanary
json.keys_under_root: true
json.add_error_key: true
- type: log
enabled: true
paths:
- /var/log/mailoney/commands.log
fields:
honeypot: mailoney
json.keys_under_root: true
json.add_error_key: true
processors:
- rename:
fields:
- from: "src_ip"
to: "src_host"
ignore_missing: true
fail_on_error: false
output.elasticsearch:
hosts: ["https://10.10.10.10:9200"]
username: "filebeat_honeypot"
password: "${ES_PASSWORD}"
ssl.certificate_authorities: ["/etc/filebeat/http_ca.crt"]
index: "honeypot"
setup.kibana:
host: "http://10.10.10.10:5601"
setup.ilm.enabled: false
setup.template.name: "honeypot"
setup.template.pattern: "honeypot*"
La contraseña va en el keystore de Filebeat, no en texto plano. Esto lo hacemos con filebeat keystore add ES_PASSWORD. Así si el honeypot se viera comprometido, conseguimos frenar el impacto en nuestro centro de datos.
El usuario filebeat_honeypot tiene un rol con permisos mínimos: solo escribir en índices honeypot-* y los cluster privileges necesarios (monitor, manage_index_templates) declarados en el servidor ELK.
El resultado: un data stream honeypot-YYYY.MM.DD con todos los eventos de los honeypots, visualizable en Kibana con un dashboard de IPs atacantes, puertos más golpeados, credenciales probadas y actividad temporal.
Resultado
Pasados unos días veremos en nuestra Dasboard de Kibana que van apareciendo logs, ataques, direcciones IP, credenciales probadas, puertos etc.
Credenciales más usadas
| User | Password | Records |
|---|---|---|
| admin | admin | 5,612 |
| root | xc3511 | 4,886 |
| root | vizxv | 4,402 |
| root | admin | 4,223 |
| root | 123456 | 2,893 |
| root | 888888 | 2,746 |
| root | root | 2,736 |
| support | support | 2,614 |
Ataques por día

Localizaciones de ataques

Terraform
En construcción.
El objetivo final es que levantar un nuevo nodo worker sea un único comando:
terraform apply
Y destruirlo también:
terraform destroy
Terraform provisionará el VPS. Cloud-init se ejecutará al arrancar y hará todo lo demás: instalar las dependencias, configurar WireGuard, unir el nodo al cluster, desplegar los honeypots y arrancar Filebeat. Una IP limpia, lista para recibir ataques, en menos de cinco minutos.
La rotación será manual al principio (cuando vea que el nodo lleva demasiado tiempo activo o que el volumen de datos cae porque los bots ya lo han marcado) y automática más adelante con un script que evalúe los datos de Kibana.
Conclusiones
Este proyecto no está terminado. Terraform, cloud-init, la automatización de la rotación, agregar nuevos tipos de honeypots, hacerlos más convincentes, análisis de los datos… queda mucho por exprimir.
Actualmente funciona: un cluster de Kubernetes con un nodo Master y un worker en un VPS, comunicados por un túnel WireGuard que pasa por un MikroTik configurado a mano, con honeypots recogiendo credenciales e intentos de acceso reales, y todo centralizado en un Elasticsearch con Kibana.
Con esto ya se puede desarrollar una inteligencia de datos, bloquear rangos de IP’s que estan escaneando constantemente internet, identificar ataques antes de que ocurran, recoger credenciales más utilizadas… una infinidad de cosas.
Esta infraestructura puede aplicar a cualquier entorno de producción, el despliegue automatico, la gestion descentralizada desde un orquestador de contenedores y la seguridad que ofrece crear tuneles cifrados internamente para el intercambio de datos otorgan infinitas posibilidades.
Stack: MikroTik RouterOS v7 · WireGuard · Proxmox · Kubernetes v1.36 · Calico · OpenCanary · Mailoney · Filebeat · Elasticsearch 8.x · Kibana
Kubernetes Cluster Calico Honeypot Cloud Docker WireGuard Elasticsearch Kibana Filebeat
2635 Words
2026-09-06 22:42