open - ¿Cuál es la mejor práctica de docker+ufw en Ubuntu?
iptables docker problem (4)
Acabo de probar Docker. Es impresionante, pero parece que no funciona bien con ufw. Por defecto, la ventana acoplable manipulará un poco las iptables. El resultado no es un error pero no es lo que esperaba. Para más detalles puedes leer Los peligros de UFW + Docker
Mi objetivo es configurar un sistema como
Host (running ufw) -> docker container 1 - nginx (as a reverse proxy)
-> docker container 2 - node web 1
-> docker container 3 - node web 2
-> .......
Quiero administrar el tráfico entrante (por ejemplo, restringir el acceso) a través de ufw, por lo tanto, no quiero que la ventana acoplable toque mis iptables. Aqui esta mi prueba
Ambiente:
- un Ubuntu 14.04 recién instalado (kernel: 3.13.0-53)
- Docker 1.6.2
- El reenvío ufw está habilitado. ( Habilitar el reenvío UFW )
-
--iptables=false
fue agregado al demonio Docker.
Primer intento
docker run --name ghost -v /home/xxxx/ghost_content:/var/lib/ghost -d ghost
docker run --name nginx -p 80:80 -v /home/xxxx/nginx_site_enable:/etc/nginx/conf.d:ro --link ghost:ghost -d nginx
Sin suerte. El primer comando está bien, pero el segundo comando arrojará un error
Error response from daemon: Cannot start container
Segundo intento
Entonces encontré esto: no se pueden vincular contenedores con --iptables = false # 12701
Después de ejecutar el siguiente comando, todo se ve bien.
sudo iptables -N DOCKER
Sin embargo, noté que no puedo establecer ninguna conexión de salida dentro de los contenedores. Por ejemplo:
xxxxg@ubuntu:~$ sudo docker exec -t -i nginx /bin/bash
root@b0d33f22d3f4:/# ping 74.125.21.147
PING 74.125.21.147 (74.125.21.147): 56 data bytes
^C--- 74.125.21.147 ping statistics ---
35 packets transmitted, 0 packets received, 100% packet loss
root@b0d33f22d3f4:/#
Si --iptables=false
del demonio Docker, entonces la conexión a Internet de los contenedores volverá a la normalidad, pero el ufw no funcionará "correctamente" (bueno ... por mi definición).
Entonces, ¿cuál es la mejor práctica de docker + ufw? ¿Alguien puede proporcionar alguna ayuda?
Gracias.
Bart.
Problema
Este problema ha existido durante mucho tiempo.
Deshabilitar iptables en Docker tendrá otros problemas.
La reversión cambia primero
Si ha modificado su servidor de acuerdo con la solución actual que encontramos en Internet, restaure estos cambios primero, incluyendo:
- Habilitar la función iptables de Docker. Elimine todos los cambios como
--iptables=false
, incluido el archivo de configuración/etc/docker/daemon.json
. - La regla ADELANTE predeterminada de UFW vuelve a cambiar al valor predeterminado
DROP
lugar deACCEPT
. - Elimine las reglas relacionadas con la red Docker en el archivo de configuración de UFW
/etc/ufw/after.rules
. - Si ha modificado los archivos de configuración de Docker, reinicie Docker primero. Modificaremos la configuración de UFW más tarde y podremos reiniciarla luego.
Resolviendo problemas de UFW y Docker
Esta solución necesita modificar solo un archivo de configuración UFW, todas las configuraciones y opciones de Docker siguen siendo las predeterminadas. No necesita deshabilitar la función iptables docker.
Modifique el archivo de configuración de UFW /etc/ufw/after.rules
y agregue las siguientes reglas al final del archivo:
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
Usando el comando sudo systemctl restart ufw
para reiniciar UFW después de cambiar el archivo. Ahora la red pública no puede acceder a los puertos de la ventana acoplable publicados, el contenedor y la red privada pueden visitarse regularmente y los contenedores también pueden acceder a la red externa desde el interior.
Si desea permitir que las redes públicas accedan a los servicios proporcionados por el contenedor Docker, por ejemplo, el puerto de servicio de un contenedor es 80
. Ejecute el siguiente comando para permitir que las redes públicas accedan a este servicio:
ufw route allow proto tcp from any to any port 80
Este comando le permite a la red pública acceder a todos los puertos publicados cuyo puerto de contenedor es 80.
Nota: Si publicamos un puerto mediante la opción -p 8080:80
, deberíamos usar el puerto de contenedor 80
, no el puerto de host 8080
.
Si hay varios contenedores con un puerto de servicio de 80, pero solo queremos que la red externa acceda a un contenedor en particular. Por ejemplo, si la dirección privada del contenedor es 172.17.0.2, use el siguiente comando:
ufw route allow proto tcp from any to 172.17.0.2 port 80
Si el protocolo de red del servicio es UDP, por ejemplo, un servicio DNS, puede usar el siguiente comando para permitir que la red externa acceda a todos los servicios DNS publicados:
ufw route allow proto udp from any to any port 53
De manera similar, aunque solo sea para un contenedor específico, como la dirección IP 172.17.0.2:
ufw route allow proto udp from any to 172.17.0.2 port 53
¿Cómo funciona?
Las siguientes reglas permiten que las redes privadas puedan visitarse entre sí. Normalmente, las redes privadas son más confiables que las redes públicas.
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
Las siguientes reglas permiten a UFW administrar si las redes públicas pueden visitar los servicios proporcionados por el contenedor Docker. Para que podamos gestionar todas las reglas de firewall en un solo lugar.
-A DOCKER-USER -j ufw-user-forward
Las siguientes reglas bloquean las solicitudes de conexión iniciadas por todas las redes públicas, pero permiten que las redes internas accedan a redes externas. Para el protocolo TCP, impide el establecimiento activo de una conexión TCP desde redes públicas. Para el protocolo UDP, se bloquean todos los accesos a puertos que son menos de 32767. ¿Por qué es este puerto? Debido a que el protocolo UDP no tiene estado, no es posible bloquear la señal de intercambio que inicia la solicitud de conexión como lo hace TCP. Para GNU / Linux podemos encontrar el rango de puertos locales en el archivo /proc/sys/net/ipv4/ip_local_port_range
. El rango predeterminado es 32768 60999
. Al acceder a un servicio de protocolo UDP desde un contenedor en ejecución, el puerto local se seleccionará al azar del puerto y el servidor devolverá los datos a este puerto aleatorio. Por lo tanto, podemos suponer que el puerto de escucha del protocolo UDP dentro de todos los contenedores es menos de 32768. Esta es la razón por la que no queremos que las redes públicas accedan a los puertos UDP que menos de 32768.
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
Más
https://github.com/chaifeng/ufw-docker
sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
chmod +x /usr/local/bin/ufw-docker
Uso
ufw-docker help
ufw-docker install
ufw-docker status
ufw-docker allow webapp
ufw-docker allow webapp 80
ufw-docker allow webapp 53/udp
ufw-docker list webapp
ufw-docker delete allow webapp 80/tcp
ufw-docker delete allow webapp
Actualización: 2018-09-10
La razón para elegir ufw-user-forward
, no ufw-user-input
usando ufw-user-input
Pro:
Fácil de usar y entender, soporta versiones anteriores de Ubuntu.
Por ejemplo, para permitir que el público visite un puerto publicado cuyo puerto de contenedor es 8080
, use el comando:
ufw allow 8080
Estafa:
No solo expone los puertos de contenedores sino que también expone los puertos del host.
Por ejemplo, si un servicio se está ejecutando en el host y el puerto es 8080
. El comando ufw allow 8080
permite a la red pública visitar el servicio y todos los puertos publicados cuyo puerto de contenedores es 8080
. Pero solo queremos exponer el servicio que se ejecuta en el host, o simplemente el servicio que se ejecuta dentro de contenedores, no ambos.
Para evitar este problema, es posible que necesitemos usar un comando similar al siguiente para todos los contenedores:
ufw allow proto tcp from any to 172.16.0.3 port 8080
usando ufw-user-forward
Pro:
No se pueden exponer servicios que se ejecutan en hosts y contenedores al mismo tiempo con el mismo comando.
Por ejemplo, si queremos publicar el puerto 8080
de contenedores, use el siguiente comando:
ufw route allow 8080
La red pública puede acceder a todos los puertos publicados cuyos puertos de contenedor son 8080
.
Pero a la red pública todavía no se puede acceder al puerto 8080
del host. Si queremos hacerlo, ejecute el siguiente comando para permitir que el público acceda al puerto en el host por separado:
ufw allow 8080
Estafa:
No es compatible con versiones anteriores de Ubuntu, y el comando es un poco más complicado. Pero puede usar mi script https://github.com/chaifeng/ufw-docker .
Conclusión
Si estamos usando una versión anterior de Ubuntu, podemos usar la cadena de ufw-user-input
. Pero tenga cuidado de evitar exponer servicios que no deben ser expuestos.
Si estamos usando una versión más nueva de Ubuntu que es compatible con el subcomando ufw route
, es mejor que utilicemos la cadena ufw-user-forward
, y que utilicemos el comando ufw route
para administrar las reglas del firewall para los contenedores.
Actualización: 6 de octubre de 2018
El script https://github.com/chaifeng/ufw-docker compatible con Docker Swarm ahora. Consulte el último código para obtener más información, https://github.com/chaifeng/ufw-docker
Instalar para el modo Docker SwarmSolo podemos usar este script en los nodos del administrador para administrar las reglas del firewall cuando se usa en modo de enjambre.
- Modificación de todos los archivos
after.rules
en todos los nodos, incluidos los administradores y los trabajadores - Despliegue de este script en los nodos del administrador
Al ejecutarse en el modo Docker Swarm, este script agregará un servicio global ufw-docker-agent
. La imagen chaifeng/ufw-docker-agent también se chaifeng/ufw-docker-agent automáticamente a partir de este proyecto.
He tenido un problema como hace meses y últimamente he decidido describir el problema junto con la solución en mi blog. Aquí está el atajo.
Usar --iptables=false
no te ayudará mucho con el caso que describiste. Simplemente no es suficiente aquí. Por defecto, ninguno de sus contenedores puede hacer ninguna conexión saliente.
Hay un pequeño paso que está omitiendo en su camino para tener contenedores detrás de UFW aquí. Puede usar el --iptables=false
o crear el archivo /etc/docker/daemon.json
con el siguiente contenido
{
"iptables": false
}
el resultado será el mismo, pero la última opción requiere que reinicie el servicio completo de la ventana acoplable con el service docker restart
o incluso que reinicie si la ventana acoplable tuvo la oportunidad de agregar reglas de iptables antes de desactivar esta función.
Cuando esté hecho, haz dos cosas más:
$ sed -i -e ''s/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g'' /etc/default/ufw
$ ufw reload
así que configura la política de reenvío predeterminada en UFW para aceptar, y usa:
$ iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE
De esa manera, lo que está logrando es deshabilitar el comportamiento desordenado de la ventana acoplable en sus reglas de iptables y al mismo tiempo se le proporciona el enrutamiento necesario para que los contenedores hagan las conexiones salientes muy bien. Sin embargo, las reglas de UFW todavía estarán restringidas a partir de este punto.
Espero que esto resuelva el problema para usted y cualquiera que llegue aquí en busca de una respuesta.
Describí el problema y la solución de manera más completa en https://www.mkubaczyk.com/2017/09/05/force-docker-not-bypass-ufw-rules-ubuntu-16-04/
No estoy seguro de lo que está preguntando, pero de lo que puedo reunir, ¿le gustaría tener un mejor control sobre quién puede acceder a sus aplicaciones que se ejecutan dentro de Docker? He respondido una pregunta similar aquí para controlar el tráfico a través de un proxy de front-end en lugar de hacerlo con tablas de IP.
Espero que esto ayude
Dylan
Editar
Con el enfoque anterior, puede usar UFW para permitir solo las conexiones entrantes al puerto 80 (es decir, el proxy). Esto mantiene cualquier exposición de puerto al mínimo con la ventaja adicional de que puede controlar el tráfico a través de una configuración de proxy y DNS
Para lo que vale, aquí hay un addendum a la respuesta de @mkubaczyk para el caso donde hay más redes de puentes involucradas en toda la configuración. Estos pueden ser proporcionados por proyectos Docker-Compose y aquí es cómo se pueden generar las reglas adecuadas, dado que estos proyectos son controlados por systemd
.
/etc/systemd/system/[email protected]
[Unit]
Description=Docker-Compose project: %I
After=docker.service
BindsTo=docker.service
AssertPathIsDirectory=/<projects_path>/%I
AssertFileNotEmpty=/<projects_path>/%I/docker-compose.yml
[Service]
Type=simple
Restart=always
WorkingDirectory=/<projects_path>/%I
ExecStartPre=/usr/bin/docker-compose up --no-start --remove-orphans
ExecStartPre=+/usr/local/bin/update-iptables-for-docker-bridges
ExecStart=/usr/bin/docker-compose up
ExecStop=/usr/bin/docker-compose stop --timeout 30
TimeoutStopSec=30
User=<…>
StandardOutput=null
[Install]
WantedBy=multi-user.target
/usr/local/bin/update-iptables-for-docker-bridges
#!/bin/sh
for network in $(docker network ls --filter ''driver=bridge'' --quiet); do
iface=$(docker network inspect --format ''{{index .Options "com.docker.network.bridge.name"}}'' ${network})
[ -z $iface ] && iface="br-${network}"
subnet=$(docker network inspect --format ''{{range .IPAM.Config}}{{.Subnet}}{{end}}'' ${network})
rule="! --out-interface ${iface} --source ${subnet} --jump MASQUERADE"
iptables --table nat --check POSTROUTING ${rule} || iptables --table nat --append POSTROUTING ${rule}
done
Obviamente, esto no escalará tan bien.
También es digno de mencionar que todo el concepto básico disfrazará la fuente de cualquier conexión para las aplicaciones que se ejecutan en un contenedor.