Настраиваем 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