Настраиваем iptables для докера

Недавно обнаружил, что трафик через докер идет в обход "обычных" правил фаервола.

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

dima@server:~$ docker run --rm -p 8123:8123 yandex/clickhouse-server

То этот порт будет доступен для всего интернета:

dima@home:~$ curl http://webserver.ru:8123/ping
Ok.

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

К слову, если запустить контейнер с параметром --network host, то все окей:

$ docker run --rm --network host redis

Как идут пакеты докера через iptables?

Обычно, когда пакет пападает в ядро, он последовательно проходит цепочки PREROUTING.nat и INPUT.filter.  Но пакеты в докера не проходят через стандартную цепочку INPUT.

Они движутся другим маршрутом:

PREROUTING.nat
DOCKER.nat
FORWARD.filter
DOCKER-USER.filter
DOCKER-ISOLATION-STAGE-1.filter

Чтобы фильтровать трафик, который идет из внешней сети в докер, нам нужно модифицировать таблицу DOCKER-USER. О чем, собственно и говорит официальная документация. Покажу несколько рецептов.

По-умолчанию таблица не задерживает пакеты и просто пропускает их дальше (RETURN).

$ iptables -S DOCKER-USER
-N DOCKER-USER
-A DOCKER-USER -j RETURN

Чтобы фильтровать трафик, нам нужно привести цепочку к виду, как показано ниже.

Настраиваем

Первым делом нужно все перекрыть. Политика по-умолчанию должна быть "запрещено все, что не разрешено явно":

# очистим таблицу
sudo iptables -F DOCKER-USER

# запретим все
sudo iptables -I DOCKER-USER -j REJECT

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

$ sudo iptables -I DOCKER-USER -o docker0 -s 192.168.0.0/24 -p tcp --dport 8123 -j RETURN

И доступ к редису без ограничений:

$ sudo iptables -I DOCKER-USER -o docker0 -p tcp --dport 6379 -j RETURN

Кстати, вы заметили, что мы пишем -o - то есть "исходящий интерфейс". Но ведь эти правила фильтруют трафик, который идет в контейнеры. Как так?

Дело в том, что пакеты "входят" через внешний интерфейс, потом попадают в цепочку переадресации (FORWARD) и из нее "выходят" в докер. Тут-то мы их и фильтруем.

# пакеты из внешнего мира к контейнерам
-i eno1 -> FORWARD -> -o docker0


# пакеты от докера в интернет
-i docker0 -> FORWARD -> -o eno1

Но вернемся к правилам.

Посмотрим, что мы имеем на текущий момент:

$ sudo iptables -S DOCKER-USER
-N DOCKER-USER
-A DOCKER-USER -s 192.168.0.0/24 -o docker0 -p tcp -m tcp --dport 6379 -j RETURN
-A DOCKER-USER -o docker0 -p tcp -m tcp --dport 8123 -j RETURN
-A DOCKER-USER -j REJECT --reject-with icmp-port-unreachable

Давайте убедимся, что порты действительно закрыты:

dima@server$ docker run --rm -p 7777:80 nginx
/docker-entrypoint.sh: Configuration complete; ready for start up

dima@home$ curl myserver.ru:7777
curl: (7) Failed to connect to myserver.ru port 7777: Connection refused

Работает!

Но этого мало

Ведь пакеты идут не только из внешнего мира к контейнерам, но и в обратную сторону. И они тоже проходят через DOCKER-USER.

Обратный путь выглядит так:

PREROUTING.nat
FORWARD.filter
DOCKER-USER.filter

Поэтому нам нужно пропускать трафик от контейнеров наружу:

$ iptables -A DOCKER-USER -i docker0 -j RETURN

И последний штрих: нужно разрешить ответы в соединениях, которые инициированы изнутри контейнера (например, вы запустите в контейнере apt update):

$ iptables -A DOCKER-USER -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

Итоговый скрипт:

# очистим таблицу
sudo iptables -F DOCKER-USER

# разрешим исходящие от докера и ответы на них снаружи
sudo iptables -A DOCKER-USER -i docker0 -j RETURN
sudo iptables -A DOCKER-USER -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# откроем доступ к кликхаусу и редису только из внутренней сети
sudo iptables -A DOCKER-USER -o docker0 -s 192.168.0.0/24 -p tcp --dport 8123 -j RETURN
sudo iptables -A DOCKER-USER -o docker0 -s 192.168.0.0/24 -p tcp --dport 6713 -j RETURN

# запретим все
sudo iptables -A DOCKER-USER -j REJECT

Если где-то накосячили, то можно вернуть все в исходное состояние:

# очистим таблицу
sudo iptables -F DOCKER-USER

# все разрешим
sudo iptables -A DOCKER-USER -j RETURN

Ссылки

Одно из самых понятных объяснений работы iptables so far: https://www.k-max.name/linux/netfilter-iptables-v-linux/

Официальная страничка, тоже отличная, но хрен найдешь: http://www.faqs.org/docs/iptables/traversingoftables.html