Назад | Перейти на главную страницу

docker-compose, открывая только одну сеть для внешнего мира

У меня есть новое приложение, которое я создал с помощью файла docker-compose. Этот файл содержит 2 сети:

version: '2.1'

# ----------------------------------
# Services
# ----------------------------------
services:
  application:
    image: tianon/true
    volumes:
      - ${APPLICATION_PATH}:/var/www

  nginx:
    build:
      context: ./docker/nginx
    volumes_from: 
      - application
    volumes:
      - ${DOCKER_STORAGE}/nginx-logs:/var/log/nginx
      - ${NGINX_SITES_PATH}:/etc/nginx/sites-available
    ports:
      - "${NGINX_HTTP_PORT}:80"
      - "${NGINX_HTTPS_PORT}:443"
    networks:
      - frontend
      - backend

  redis:
    build:
      context: ./docker/redis
    volumes: 
      - ${DOCKER_STORAGE}/redis:/data
    ports:
      - "${REDIS_PORT}:6379"
    networks: 
      - backend

# ----------------------------------
# Networks
# ----------------------------------
networks:
  frontend:
    driver: "bridge"
  backend:
    driver: "bridge"

# ----------------------------------
# Volumes
# ----------------------------------
volumes:
  redis:
    driver: "local"

Вы заметите, что у меня здесь 2 сети, frontend и backend. Вопрос простой:

За щедрость: В докере явно есть способ определить несколько оверлейных сетей. Я предполагал, что будет просто открыть одну сеть для внешнего мира. Я пытаюсь найти простой способ сделать это в docker или iptables. Текущий ответ дает мне некоторое направление, но требует ручных правил для каждого порта. Я хотел бы получить правильный ответ о том, как защитить мою "внешнюю" сеть, без необходимости указывать конкретные порты в iptables.

Хорошо, если вы хотите, чтобы ваш redis был доступен только с определенных хостов, вам нужно использовать iptables.

iptables -I FORWARD -i public_iface -p tcp --dport 6379 -j DROP
iptables -I FORWARD -i public_iface -s allowed_ip -p tcp --dport 6379 -j ACCEPT

Поскольку я не знаю, есть ли у вас что-нибудь в iptables, я решил вставить правила вверху.

Параметр -I вставляет правило в начало цепочки, поэтому сначала вставьте правило, чтобы запретить весь трафик для redis, затем вставьте другое правило перед этим, чтобы разрешить трафик с определенного хоста.

Здесь мы используем цепочку FORWARD, а не цепочку INPUT, потому что трафик не предназначен напрямую для хоста докеров.

Вы можете проверить результат с помощью

iptables -L

И вы, возможно, захотите узнать, как сделать защиту от перезагрузки правил межсетевого экрана.

Чтобы завершить ответ @BMitch по Вашему запросу ..

Вы можете сделать это с помощью дополнительной межконтейнерной сети (или нескольких, если вы видите необходимость или использование в этом ...), потому что для такой настройки для надежной работы важно, чтобы каждому контейнеру была назначена только одна не внутренняя сеть. В противном случае вы не можете быть действительно уверены, какая мостовая сеть будет выбрана для входящего трафика на сопоставленном порту (или, по крайней мере, это не действительно настраивается).

Пример составного файла, который показывает такую ​​настройку:

version: '3.6'
services:
  c1:
    container_name: c1
    image: "centos:latest"
    entrypoint: /bin/bash -c "while sleep 10; do sleep 10; done"
    ports:
      - "5000:5000"
    networks:
      - front
      - inter
  c2:
    container_name: c2
    image: "centos:latest"
    entrypoint: /bin/bash -c "while sleep 10; do sleep 10; done"
    ports:
      - "5001:5001"
    networks:
      - inter
  c3:
    container_name: c3
    image: "centos:latest"
    entrypoint: /bin/bash -c "while sleep 10; do sleep 10; done"
    ports:
      - "5002:5002"
    networks:
      - back
      - inter
  c4:
    container_name: c4
    image: "centos:latest"
    entrypoint: /bin/bash -c "while sleep 10; do sleep 10; done"
    ports:
      - "5003:5003"
    networks:
      - back
      - inter
networks:
  front:
    name: front
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: dockerfront
  back:
    name: back
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: dockerback
  inter:
    name: inter
    driver: bridge
    internal: true
    driver_opts:
      com.docker.network.bridge.name: dockerinter

