¿Pasos para limitar las conexiones externas al contenedor acoplable con iptables?

20

Mi objetivo es limitar el acceso a los contenedores acoplables a solo unas pocas direcciones IP públicas. ¿Existe un proceso simple y repetible para lograr mi objetivo? Al comprender solo los conceptos básicos de iptables al usar las opciones predeterminadas de Docker, me resulta muy difícil.

Me gustaría ejecutar un contenedor, hacerlo visible para el Internet público, pero solo permitir conexiones de hosts seleccionados. Esperaría establecer una política de ENTRADA predeterminada de RECHAZAR y luego solo permitir conexiones desde mis hosts. Pero las reglas y cadenas NAT de Docker se interponen y mis reglas INPUT son ignoradas.

¿Alguien puede proporcionar un ejemplo de cómo lograr mi objetivo teniendo en cuenta los siguientes supuestos?

  • Host public IP 80.80.80.80 en eth0
  • Host privado IP 192.168.1.10 en eth1
  • docker run -d -p 3306:3306 mysql
  • Bloquee todas las conexiones al host / contenedor 3306 excepto desde los hosts 4.4.4.4 y 8.8.8.8

Me complace vincular el contenedor solo a la dirección IP local, pero necesitaría instrucciones sobre cómo configurar correctamente las reglas de reenvío de iptables que sobrevivan al proceso de acoplamiento y reinicios del host.

¡Gracias!

GGGforce
fuente

Respuestas:

15

Dos cosas a tener en cuenta al trabajar con las reglas de firewall de Docker:

  1. Para evitar que sus reglas sean golpeadas por el acoplador, use la DOCKER-USERcadena
  2. Docker hace el mapeo de puertos en la PREROUTINGcadena de la nattabla. Esto sucede antes de que las filterreglas, así --desty --dportverá la IP interna y el puerto del envase. Para acceder al destino original, puede usar -m conntrack --ctorigdstport.

Por ejemplo:

iptables -A DOCKER-USER -i eth0 -s 8.8.8.8 -p tcp -m conntrack --ctorigdstport 3306 --ctdir ORIGINAL -j ACCEPT
iptables -A DOCKER-USER -i eth0 -s 4.4.4.4 -p tcp -m conntrack --ctorigdstport 3306 --ctdir ORIGINAL -j ACCEPT
iptables -A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 3306 --ctdir ORIGINAL -j DROP

NOTA: Sin --ctdir ORIGINALesto, esto también coincidiría con los paquetes de respuesta que regresan para una conexión desde el contenedor al puerto 3306 en algún otro servidor, ¡lo que casi seguro no es lo que desea! No necesita estrictamente esto si, como yo, su primera regla es -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT, ya que se ocupará de todos los paquetes de respuesta, pero de --ctdir ORIGINALtodos modos sería más seguro usarlo .

SystemParadox
fuente
¿Debería esto ser editado para incluir el --ctdir ? Yo uso-m conntrack --ctstate NEW --ctorigdstport 3306 --ctdir ORIGINAL
lonix
@ Ionix, sí debería, aunque acabo de averiguar por qué me ha estado confundiendo. He agregado un poco de explicación.
SystemParadox
1
Tenga en cuenta que la DOCKER-USERtabla predeterminada contiene la entrada: -A DOCKER-USER -j RETURNque se ejecutará antes de lo anterior si la usa -A. Una solución es insertar reglas en la cabeza en orden inverso con -I.
BMitch
@BMitch O incluso mejor aún , agregue todas las reglas en una nueva FILTERScadena e -Iinserte nuevas reglas (como usted dijo), para saltar a ella: -I INPUT -j FILTERSy-I DOCKER-USER -i eth0 -j FILTERS
lonix
@BMitch Sin embargo, acabo de revisar mi servidor y la regla de devolución no está allí, ¿tal vez la última versión de Docker ya no la inserte? Sin -Iembargo, es una buena idea usar , solo para estar seguro.
lonix
8

Con Docker v.17.06 hay una nueva cadena de iptables llamada DOCKER-USER. Este es para sus reglas personalizadas: https://docs.docker.com/network/iptables/

A diferencia del DOCKER de cadena, no se reinicia al construir / iniciar contenedores. Por lo tanto, puede agregar estas líneas a su configuración / script de iptables para aprovisionar el servidor incluso antes de instalar Docker e iniciar los contenedores:

-N DOCKER
-N DOCKER-ISOLATION
-N DOCKER-USER
-A DOCKER-ISOLATION -j RETURN
-A DOCKER-USER -i eth0 -p tcp -m tcp --dport 3306 -j DROP
-A DOCKER-USER -j RETURN

Ahora el puerto para MySQL está bloqueado de acceso externo (eth0) incluso aunque Docker abre el puerto para el mundo. (Estas reglas suponen que su interfaz externa es eth0.)

