Cómo usar `iptables` dentro del contenedor Docker de Discourse

Lo configuré así. La parte más complicada fue que, incluso si eres root, seguirás obteniendo errores de “permiso denegado” al intentar ver o editar las reglas de iptables dentro del contenedor Docker predeterminado de Discourse.

root@24a1f9f4c038:/# iptables -L
# Advertencia: existen tablas iptables-legacy, usa iptables-legacy para verlas
iptables: Permiso denegado (debes ser root).
root@24a1f9f4c038:/# 

Esto se debe a que, por defecto, el script launcher de Discourse, que envuelve los comandos de Docker, ejecuta el contenedor Docker de Discourse sin la capacidad “NET_ADMIN”.

La forma más robusta de agregar la capacidad NET_ADMIN al contenedor Docker de Discourse es actualizar el archivo yaml de tu contenedor para incluir el argumento necesario en el comando docker run ... /sbin/boot mediante la cadena yaml docker_args:

docker_args: "--cap-add NET_ADMIN"

Por ejemplo:

[root@osestaging1 discourse]# head -n15 containers/app.yml
## esta es la plantilla del contenedor Docker de Discourse todo-en-uno, independiente
##
## Después de realizar cambios en este archivo, DEBES reconstruir
## /var/discourse/launcher rebuild app
##
## TEN *MUCHO* CUIDADO AL EDITAR!
## ¡LOS ARCHIVOS YAML SON SUPER SENSIBLES A ERRORES EN LOS ESPACIOS EN BLANCO O EN LA ALINEACIÓN!
## visita http://www.yamllint.com/ para validar este archivo según sea necesario

docker_args: "--cap-add NET_ADMIN"

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
[root@osestaging1 discourse]# 

Estos se agregarán al comando docker run ... /sbin/boot ejecutado por launcher a través de la variable $user_args:

[root@osestaging1 discourse]# grep -A2 -E '^\s*\$docker_path run' launcher
     $docker_path run --shm-size=512m $links $attach_on_run $restart_policy "${env[@]}" "${labels[@]}" -h "$hostname" \
        -e DOCKER_HOST_IP="$docker_ip" --name $config -t "${ports[@]}" $volumes $mac_address $user_args \
        $run_image $boot_command
[root@osestaging1 discourse]# 

Dado que es muy complejo realizar cambios integrados en la imagen Docker de Discourse después de un docker pull y antes del docker run ... /sbin/boot, decidí instalar y configurar iptables mediante un script simple ejecutado por runit cuando el contenedor se inicia.

El siguiente comando creará el archivo yaml de plantilla necesario, que generará el script runit en el próximo ./launcher rebuild app:

cat << EOF > /var/discourse/templates/iptables.template.yml
run:
  - file:
     path: /etc/runit/1.d/01-iptables
     chmod: "+x"
     contents: |
        #!/bin/bash
        ################################################################################
        # Archivo:    /etc/runit/1.d/01-iptables
        # Versión: 0.2
        # Propósito: instala y bloquea iptables
        # Autor:  Michael Altfield <michael@opensourceecology.org>
        # Creado: 2019-11-26
        # Actualizado: 2019-12-17
        ################################################################################
        sudo apt-get update
        sudo apt-get install -y iptables 
        sudo iptables -A INPUT -i lo -j ACCEPT
        sudo iptables -A INPUT -s 127.0.0.1/32 -j DROP
        sudo iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
        sudo iptables -A INPUT -j DROP
        sudo iptables -A OUTPUT -s 127.0.0.1/32 -d 127.0.0.1/32 -j ACCEPT
        sudo iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
        sudo iptables -A OUTPUT -m owner --uid-owner 0 -j ACCEPT
        sudo iptables -A OUTPUT -m owner --uid-owner 100 -j ACCEPT
        sudo iptables -A OUTPUT -j DROP
        sudo ip6tables -A INPUT -i lo -j ACCEPT
        sudo ip6tables -A INPUT -s ::1/128 -j DROP
        sudo ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
        sudo ip6tables -A INPUT -j DROP
        sudo ip6tables -A OUTPUT -s ::1/128 -d ::1/128 -j ACCEPT
        sudo ip6tables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
        sudo ip6tables -A OUTPUT -m owner --uid-owner 0 -j ACCEPT
        sudo ip6tables -A OUTPUT -m owner --uid-owner 100 -j ACCEPT
        sudo ip6tables -A OUTPUT -j DROP

EOF

Ten en cuenta que esta es una configuración de iptables muy básica. Con el archivo web.socketed.template.yml (que uso), el contenedor Docker técnicamente no necesita acceso a Internet, ya que el proxy inverso nginx en el host de Docker se comunica con el nginx de Discourse en el contenedor Docker a través de un socket de dominio Unix. La razón principal por la que permito el acceso a Internet es para que el sistema operativo base del contenedor Docker pueda actualizarse con parches de seguridad críticos mediante unattended-upgrades. De ahí que abra el acceso para el usuario 100, que es el usuario apt.

Finalmente, agrega el archivo templates/iptables.template.yml anterior al archivo app.yaml de tu contenedor.

[root@osestaging1 discourse]# head -n15 /var/discourse/containers/discourse_ose.yml
## esta es la plantilla del contenedor Docker de Discourse todo-en-uno, independiente
##
## Después de realizar cambios en este archivo, DEBES reconstruir
## /var/discourse/launcher rebuild app
##
## TEN *MUCHO* CUIDADO AL EDITAR!
## ¡LOS ARCHIVOS YAML SON SUPER SENSIBLES A ERRORES EN LOS ESPACIOS EN BLANCO O EN LA ALINEACIÓN!
## visita http://www.yamllint.com/ para validar este archivo según sea necesario

docker_args: "--cap-add NET_ADMIN"

templates:
  - "templates/iptables.template.yml"
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
[root@osestaging1 discourse]# 

Ahora deberías poder ejecutar:

./launcher rebuild app

Y después de esperar unos 10 minutos, tu contenedor Docker de Discourse tendrá iptables completamente funcional :slight_smile: