ryandaniels.iptables_docker

Rol de Ansible: iptables para Docker (y Docker Swarm)

Agrega reglas de firewall al servidor a través de iptables, para Docker y Docker Swarm. ¡Esto protegerá tus contenedores de Docker!
Este Rol de Ansible existe porque firewalld y Docker (así como Docker Swarm) no funcionan bien juntos.

Problema que se soluciona: Al iniciar un contenedor en Docker con un puerto "publicado", no tienes control y el puerto queda expuesto a través del firewall de tu servidor. Incluso si estás usando iptables, o otro firewall en tu servidor. Docker abre ese puerto "publicado" para todos y evita tu firewall.

Caso de uso para esta solución: Permitir que IPs de confianza se conecten a contenedores de Docker (y contenedores de Docker Swarm), junto con otros puertos del sistema operativo abiertos. Con una opción para exponer puertos específicos públicamente (Docker/Docker Swarm y OS). Las IPs de confianza pueden no estar en el mismo rango de red, o incluso en la misma subred.

Se suponía que esto sería simple. Asegurar Docker con un firewall. Pero desafortunadamente no es así. He intentado mantenerlo lo más simple posible.

Puede haber problemas desconocidos con esto... ¡úsalo bajo tu propio riesgo!

Ver también: https://ryandaniels.ca/blog/secure-docker-with-iptables-firewall-and-ansible/
Y sobre el uso de Docker de la cadena INPUT: https://ryandaniels.ca/blog/docker-iptables-input-chain/

Actualmente probado y funcionando en:

  • CentOS/RHEL 7
  • Ubuntu 18.04
  • Ubuntu 20.04

Características

  • Funciona con Docker y Docker Swarm (también conocido como Docker SwarmKit).
  • Seguro por defecto. Una vez configurado, solo las IPs de Docker pueden acceder a todos los contenedores, y a todos los demás procesos del sistema que tengan puertos abiertos en el servidor.
  • Lo más simple posible. Cuantas menos reglas de iptables, mejor será el rendimiento (en teoría al menos).
  • Automático. No hay que agregar manualmente puertos a la configuración del firewall (si usas un conjunto de IPs de confianza).
  • Agrega IPs "confiables" que pueden comunicarse con todos los contenedores de Docker y todos los demás procesos del sistema que tengan puertos abiertos en el servidor.
  • Abre puertos de contenedores Docker específicos, o puertos del sistema operativo al público (todos) a través del firewall, como SSH.
  • También se pueden especificar interfaces. Por defecto, todas las interfaces son filtradas. Puedes filtrar una o más interfaces específicas y permitir todas las demás (solo especificar una interfaz no confiable).
  • Todo se hace en modo "offline". Por lo que no debería haber problemas con Docker cuando se activan las reglas de iptables.
  • No necesitas ser un experto en iptables para usar esto.
  • Funciona con el uso no documentado de iptables de Docker Swarm y redes superpuestas encriptadas. (las reglas de iptables se agregan a la cadena INPUT).

Esta solución utiliza iptables como firewall, y ipset para permitir que iptables tenga una lista de IPs permitidas. ipset también permite usar un rango de IPs no continuo.

Cadenas de iptables usadas, y cómo:
INPUT, no se flush. Regla insertada en la parte superior para saltar a la cadena personalizada para reglas relacionadas con el OS.
DOCKER-USER, se flush. Todas las reglas relacionadas con Docker (y Docker Swarm) están aquí para bloquear contenedores de ser expuestos a todos por defecto. Por defecto, solo se permiten las IPs del servidor Docker. Otras IPs y puertos de contenedores se pueden añadir por el usuario.
FILTERS, se flush. Cadena personalizada para los procesos del servidor (que no son Docker). Por defecto, solo se permiten las IPs del servidor Docker. Otras IPs y puertos de contenedores se pueden añadir por el usuario.

Manual de iptables: http://ipset.netfilter.org/iptables.man.html

Advertencias

No te bloquees a ti mismo del servidor. Esto modifica tu firewall. Siempre ten otra manera de acceder, como una "consola".

