Какова наилучшая практика docker + ufw под Ubuntu

Я только что опробовал Докера. Это потрясающе, но, похоже, не работает с UFW. По умолчанию докеры немного манипулируют iptables. Результат не ошибка, а не то, что я ожидал. Для более подробной информации вы можете прочитать Опасности UFW + Docker

Моя цель - создать такую ​​систему, как

    Host (running ufw) -> docker container 1 - nginx (as a reverse proxy)
                       -> docker container 2 - node web 1
                       -> docker container 3 - node web 2
                       -> .......

Я хочу управлять входящим трафиком (например, ограничивать доступ) через ufw, поэтому я не хочу, чтобы докер касался моих iptables. Вот мой тест

Окружающая среда:

--iptables=false был добавлен к демону Docker.

Первая попытка

docker run --name ghost -v /home/xxxx/ghost_content:/var/lib/ghost -d ghost
docker run --name nginx -p 80:80 -v /home/xxxx/nginx_site_enable:/etc/nginx/conf.d:ro --link ghost:ghost -d nginx

Не повезло. Первая команда прекрасна, но вторая команда выдает ошибку

Error response from daemon: Cannot start container

Вторая попытка

Тогда я нашел это: не удалось связать контейнеры с --iptables = false # 12701

После выполнения следующей команды все выглядит нормально.

sudo iptables -N DOCKER

Однако я заметил, что я не могу установить исходящие соединения внутри контейнеров. Например:

[email protected]:~$ sudo docker exec -t -i nginx /bin/bash
[email protected]:/# ping 74.125.21.147
PING 74.125.21.147 (74.125.21.147): 56 data bytes
^C--- 74.125.21.147 ping statistics ---
35 packets transmitted, 0 packets received, 100% packet loss
[email protected]:/# 

Если я удалю --iptables=false из демона Docker, подключение к интернету будет возвращено к нормальному состоянию, но ufw не будет работать "правильно" (ну... по моему определению).

Итак, что такое лучшая практика docker + ufw? Может ли кто-нибудь помочь?

Спасибо.

Bart.

Ответ 1

У меня была такая проблема, как и несколько месяцев назад, и в последнее время я решил описать эту проблему вместе с решением в своем блоге. Здесь ярлык.

Использование --iptables=false мало поможет вам в описанном вами случае. Это просто не достаточно здесь. По умолчанию ни один из ваших контейнеров не может устанавливать исходящее соединение.

Там есть небольшой шаг, который вы пропускаете на своем пути, чтобы иметь контейнеры позади UFW здесь. Вы можете использовать --iptables=false или создать файл /etc/docker/daemon.json с содержимым следующим образом

{
  "iptables": false
}

результат будет таким же, но последний вариант требует, чтобы вы перезапустили всю службу service docker restart или даже сделали перезагрузку, если у Docker была возможность добавить правила iptables до того, как вы отключили эту функцию.

Когда это будет сделано, просто сделайте еще две вещи:

$ sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw
$ ufw reload

поэтому вы устанавливаете политику пересылки по умолчанию в UFW для принятия и используете:

$ iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE

Таким образом, вы добиваетесь отключения грязного поведения докера в ваших правилах iptables, и в то же время докеру предоставляется необходимая маршрутизация, поэтому контейнеры прекрасно справляются с исходящими соединениями. Правила UFW все еще будут ограничены с этого момента.

Надеюсь, что это решит проблему для вас и любого, кто попадает сюда в поисках ответа.

Я описал проблему и решение более подробно на https://www.mkubaczyk.com/2017/09/05/force-docker-not-bypass-ufw-rules-ubuntu-16-04/

Ответ 2

проблема

Эта проблема существует уже давно.

Отключение iptables в Docker приведет к другим проблемам.

Откат меняется первым

Если вы изменили свой сервер в соответствии с текущим решением, которое мы находим в Интернете, сначала откатите эти изменения, в том числе:

  • Включить Docker функцию iptables. Удалите все изменения, например --iptables=false, включая файл конфигурации /etc/docker/daemon.json.
  • UFW правило FORWARD по умолчанию изменяется обратно на DROP по умолчанию вместо ACCEPT.
  • Удалите правила, относящиеся к сети Docker, в файле конфигурации UFW /etc/ufw/after.rules.
  • Если вы изменили файлы конфигурации Docker, сначала перезапустите Docker. Мы изменим конфигурацию UFW позже, и затем сможем ее перезапустить.

Решение проблем UFW и Docker

Это решение должно изменить только один файл конфигурации UFW, все конфигурации и опции Docker остаются по умолчанию. Не нужно отключать функцию Docker iptables.

Измените файл конфигурации UFW /etc/ufw/after.rules и добавьте следующие правила в конец файла:

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER

С помощью команды sudo systemctl restart ufw чтобы перезапустить UFW после изменения файла. Теперь общедоступная сеть не может получить доступ к каким-либо опубликованным портам док-станции, контейнер и частная сеть могут регулярно посещать друг друга, а контейнеры также могут получать доступ к внешней сети изнутри.

Если вы хотите разрешить общедоступным сетям доступ к сервисам, предоставляемым, например, контейнером Docker, порт сервиса контейнера равен 80. Выполните следующую команду, чтобы разрешить общедоступным сетям доступ к этой службе:

ufw route allow proto tcp from any to any port 80

Эта команда позволяет общедоступной сети получить доступ ко всем опубликованным портам, чей порт контейнера равен 80.

Примечание. Если мы публикуем порт с помощью опции -p 8080:80, нам следует использовать контейнерный порт 80, а не хост-порт 8080.

Если имеется несколько контейнеров с сервисным портом 80, но мы хотим, чтобы внешняя сеть имела доступ к определенному контейнеру. Например, если частный адрес контейнера - 172.17.0.2, используйте следующую команду:

ufw route allow proto tcp from any to 172.17.0.2 port 80

Если сетевым протоколом службы является UDP, например, служба DNS, вы можете использовать следующую команду, чтобы разрешить внешней сети доступ ко всем опубликованным службам DNS:

ufw route allow proto udp from any to any port 53

Точно так же, если только для определенного контейнера, такого как IP-адрес 172.17.0.2:

ufw route allow proto udp from any to 172.17.0.2 port 53

Как это устроено?

Следующие правила позволяют частным сетям иметь возможность посещать друг друга. Как правило, частные сети являются более надежными, чем публичные сети.

-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

Следующие правила позволяют UFW определять, разрешено ли общедоступным сетям посещать службы, предоставляемые контейнером Docker. Так что мы можем управлять всеми правилами брандмауэра в одном месте.

-A DOCKER-USER -j ufw-user-forward

Следующие правила блокируют запросы на подключение, инициированные всеми общедоступными сетями, но разрешают внутренним сетям доступ к внешним сетям. Для протокола TCP это препятствует активному установлению соединения TCP из общедоступных сетей. Для протокола UDP все доступы к портам меньше 32767 заблокированы. Почему этот порт? Поскольку протокол UDP не имеет состояния, невозможно заблокировать сигнал квитирования, который инициирует запрос соединения, как это делает TCP. Для GNU/Linux мы можем найти диапазон локальных портов в файле /proc/sys/net/ipv4/ip_local_port_range. Диапазон по умолчанию: 32768 60999. При доступе к службе протокола UDP из запущенного контейнера локальный порт будет случайным образом выбран из диапазона портов, и сервер вернет данные на этот случайный порт. Поэтому мы можем предположить, что порт прослушивания протокола UDP во всех контейнерах меньше 32768. Это причина того, что мы не хотим, чтобы общедоступные сети имели доступ к портам UDP, которые меньше 32768.

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN

Больше

https://github.com/chaifeng/ufw-docker

sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
chmod +x /usr/local/bin/ufw-docker

использование

ufw-docker help
ufw-docker install
ufw-docker status
ufw-docker allow webapp
ufw-docker allow webapp 80
ufw-docker allow webapp 53/udp
ufw-docker list webapp
ufw-docker delete allow webapp 80/tcp
ufw-docker delete allow webapp

Обновление: 2018-09-10

Причина выбора ufw-user-forward, а не ufw-user-input

используя ufw-user-input

Pro:

Прост в использовании и понимании, поддерживает старые версии Ubuntu.

Например, чтобы разрешить общественности посещать опубликованный порт, порт контейнера которого 8080, используйте команду:

ufw allow 8080

Против:

Он не только предоставляет порты контейнеров, но также предоставляет порты хоста.

Например, если на хосте запущена служба, а порт - 8080. Команда ufw allow 8080 позволяет общедоступной сети посещать службу и все опубликованные порты, чьи порты контейнеров - 8080. Но мы просто хотим показать сервис, работающий на хосте, или просто сервис, работающий внутри контейнеров, а не оба.

Чтобы избежать этой проблемы, нам может понадобиться использовать команду, аналогичную следующей для всех контейнеров:

ufw allow proto tcp from any to 172.16.0.3 port 8080

используя ufw-user-forward

Pro:

Невозможно выставить службы, запущенные на хостах и контейнерах одновременно, одной и той же командой.

Например, если мы хотим опубликовать порт 8080 контейнеров, используйте следующую команду:

ufw route allow 8080

Публичная сеть может получить доступ ко всем опубликованным портам, чьи контейнеры имеют порт 8080.

Но порт 8080 хоста по-прежнему недоступен для публичной сети. Если мы хотим сделать это, выполните следующую команду, чтобы разрешить общий доступ к порту на хосте отдельно:

ufw allow 8080

Против:

Не поддерживает старые версии Ubuntu, и команда немного сложнее. Но вы можете использовать мой скрипт https://github.com/chaifeng/ufw-docker.

Заключение

Если мы используем более старую версию Ubuntu, мы можем использовать ufw-user-input. Но будьте осторожны, чтобы не подвергать службы, которые не должны подвергаться.

Если мы используем более новую версию Ubuntu, которая поддерживает ufw route support ufw route, нам лучше использовать ufw-user-forward и ufw route для управления правилами межсетевого экрана для контейнеров.


Обновление: 6 октября 2018 г.

Скрипт ufw-docker теперь поддерживает Docker Swarm. Пожалуйста, смотрите последний код для получения дополнительной информации, https://github.com/chaifeng/ufw-docker

Установить для режима Docker Swarm

Мы можем использовать этот скрипт только на узлах менеджера для управления правилами брандмауэра при использовании в режиме Swarm.

  • Изменение всех файлов after.rules на всех узлах, включая менеджеров и работников
  • Развертывание этого скрипта на узлах менеджера

Работая в режиме Docker Swarm, этот скрипт добавит глобальный сервис ufw-docker-agent. Образ chaifeng/ufw-docker-agent также автоматически создается из этого проекта.

Ответ 3

Не совсем уверен, что ваш запрос, но из того, что я могу собрать, вам лучше контролировать, кто может получать доступ к вашим приложениям, работающим внутри Docker? Я ответил на аналогичный вопрос здесь, чтобы контролировать трафик через внешний прокси-сервер, а не с IP-таблицами Блокировать внешний доступ к контейнерам докеров

Надеюсь, что это поможет

Дилан

Изменить

С помощью вышеуказанного подхода вы можете использовать UFW, чтобы разрешать входящие подключения к порту 80 (т.е. прокси). Это обеспечивает минимальное количество портов при добавлении дополнительного бонуса, что вы можете контролировать трафик через конфигурацию прокси и DNS

Ответ 4

Для чего это стоит добавление к @mkubaczyk answer для случая, когда больше сетей моста задействовано во всей установке. Они могут быть предоставлены проектами Docker-Compose и здесь, как можно создать правильные правила, учитывая, что эти проекты контролируются systemd.

/etc/systemd/system/[email protected]

[Unit]
Description=Docker-Compose project: %I
After=docker.service
BindsTo=docker.service
AssertPathIsDirectory=/<projects_path>/%I
AssertFileNotEmpty=/<projects_path>/%I/docker-compose.yml

[Service]
Type=simple
Restart=always
WorkingDirectory=/<projects_path>/%I
ExecStartPre=/usr/bin/docker-compose up --no-start --remove-orphans
ExecStartPre=+/usr/local/bin/update-iptables-for-docker-bridges
ExecStart=/usr/bin/docker-compose up
ExecStop=/usr/bin/docker-compose stop --timeout 30
TimeoutStopSec=30
User=<…>
StandardOutput=null

[Install]
WantedBy=multi-user.target

/usr/local/bin/update-iptables-for-docker-bridges

#!/bin/sh

for network in $(docker network ls --filter 'driver=bridge' --quiet); do
  iface=$(docker network inspect --format '{{index .Options "com.docker.network.bridge.name"}}' ${network})
  [ -z $iface ] && iface="br-${network}"
  subnet=$(docker network inspect --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' ${network})
  rule="! --out-interface ${iface} --source ${subnet} --jump MASQUERADE"
  iptables --table nat --check POSTROUTING ${rule} || iptables --table nat --append POSTROUTING ${rule}
done

Очевидно, что это не будет хорошо масштабироваться.

Также следует отметить, что вся базовая концепция замаскирует источник любого соединения для приложений, запущенных в контейнере.

Ответ 5

Я провел два часа, пробуя предложения выше и из других постов. Единственное решение, которое сработало, было из сообщения Цуна в этой теме Github:

Append the following at the end of /etc/ufw/after.rules (replace eth0 with your external facing interface):

# Put Docker behind UFW
*filter
:DOCKER-USER - [0:0]
:ufw-user-input - [0:0]

-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-USER -m conntrack --ctstate INVALID -j DROP
-A DOCKER-USER -i eth0 -j ufw-user-input
-A DOCKER-USER -i eth0 -j DROP
COMMIT
And undo any and all of:

Remove "iptables": "false" from /etc/docker/daemon.json
Revert to DEFAULT_FORWARD_POLICY="DROP" in /etc/default/ufw
Remove any docker related changes to /etc/ufw/before.rules
Be sure to test that everything comes up fine after a reboot.

I still believe Docker out of the box behavior is dangerous and many more people will continue to unintentionally expose internal services to the outside world due to Docker punching holes in otherwise safe iptables configs.