Docker динамически прописывает правила в iptables после своей загрузки. Делает он это хитрозакрученным способом. При этом порт докера становится доступен из интернета вне зависимости от того, какие правила были раньше прописаны в iptables. Расскажу как закрыть порт.
Первая уловка в том, что правила для DOCKER пишутся не в INPUT/OUTPUT у iptables, а в FORWARD. Иначе они просто не будут работать. Вторая уловка в том, что Docker создаёт себе цепочки, в которые и пишет правила. Эти цепочки обрабатываются в какой-то невероятно закрученной последовательности.
Официальная документация рекомендует изменять только цепочку DOCKER-USER, что мы конечно же делать не будем (потому что от версии к версии она вообще может не создаваться). Для закрытия порта из интернета мы используем цепочку DOCKER. Нагло прописав следующие два правила:
iptables -I DOCKER -i внешний_интерфейс ! -s внешний_ip_сервера -j REJECT
iptables -I DOCKER -i внешний_интерфейс -m state --state ESTABLISHED,RELATED -j ACCEPT
Не забудьте изменить внешний_интерфейс на имя интерфейса, который можно найти используя команду ifconfig (он будет со значением внешний_ip_сервера в одном блоке) либо команду route (в одной строке с "default").
Как можно заметить, правила будут сохранены в обратном порядке, потому что "-I" добавляет правило в начало цепочки. То есть после выполнения этих команд в iptables будет сначала делаться ACCEPT соединения, а только потом REJECT.
Автоматизация создания правил
Остаётся последняя задача - применять эти правила в том случае, если docker только запустился. Потому что при перезапуске он прописывает правила в iptables динамически, затирая свои существующие цепочки. К сожалению, не знаю изящного способа привязки выполнения bash команд к моменту перезапуска docker.
Единственная мысль - сделать sh файл и запускать его каждый час. А внутри файла сделать проверку на сущестование нужных правил. И если правила не прописаны, то прописывать их. Содержание этого файла будет таким:
#!/bin/bash
{ iptables -L DOCKER -v -n | grep REJECT | grep внешний_интерфейс; } || { iptables -I DOCKER -i внешний_интерфейс ! -s внешний_ip_сервера -j REJECT }
{ iptables -L DOCKER -v -n | grep ACCEPT | grep внешний_интерфейс; } || { iptables -I DOCKER -i внешний_интерфейс -m state --state ESTABLISHED,RELATED -j ACCEPT }
Если команды в первых фигурных скобках возвращают пустую строку, то выполняются команды из вторых фигурных скобок. Таким образом, если нужные правила iptables уже существуют, то они второй раз не будут установлены.
Ставим выполнение этого файла на планировщик CRON раз в час. И если докер будет перезагружен, то правила будут прописаны заново.
P.S.
Можете проверить доступность порта с помощью утилиты NetCat до и после выполнения команд: "NetCat. Как проверить заблокированные порты"
P.P.S.
Docker делает неприятные неожиданности не только на сервере. К примеру, если роутер, который выдаёт докеру ip адрес, имеет функцию UPnP, то Docker откроет внешний порт на роутере и пробросит его к себе. После чего кто угодно сможет добраться до вашей машины из интернета. Поэтому строго следите за Docker контейнерами. И ругайте их, если будут самовольничать.
Добрый день!
А есть рецепт "обратной задачи"?
На хосте установлена СУБД Postgres, а в Docker предполагается запустить разные прикладухи и инструменты управления, с доступом к ним снаружи хоста (через vpn и из сети предприятия), и приложения лазают на разные внешние ресурсы (за обрабатываемой информацией).
Docker и СУБД установлены, контейнеры запускаются и к ним есть доступ, доступ к СУБД с хоста через сокет работает, даже telnet с хоста выдает что положено, а вот извне и из контейнеров доступ к СУБД режется, причем как до перенастройки iptables (с тем, что слепил docker при установке и создании в нем объектов, новой виртуальной сети, например), так и после полной очистки через -F (правда в таблице NAT что-то осталось, но оно же не должно влиять или как?)
Буду благодарен за любые идеи
Верно ли понимаю, что доступ из контейнера Docker к СУБД на основной машине через сокет не проходит? А в Docker верно прописано расположение сокет файла субд в качестве volume? Попробуйте ради теста сделать доступ через сеть, а не сокет. Это хоть и будет на 30% медленнее при соединении с базой, но работы по настройке будет меньше, чем с файлами сокетов.
Осторожнее с iptables -F. Оно трёт все правила, включая часть правил работающего Docker. Хоть Docker сразу и восстанавливает некоторые из них автоматически, но только часть. Остальную часть надо восстанавливать через полную перезагрузку Docker (systemctl restart Docker).
Уточняю:
1. Postgres запущен на хосте. Доступ psql с хоста (через сокет) работает. telnet на порт работает.
2. Доступ к postgres из контейнера (в частности, в котором запущен pgAdmin) через сеть (так и предполагалось, поэтому никаких настроек сокета не делалось) не работает, пинг проходит, telnet запустить не удается, т.к. его нет в контейнере.
3. Доступ к postgres из внешней сети (через vpn) ни с telnet ни с ПО управления СУБД (DBeaver) не работает с сообщениями типа Connection refused (предполагаю, что отказ фаейрвола).
После манипуляций с iptables -F действительно пропали все правила из tables (но доступ по ssh остался в рабочем состоянии), но в nat остались докеровские куски. Добавление правил типа "разрешено все отовсюду" ни на что не повлияли. После полной перезагрузки действительно вернулись все правила (хоть я и пытался сохранить "пустую" конфигурацию).
Документация docker рекомендует добавлять правила только в цепочку DOCKER-USER, вероятно так и надо делать, только вот понять бы, какие правила туда лепить
Ох, попробую угадать в чём дело... Пока напишу первую мысли по ситуации (потом допишу).
Возможно, неверно выставлена сеть, в которой запущен контейнер. То есть то, что прописывается с ключом "--network". К примеру, если нужно достучаться до порта докера из материнской системы, то запускать контейнер нужно (как вариант) со значением "host" в "network":
docker run --network host ...
Пожалуйста, посмотрите. Возможно, в этом проблема. Ну, чтобы контейнер оказался в одной сети в основной ОС. Но если не указывать этот ключ "network", то и из материнской системы невозможно достучаться до портов контейнера. Даже указывая ip адрес. Это Ваш случай?
P.S.
У докера много подводных камней. Мне стоит почитать хотя бы несколько книг по нему перед раздачей рекомендаций. Чтобы целиком понять, какую грязь он делает в системе. К примеру, ещё пару лет назад заметил, что Docker при запуске контейнера отправляет uPnP запрос на мой домашний роутер, приказывая открыть для всего мира и пробросить порт на мою машину до контейнера... Ещё у него есть какой-то порт, который отвечает за API соединение с ним. И если не отключить его, то на твоей машине кто угодно может загрузить и запустить любой контейнер. Ну, и конечно не стоит забывать, что докер переписывает правила в iptables так, что твои старые перестают работать. То есть по записям порт может быть закрыт, а по сути открыт... С Docker'ом всегда подстава на подставе.
Думаю, следующая настольная книга у меня будет именно по докеру. Время пришло.
P.P.S.
Часто контейнеры делают на базе debian/ubuntu, в которых есть менеджер пакетов "apt". Можно использовать его, чтобы установить ПО внутри контейнера. К примеру, установить тот же telnet. Для этого перейдите в консоль контейнера:
docker exec -it __id_контейнера__ bash
А если в контейнере сделан пользователь по умолчанию, то войдите в консоль контейнера от лица root:
docker exec -u root -it __id_контейнера__ bash
И выполните обычную установку:
apt update
apt install telnet
Конечно, изменения внутри контейнера будут до обновления образа. Но зато получится сделать тесты.
Спасибо за отклик!
Я для управления контейнерами использую portainer - в нем можно, в том числе, удобно подключать сетки.
Контейнер с pgadmin запущен по умолчанию с нативной сетью bridge, в добавок к ней я создал еще одну сетку типа bridge и подключил ее тоже (ну и настроил по знаниям доступ из обоих сеток) - не помогает
Вчера как раз прочитал про вариант подключения через network=host, проверил - работает (ура!)
Но этот вариант как-то не очень хорош, т.к. на хосте главное будет - это информационная система (тоже в контейнере и тоже соединенная с СУБД), а не управление СУБД (которая по умолчанию, например, перехватила 80 порт) 🙂
Хочется что-то более универсальное;)
Установка telnet внутри контейнера pgadmin через apt не срабатывает, т.к. не понятно, какую оболочку он использует (bash, zsh не запускаются, других вариантов я уже ... не помню 🙂 слишком давно изучал, но инфо portainer там ее вообще нет)
Запустил в контейнере busy_box telnet n.n.n.n 5432 - выдает Connection refused
Книжек по docker на русском немного - я знаю всего 3, все от ДМК-Пресс, про разные версии, но вполне годные