Securizando Nginx proxy en Docker con Fail2Ban en el host
En este post continuamos con el tutorial donde enseñé como configurar nginx-proxy junto con acme-companion y así unificar el acceso a nuestros contenedores con servicios web, además de tenerlos bajo HTTPS. Hoy, vamos a securizar todo esto gracias a Fail2Ban.
Hoy continuaremos con el tutorial que hice el otro día sobre como configurar nginx-proxy para tener una entrada única a tus aplicaciones web y junto con acme-companion que esa entrada se haga a través de HTTPS.
Lo que hoy haremos será incrementar la seguridad de nginx-proxy frente a peticiones no fidedignas que pueda recibir desde internet.
¿Qué vamos a montar?
Con Fail2Ban lo que haremos será bunkerizar Nginx frente a intentos de accesos y ataques. Si la aplicación detecta algo raro en los logs, en función de unas reglas que nosotros le daremos, baneará la IP a través del cortafuegos y le impedirá cualquier intento de conexión. Simple y eficaz. Fail2Ban es útil no solo a nivel de lo que tienes corriendo en Docker, si no también en el sistema host, puede, por ejemplo, supervisar los intentos de login por ssh, por eso en este tutorial Fail2Ban estará instaldo en el host en vez de en un contenedor.
Por que sí, cualquier máquina conectada a internet, está expuesta a ser vulnerada. Tu Synology, Raspberry Pi o incluso router no son una excepción. Si te piensas que a ti esto no te va a ocurrir porque "quien soy yo para que los malos se fijen en mi", te equivocas. Puedes comprobarlo tan solo con mirar los logs de accesos de Nginx para darte cuenta del bombardeo constante de bots y sondas en búsqueda de vulnerabilidades que explotar, no para robarte tus datos (o si), sino para tener una máquina más en las redes de botnets que existen.
Pasos previos
Igual que en el anterior tutorial, esto no es un tutorial para principiantes que busquen montar todo desde 0. Aquí voy a dar por supuesto que has seguido mi anterior tutorial, o que al menos tienes un contenedor con nginx-proxy operativo y Fail2Ban está funcionando en el host.
Si no es así, te recomiendo mi anterior tutorial y cualquier otro para instalar y configurar mínimamente Fail2Ban.
Exponiendo los logs de nginx-proxy
Dado que Fail2Ban supervisa logs, lo primero que tendremos que hacer será recoger los logs de Nginx para que pueda leerlos. Así que tendremos que modificar el contenedor de nginx-proxy para añadir un nuevo volumen donde tener accesibles los logs desde el sistema host, siguiendo el ejemplo del otro día:
docker run -d \
--name nginx-proxy \
--net proxy-network \
-p 80:80 \
-p 443:443 \
-e TZ="Europe/Madrid" \
-v ~/docker_volumes/nginx-proxy/certs:/etc/nginx/certs \
-v ~/docker_volumes/nginx-proxy/vhost:/etc/nginx/vhost.d \
-v ~/docker_volumes/nginx-proxy/html:/usr/share/nginx/html \
-v ~/docker_volumes/nginx-proxy/logs:/var/log/nginx \
-v /var/run/docker.sock:/tmp/docker.sock:ro \
--restart unless-stopped \
nginxproxy/nginx-proxy:1.5Fíjate que lo único que estoy haciendo diferente es añadir un nuevo volumen donde estarán los logs.
El rotado de logs
Este es un paso bastante importante si no queremos que la máquina se llene de logs. Haber, seguramente no va a ser un problema en los tiempos que corren donde tendremos gigas y gigas libres, pero está bien no tener algo creciendo sin supervisión. Lo que voy a explicar aquí utilizará el logrotate de Debian o derivados (Ubuntu, Raspbian, Mint, etc.). Para otros sistemas operativos Linux será prácticamente igual, pero más allá del pingüino tendrás que investigar por tu cuenta.

