Правила iptables, чтобы контейнер Docker мог получить доступ к сервису по IP-адресу хоста.

Вопрос или проблема

У меня проблемы с доступом к приватному интерфейсу хоста (ip) из контейнера Docker. Я вполне уверен, что это связано с правилами Iptables (или, возможно, маршрутизацией). Когда я добавляю флаг --net=host к docker run, все работает как положено. Также, когда я указываю, что политика INPUT должна быть либеральной -P INPUT ACCEPT, все также работает так, как я ожидал. Однако это нежелательные и небезопасные варианты, которых я хотел бы избежать.

Поскольку это не связано с моими службами (DNS), я исключил это из проблемы, поскольку поиск этого в сочетании с Docker приводит к другой (популярной) проблеме, добавляя шум в результаты поиска.

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

У меня есть следующие правила Iptables. Комбинация CoreOS, Digital Ocean и Docker, я предполагаю.

-P INPUT DROP
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-A INPUT -i lo -j ACCEPT
-A INPUT -i eth1 -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 3 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 11 -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT

Мои (релевантные) интерфейсы хоста:

3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    inet 10.129.112.210/16 brd 10.129.255.255 scope global eth1
       valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    inet 172.17.42.1/16 scope global docker0
       valid_lft forever preferred_lft forever

И я запускаю контейнер Docker:

$ docker run --rm -it --dns=10.129.112.210 debian:jessie # Указание DNS необходимо, чтобы не использовались публичные DNS-серверы.

На этом этапе я хочу иметь возможность использовать локальную службу, привязанную к 10.129.112.210:53. Так что следующее должно дать ответ:

$ ping google.com
^C
$ ping user.skydns.local
^C

Когда я запускаю ту же команду с моего хоста:

$ ping photo.skydns.localPING photo.skydns.local (10.129.112.206) 56(84) bytes of data.
64 bytes from 10.129.112.206: icmp_seq=1 ttl=64 time=0.790 ms
^C

Мой resolv.conf

$ cat /etc/resolv.conf
nameserver 10.129.112.210
nameserver 127.0.0.1
nameserver 8.8.8.8
nameserver 8.8.4.4

Суть здесь не в доступе к публичным хостам, а скорее к внутренним, используя локальный DNS-сервис, доступный на хосте (через другой экземпляр Docker).

Чтобы еще больше проиллюстрировать это (мои навыки ASCII-арта превосходят мои навыки в iptables, так что это должно сказать достаточно на данный момент):

 ______________________________________________
