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
Manage iptables configuration to secure Docker (including Docker Swarm).
ansible-galaxy install ryandaniels.iptables_docker