El gran problema entre Docker y UFW

 

Docker es una herramienta fantástica para desplegar aplicaciones en contenedores y mantener un sistema ordenado y limpio y UFW (Uncomplicated Firewall) es el cortafuegos “sin complicaciones” que se suele usar en los despliegues de Ubuntu o Debian. 

El problema viene cuando los juntas ya que, por defecto, Docker ignora totalmente las reglas de UFW creando sus propias reglas de iptables antes que las de UFW, de modo que un puerto publicado por Docker queda abierto al exterior aunque UFW diga que está cerrado. Por ejemplo, si ejecutas:

docker run -d --name webtest -p 8080:80 nginx

E intentamos denegar ese acceso con:

ufw deny 8080

seguirás pudiendo acceder al contenedor desde fuera (el puerto 8080 se abre a todas las IP).

En la práctica esto puede generar que servicios (como bases de datos :D) que creías “solo internos” terminen expuesto a Internet mientras UFW se queda mirando.

¿Cómo podemos arreglar esto?

 


 

Solución 1: publicar puertos solo en localhost

Una forma sencilla y clásica de evitar esto es no publicar el puerto en todas las interfaces, sino limitarlo a la interfaz de loopback (localhost). Así Docker solo abre el puerto 8080 en la dirección local y no queda expuesto públicamente. Por ejemplo:

docker run -d --name webtest -p 127.0.0.1:8080:80 nginx

De esta manera, incluso si “publicas” el contenedor, solo será accesible desde la máquina local. UFW puede entonces bloquear el acceso a ese puerto desde cualquier otra IP, porque técnicamente no está abierto al público. Para pasar el tráfico de internet a localhost, usamos un proxy y ya tenemos una arquitectura más segura. Quedaría algo así:

Solución 2: reglas en la cadena DOCKER-USER de iptables

Desde Docker 17.06+, existe la cadena especial DOCKER-USER en iptables, que segun la documentación oficial se va a procesar antes que las reglas de Docker.Por ejemplo, podrías agregar reglas que bloqueen todo por defecto y luego permitan sólo lo que necesitas. Un caso típico es algo así:

# Aceptar conexiones establecidas
iptables -I DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Permitir sólo MySQL (3306) como ejemplo
iptables -I DOCKER-USER -p tcp --dport 3306 -j ACCEPT
# Bloquear todo lo demás
iptables -I DOCKER-USER -j DROP

Por otro lado, en vez de escribir directamente iptables, se pueden integrar estas reglas con UFW (por ejemplo, editando /etc/ufw/after.rules para que llame a ufw-user-forward o a tu propio DOCKER-USER para que UFW meta sus reglas a la cadena DOCKER-USER en cada reinicio, de forma que Docker ya no pueda saltearlas. Sin embargo es bastante más complicado que la solución 1 en mi opinión.

Solución 3: usar ufw-docker

 

Como comprenderéis este asunto es de sobra conocido por la comunidad y un dev decidió ponerle remedio mediante su propio wrapper que añade reglas específicas a UFW para manejar correctamente el tráfico de Docker: ufw-docker 

 

El script funciona parecido a ufw, pero orientado a contenedores, por ejemplo algunas de las operaciones más comunes que podemos hacer:

 

Ver contenedores en ejecución:

 

sudo ufw-docker list

Permitir acceso a un puerto de un contenedor

sudo ufw-docker allow httpd 80

 Denegar acceso

sudo ufw-docker deny nginx 443

Limitar acceso solo a una IP

sudo ufw-docker allow from 203.0.113.5 to nginx 8080

 

 

Obviamente existen más maneras de limitar el acceso como poner un firewall físicco delante del servidor pero estas son las más sencillas a mi parecer.

 

 

Copyright © TheDarknest All Rights Reserved