|  __________________________           Хост   |
| |   Docker-контейнер DNS   |                 |
|  ``````````````````````|```                  |
|                        |                     |
|     ,----------,---( приватный n. интерфейс )  |
|     |          |                             |
|     |          |   ( публичный n. интерфейс )---
|     |          |                             |
|     |          |   ( интерфейс loopback )  |
|     |          |                             |
|     |          |                             |
|     |        __|_______________________      |
|     |       | Контейнер службы Docker |     |
|     |        ``````````````````````````      |
|     |                                        |
|     |                                        |
| [ Локальная служба хоста с использованием DNS. ]            |
|                                              |
|______________________________________________|

  приватный (хост) сетевой интерфейс: eth1 (10.129.0.0/16)
  Сетевой интерфейс Docker: docker0 (172.17.0.0/16)

Я искал, читал и применял различные примеры конфигураций Iptables, но я знаю слишком мало о более “расширенных” правилах Iptables, чтобы понять, что происходит, и, следовательно, добиться желаемого результата.

Вывод iptables -t nat -nL:

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0

Chain DOCKER (2 references)
target     prot opt source               destination

Вывод cat /proc/sys/net/ipv4/ip_forward:

1

Контейнер взаимодействует с хостом через интерфейс docker0.
Чтобы разрешить трафик из контейнера, добавьте:

-A INPUT -i docker0 -j ACCEPT

Я столкнулся с очень похожей ситуацией, но добавление -A INPUT -i docker0 -j ACCEPT откроет весь доступ через мой интерфейс eth0 хоста Docker для контейнеров, что совершенно не то, что я намеревался.

И поскольку я заметил, что мой контейнер имел ограниченный доступ (скажем, только порт 22) к хост-интерфейсу, вместо полного закрытия от сетевой сети хоста, я проверил свои правила iptables и нашел правило в цепочке IN_public_allow, которое должно быть ответственным за это. Правило: -A IN_public_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT. Так что я добавил подобные правила, чтобы разрешить моему контейнеру доступ к другим желаемым портам хоста, что, как я думаю, может быть немного более точным способом открыть доступ к сетевой сети хоста для контейнеров.

iptables -A INPUT -i docker0 -j ACCEPT

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

Однако, если вы используете docker-compose для настройки нескольких контейнеров, то вышеуказанная команда не то, что вам нужно, потому что docker-compose создает новый сетевой интерфейс для каждого приложения compose.

И вот решение (все команды должны выполняться с правами root):

Убедитесь, что ваше приложение compose запущено, и используйте:

docker network list

Вы получите что-то вроде:

NETWORK ID     NAME                      DRIVER    SCOPE
e25c1404cc22   bridge                    bridge    local
795ca4fd1a57   host                      host      local
a8ad49db25eb   none                      null      local
75b8e1fd5f8c   myapp_default             bridge    local

Посмотрите на сеть, названную myapp_default, это то, что вы получаете, если ваш docker-compose.yml находится в папке myapp и не имеет настроек сети. Это
сеть, которую создало ваше приложение compose.

Теперь ID вашего приложения compose, префиксированный br-, будет интерфейсом этой сети в Linux, например: br-75b8e1fd5f8c.

Вы можете подтвердить это с помощью:

ip link show | grep br-

И вы должны получить что-то вроде:

169: br-75b8e1fd5f8c: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
181: veth077cc64@if180: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-75b8e1fd5f8c state UP mode DEFAULT group default
183: vethd355b97@if182: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-75b8e1fd5f8c state UP mode DEFAULT group default

Интерфейс br-75b8e1fd5f8c является сетевым интерфейсом мостовой сети приложения compose, а другие vethxxx – это виртуальная сеть контейнеров того же приложения compose.

Таким образом, для вашего приложения compose вы можете добавить правила iptables, как:

iptables -A INPUT -i br-75b8e1fd5f8c -j ACCEPT

что предоставляет всем контейнерам в вашем приложении compose доступ к хосту.

Однако, если вы выполняете docker compose down->docker compose up, то идентификатор сети и имя интерфейса, а также диапазон IP контейнеров изменятся. Поэтому правило iptables нужно обновить.

Один из способов избавиться от этой рутины – установить сетевой интерфейс приложения compose с фиксированным именем, это можно сделать, установив опцию com.docker.network.bridge.name драйвера мост.

Для этого остановите ваше приложение compose и добавьте/измените параметры сети в файле docker-compose.yml следующим образом:

services:
    # Определения ваших служб...
networks:
    myapp-network:
        driver: bridge
        driver_opts:
            com.docker.network.bridge.name: myapp-bridge

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

iptables -A INPUT -i myapp-bridge -j ACCEPT

(Вы можете использовать ip link show после up вашего приложения compose, чтобы убедиться, что имя сетевого интерфейса установлено правильно.)

Ответ или решение

Настройка iptables для доступа контейнера Docker к службам на IP хоста

Ваша задача состоит в том, чтобы настроить iptables так, чтобы контейнер Docker мог обращаться к службам, работающим на частном IP-адресе хоста, без использования опций --net=host и без установки политики INPUT в ACCEPT, что небезопасно. Давайте рассмотрим необходимые шаги для достижения этой цели.

1. Понимание текущих правил iptables

Ваши текущие правила iptables выглядят следующим образом:

-P INPUT DROP
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-A INPUT -i lo -j ACCEPT
-A INPUT -i eth1 -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 3 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 11 -j ACCEPT

Эти правила запрещают все входящие соединения, кроме установленных и разрешенных, таких как SSH (порт 22) и локальные соединения.

2. Предоставление доступа к хост-сервисам из контейнера

Чтобы контейнер мог получить доступ к службам, работающим на IP-адресе хоста (в вашем случае это 10.129.112.210), нужно добавить в правила iptables правила, разрешающие доступ к определённым портам, которые будут использоваться.

Например, если вы хотите разрешить контейнеру доступ к DNS-сервису, работающему на порту 53, вам нужно добавить следующие строки к правилам iptables:

# Разрешаем доступ к DNS (порт 53) из контейнера
iptables -A INPUT -p tcp -m tcp --dport 53 -s 172.17.0.0/16 -j ACCEPT
iptables -A INPUT -p udp -m udp --dport 53 -s 172.17.0.0/16 -j ACCEPT

Этот пример разрешает доступ к порту 53 для всех контейнеров, работающих на интерфейсе docker0 (по умолчанию 172.17.0.0/16).

3. Проверка настройки

После внесения изменений рекомендуется проверить, правильно ли выполняются ваши правила. Вы можете использовать команду:

iptables -L -v -n

Это покажет вам текущее состояние правил и количество проходящих через них пакетов.

4. Остальные рекомендации

  • Безопасность: Следите за безопасностью ваших правил, добавляя только те разрешения, которые необходимы. Излишние разрешения могут представлять угрозу.

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

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

Оцените материал
Добавить комментарий

Капча загружается...