Как использовать `iptables` внутри контейнера Docker Discourse

Как настроить iptables в контейнере Docker с Discourse?

Мне конкретно нужны функции брандмауэра внутри контейнера Docker, так как я хочу иметь возможность добавлять условные правила на основе идентификатора пользователя.

Сначала я полностью блокировал доступ контейнера Docker к интернету (это возможно, если использовать web.socketed.template.yml, поскольку nginx будет общаться через сокеты Unix вместо сети) с помощью параметра 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]# 

…но я понял, что контейнер Docker, вероятно, должен иметь хотя бы доступ в интернет, чтобы он мог обновлять свою базовую операционную систему (сейчас это Debian 10) с помощью критических обновлений безопасности через unattended-upgrades. Поэтому я предпочту предоставить root и apt доступ в интернет и запретить всё остальное с помощью iptables, запущенного внутри контейнера Docker с Discourse.

Я настроил это. Самая сложная часть заключалась в том, что даже при наличии прав root вы всё равно будете получать ошибки «permission denied» при попытке просмотра или редактирования правил iptables внутри стандартного Docker-контейнера 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:/# 

Это происходит потому, что скрипт launcher для Discourse, который оборачивает команды Docker, по умолчанию запускает контейнер Discourse без возможности «NET_ADMIN».

Самый надёжный способ добавить возможность NET_ADMIN в контейнер Discourse — обновить YAML-файл контейнера, включив необходимый аргумент в команду docker run ... /sbin/boot через строку docker_args в YAML:

docker_args: "--cap-add NET_ADMIN"

Например:

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

Эти аргументы будут добавлены к команде docker run ... /sbin/boot, выполняемой launcher, через переменную $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]# 

Поскольку внесение изменений, встроенных в образ Docker Discourse, после docker pull и перед docker run ... /sbin/boot крайне сложно, я решил установить и настроить iptables с помощью простого скрипта, выполняемого runit при запуске контейнера.

Следующая команда создаст необходимый шаблонный YAML-файл, который создаст скрипт runit при следующей команде ./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

Обратите внимание, что это очень базовая конфигурация iptables. С файлом web.socketed.template.yml (который я использую) контейнеру Docker технически не нужен доступ в Интернет, поскольку обратный прокси-сервер nginx на хосте Docker общается с nginx Discourse внутри контейнера Docker через сокет Unix-домена. Основная причина, по которой я разрешаю доступ в Интернет, — чтобы базовая ОС контейнера Docker могла обновляться критическими исправлениями безопасности через unattended-upgrades. Поэтому я открываю доступ для пользователя 100, который является пользователем apt.

Наконец, добавьте вышеуказанный файл templates/iptables.template.yml в файл app.yaml вашего контейнера.

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

Теперь вы должны иметь возможность выполнить:

./launcher rebuild app

После ожидания примерно 10 минут ваш контейнер Docker Discourse будет иметь полностью функционирующий iptables :slight_smile:

:warning: Кто бы вы ни были и чем бы вы ни занимались, пожалуйста, не пытайтесь повторить это дома, в школе или где-либо ещё!

Я почти уверен, что вы выводите из строя ваш экземпляр Discourse, запрещая процессам unicorn и sidekiq (принадлежащим пользователю discourse) доступ в интернет. Но да, я уверен, что вы знаете, что делаете.

Какую проблему это решает?