Дело не в портах или безопасности. Дело в людях за клавиатурой.
Одна команда, запускающая все ваше приложение, его окружение и все зависимости, — это мечта. Если вы знаете, как работает Docker, то это невероятный инструмент для безопасного развертывания приложений.
Но для непосвященных это ужасающе просто — невольно обойти все настройки брандмауэра и открыть доступ к контейнерам в открытый интернет. Недавно я читал документацию Docker по работе с сетями, и огромное оранжевое предупреждение на странице напомнило мне, что застало меня врасплох, когда я только начинал работать с Docker.
Хотя эта статья посвящена одной конкретной проблеме с Docker, моя основная претензия не связана с Docker вообще. Существуют веские причины, по которым Docker работает именно так, как работает. Он даже достаточно хорошо документирован. На странице, посвященной сетевым технологиям, есть строгое предупреждение, а если вы прочтете немного глубже, то найдете следующий абзац, в котором четко указано, как взаимодействуют Docker и брандмауэры:
Это означает, что если вы публикуете порт через Docker, то этот порт будет опубликован независимо от того, какие правила настроены в вашем файерволе. Если вы хотите, чтобы правила применялись, даже когда порт публикуется через Docker, вы должны добавить эти правила в цепочку DOCKER-USER.
Эта статья — скорее размышления о реакции и распространенном отношении разработчиков, которые я наблюдаю в связи с этим вопросом и другими подобными проблемами. Меня беспокоит то, что индустрия разработки программного обеспечения специально ориентирована на создание разработчиков, которые используют инструменты, не понимая, как они работают, а затем наказывают их, когда они неизбежно попадают впросак.
Для создания и развертывания приложения не существует особых барьеров. Если вы хотите построить здание, вам придется пройти через ворота и бюрократические препоны. Перед началом строительства необходимо утвердить планы, чтобы обеспечить соответствие законам о безопасности, охране окружающей среды и зонировании. Необходимо соблюдать всевозможные правила, например, пожарные нормы. Прежде чем заселить здание, его необходимо проверить на соответствие нормам, особенно в отношении таких важных систем, как электричество, водопровод и газ. С другой стороны, никто не помешает вам писать и внедрять программное обеспечение. Хотя эта свобода является преимуществом нашей профессии, она также является обоюдоострым мечом.
В профессиональном мире в идеале должны существовать проверки кода опытными разработчиками, аудиты безопасности, тесты на проникновение и прочие проверки и балансы для обеспечения безопасности приложений. Но далеко не каждый разработчик-одиночка, написавший приложение для развлечения, обладает достаточными знаниями для проведения таких проверок или даже знает, что их нужно проводить. Именно к таким разработчикам я обращаюсь в этой статье.
Проблема
Я расскажу вам о ситуации, в которой я впервые столкнулся с этой проблемой. Я запускал небольшую базу данных MySQL на своем компьютере с общедоступным набором данных для стороннего проекта. База данных работала на порту 3306, но брандмауэр был настроен на блокировку всех входящих соединений. У меня был небольшой скрипт на Python, который я мог запускать для загрузки данных из первоисточника и обновлять мою маленькую локальную базу данных, когда мне нужны были свежие данные для работы. Со временем я перешел с установленной версии MySQL на контейнер Docker. Чтобы скрипты Python выполнялись без необходимости вносить в них изменения, я просто добавил аргумент `-p 3306:3306` в команду `docker run`.
Я точно помню, что читал ранее упомянутое большое оранжевое предупреждение в документации по сети Docker:
Публикация портов контейнера по умолчанию небезопасна. То есть, когда вы публикуете порты контейнера, они становятся доступными не только для хоста Docker, но и для внешнего мира.
"Вау, определенно важно! Хорошо, что есть большое оранжевое предупреждение, чтобы убедиться, что люди его прочитают. Я лучше еще раз проверю, блокируют ли мои настройки файервола это".
Однажды я отлаживал совершенно несвязанную с сетью проблему — кажется, я был в другой комнате на своем ноутбуке, пытаясь отладить общий ресурс Samba или что-то в этом роде. Я запустил nmap, чтобы проверить, какие порты открыты на моем рабочем столе, и был шокирован, увидев открытый порт 3306, несмотря на то, что в брандмауэре было установлено правило, запрещающее соединения по этому порту.
К счастью, мое воздействие ограничилось домашней сетью. База данных была защищена паролем (хотя и довольно слабым) и в любом случае содержала только общедоступную информацию. Но я с ужасом подумал, сколько разработчиков могут запускать базы данных своих приложений в контейнерах с опубликованными портами, невольно открывая доступ к своим данным всему Интернету.
Это точно не только у меня?
Очевидно, что я не одинок в своем беспокойстве по поводу этой проблемы. Существуют сообщения об ошибках, такие как это и это, обсуждения и статьи, разбросанные по разным платформам, и даже проект, в котором пытаются исправить эту проблему. Если вы почитаете обсуждения сообщений об ошибках, то обнаружите бесконечные споры о том, является ли это проблемой вообще.
Как уже говорилось, меня раздражает не то, что Docker работает именно так, а преобладающее пренебрежительное отношение, которое многие демонстрируют, отвечая на подобные вопросы и обсуждения. Следующие комментарии на Reddit 9-летней давности, но прекрасно иллюстрируют мою точку зрения
"Я хочу сказать, что если вы используете docker, вы должны знать, что UFW - это всего лишь фронтенд для iptables, и что любой процесс с соответствующими привилегиями может изменить цепочку iptables".
и
"Я уверен, и надеюсь, что я прав, что люди, использующие Docker, должны быть достаточно умны, чтобы разобраться в этом самостоятельно".
Такое отношение неконструктивно. Конечно, было бы здорово, если бы ваш обычный любитель-самоучка, развертывающий приложение, также хорошо разбирался в лучших практиках безопасности, но реальность такова, что мы живем в мире, где нет никаких барьеров или бюрократических проволочек для обеспечения этого.
Примечание по поводу обучения
Вы не знаете того, чего не знаете.
Мы лучше всего учимся благодаря обратной связи — если вы пишете код, который ломается, то сразу понимаете, что сделали что-то не так; исправление кода и то, что он работает, дает положительную обратную связь, укрепляя ваш подход к решению этой конкретной проблемы. Благодаря этой незамедлительной обратной связи можно легко научиться писать код, просто начав проект и решая возникающие проблемы. Но если написанный вами код не поддерживается или небезопасен, вы получите обратную связь только через несколько месяцев или лет, если вообще получите. А это значит, что такие понятия, как безопасность, нелегко освоить самостоятельно, методом проб и ошибок.
Решение этой специфической для Docker проблемы
Если вы добрались до этого места, возможно, вы просто ищете способ защитить свои контейнеры. Если это так, то я благодарен вам за то, что вы не остались равнодушными. Вот несколько подходов:
- Оптимальное решение — вообще не публиковать порты, если только вы действительно не хотите публично открыть этот порт. Для межконтейнерного взаимодействия лучше всего создать выделенную сеть Docker специально для этого трафика и убедиться, что контейнеры подключены к этой сети. Основное ограничение заключается в том, что он не работает для процессов, которые не запущены внутри контейнера. Не все приложения подходят для контейнеров, и иногда вам нужно запустить процесс на хосте, который должен получить доступ к контейнеру.
- Распространенным предложением, которое, однако, в официальной документации не рекомендуется, является установка значения iptables в false в файле /etc/docker/daemon.json. Это не позволит Docker добавить свои сетевые правила, что означает, что сетевая работа в контейнерах не будет работать вообще. Если вы все же хотите пойти этим путем, вам нужно добавить правила вручную. Это нетривиально, и легко ошибиться — если вы не знаете, что делаете, вы можете получить небезопасную конфигурацию или, с другой стороны, заблокировать доступ к своему серверу.
- Несколько лучший подход, не ломающий контейнерную сеть, — установить привязку адреса по умолчанию только к локальному. Вы можете сделать это, установив ip на 127.0.0.1 в файле /etc/docker/daemon.json. На самом деле, я бы утверждал, что это должно быть по умолчанию с самого начала. И что контейнеры должны быть доступны извне хоста только в том случае, если они явно настроены для этого. Но сейчас уже слишком поздно что-то менять. Я использовал именно такой подход, но у него есть один недостаток. Он противоречит переносимости контейнеров. Если вы хотите запустить контейнер на другом хосте, вам нужно не забыть изменить конфигурацию. В идеале нужно, чтобы контейнер работал одинаково на любом хосте. Контейнер, который по своей сути небезопасен, если вы не забыли изменить настройки на хосте, — не лучшая идея.
- Лучшей практикой должно стать привыкание к явной привязке IP-адресов в командах Docker и в файле docker-compose.yml. Вы должны привыкнуть никогда не писать -p 3306:3306, а предпочесть писать -p 127.0.0.1:3306:3306, а в тех случаях, когда вам действительно нужно открыть порт для внешнего мира, явно указывать его: -p 0.0.0.0:3306:3306. Именно такого подхода я придерживаюсь в тех случаях, когда сети Docker не подходят.
Теперь вопрос, на который я не знаю ответа: как исправить обилие руководств, непреднамеренно рекомендующих открыть свою базу данных всему миру?
Решение главной проблемы
Я давно хотел написать эту статью. Отношение разработчиков — моя любимая тема. Мышление, основанное на принципах "если вы не знаете всего, что знаю я, значит, вам не стоит этим заниматься", слишком распространено.
Но в этой странной профессии, где многие из нас занимаются этим, чтобы получать деньги, а многие - просто ради удовольствия, я немного переживаю, что благонамеренные усилия по обеспечению безопасности, предпринимаемые обычным любителем, могут многое упустить при размещении приложения на каком-нибудь VPS или запасном ноутбуке, который он называет сервером.
Решение, с точки зрения общей картины, двустороннее:
- Для неопытных разработчиков, которые просто хотят создавать вещи: Не останавливайтесь! Читайте как можно больше, не пропускайте официальную документацию по используемым инструментам и впитывайте как можно больше. Помните, что вы не знаете того, чего не знаете, и что лучший способ научиться — это продолжать пробовать новое.
- Опытным разработчикам, особенно тем, кто участвует в разработке широко используемых инструментов с открытым исходным кодом, принимая ключевые решения о поведении по умолчанию: помните, что вы все еще обязаны защищать тех, кто никогда не будет читать документацию и полагается на прочтение руководств, написанных другими, кто сделал то же самое. Мы обязаны выбирать безопасные настройки по умолчанию — может быть, для Docker уже слишком поздно, но для следующего инструмента, с которым вы работаете, это может быть не так. И когда наши инструменты используются не по назначению, старайтесь не относиться к ним пренебрежительно и неуважительно, а использовать их как момент для обучения и воспитания, а возможно, и для получения новых знаний.
Были ли у вас похожие случаи? Есть ли другие инструменты, которые, по вашему мнению, используются таким образом, что они небезопасны по умолчанию?
Перевод статьи: Docker Ports: What Are You Really Publishing?