Ограничение доступа в интернет
Ubuntu 20.04
Docker version 20.10.16, build aa7e414
docker-compose version 1.25.0
Вводная информация
Будем рассматривать случай, когда контейнер(ы) поднимаются через docker-compose с сетью по умолчанию типа bridge, т.е. самый стандартный случай.
И ограничения должны быть только для этого(их) контейнера(ов).
Доступ в локальную сеть должен быть сохранен.
Оценим несколько возможных вариантов.
Для примера возьмем некий контейнер с приложением testapp и попробуем закрыть ему доступ к интернету.
docker-compose.yml
version: '2.2' services: testapp: image: testapp container_name: testapp ports: - 8080:8080
Вариант: Network internal mode
https://docs.docker.com/engine/reference/commandline/network_create/
По умолчанию сеть создается без ограничений, т.е. internal: false
Проверим с помощью команды inspect
$ docker network ls NETWORK ID NAME DRIVER SCOPE 5170de027161 bridge bridge local 6097d465b2de host host local d3abd08bf3ea none null local 89c7d3e6ebf3 testapp_default bridge local $ docker inspect testapp_default / ... "Name": "testapp_default ", "Id": "89c7d3e6ebf353793b74404c478a2471d3361e3184be30ee8bdc397141cbb131", <----- на заметку, id используется в имени моста br-<Id> ... "Internal": false, <----------------------- вот тут "Attachable": true, "Ingress": false, "ConfigFrom": { "Network": "" }, ...
Сделаем её внутренней, добавим соответствующие параметры в нашу конфигурацию
docker-compose.yml
version: '2.2' networks: default: internal: true services: testapp: image: testapp container_name: testapp ports: - 8080:8080
Смена сети на internal, это по сути другой набор правил iptables, но об этом чуть ниже.
Применим изменения
docker-compose down
docker-compose up -d
Перед применением новой конфигурации приложение должно быть остановлено, т.к. нельзя будет пересоздать сеть
ERROR: Network "testapp_default" needs to be recreated - internal has changed
Что в результате получили:
- Доступ в интернет нет
- Доступ в локальную сеть 192.168.0.0/24 нет
- Возможности обращаться к приложению по порту 8080 нет. Порт просто не открывается.
- Доступ к localhost через адрес 172.17.0.1 (docker0) есть
Такой вариант ограничения мне совершенно не подходит.
iptables: DOCKER-USER
https://docs.docker.com/network/iptables/
По умолчанию, Docker сам создает и управляет правилами iptables.
Без них контейнер потеряет какую либо сетевую связность.
Посмотрим кусочек как пример, на тот момент где разрешается выход в сеть:
$ iptables-save *filter :INPUT ACCEPT [3384:8529254] :FORWARD DROP [0:0] :OUTPUT ACCEPT [3233:5880536] :DOCKER - [0:0] :DOCKER-ISOLATION-STAGE-1 - [0:0] :DOCKER-ISOLATION-STAGE-2 - [0:0] :DOCKER-USER - [0:0] -A FORWARD -j DOCKER-USER <------ пользовательская цепочка -A FORWARD -j DOCKER-ISOLATION-STAGE-1 -A FORWARD -o br-89c7d3e6ebf3 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -o br-89c7d3e6ebf3 -j DOCKER -A FORWARD -i br-89c7d3e6ebf3 ! -o br-89c7d3e6ebf3 -j ACCEPT <------ вот тут разрешает выход пакетов в любой интерфейс в т.ч. eth0 локальную сеть и интернет -A FORWARD -i br-89c7d3e6ebf3 -o br-89c7d3e6ebf3 -j ACCEPT -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -o docker0 -j DOCKER -A FORWARD -i docker0 ! -o docker0 -j ACCEPT -A FORWARD -i docker0 -o docker0 -j ACCEPT ...
Docker размещает свои правила первыми в цепочке и при каждом запуске, поднятии или остановки контейнера следит за этим.
Но разработчики оставили нам возможность вмешиваться через цепочку DOCKER-USER, ей содержимое Docker не будет трогать.
Варианты привязки ограничения:
- по имени моста br-* (поменяется при пересоздании сети)
- по ip адресу или подсети (поменяется при пересоздании сети, если явно не указать конфиг сети)
- по uid, под которым выполняется процесс testapp
он должен быть уникальный и не совпадать с локальными и другими контейнерами
Через цепочку DOCKER-USER прогоняется весь FORWARD трафик.
Фиксируем адресное пространство
docker-compose.yml
version: '2.2' networks: default: ipam: config: - subnet: 172.18.0.0/24 services: testapp: image: testapp container_name: testapp ports: - 8080:8080
Применим изменения
docker-compose down
docker-compose up -d
Добавляем ограничение и логирование для наглядности что бы посмотреть.
iptables -I DOCKER-USER -s 172.18.0.0/24 -o eth0 '!' -d 192.168.0.0/24 -j REJECT iptables -I DOCKER-USER -m conntrack --ctstate NEW -j LOG --log-prefix "DOCKER-USER: "
Обратите внимание, что правила в цепочку в данном случае вставляются через -I
т.к. должны быть вставлены до RETURN
-A DOCKER-USER -j RETURNэто правило создал сам Docker вместе с цепочкой DOCKER-USER.
А если создать DOCKER-USER до запуска Docker'a, то он добавить его в конец.
kernel: [164310.641078] DOCKER-USER: IN=br-d26212ba35b2 OUT=eth0 PHYSIN=veth17a3e13 MAC=02:42:4e:f1:f6:bd:02:42:ac:12:00:02:08:00 SRC=172.18.0.2 DST=87.250.250.242 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=44462 DF PROTO=TCP SPT=40998 DPT=80 WINDOW=64240 RES=0x00 SYN URGP=0
Что в результате получили:
- Доступ в интернет нет
- Доступ в локальную сеть 192.168.0.0/24 есть
- Возможности обращаться к приложению по порту 8080 есть
- Доступ к localhost через адрес 172.17.0.1 (docker0) есть
Вот это то, что было мне нужно.
Вот эти команды или сохраненные правила через iptables-restore нужно добавить в автозагрузку до запуска Docker'a
iptables -N DOCKER-USER iptables -F DOCKER-USER iptables -A DOCKER-USER -s 172.18.0.0/24 -o eth0 '!' -d 192.168.0.0/24 -j REJECTАвтозагрузка правил iptables на Ubuntu Server 20.04
iptables: false
Выключить автоматическое управление правилами iptables и все прописывать руками.
/etc/docker/daemon.json
{ "iptables": false }
Примечание
Входящие пакеты на порт 8080 гипотетического приложения не проходят через цепочку INPUT
kernel: [ 3154.917601] DOCKER-USER: IN=eth0 OUT=br-81494b6a085f MAC=24:b6:fd:14:58:f4:12:0d:7f:4c:d0:9c:08:00 SRC=192.168.0.1 DST=172.18.0.2 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=62257 DF PROTO=TCP SPT=46034 DPT=8080 WINDOW=29200 RES=0x00 SYN URGP=0
Поэтому, когда поднимаете приложение в Docker'e, то будьте бдительны.
Т.к. его порт будет «торчать наружу».
Даже если у вас включен firewall и политика DROP в INPUT - это не помешает.
Запрещать надо в FORWARD в цепочке DOCKER-USER
Или делайте bind на localhost
ports: - 127.0.0.1:8080:8080
Обсуждение