Come usare `iptables` all'interno del contenitore Docker di Discourse

Come posso configurare iptables nel contenitore Docker di Discourse?

Ho specificamente bisogno dei controlli del firewall \u003cem\u003edentro\u003c/em\u003e il contenitore Docker perché voglio essere in grado di aggiungere regole condizionali in base all’ID utente.

Inizialmente stavo semplicemente negando qualsiasi accesso a Internet al contenitore Docker (questo è possibile se si utilizza web.socketed.template.yml perché nginx comunicherà tramite socket di dominio Unix invece della rete) tramite dockerd ... --iptables=false.

[root@osestaging1 templates]# grep ExecStart /usr/lib/systemd/system/docker.service
#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --iptables=false
[root@osestaging1 templates]# 

…ma ho realizzato che il contenitore Docker dovrebbe probabilmente avere almeno accesso a Internet in modo da poter aggiornare il suo sistema operativo di base (attualmente Debian 10) con aggiornamenti di sicurezza critici tramite unattended-upgrades. Preferirei quindi concedere l’accesso a Internet a root e ad apt e negare tutto il resto tramite iptables in esecuzione all’interno del contenitore Docker di Discourse.

Ho configurato tutto. La parte più complessa è stata che, anche se sei root, riceverai comunque errori di “permesso negato” quando provi a visualizzare o modificare le regole iptables all’interno del container Docker Discourse predefinito.

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

Questo accade perché, di default, lo script launcher di Discourse che avvolge i comandi Docker esegue il container Docker Discourse senza la capacità “NET_ADMIN”.

Il modo più robusto per aggiungere la capacità NET_ADMIN al container Docker di Discourse è aggiornare il file YAML del tuo container per includere l’argomento necessario al comando docker run ... /sbin/boot tramite la stringa YAML docker_args:

docker_args: "--cap-add NET_ADMIN"

Ad esempio:

[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]# 

Questi verranno aggiunti al comando docker run ... /sbin/boot eseguito da launcher tramite la variabile $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]# 

Poiché è estremamente complesso apportare modifiche incorporate nell’immagine Docker di Discourse dopo un docker pull e prima di docker run ... /sbin/boot, ho deciso di installare e configurare iptables tramite uno script semplice eseguito da runit all’avvio del container.

Il seguente comando creerà il file YAML modello necessario, che genererà lo script runit al prossimo ./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

Nota che questa è una configurazione iptables molto basilare. Con il file web.socketed.template.yml (che io uso), il container Docker tecnicamente non ha bisogno di accesso a Internet, poiché il reverse proxy nginx sull’host Docker comunica con nginx di Discourse nel container Docker tramite un socket di dominio Unix. Il motivo principale per cui permetto l’accesso a Internet è affinché il sistema operativo di base del container Docker possa aggiornarsi con patch di sicurezza critiche tramite unattended-upgrades. Da qui l’apertura per l’utente 100, che è l’utente apt.

Infine, aggiungi il file templates/iptables.template.yml sopra al file app.yaml del tuo container.

[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]# 

Ora dovresti essere in grado di eseguire

./launcher rebuild app

E dopo aver atteso circa 10 minuti, il tuo container Docker Discourse avrà iptables completamente funzionante :slight_smile:

:warning: Chiunque tu sia, qualsiasi cosa tu stia facendo, per favore non provare questo a casa, a scuola o in qualsiasi altro luogo!

Sono piuttosto sicuro che stai compromettendo la tua istanza di Discourse impedendo ai processi unicorn e sidekiq (di proprietà dell’utente discourse) di accedere a Internet. Ma sì, sono sicuro che sai quello che stai facendo.

Quale problema sta risolvendo?