Nota sobre los IPs: Esto es solo para IPv4. IPv6 no ha sido probado. Es más seguro si desactivas IPv6 en tus servidores.

Otra consideración de seguridad:
Si usas Docker normal (no Swarm), considera también enlazar un puerto a una IP interna para mejor seguridad.
Si usas Swarm, considera usar IPs específicas para la comunicación de Docker Swarm.
Ej. docker swarm init --advertise-addr 192.168.100.100 --listen-addr=192.168.100.100 --data-path-addr=192.168.100.100

Nota importante: Docker y firewalld no se llevan bien. Este Rol de Ansible tiene un chequeo habilitado para fallar si el servicio firewalld está en funcionamiento o habilitado.
Para más información sobre firewalld y Docker:
https://success.docker.com/article/why-am-i-having-network-problems-after-firewalld-is-restarted
https://www.tripwire.com/state-of-security/devops/psa-beware-exposing-ports-docker/
https://docs.docker.com/network/iptables/

Bug de SELinux:
Actualmente hay un bug con SELinux que impide guardar las reglas de iptables en el archivo iptables.save.
Impacto: Guardar las reglas de iptables una segunda vez fallará silenciosamente.
Se ha añadido una solución alternativa para que SELinux permita que chmod interactúe con el archivo iptables.save.
Alternativamente, podrías desactivar SELinux, pero eso no es recomendable.
Informe de error: https://bugs.centos.org/view.php?id=12648
Ver abajo para más detalles sobre cómo realizar manualmente la solución alternativa.

ADVERTENCIA:
Asegúrate de probar en no producción primero, no puedo hacer garantías ni ser responsable.
Ten cuidado, esto eliminará y añadirá reglas de iptables en el OS. Úsalo con precaución.
¡Las reglas de iptables existentes podrían ser eliminadas! Confirma lo que tienes configurado antes de ejecutar esto.

Puede haber problemas desconocidos con esto... ¡úsalo bajo tu propio riesgo!

Versiones de Docker probadas

Docker Engine - Comunidad edición versión:

  • 19.03.8
  • 19.03.9
  • 19.03.12

Probado en modo Docker normal, y en un clúster de 3 nodos de Docker Swarm.

Distribuciones probadas

  • CentOS: 7.7, 7.8
  • Ubuntu 18.04
  • Ubuntu 20.04

Dependencias

  • iptables & iptables-services

Probado con v1.4.21 (Última disponible en CentOS 7)

  • ipset & ipset-service

Probado con v7.1 (Última disponible en CentOS 7)

Configuración por defecto

  • Habilitar debug
debug_enabled_default: false
  • Proxy (Necesario al instalar paquetes requeridos si estás detrás de un proxy)
proxy_env: []
  • Rol deshabilitado por defecto. Cambiar a true en group_vars o playbook, etc.
iptables_docker_managed: false
  • Verificar si el servicio (Docker) está corriendo o habilitado, y fallar el rol
iptables_docker_check_problem_service_managed: true
  • Servicios a verificar, y fallar el rol
iptables_docker_check_problem_service:
  - docker.service
  • Mostrar configuración de variables
iptables_docker_show_config: true
  • Iniciar el servicio de iptables
iptables_docker_start: true
  • Instalar el paquete de iptables
iptables_docker_managed_pkg: true
iptables_docker_packages:
  - iptables
  - iptables-services
  - ipset
  - ipset-service
  - policycoreutils-python #requerido para semodule
  • Forzar la copia del archivo ipset para activar la recarga de ipset
iptables_docker_copy_ipset_force: false
  • Forzar la copia del archivo iptables para activar la recarga de iptables
iptables_docker_copy_iptables_force: false
  • Ubicación de configuración guardada de iptables
iptables_docker_iptables_config_save: /etc/sysconfig/iptables
  • Ubicación de configuración guardada de ipset
iptables_docker_ipset_config_dir: /etc/sysconfig/ipset.d
  • Máximo de elementos en ipset (IPs en la lista de permitidos)

    Si se cambia después de la primera creación, debe ser eliminado y recreado manualmente. 64k IPs deberían ser suficientes.

iptables_docker_ipset_maxelem: 65536

Configuraciones de Usuario

  • Sobrescribir IPs del servidor Docker (Opcional)

    Opcionalmente especificar las IPs del servidor Docker. Si no se establece, las IPs se determinarán del grupo docker_hosts en el inventario de Ansible.

# iptables_docker_server_ip_allow_set:
#   - 192.168.100.100
#   - 192.168.100.101
#   - 192.168.100.102
  • IPs permitidas para usar todos los puertos expuestos de los contenedores de Docker y todos los puertos expuestos de los procesos del servidor.
# iptables_docker_ip_allow_set: []
iptables_docker_ip_allow_set:
  - 192.168.100.1
  - 192.168.101.0/24
  - 192.168.102.0/24
  • Adaptador de red a restringir para reglas del OS

    Solo se bloquearán los adaptadores listados. Otros serán permitidos. Por defecto se bloquean todos (con '+').
    Si deseas restringir solo interfaces de red específicas utiliza el nombre exacto.
    Si deseas restringir todas las interfaces del mismo tipo, usa "interface+" para coincidir con cada interfaz, ya que + es el comodín para iptables.
    Ej. Para restringir las interfaces ethX, usa "eth+". "eth+" es un comodín para todo lo que comienza con eth.
    NO uses "*". Este no es un comodín y no coincide con nada.
    Cuanto menos se ponga aquí, mejor. Es más seguro bloquear todo ('+') pero si no puedes, añade adaptadores de red con tráfico alto primero.
    local (lo) no es necesario aquí.

iptables_docker_external_network_adapter:
  - "+" #Comodín para todo
  # - "eth+"
  # - "enp0s+"
  # - "wlp1s+"
  • Puertos TCP del sistema operativo abiertos al público

    Puertos que permiten la conexión de todos (serán accesibles públicamente). Los puertos aquí permitirán todo el tráfico TCP a estos puertos desde el nivel de iptables.
    Solo para puertos en el OS, no para contenedores Docker.

iptables_docker_global_ports_allow_tcp:
  - 22                   # SSH
  • Puertos UDP del sistema operativo abiertos al público

    Puertos que permitirán la conexión de todos (serán accesibles públicamente). Los puertos aquí permitirán todo el tráfico UDP a estos puertos desde el nivel de iptables.
    Solo para puertos en el OS, no para contenedores Docker.

iptables_docker_global_ports_allow_udp: []
  • Adaptador de red a restringir para reglas de Docker

    Por defecto, se utiliza la misma configuración que el adaptador de red para el OS.

iptables_docker_swarm_network_adapter: "{{ iptables_docker_external_network_adapter }}"
# iptables_docker_swarm_network_adapter:
#   - "+" #Comodín para todo
#   # - "eth+"
  • Puertos TCP de Docker abiertos al público

    Añade puertos TCP de contenedores Docker que quieras abiertos para todos. Para Docker y Docker Swarm.
    Los puertos de Docker Swarm no son necesarios aquí.

iptables_docker_swarm_ports_allow_tcp: []
# iptables_docker_swarm_ports_allow_tcp:
#   - 9000
  • Puertos UDP de Docker abiertos al público

    Añade puertos UDP de contenedores Docker que quieras abiertos para todos. Para Docker y Docker Swarm.
    Los puertos de Docker Swarm no son necesarios aquí.

iptables_docker_swarm_ports_allow_udp: []
  • Nombre de la red puente de Docker (docker0), y rango IP (para permitir iptables DOCKER-USER)
iptables_docker_bridge_name: docker0
iptables_docker_bridge_ips: 172.17.0.0/16
  • Rango IP de la red puente de Docker Swarm (docker_gwbridge) (para permitir iptables DOCKER-USER)
iptables_docker_swarm_bridge_name: docker_gwbridge
iptables_docker_swarm_bridge_ips: 172.18.0.0/16

Ejemplo de archivo de configuración (inventories/dev-env/group_vars/all.yml)

Del ejemplo a continuación:
Las IPs se añadirán a la lista de confianza:

  • 192.168.100.1
  • 192.168.101.0/24

