Como usar `iptables` dentro do contêiner Docker do Discourse

Eu configurei isso. A parte mais complicada foi que — mesmo sendo root — você ainda receberá erros de “permission denied” ao tentar visualizar ou editar regras do iptables dentro do contêiner Docker padrão do Discourse.

root@24a1f9f4c038:/# iptables -L
# Warning: iptables-legacy tables present, use iptables-legacy to see them
iptables: Permission denied (you must be root).
root@24a1f9f4c038:/# 

Isso ocorre porque, por padrão, o script launcher do Discourse, que envolve os comandos do Docker, executa o contêiner Docker do Discourse sem a capacidade “NET_ADMIN”.

A maneira mais robusta de adicionar a capacidade NET_ADMIN ao contêiner Docker do Discourse é atualizar o arquivo YAML do seu contêiner para incluir o argumento necessário no comando docker run ... /sbin/boot por meio da string YAML docker_args:

docker_args: "--cap-add NET_ADMIN"

Por exemplo:

[root@osestaging1 discourse]# head -n15 containers/app.yml
## this is the all-in-one, standalone Discourse Docker container template
##
## After making changes to this file, you MUST rebuild
## /var/discourse/launcher rebuild app
##
## BE *VERY* CAREFUL WHEN EDITING!
## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
## visit http://www.yamllint.com/ to validate this file as needed

docker_args: "--cap-add NET_ADMIN"

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

Esses argumentos serão adicionados ao comando docker run ... /sbin/boot executado pelo launcher por meio da variável $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]# 

Como é extremamente complexo fazer alterações embutidas na imagem Docker do Discourse após um docker pull e antes do docker run ... /sbin/boot, decidi instalar e configurar o iptables por meio de um script simples executado pelo runit quando o contêiner inicia.

O seguinte comando criará o arquivo YAML de modelo necessário, que gerará o script runit na próxima execução de ./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
        ################################################################################
        # File:    /etc/runit/1.d/01-iptables
        # Version: 0.2
        # Purpose: installs & locks-down iptables
        # Author:  Michael Altfield <michael@opensourceecology.org>
        # Created: 2019-11-26
        # Updated: 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

Observe que esta é uma configuração básica de iptables. Com o arquivo web.socketed.template.yml (que eu uso), o contêiner Docker tecnicamente não precisa de acesso à Internet, já que o proxy reverso nginx no host Docker se comunica com o nginx do Discourse no contêiner Docker por meio de um socket de domínio Unix. A principal razão para permitir o acesso à Internet é para que o sistema operacional base do contêiner Docker possa se atualizar com patches de segurança críticos por meio do unattended-upgrades. Por isso, abri o acesso para o usuário 100, que é o usuário apt.

Por fim, adicione o arquivo templates/iptables.template.yml acima ao arquivo app.yaml do seu contêiner.

[root@osestaging1 discourse]# head -n15 /var/discourse/containers/discourse_ose.yml
## this is the all-in-one, standalone Discourse Docker container template
##
## After making changes to this file, you MUST rebuild
## /var/discourse/launcher rebuild app
##
## BE *VERY* CAREFUL WHEN EDITING!
## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
## visit http://www.yamllint.com/ to validate this file as needed

docker_args: "--cap-add NET_ADMIN"

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

Agora você deve ser capaz de executar:

./launcher rebuild app

E, após aguardar cerca de 10 minutos, seu contêiner Docker do Discourse terá o iptables totalmente funcional :slight_smile: