Описание
В режиме docker swarm привязка порта к 127.0.0.1 приводит к тому, что порт также открывается на 0.0.0.0. Это может быть серьезной проблемой безопасности и должно быть объяснено в документации.
Шаги по воспроизведению проблемы:
mongodb:
image: mongo:3.2
volumes:
- ./persistent-data/mongodb:/data
- ./persistent-data/mongodb/db:/data/db
networks:
data:
aliases:
- mongo.docker
logging:
driver: syslog
options:
syslog-address: "udp://10.129.26.80:5514"
tag: "docker[mongodb]"
ports:
- "127.0.0.1:27017:27017"
deploy:
placement:
constraints: [node.labels.purpose == main-data]
Опишите полученные результаты:
nc -vz PUBLIC_NODE_IP 27017
found 0 associations
found 1 connections:
[...]
Connection to PUBLIC_NODE_IP port 27017 [tcp/*] succeeded!
Опишите ожидаемые результаты:
Порт доступен только на 127.0.0.1, по крайней мере, на узлах роя, на которых запущена эта служба.
Дополнительная информация, которую вы считаете важной (например, проблема возникает только иногда):
Вывод docker version
:
Docker version 17.03.1-ce, build c6d412e
Вывод docker info
:
информация о докере для менеджера роя:
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 1
Server Version: 17.03.1-ce
Storage Driver: aufs
Root Dir: /var/lib/docker/aufs
Backing Filesystem: extfs
Dirs: 3
Dirperm1 Supported: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host macvlan null overlay
Swarm: active
NodeID: pk7ulemi0z0chgtsg0azfrjz5
Is Manager: true
ClusterID: 27etomlyjvtmygrm6rcdgr2ni
Managers: 1
Nodes: 6
Orchestration:
Task History Retention Limit: 5
Raft:
Snapshot Interval: 10000
Number of Old Snapshots to Retain: 0
Heartbeat Tick: 1
Election Tick: 3
Dispatcher:
Heartbeat Period: 5 seconds
CA Configuration:
Expiry Duration: 3 months
Node Address: 10.129.26.165
Manager Addresses:
10.129.26.165:2377
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 4ab9917febca54791c5f071a9d1f404867857fcc
runc version: 54296cf40ad8143b62dbcaa1d90e520a2136ddfe
init version: 949e6fa
Security Options:
apparmor
seccomp
Profile: default
Kernel Version: 4.4.0-64-generic
Operating System: Ubuntu 16.04.2 LTS
OSType: linux
Architecture: x86_64
CPUs: 1
Total Memory: 992.4 MiB
Name: <HIDDEN>
ID: IMOK:QIR7:WU5Y:WTPP:EPRQ:F77G:ULGE:WOG4:O7S7:6AFE:V7QG:2XEK
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Username: <HIDDEN>
Registry: https://index.docker.io/v1/
WARNING: No swap limit support
Experimental: false
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
Дополнительные сведения о среде (AWS, VirtualBox, физическая и т. Д.):
Протестировано на каплях Digital Ocean.
Да, это должно привести к ошибке; службы (по умолчанию) «публикуют», используя «входящую» сеть, и не поддерживают указание IP-адреса, так как невозможно предсказать, на каком _node_ они окажутся (таким образом, неизвестно, какие IP-адреса доступны, хотя 127.0 .0.1 может быть возможно). Эта проблема связана с отслеживанием этой функции https://github.com/docker/docker/issues/26696 (и этот «эпический» трек отслеживает другие параметры, которые (пока) не поддерживаются службами https://github.com/docker/docker/issues / 25303)
Ошибка здесь в том, что докер должен выдавать ошибку вместо того, чтобы молча игнорировать эту опцию; воспроизводимый с использованием этого минимального файла для создания докеров;
version: "3.2"
services:
mongodb:
image: nginx:alpine
ports:
- "127.0.0.1:27017:80"
пинг @dnephin @vdemeester
@ fer2d2 В режиме роя, если вы публикуете что-либо ( ports
для stack deploy
), оно публикуется в сети ingress
и, следовательно, становится общедоступным. Есть несколько способов обойти это ограничение, но добавление kind/bug
к этому, потому что мы должны по крайней мере предупреждать людей об этом при выполнении stack deploy
с портами, имеющими эту нотацию (например, host:port:port
).
Чтобы обойти это, есть несколько способов:
mongo
только в том случае, если хотите, чтобы они были общедоступными , в противном случае он доступен через пакет обнаружения имен в докере (другой контейнер / служба в той же сети сможет получить доступ к нему через mongo
имя ingress
(то есть не в рое публично, а только на хосте, на котором он запущен, так же, как и без режима роя), вам необходимо использовать расширенный синтаксис портов . ports:
- mode: host
target: 80
published: 9005
Он будет делать то же самое, что и docker run -p 80:9005 …
поэтому он привяжет его к 0.0.0.0
, но ограничен узлом.
Но, как сказал @thaJeztah : «Ошибка в том, что докер должен выдавать ошибку, вместо того, чтобы молча игнорировать этот параметр» 👼
/ cc @mavenugo @aboch, чтобы узнать, есть ли способ привязать его к конкретному IP-адресу? (действительно сложно добиться, потому что IP-адрес узла будет другим, поэтому ..)
@vdemeester Могу ли я указать localhost в качестве целевого хоста, используя эту нотацию?
ports:
- mode: host
target: 127.0.0.1:80
published: 9005
Поскольку это расширенный формат конфигурации портов , он должен работать правильно.
заранее спасибо
Кажется, что и целевой, и опубликованный используются как целочисленный тип в длинном синтаксисе
Я думаю, что это нежелательное поведение, если вы подключаетесь к некоторым службам через туннели SSH. Например, если вы хотите иметь сервер MySQL или MongoDB на 127.0.0.1 и подключаться через туннель SSH, с Docker Swarm вы должны открыть порт базы данных на 0.0.0.0 или создать собственный контейнер базы данных с SSH, работающим внутри (и оба варианта очень небезопасны).
Существует множество клиентов баз данных, использующих туннели SSH, например SQL Workbench или Robomongo, которые нельзя использовать из-за этого ограничения (специфическая привязка интерфейса).
У нас в компании та же проблема, что и у @ fer2d2 , при попытке подключить Mongobooster к рою докеров через туннель ssh. Единственное решение, которое мы нашли, - открыть порт 27017 и защитить базу данных с помощью пользователя и пароля.
Любые новости?
+1
+1
Другой вариант использования для разрешения пары ip_address : port для сопоставления портов длинной формы - для адресов anycast или любых других адресов, которые могут быть связаны с loopback. Они будут похожи на адрес 127.0.0.1 в том смысле, что они видны только в кольцевой сети. Служба, ограниченная узлами с этим свойством, может захотеть открыть порт только на произвольном адресе, чтобы избежать конфликтов портов, избегая правил iptables для трансляции портов.
Может ли это быть вариантом, если вы укажете:
placement:
constraints:
- node.id ==
Ура
+1
+1
+1
для себя я решил эту проблему так:
iptables -I DOCKER-USER -i eth0 -j DROP
iptables -I DOCKER-USER -m state --state RELATED,ESTABLISHED -j ACCEPT
Докер эти правила не трогает. просто добавляет свой
-A DOCKER-USER -j RETURN
В результате, хотя порт прослушивает 0.0.0.0, но недоступен из внешнего интерфейса eth0
Такое поведение нарушает «безопасность по умолчанию», и добавления заметки в документах будет недостаточно. А пока это должно вызвать ошибку.
Это также связано с режимом: ingress / host (эти два вопроса, кажется, путают в обсуждении). В режиме входа нет ничего, что могло бы помешать привязке службы к локальным адресам на всех узлах, но не к внешним адресам. Следовательно, 127.xxx должно быть разрешено. (в режиме без роя (с использованием docker run) я привязываюсь к 127.0.0.2:80 и 127.0.0.3:80 и т. д. Для тестирования нескольких серверов локально в разработке.).
Другая проблема заключается в том, что по умолчанию используется входной режим. Это неожиданно и также приводит к проблемам с безопасностью. Я просто попытался запустить службу на узле, который должен находиться в частной части сети, с портом, привязанным к 127.0.0.3:80. Затем он также был привязан к общедоступному интерфейсу общедоступного узла. (То есть молча игнорирует IP-адрес и молча использует входной режим, и мои данные становятся общедоступными).
/etc/hosts
, чтобы каждое доменное имя отправлялось в другой контейнер. Это работает с docker run
, но не с compose.Разрешить привязку к локальному адресу полезно для узлов с ограничениями. Разрешить привязку к определенному адресу, будет работать для ограниченного узла или маршрутизироваться через рой на один из адресов на одном из узлов (может быть только входной режим). Маршрутизация уже выполнена
@ richard-delorenzi Moby в настоящее время даже не принимает IP-адрес хоста. Таким образом, вне запроса функции это звучит как проблема на стороне клиента ... в частности, как compose yaml переводится в Docker CLI.
Принцип работы входа довольно хорошо задокументирован, но согласитесь, что это плохое поведение в интерфейсе командной строки.
+1
+1
+1
У меня есть своего рода обходной путь, который я использую. Я запускаю автономные контейнеры и подключаю их к сети с именем core, которая используется всеми нашими внутренними службами (mongo, elasticsearch, infxdb и т. Д.), Которые работают внутри роя.
Я не вижу, как это сделать в файле набора, поэтому мы просто запускаем автономные контейнеры, например:
docker run --name kibana --rm -d -v /var/lib/kibana:/usr/share/kibana/config -p 127.0.0.1:5601:5601 --network core docker.elastic.co/kibana/kibana:6.1.2
docker run --name chronograf --rm -d -v /var/lib/chronograf:/var/lib/chronograf -p 127.0.0.1:8888:8888 --network core chronograf:1.4 chronograf --influxdb-url=http://influxdb:8086
После их запуска docker ps показывает, что новые контейнеры привязаны к 127.0.0.1. Аминь. Затем я могу подключиться к хосту докеров с моей локальной рабочей станции для безопасного доступа, например:
ssh -i my_ssh_key.pem [email protected] -L 8888:localhost:8888 -L 5601:localhost:5601 -N
Затем из моего браузера я могу подключиться к http: // localhost : 8888 или http: // localhost : 5601
Работает на меня.
В случае, если сокет UNIX может заменить сокет 127.0.0.1 TCP / IP, возможный обходной путь, который я реализовал для fluent-bit, доступен здесь
Может быть, добавление еще одной опции в mode
может помочь. Что-то вроде local
в дополнение к host
и ingress
.
Удалите формулировку «Полезная безопасность: Moby обеспечивает безопасные настройки по умолчанию без ущерба для удобства использования». в файле readme moby . Это определенно ложная реклама, см. Комментарий @ richard-delorenzi.
По умолчанию службы не публикуют порты, поэтому будут недоступны, если вы не укажете, что они должны публиковать порт. Привязка к конкретному IP-адресу в настоящее время не поддерживается; если ваша служба не должна быть доступна, не публикуйте порты и подключайтесь к службе через внутреннюю (оверлейную) сеть.
Добавление поддержки привязки к IP-адресу обсуждается в https://github.com/moby/moby/issues/26696 , но нетривиально реализовать (с учетом IP-адресов, отличных от "localhost")
Добавлено предупреждение при развертывании стека;
docker stack deploy -c- test <<'EOF'
version: '3'
services:
web:
image: nginx:alpine
ports:
- "127.0.0.1:8080:80"
EOF
WARN[0000] ignoring IP-address (127.0.0.1:8080:80/tcp) service will listen on '0.0.0.0'
Creating network test_default
Creating service test_web
А при попытке развернуть сервис с указанным IP-адресом он не будет развернут с ошибкой;
docker service create -p 127.0.0.1:9090:80 nginx:alpine
invalid argument "127.0.0.1:9090:80" for "-p, --publish" flag: hostip is not supported
See 'docker service create --help'.
@dalu, если ваша система
Определенно, этот формат компоновки, который мешает разработчикам и фактическому развертыванию, имеет некоторые серьезные компромиссы.
@ cpuguy83
если ваша система подключена к Интернету, и вы сказали Docker открыть службу в кластере, я не уверен, почему ожидалось что-то еще.
Нет. Почему он должен быть общедоступным, если кто-то привяжет его к непубличному ip, например 127.0.0.1 или 10.0.0.0? Собственно, это правильный ответ:
Привязка к конкретному IP-адресу в настоящее время не поддерживается
@dalu
Но он должен быть доступен, но не публично. И в этом все дело.
По умолчанию вы небезопасны и избегаете исправления с помощью семантики.
Вопрос не решен уже почти 2 года.
Я перехожу от роя к кубернетам, потому что рой нельзя использовать. Я полностью доволен этим решением, даже если этот переход стоит очень дорого.
@Bessonv Он
Проблема в том, что формат compose разработан для dev envs и был продвинут для поддержки развертывания кластера. "docker stack" должен просто выйти из строя, но тогда люди хотят иметь возможность использовать один составной файл, чтобы управлять ими всеми, и вот здесь беспорядок.
@ cpuguy83
Я не уверен, что меня устраивает это описание. В конце формат компоновки - это просто описание желаемого состояния. Совершенно нормально иметь некоторые различия между одной машиной (составить) и кластером (роем). С моей точки зрения, вообще нет смысла поддерживать compose. Тем более, что активировать режим роя так просто. Но это требует исправления роя.
Проблема вовсе не в рое и 100% в формате compose + реализация в docker cli.
Обратите внимание, что в настоящее время стеки на 100% являются реализацией на стороне клиента.
Мы обнаружили, что внутри стека вам не нужно явно открывать какие-либо порты для внутренних служб, таких как база данных, redis и т. Д. Просто пропустите конфигурацию ports
внутренней службы и ссылку по имени отлично работает .
Пример службы БД внутри стека
services:
db:
image: postgres:11-alpine
networks:
- backend
... может использоваться службой Django app
по умолчанию следующим образом:
DATABASES = {
'default': env.db(default='postgres://user:pass<strong i="13">@db</strong>:5432/catalog'),
}
Таким образом, в этом случае, когда вы явно предоставляете только общедоступные службы, это выглядит как безопасный по умолчанию
Проблема вовсе не в рое и 100% в формате compose + реализация в docker cli.
Обратите внимание, что в настоящее время стеки на 100% являются реализацией на стороне клиента.
Как бы то ни было: я перестал использовать стек (из-за этой проблемы), и мне все равно. Винить библиотеку, винить докера, винить мою кошку.
Я не видел этой проблемы при прямом использовании docker или при использовании compose.
Похоже, этот подход может помочь (должен выполняться на каждом узле в рое):
Ужасное решение:
$ mv /usr/bin/docker-proxy /usr/bin/docker-proxy-original
$ cat << 'EOF' > /usr/bin/docker-proxy
#!/bin/sh
exec /usr/bin/docker-proxy-original `echo $* | sed s/0.0.0.0/127.0.0.1/g`
EOF
$ chmod 755 /usr/bin/docker-proxy
$ service docker restart
@jsmouret Я даже не могу найти докер-прокси в последней версии докера. Это какое-то наследие? Или название другое?
Похоже, это зависит ...
$ apt-file search docker-proxy
docker-ce: /usr/bin/docker-proxy
docker.io: /usr/sbin/docker-proxy
Это поведение должно быть как-то задокументировано в документации .
В настоящее время он просто игнорирует хост из краткого сопоставления портов. И тихо не работает.
Еще одна странность заключается в том, что вы не можете установить хост в длинной синтаксической схеме.
Это поведение следует как-то задокументировать в документации.
Я согласен; Я думал, что это упоминалось где-то на той странице, но не могу найти; не стесняйтесь открывать вопрос в репозитории документации; https://github.com/docker/docker.github.io/issues
В настоящее время он просто игнорирует хост из краткого сопоставления портов. И тихо не работает.
какую версию докера вы используете? он должен вывести предупреждение (при использовании docker stack deploy
) или _error_ (при использовании docker service create
); см. https://github.com/moby/moby/issues/32299#issuecomment -472793444
какую версию докера вы используете? он должен распечатать предупреждение (при использовании docker stack deploy) или ошибку (при использовании docker service create);
Ух, похоже, это моя вина. Это действительно так, когда я пытался развернуть стек с консоли.
Раньше я делал это через пользовательский интерфейс portainer, и он не отображал никаких ошибок или предупреждений.
Действительно разочарован тем, что почти два года некоторые из разработчиков Docker игнорируют один допустимый и очень полезный вариант использования, когда эта функциональность необходима: когда вы хотите привязать сдерживаемую реляционную базу данных в качестве службы роя к локальному интерфейсу, чтобы безопасно получить к ней доступ через туннель SSH . В настоящее время этот сценарий невозможен.
Работоспособное, чистое решение - это запуск SSH-сервера во втором контейнере, который подключен к той же сети докеров, что и ваша база данных. Затем порт SSH может быть опубликован на хосте (конечно, на порт, отличный от 22), так что вы можете перенаправить через контейнер SSH в свою базу данных.
@nartamonov Я не понимаю, как это можно сделать безопасно от входа, если сам протокол не является безопасным.
Безопасный доступ к нему будет через плоскость зашифрованных данных ( --opt encrypted
для чрезмерных сетей) и развернуть контейнер с любыми инструментами, которые вам нужны, подключенными к этой сети.
Это, вероятно, имеет другие побочные эффекты, не связанные с этим, но установка "iptables": false
в /etc/docker/daemon.json
дает обходной путь. Менее радикальное решение - добавить только настраиваемое правило, подобное предложению @helldweller .
В любом случае, я хотел бы увидеть дополнительную поддержку этого через 3 года.
Похоже, этот подход может помочь (должен выполняться на каждом узле в рое):
1. leave swarm 2. remove network docker_gwbridge 3. recreate network docker_gwbridge with additional option com.docker.network.bridge.host_binding_ipv4=IP 4. join swarm back Works for ports published in mode "host". Without mode "host" ingress network is used with other driver and scope "swarm".
@ienovytskyi
Если я не ошибаюсь, при этом все опубликованные порты привязываются к заданному адресу по умолчаниюIP
? Чтобы быть ясным, это не годный обходной путь, если вы хотите ограничить привязанный интерфейс только для некоторых портов некоторых служб.
Я хочу сообщить о своем обходном пути.
Пример использования:
Некоторые службы в рое должны прослушивать все интерфейсы или, по крайней мере, общедоступный интерфейс - этот контейнер в моем примере является обратным прокси.
В этих узлах роя также есть экземпляр базы данных на каждом узле, они используют сеть роя, определенную как:
docker network create --scope swarm NETWORK_NAME --attachable -d overlay
Веб-сервисы, которым нужны соединения с базой данных, конечно, должны быть присоединены к этому NETWORK_NAME
Иногда для административных целей необходимо подключиться напрямую к базе данных.
Решение:
Только сервисы, которые должны быть доступны во всех сетях (обратные прокси в моем примере), могут иметь ports: ['SOMEPORT:ANOTHERPORT']
в определении своих сервисов.
Все остальные службы должны иметь на хосте парный докер-контейнер без роя.
Этот контейнер без роя соединит порт, присутствующий на NETWORK_NAME/nodeXYZ:port
с localhost
Пример с mongodb:
docker run --rm -it --net=NETWORK_NAME -d --name expose-mongo -p 127.0.0.1:27017:47017 alpine/socat tcp-listen:47017,fork,reuseaddr tcp-connect:mongo01:27017
Оборотная сторона: для каждого узла роя должен быть контейнер без роя, поэтому со многими нодами это действительно скучно, если не использовать ansible / тяжелые скрипты
Мое решение проблемы «если вы подключаетесь к некоторым службам через туннели SSH», о которой упоминал
FROM alpine
RUN apk add --no-cache openssh
RUN mkdir ~/.ssh
RUN ssh-keygen -A
RUN echo "root:root" | chpasswd
RUN echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config
RUN echo 'Port 22' >> /etc/ssh/sshd_config
RUN echo -e " \
Match User root \n\
AllowTcpForwarding yes\n\
X11Forwarding no\n\
AllowAgentForwarding no\n\
ForceCommand /bin/false\n\
" >> /etc/ssh/sshd_config
EXPOSE 22
CMD /usr/sbin/sshd -D -e "$@"
Затем в docker-compose.yml:
...
db:
image: mysql:5.6
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?err}
MYSQL_ROOT_HOST: '%'
volumes:
- "./mysql:/var/lib/mysql"
deploy:
placement:
constraints: [node.role == manager]
sshd:
image: maxisme/sshd:latest
volumes:
- "~/.ssh:/root/.ssh"
ports:
- "2223:22"
deploy:
placement:
constraints: [node.role == manager]
что позволяет мне добавить мои authorized_keys в папку ~/.ssh
а затем прокси-сервер ssh перескочит через порт 2223
в мою базу данных, используя имя хоста db
.
Работоспособное, чистое решение - это запуск SSH-сервера во втором контейнере, который подключен к той же сети докеров, что и ваша база данных. Затем порт SSH может быть опубликован на хосте (конечно, на порт, отличный от 22), так что вы можете перенаправить через контейнер SSH в свою базу данных.
действительный
еще один пример, почему эта функция важна.
У меня есть сервер с установленным plesk, у plesk уже есть его конфигурации, но я могу добавить еще одну конфигурацию, чтобы указать на службу docker swarm. Этот сервер plesk является узлом роя.
Я хочу использовать plesk для proxy_pass на порт. Этот порт следует опубликовать, потому что контейнер находится в оверлейной сети, но ему нужен внешний порт для связи с миром.
Таким образом, proxypass должен указывать на локальный интерфейс, например 127.0.0.1: someport
а контейнер в рое должен публиковать порт только для localhost.
Таким образом, порт контейнера доступен только прокси-серверу, а не напрямую из мира.
Мне нравится ваш обходной путь @maxisme , но как вы управляете владением authorized_keys
? В OS X это работает для меня (монтирование принадлежит root
), но на производственной машине Linux я получаю:
Authentication refused: bad ownership or modes for file /root/.ssh/authorized_keys
Connection closed by authenticating user root 85.145.195.174 port 60535 [preauth]
Том принадлежит UID пользователя хоста, который не равен root
и SSHD отказывается с ним работать. Обходной путь поверх вашего обходного пути 😬 - использовать configs
, например:
services:
sshd:
image: [...]/sshd:${version}
configs:
# FIXME: It would be much better to use a bind volume for this, as it
# would always be in sync with the host configuration. So revoking a key
# in the host machine would automatically revoke it in the container. But
# I can't figure out how to give the volume right ownership. It keeps UID
# from the host which doesn't align with the container user.
- source: authorized_keys
target: /root/.ssh/authorized_keys
mode: 0600
configs:
authorized_keys:
file: ~/.ssh/authorized_keys
Я понимаю, что из-за того, что вы не знаете, на каком хосте будет развернут контейнер, вы не можете указать службе для привязки к определенному IP-адресу хоста.
Однако часто хосты имеют, например, северные и южные интерфейсы. Вы можете захотеть, чтобы порты роя связывались только с интерфейсами северного направления на всех хостах роя.
Если имена интерфейсов для всех интерфейсов, к которым вы хотите привязать службу, одинаковы (например, eth0), можно было бы предложить возможность указать имя интерфейса для привязки портов роя (в разделе портов службы).
nginx:
image: nvbeta/swarm_nginx
networks:
- demonet1
ports:
- "eth0:8088:80"
Когда eth0 недоступен на узле роя, указанный порт не будет привязан ни к какому интерфейсу.
@ tad-lispy У вас должна быть возможность изменить uid и gid пользователя контейнера, чтобы они совпадали с владельцем тома на хосте.
Образ linuxserver поддерживает это путем установки переменных среды (см. Https://hub.docker.com/r/linuxserver/openssh-server, User / Group Identifiers
),
Самый полезный комментарий
Действительно разочарован тем, что почти два года некоторые из разработчиков Docker игнорируют один допустимый и очень полезный вариант использования, когда эта функциональность необходима: когда вы хотите привязать сдерживаемую реляционную базу данных в качестве службы роя к локальному интерфейсу, чтобы безопасно получить к ней доступ через туннель SSH . В настоящее время этот сценарий невозможен.