Eventualmente, tendrá que limpiar iptables, reinicie el servicio docker primero, si lo desordenó demasiado tratando de bloquear el puerto como lo hice yo.

ck1
fuente
Extraño por qué esta tabla DOCKER-USER es diferente de cualquier otra tabla agregada por el usuario. No tiene ningún filtro aplicado previamente, por lo que aún debe especificar los nombres de la interfaz usted mismo. Si crea un "MY-CHAIN" y lo inserta en la cadena FORWARD tendrá el mismo resultado, ¿no?
ColinM
Sí, hace la diferencia, porque Docker inserta la cadena DOCKER-USER en la cadena FORWARD: -A FORWARD -j DOCKER-USER -A FORWARD -j DOCKER-ISOLATION es por eso que las instrucciones personalizadas se ejecutan antes que la cadena DOCKER.
ck1
Tenga en cuenta que si usa --dportdentro de DOCKER-USER, debe coincidir con la IP interna del servicio de contenedor, no con el puerto expuesto. A menudo coinciden, pero no siempre, y esto podría entrar fácilmente en conflicto con otros servicios, por lo que sigo argumentando que esta solución DOCKER-USER está a medias.
ColinM
4

ACTUALIZACIÓN : aunque es válida en 2015, esta solución ya no es la forma correcta de hacerlo.

La respuesta parece estar en la documentación de Docker en https://docs.docker.com/articles/networking/#the-world

Las reglas de reenvío de Docker permiten todas las IP de origen externo de forma predeterminada. Para permitir que solo una IP o red específica acceda a los contenedores, inserte una regla negada en la parte superior de la cadena de filtros DOCKER. Por ejemplo, para restringir el acceso externo de modo que solo la fuente IP 8.8.8.8 pueda acceder a los contenedores, se podría agregar la siguiente regla:iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j DROP

Lo que terminé haciendo fue:

iptables -I DOCKER -i eth0 -s 8.8.8.8 -p tcp --dport 3306 -j ACCEPT
iptables -I DOCKER -i eth0 -s 4.4.4.4 -p tcp --dport 3306 -j ACCEPT
iptables -I DOCKER 3 -i eth0 -p tcp --dport 3306 -j DROP

No toqué --iptablesni las --iccopciones.

GGGforce
fuente
1
Si lo hace iptables -vnL DOCKER, los puertos de destino son todos los puertos dentro del contenedor. Si lo entiendo bien, eso significa que las reglas anteriores solo afectarían el puerto 3306dentro del contenedor, es decir, si estuviera en -p 12345:3306su contenedor, su regla aún sería la que se requiere para bloquear el acceso (es decir --dport 12345, no funcionaría) , porque las reglas ACEPTAR de la cadena DOCKER son posteriores a NAT.
Sunside
Así es, las reglas deben relacionarse con los puertos dentro de los contenedores.
GGGforce
1
Hum, eso es feo si ejecutas múltiples contenedores que usan, por ejemplo, un NGINX interno para hacer proxy inverso (por ejemplo, Zabbix, un equilibrador de carga personalizado, etc.) porque requeriría que conozcas la IP del contenedor de antemano. Todavía estoy buscando una solución para ese problema que no requiera --iptables=false, porque esta parece ser la peor opción de todas.
Sunside
¡Gracias! Has resuelto mi problema después de muchas horas de búsqueda. Ahora finalmente puedo encarcelar MySQL solo a la dirección IP de mi casa sin exponer la parte más vulnerable a todo el mundo.
Matt Cavanagh
1
¡Se supone que la cadena DOCKER no debe ser manipulada directamente por el usuario! Use la cadena DOCKER-USER para eso. Verifique la respuesta aceptada.
Paul-Sebastian Manole
3

ACTUALIZACIÓN: Si bien esta respuesta sigue siendo válida, la respuesta de @SystemParadox DOCKER-USERen combinación con --ctorigdstportes mejor.

Aquí hay una solución que persiste bien entre reinicios y le permite afectar el puerto expuesto en lugar del puerto interno .

iptables -t mangle -N DOCKER-mysql iptables -t mangle -A DOCKER-mysql -s 22.33.44.144/32 -j RETURN iptables -t mangle -A DOCKER-mysql -s 22.33.44.233/32 -j RETURN iptables -t mangle -A DOCKER-mysql -j DROP iptables -t mangle -A PREROUTING -i eth0 -p tcp -m tcp --dport 3306 -j DOCKER-mysql

He creado una imagen de Docker que utiliza este método para administrar automáticamente las tablas ip por usted, utilizando variables de entorno o dinámicamente con etcd (o ambos):

https://hub.docker.com/r/colinmollenhour/confd-firewall/

ColinM
fuente