Lo que vamos a hacer es, apoyándonos en el servicio de rotado de logs del sistema, rotar los propios logs de Nginx. Con esto, el sistema de manera autónoma comprimirá y borrará logs muy antiguos siguiendo unas reglas. Vamos a ello.
Como sudo crearemos el fichero de configuración, yo utilizo vi, pero cada uno el que quiera.
sudo vi /etc/logrotate.d/nginxY lo completaremos de la siguiente forma:
/home/tu_usuario/docker_volumes/nginx-proxy/logs/*.log {
weekly
rotate 4
compress
delaycompress
missingok
notifempty
postrotate
docker restart nginx-proxy
endscript
}Esta es mi configuración línea por línea:
- El path hacia donde están los logs, que debe ser el mismo que el path que montará por volumen el contenedor de nginx-proxy, pongo el *.log para que rote el access.log y el error.log.
- El rotado se producirá semanalmente
- Guardará el histórico de las últimas 4 semanas.
- Los logs más antiguos se comprimirán.
- Con delaycompress vamos a conseguir tener el fichero de log rotado más reciente sin compresión. Útil para poder mirarlo rápidamente.
- No se lanzará error si no se encuentran ficheros a rotar.
- No se rotará un fichero de log vacío.
- El postrotate y la siguiente línea son bastante importantes para que todo esto funcione. Una vez se haya realizado el rotado, el contenedor debe reiniciarse, de no hacerlo seguirá escribiendo logs en el fichero rotado y el nuevo estará vacío.
Configurando Fail2Ban
Ahora nos queda que Fail2Ban supervise los logs y empiece a bannear la actividad sospechosa.
Para ello vamos a crear el fichero de configuración que define qué debe supervisar y qué se hará, lo que se domina la cárcel (jail).
sudo vi /etc/fail2ban/jail.d/nginx-proxy-container.confY añadimos lo siguiente:
[nginx-proxy-container]
enabled = true
port = http,https
filter = aa_nginx-proxy-container
logpath = /home/tu_usuario/docker_volumes/nginx-proxy/logs/access.log
banaction = %(banaction_allports)s
maxretry = 3
bantime = 14400
findtime = 14400
chain = FORWARDComo siempre, comentamos algunos puntos interesantes:
- El filter, podéis poner el nombre que más os guste, a mi me gusta que empiece por "aa" porque así en el directorio aparecerá de los primeros. Debe tener el mismo nombre que el fichero que crearemos más abajo, sin el ".conf".
- La ruta al path hay que indicar donde está el contenedor de Nginx escribiendo el access.log, igual que antes con el rotado.
- Los tiempos y los intentos podéis configurar los que os gusten. Eso sí, la unidad son segundos si no se especifica.
- El chain del cortafuegos debe ser FORWARD, esto es así porque ese es por el que pasa el tráfico de/hacia Docker.
Ahora lo que tenemos que hacer es indicar cómo debe leerse el log y que condiciones deben cumplir las líneas del fichero para que sean calificadas como accesos maliciosos. Para ello creamos el siguiente fichero:
sudo vi /etc/fail2ban/filter.d/aa_nginx-proxy-container.confY el contenido será el siguiente:
[Definition]
failregex = ^.* <ADDR>.*"(GET|POST).*" (400|403|404|503) .*$
ignoreregex =Lo que estamos haciendo es indicar en la posición de la línea del log donde está la IP del posible atacante (<ADDR>), que pueden ser peticiones GET y POST y que los códigos de error que cuentan como intentos de ataque son el 400, 403, 404 y 503.
Obviamente esto podéis modificarlo con los códigos que queráis, ahora mismo así es restrictivo porque se tendrán en cuenta, además de los intentos más evidentes de envío de payloads, también la recolección de ficheros.
Finalmente reiniciamos y confirmamos que el servicio se inició sin problemas:
sudo systemctl reload fail2ban
sudo systemctl status fail2banAlgunos comandos útiles
Antes de seguir conviene que te guardes estos comandos porque te serán útiles, si has seguido el tutorial, el jail será exactamente "nginx-proxy-container".
Ver las IPs baneadas
sudo fail2ban-client status <JAIL>Desbanear una IP
sudo fail2ban-client set <JAIL> unbanip <IP>Ahora ya podemos dormir tranquilos
Quizá quieras confirmar que todo esto funciona, puedes hacerlo muy fácil. Llama repetidamente a algún recurso que no exista para provocar registros 404. Experimentarás que la cuarta vez que lo intentas, el navegador se quedará esperando hasta que te indique el error de que no se pudo conectar. Esto significa que Fail2Ban estará haciendo su trabajo, podrás confirmarlo todavía más si miras las IPs baneadas.
Una vez llegados a este punto ya puedes estar tranquilo que si algún posible atacante en las últimas 4 horas realiza 3 accesos a Nginx y recibe como respuesta alguno de los códigos indicados, su IP será baneada durante otras 4 horas. Esto, aunque no es infalible, será un dolor de cabeza para el que esté escaneando IPs y buscando máquinas que infectar.