Todas las interfaces de red serán restringidas, ya que se usa el comodín '+' para iptables_docker_external_network_adapter.

El puerto 22 será abierto públicamente.

---
iptables_docker_ip_allow_set:
  - 192.168.100.1
  - 192.168.101.0/24

iptables_docker_external_network_adapter:
  - "+" #Comodín para todo

iptables_docker_global_ports_allow_tcp:
  - 22                   # SSH

Ejemplo de archivo de inventario

[docker_hosts]
centoslead1 ansible_host=192.168.100.100
centoswork1 ansible_host=192.168.100.101
centoswork2 ansible_host=192.168.100.102

Ejemplo de Playbook iptables_docker.yml

---
- hosts: '{{ inventory }}'
  become: yes
  vars:
    # Usar este rol
    iptables_docker_managed: true
  roles:
  - ryandaniels.iptables_docker

Uso

Antes de ejecutar, asegúrate de comprobar si ya estás usando iptables. No debería sobrescribirse/eliminarse nada, a menos que estés usando las mismas cadenas de iptables que esto.

Por defecto, no se ejecutarán tareas a menos que establezcas iptables_docker_managed=true. Esto es intencionado para prevenir accidentes por parte de personas que no leen las instrucciones.

ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true" -i hosts-dev

Saltar la instalación de paquetes (si ya sabes que están allí - acelera la tarea)

ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true" -i hosts --skip-tags=iptables_docker_pkg_install

Mostrar salida más detallada (información de debug)

ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true debug_enabled_default=true" -i hosts-dev

No iniciar el servicio de iptables ni agregar reglas para iptables

ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true iptables_docker_start=false" -i hosts-dev

Forzar a ipset y iptables a actualizar

ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true iptables_docker_copy_ipset_force=true iptables_docker_copy_iptables_force=true" -i hosts-dev

Solo mostrar configuración (de variables)

ansible-playbook iptables_docker.yml --extra-vars "inventory=centos7 iptables_docker_managed=true iptables_docker_show_config=true" -i hosts --tags "iptables_docker_show_config"

Nota sobre el límite de tamaño de ipset

Importante: Toma nota del tamaño de "Número de entradas". Si ese número se acerca al tamaño máximo (65536), entonces necesitas eliminar el ipset "ip_allow" y recrearlo con un tamaño máximo mayor.
64K debería ser suficiente para cualquiera.

El archivo está en: templates/ip_allow.set.j2

create -exist ip_allow hash:ip family inet hashsize 1024 maxelem 65536

Ver el tamaño de la lista de ipset:

ipset list |grep "Number of entries"

Salida importante:

Number of entries: 3

Solución alternativa manual de SELinux para iptables y chmod

Detalles del bug: https://bugs.centos.org/view.php?id=12648

El problema es que cuando se guarda iptables una segunda vez, SELinux lo bloquea ya que chmod tiene un problema con el archivo iptables.save.
Utiliza lo siguiente como solución alternativa para permitir que chmod modifique el archivo iptables.save si no estás usando el rol de Ansible.
Para reproducir, reinicia el servicio iptables después de configurar el iptables para guardar después de reiniciar/parar.

yum install audit policycoreutils policycoreutils-python
ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -i|tail -55
ausearch -c 'chmod' --raw | audit2allow -M iptables_save_chmod
#o grep "iptables.save" /var/log/audit/audit.log|tail | audit2allow -M iptables_save_chmod
semodule -i iptables_save_chmod.pp

Referencia de Comando de iptables

Más comandos se pueden encontrar en la documentación de iptables: http://ipset.netfilter.org/iptables.man.html

Lista de iptables que están activas:

iptables -nvL --line-numbers

Comandos útiles de CentOS/RHEL:

cat /etc/sysconfig/ipset.d/ip_allow.set
systemctl restart ipset
ipset list | head

iptables -F DOCKER-USER
iptables -F FILTERS
iptables-restore -n < ansible_iptables_docker-iptables

grep -v "^#" ansible_iptables_docker-iptables
iptables -S INPUT
iptables -S DOCKER-USER
iptables -S FILTERS

Comandos útiles de Ubuntu:

vi /etc/iptables/ipsets
# Añadir manualmente 'flush' antes de agregar, si eliminas IPs manualmente.

 /usr/sbin/netfilter-persistent reload

cat /etc/iptables/ipsets

cat /etc/iptables/rules.v4

Comandos Manuales (CentOS/RHEL)

Verifica qué reglas de iptables ya tienes. Toma nota en caso de que se pierdan.

iptables -nvL --line-numbers

Instala los paquetes requeridos:

yum install iptables iptables-services ipset ipset-service

Si usas SELinux, instala también:

yum install policycoreutils-python

Configura ipset con las IPs de tu servidor y otras IPs de confianza:

mkdir -p /etc/sysconfig/ipset.d
cat > /etc/sysconfig/ipset.d/ip_allow.set  << 'EOF'
create -exist ip_allow hash:ip family inet hashsize 1024 maxelem 65536
add ip_allow 192.168.1.123
add ip_allow 192.168.101.0/24
add ip_allow 192.168.102.0/24
EOF

Inicia y habilita el servicio de ipset:

systemctl status ipset
systemctl start ipset
systemctl enable ipset

Ver qué tiene cargado ipset en su configuración:

ipset list | head

Reglas de iptables que se añaden (por defecto el puerto 22 está abierto para todos):

cat > ansible_iptables_docker-iptables << 'EOF'
*filter
:DOCKER-USER - [0:0]
:FILTERS - [0:0]
# No se puede flush INPUT. borrar reglas de sobrecarga encriptadas de docker swarm.
#-F INPUT
#Usa ansible o ejecuta manualmente una vez para agregar -I INPUT -j FILTERS
#-I INPUT -j FILTERS
-A DOCKER-USER -m state --state RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER -i docker_gwbridge -j RETURN
-A DOCKER-USER -s 172.18.0.0/16 -j RETURN
-A DOCKER-USER -i docker0 -j RETURN
-A DOCKER-USER -s 172.17.0.0/16 -j RETURN
#A continuación, puertos de Docker abiertos para todos si no están comentados
#-A DOCKER-USER -p tcp -m tcp -m multiport --dports 8000,8001 -j RETURN
#-A DOCKER-USER -p udp -m udp -m multiport --dports 9000,9001 -j RETURN
-A DOCKER-USER -m set ! --match-set ip_allow src -j DROP
-A DOCKER-USER -j RETURN
-F FILTERS
#Porque la red superpuesta encriptada de Docker Swarm solo añade reglas a INPUT. Tiene que estar en la parte superior desafortunadamente.
-A FILTERS -p udp -m policy --dir in --pol ipsec -m udp --dport 4789 -m set --match-set ip_allow src -j RETURN
-A FILTERS -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FILTERS -p icmp -j ACCEPT
-A FILTERS -i lo -j ACCEPT
#A continuación, puertos del sistema operativo abiertos para todos si no están comentados
-A FILTERS -p tcp -m state --state NEW -m tcp -m multiport --dports 22 -j ACCEPT
#-A FILTERS -p udp -m udp -m multiport --dports 53,123 -j ACCEPT
-A FILTERS -m set ! --match-set ip_allow src -j DROP
-A FILTERS -j RETURN
COMMIT

EOF

Usa iptables-restore para añadir las reglas anteriores a iptables. La bandera muy importante es -n. Esto asegura que no se eliminen las reglas de iptables si ya tenemos reglas en Docker (o Docker Swarm).

iptables-restore -n < ansible_iptables_docker-iptables

A continuación, añade una regla a la cadena INPUT, así comenzamos a utilizar las nuevas reglas en FILTERS. Tiene que estar en la parte superior y solo necesita ser añadida una vez:

iptables -I INPUT 1 -j FILTERS

Guarda las reglas de iptables:

/usr/libexec/iptables/iptables.init save

Inicia y habilita el servicio de iptables:

systemctl status iptables
systemctl start iptables
systemctl enable iptables

Si deseas personalizar las reglas de iptables para permitir más puertos abiertos para todos, solo añade el puerto a la regla correspondiente en el archivo de iptables (tcp o udp), luego vuelve a ejecutar los mismos comandos de arriba:

iptables-restore -n < ansible_iptables_docker-iptables
/usr/libexec/iptables/iptables.init save

¡No te olvides de las Advertencias anteriores! Especialmente sobre SELinux.

Comandos Manuales (Ubuntu 20.04)

Verifica qué reglas de iptables ya tienes. Toma nota en caso de que se pierdan.
Ubuntu 18.04 es casi igual. Excepto que el paquete ipset-persistent no existe en Ubuntu 18.04, así que omite ese paquete y copia los archivos de files/ubuntu/iptables-persistent*/plugins/*-ipset a /usr/share/netfilter-persistent/plugins.d/.

iptables -nvL --line-numbers

Instala los paquetes requeridos:

apt install iptables iptables-persistent netfilter-persistent ipset ipset-persistent

Configura ipset con las IPs de tu servidor y otras IPs de confianza:

mkdir -p /etc/iptables
cat > /etc/iptables/ipsets  << 'EOF'
create -exist ip_allow hash:ip family inet hashsize 1024 maxelem 65536
flush
add ip_allow 192.168.1.123
add ip_allow 192.168.101.0/24
add ip_allow 192.168.102.0/24
EOF

Recarga ipset:

/usr/sbin/netfilter-persistent reload

Ver qué tiene cargado ipset en su configuración:

ipset list | head

Reglas de iptables que se añaden (por defecto, el puerto 22 está abierto para todos):

Usa el mismo comando que arriba para CentOS/RHEL.

Usa iptables-restore para añadir las reglas anteriores a iptables. La bandera muy importante es -n. Esto asegura que no se eliminen las reglas de iptables si ya tenemos reglas en Docker (o Docker Swarm).

iptables-restore -n < ansible_iptables_docker-iptables

A continuación, añade una regla a la cadena INPUT, así comenzamos a utilizar las nuevas reglas en FILTERS. Tiene que estar en la parte superior y solo necesita ser añadida una vez:

iptables -I INPUT 1 -j FILTERS

Guarda las reglas de iptables:

/usr/sbin/netfilter-persistent save

Inicia y habilita el servicio de iptables:

systemctl status netfilter-persistent
systemctl start netfilter-persistent
systemctl enable netfilter-persistent

Si deseas personalizar las reglas de iptables para permitir más puertos abiertos para todos, solo añade el puerto a la regla correspondiente en el archivo de iptables (tcp o udp), luego vuelve a ejecutar los mismos comandos de arriba:

iptables-restore -n < ansible_iptables_docker-iptables
/usr/sbin/netfilter-persistent save

¡No te olvides de las Advertencias anteriores!

TODO

  • Verificar firewalld y fallar si está funcionando o habilitado
  • Problema con iptables guardando las reglas de Docker en iptables? Debería estar bien.
  • iptables_docker_ip_allow_set no puede estar vacío. Si lo está, ¡no tiene sentido esto ya que nada está bloqueado!
  • agregar chequeo en adaptadores de red para * y error
  • añadir lista automática de IPs de docker en la lista permitida (usa IPs del grupo de inventario docker_hosts)
  • Cambiar IPs de servidor Docker de confianza automáticamente para que se puedan sobrescribir
  • confirmar que "when" y "tags" están bien
  • ¿Ubuntu? Ubuntu no tiene iptables-services o ipset-service. Tiene iptables-persistent y ipset-? Sin soporte de ufw
  • ¿ipv6? Esto es solo para ipv4
  • probar que TCP, UDP de contenedores Docker y puertos del OS funcionan
  • probar que el tráfico de salida desde los contenedores Docker funciona
  • ¿añadir prueba? ¿Molecule? ¿Modo de un solo nodo de swarm? ¿cómo probar que no funciona la conexión desde una IP "no confiable"?

Autor

Ryan Daniels

Acerca del proyecto

Manage iptables configuration to secure Docker (including Docker Swarm).

Instalar
ansible-galaxy install ryandaniels.iptables_docker
Licencia
mit
Descargas
2.7k
Propietario
Ansible all the things