Он создает один мост ([докер] спереди) для ваших общедоступных сервисов, один мост ([докер] сзади) для ваших внутренних сервисов и один внутренний мост (который не публикует порты даже при запросе и, таким образом, может быть безопасно добавлен в качестве дополнительной сети) ([docker] intern) и назначьте их контейнерам.
Фактически c2 не будет доступен для порта 5001 извне, поэтому порты для этого контейнера можно не указывать. Просто чтобы показать результат:

# iptables -nvL DOCKER -t nat
Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 RETURN     all  --  dockerback *       0.0.0.0/0            0.0.0.0/0
    0     0 RETURN     all  --  dockerfront *       0.0.0.0/0            0.0.0.0/0
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0
    0     0 DNAT       tcp  --  !dockerback *       0.0.0.0/0            0.0.0.0/0            tcp dpt:5002 to:172.27.0.2:5002
    0     0 DNAT       tcp  --  !dockerfront *       0.0.0.0/0            0.0.0.0/0            tcp dpt:5000 to:172.25.0.2:5000
    0     0 DNAT       tcp  --  !dockerback *       0.0.0.0/0            0.0.0.0/0            tcp dpt:5003 to:172.27.0.3:5003

Теперь вы можете добавить правило доступа для dockerback мост к DOCKER-USER цепочка:

# iptables -I DOCKER-USER ! -s 10.0.0.0/24 -o dockerback -j DROP
# iptables -nvL DOCKER-USER
Chain DOCKER-USER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    3   156 DROP       all  --  *      dockerback !10.0.0.0/24          0.0.0.0/0
   10   460 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Будьте осторожны: в текущей версии 19.03.3 есть ошибка, которая DOCKER-USER цепочка не создается, она уже исправлена ​​на 19.03.4, которая скоро станет доступна. Если у вас есть эта версия, вы можете добавить цепочку самостоятельно (это нужно делать после каждого перезапуска демона докера):

iptables -N DOCKER-USER
iptables -I FORWARD -j DOCKER-USER
iptables -A DOCKER-USER -j RETURN 

Возможно, вы найдете более подходящее место для таких правил, но докер предлагает такой вариант. (Я мог бы подумать о фиксации сетей для мостов и использовать правило в общем правиле переадресации, не применимое к манипуляциям с докерами при перезапуске, воссоздании интерфейса и т. Д.)

В дополнение (или вместо этого) вы можете использовать дополнительный IP-адрес на своем компьютере, чтобы привязать серверную службу к другому IP-адресу, а затем интерфейсные службы и фильтровать на этом уровне. Кроме того, означает ту же настройку докера, что и выше, но с дополнительным указанием com.docker.network.bridge.host_binding_ipv4 driver-option (который является просто IP по умолчанию для опубликованных портов) - или вместо этого используйте только одну сеть и укажите адрес привязки в соответствии с типом службы. Затем заблокируйте FORWARD в DOCKER-USER или, где это возможно, с помощью ctorigdst модулей conntrack, соответствующих входящему интерфейсу.

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

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

Если вы хотите как опубликовать порт, так и ограничить доступ к нему с помощью iptables, используйте Сеть ДОКЕР-ПОЛЬЗОВАТЕЛЬ - рекомендуемый метод. Обратите внимание: недавно я видел несколько выпусков, в которых это поведение исправлялось, поэтому следите за примечаниями к выпуску и открытыми проблемами, если вы идете в этом направлении. Также обратите внимание, что цепочка запускается после того, как пакет обрабатывается для контейнера, и поэтому имеет целевой порт контейнера, а не целевой порт хоста. Для фильтрации по порту хоста вам необходимо использовать conntrack, например:

iptables -I DOCKER-USER -i eth0 -s 10.0.0.0/24 -p tcp \
  -m conntrack --ctorigdstport 8080 -j ACCEPT
iptables -I DOCKER-USER -i eth0 ! -s 10.0.0.0/24 -p tcp \
  -m conntrack --ctorigdstport 8080 -j DROP

Просто удалите часть «порт» в вашем контейнере Redis, его единственная функция - открыть его миру. Поскольку вы настроили свои 2 контейнера для работы в бэкэнд-сети, они все равно смогут обмениваться данными.