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

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

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

    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. Вот мой тест

среда:

  • недавно установленный Ubuntu 14.04 (ядро: 3.13.0-53 )
  • настройки 1.6.2
  • переадресация ufw включена.( включить пересылку UFW )
  • --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

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

xxxxg@ubuntu:~$ sudo docker exec -t -i nginx /bin/bash
root@b0d33f22d3f4:/# 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
root@b0d33f22d3f4:/# 

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

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

спасибо.

Барт

4 ответов


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

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

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

{
  "iptables": false
}

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

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

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

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

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

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

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

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


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

  • включить функцию iptables Docker. Удалите все изменения, такие как --iptables=false , включая файл конфигурации /etc/docker/daemon.json.
  • правило переадресации UFW по умолчанию возвращается к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, контейнер и частная сеть могут регулярно посещать друг друга, а контейнеры могут также получать доступ к внешней сети изнутри.

если вы хотите разрешить общедоступным сетям доступ к службам, предоставляемым контейнером 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 управлять, разрешено ли общественным сетям посещать предоставляемые услуги контейнер Докер. Чтобы мы могли управлять всеми правилами брандмауэра в одном месте.

-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

Con:

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

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

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

ufw allow proto tcp from any to 172.16.0.3 port 8080

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

Pro:

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

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

ufw route allow 8080

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

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

ufw allow 8080

Con:

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

вывод

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


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

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

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

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

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

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


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

/etc/systemd/system/compose-project@.service

[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

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

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


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

надеюсь, что это помогает

Дилан

редактировать

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