Тариф успешно добавлен в корзину
В корзину
url image

Безопасность Docker: модель угроз и базовая защита контейнеров. Часть 1

Контейнер помогает отделить приложение от других процессов на сервере, но не обеспечивает безопасность автоматически. В этой части мы разберём, какие ограничения Docker Engine применяет при обычном запуске и что владельцу сервиса нужно проверить самому. Начнём с разбора основных угроз, затем последовательно проверим хост, доступ к демону, образы и работающие контейнеры. Сеть, передачу секретов и наблюдение за сервисом подробно разберём во второй части.

Как устроена безопасность в Docker

Контейнер в Docker Engine — это не отдельная виртуальная машина, а один или несколько процессов, запущенных на ядре Linux-хоста и изолированных от других процессов. 

За изоляцию и ограничение ресурсов в Docker отвечают механизмы ядра Linux: 

  • Пространства имён (namespaces) ограничивают процессы, сетевые интерфейсы и точки подключения файловой системы, которые видят процессы контейнера. 
  • Контрольные группы (cgroups) ограничивают расход памяти и процессорного времени. 
  • При обычном запуске Docker Engine в Linux по умолчанию используется профиль seccomp. Он запрещает контейнерным процессам использовать часть системных вызовов ядра, которые редко требуются обычным приложениям, но могут быть опасны при злоупотреблении. 

Сами по себе эти механизмы не обеспечивают безопасность контейнера автоматически. Так, наличие cgroups ещё не означает, что контейнеру задан лимит: без отдельной настройки он может расходовать ресурсы в пределах, доступных на хосте. 

Фактический уровень защиты зависит от конфигурации Docker Engine и хоста, образа и способа запуска контейнера. Часть ограничений включена по умолчанию, часть нужно задавать явно, а некоторые параметры можно ослабить или отключить. Поэтому действующие ограничения нужно проверять, а недостающие — добавлять для конкретного сервиса. 

Где возникают риски

Угрозы могут возникнуть на разных этапах жизненного цикла контейнера: при подготовке образа, при запуске и во время работы. Рассмотрим, что может пойти не так на каждом этапе: 

  1. Перед запуском контейнера — риски, связанные с содержанием образа. Образ — это заготовка, из которой создается контейнер. В него могут попасть уязвимые библиотеки и системные пакеты, лишние программы, вредоносный код или секретные данные. Сам по себе запуск приложения в контейнере не устраняет уязвимости в его коде и зависимостях.
  2. При запуске контейнера — риски, связанные с правами и доступами. На этом этапе определяется, от имени какого пользователя будет работать приложение, какие устройства и файлы хоста ему доступны, какие дополнительные привилегии ему предоставлены и может ли оно обращаться к Docker-сокету. Чем шире эти возможности, тем серьезнее могут быть последствия ошибки или компрометации приложения.
  3. Во время работы контейнера — риски, связанные с обработкой данных и потреблением ресурсов . Приложение обрабатывает запросы, работает с секретными данными и записывает информацию в журналы. Пароль или ключ могут попасть в образ, историю сборки или журнал. Кроме того, ошибка приложения или вредоносный запрос могут занять слишком много памяти, процессорного времени или места на диске и помешать работе других сервисов.

Угрозы на разных этапах жизненного цикла могут усиливать друг друга. Например, уязвимость приложения опаснее, если контейнер запущен с лишними правами и имеет доступ к файлам хоста. Поэтому необходимо проверять не только образ, но и настройки Docker Engine, запуска и работающего контейнера. 

Проверку удобно проводить по компонентам системы — от общего к частному: сначала рассмотрим базовую защиту Docker-хоста, затем конфигурацию Docker Daemon и доступ к Docker API, после этого — образы и настройки работающего контейнера. Такой порядок удобен для анализа, но это не значит, что нужно полностью завершить проверку хоста, прежде чем переходить к образу или контейнеру.

Защищаем Docker-хост

Хост — это операционная система, на которой запущен Docker Engine. Контейнеры на нём используют ядро Linux и зависят от состояния самого сервера. Если злоумышленник получит административный доступ к хосту, он сможет менять настройки контейнеров и читать доступные серверу данные. Поэтому стоит начать с базовой защиты хоста.

Закрывайте известные уязвимости. Обновляйте операционную систему, ядро и Docker Engine: исправления устраняют известные уязвимости. Новые версии сначала проверяйте на тестовом сервере, а затем устанавливайте по утвержденному расписанию. Если система или Docker Engine больше не получают обновлений, запланируйте переход на поддерживаемую версию.

Сократите количество потенциальных точек атаки. Оставьте на сервере только необходимые службы и программы — лишние компоненты увеличивают поверхность атаки и объем обновлений. Перед отключением службы проверьте, не зависит ли от нее другое приложение.

Ограничивайте административный доступ. Предоставляйте административные права только тем, кому они необходимы. Используйте личные учетные записи: так проще понять, кто выполнял то или иное действие. Удаляйте старые учетные записи и проверяйте, кто может входить на сервер.

Контролируйте признаки проблем. Следите за свободным местом на диске и событиями входа на сервер. Заполненный диск может привести к остановке работы приложений или записи журналов, а неожиданный вход может указывать на несанкционированный доступ.

Настраиваем конфигурацию Docker Daemon

Docker Daemon (dockerd) — фоновая служба, которая управляет образами, контейнерами и их настройками. Docker CLI отправляет ему команды через Docker API. При локальном подключении в Linux эти команды обычно передаются через Unix-сокет /var/run/docker.sock. 

Если демон работает с правами root, пользователь, который может обращаться к его API, получает очень широкие возможности на хосте. Например, он может попросить демон запустить контейнер и подключить к нему файлы сервера. В Linux доступ к Docker-сокету обычно имеют участники группы docker. Добавлять пользователя в эту группу лучше только тогда, когда ему действительно нужно управлять контейнерами (например администратору, который развёртывает и обновляет сервисы на этом сервере).

Не передавайте  Docker-сокет контейнеру с сайтом, API-сервисом или другим приложением. Запись -v /var/run/docker.sock:/var/run/docker.sock предоставляет контейнеру доступ к API демона. Сокет может понадобиться инструменту, который управляет контейнерами, например, системе развёртывания. Ограничьте доступ к такому инструменту и учитывайте, что подключённый сокет даёт ему широкие возможности на хосте.

Не открывайте Docker API на незашифрованном TCP-порту. Если необходимо управлять Docker удалённо, настройте аутентификацию, шифрование соединения (TLS) и сетевые ограничения. Также следите за свободным местом на диске и необычными входами.

Для некоторых серверов подходит Rootless mode. В этом режиме сам демон и контейнеры работают без root-прав хоста. Это может уменьшить последствия ошибки, но не заменяет обновления и ограничения доступа. У Rootless есть требования и ограничения, поэтому сначала проверьте, подходит ли он вашей системе.

Проверяем образы

Образ — это файлы и настройки, из которых Docker создает контейнер. В образ входят приложение и его зависимости. Если среди них есть уязвимый пакет, запуск от имени непривилегированного пользователя или ограничение прав могут уменьшить последствия атаки, но не устранят саму уязвимость. Чтобы получить исправление, необходимо обновить зависимость или базовый образ и пересобрать образ приложения.

Безопасность образа следует проверять на всех этапах его жизненного цикла: при выборе источника, подготовке контекста, сборке, проверке готового образа и публикации в реестре.

  • Выбирайте надежный источник и фиксируйте версию.

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

Теги, например 3.12 или latest, удобны, но их содержимое может со временем измениться. Для воспроизводимости сборки закрепите образ по digest — контрольной сумме конкретного содержимого. Digest необходимо периодически обновлять, иначе сборка будет использовать предыдущую версию образа и сохранять известные уязвимости.

  • Контролируйте контекст сборки

Перед сборкой проверьте, какие файлы попадают в образ и контекст сборки. Файл .dockerignore помогает не отправлять в контекст локальные настройки, ключи и каталоги разработки. Однако он не удаляет секреты, которые уже попали в историю Git, поэтому репозиторий также необходимо проверять отдельно.

  • Ограничивайте состав образа

Выбирайте базовый образ, совместимый с приложением, и не добавляйте лишние пакеты. Устанавливать их на всякий случай не стоит, поскольку за каждым дополнительным пакетом нужно следить и устанавливать для него обновления.

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

  • Не включайте лишние файлы и инструменты

В итоговый образ не должны попадать инструменты сборки, временные файлы и тестовые данные, если они не нужны приложению. При многоэтапной сборке (multi-stage build) приложение собирается на одном этапе, а в итоговый образ копируются только необходимые файлы. Это уменьшает состав образа, но не заменяет проверку зависимостей.

  • Не храните секреты в образе

Не записывайте пароли и токены в Dockerfile или исходный код. Если добавить секрет в один слой образа, а затем удалить его в следующем, старое значение может сохраниться в истории слоев.

Для секретов, необходимых на этапе сборки, используйте механизм BuildKit, а для паролей работающего приложения — отдельное хранилище. Подробно это будет рассмотрено во второй части.

  • Проверяйте готовый образ

Запускайте сканирование после сборки и периодически повторяйте его для уже работающих сервисов. Сканер сравнивает установленные компоненты с базой известных уязвимостей.

Отчет сканера — это повод проверить проблему, а не доказательство того, что образ полностью безопасен или точно уязвим. Уточните, существует ли исправление, затрагивает ли проблема ваше приложение и кто отвечает за ее устранение.

  • Защитите процесс сборки и публикации

Храните Dockerfile и настройки сборки в системе контроля версий. Ограничьте доступ к серверу сборки и реестру образов. Для важных приложений можно дополнительно проверять подпись и происхождение образа, чтобы убедиться, что в окружение попал нужный файл.

Ограничиваем работающий контейнер

Образ определяет, что именно запускается, а настройки запуска — какие права и ресурсы получит процесс. Даже если в программе найдут уязвимость, ограничения могут уменьшить возможный ущерб. Поэтому проверять нужно и Dockerfile, и фактический Compose-файл или команду docker run: именно по ним Docker создаёт контейнер. 

Ограничьте права процесса

Если приложению не нужен root, запускайте его от имени обычного пользователя. Укажите пользователя в Dockerfile через USER или в настройках запуска. Если образ не выбирает другого пользователя, программа внутри контейнера запускается с UID 0 — это root внутри контейнера.

В обычной конфигурации Docker Engine в Linux числовой идентификатор пользователя 0 внутри контейнера соответствует идентификатору пользователя 0 на хосте, хотя пространства имен и другие ограничения не дают процессу автоматически всех прав администратора хоста. Режим userns-remap позволяет сопоставить идентификатор пользователя 0 контейнера с непривилегированным идентификатором на хосте. При этом сам демон продолжает работать с правами root. Режим rootless также меняет права демона.

Не используйте --privileged без особой необходимости. Этот режим снимает многие ограничения и открывает контейнеру доступ к дополнительным устройствам и возможностям хоста. Возможности — это отдельные права ядра Linux, например право изменять настройки сети. Если приложению действительно нужно дополнительное право, добавьте только его. Остальные права можно убрать с помощью --cap-drop.

Оставьте включенным стандартный профиль seccomp. Он ограничивает системные вызовы — обращения запущенной программы к функциям ядра Linux. Seccomp сам по себе не изолирует приложение, но блокирует часть ненужных действий. Если вам нужен другой профиль, проверьте его на реальных сценариях и сохраните настройки вместе с кодом.

Дополнительным барьером служит no-new-privileges. Он не позволяет процессу получить дополнительные права при запуске программы. Эта настройка не заменяет запуск от имени обычного пользователя и удаление лишних возможностей.

Ограничьте доступ к ресурсам хоста

Не включайте --network host без необходимости. В этом режиме контейнер использует сетевой стек хоста, поэтому часть сетевой изоляции исчезает. Обычно достаточно оставить стандартную сеть Docker и открыть только нужные порты.

Не подключайте Docker-сокет к приложению, которому не требуется управление Docker. Подключайте только необходимые каталоги и файлы. По возможности делайте их доступными только для чтения. Не монтируйте в приложение весь хост или его системные каталоги. Для каждого подключенного пути проверьте, откуда он берется и может ли приложение его изменять.

Ограничьте потребление ресурсов

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

По умолчанию для контейнера не задаются отдельные ограничения по памяти (mem_limit) и процессору (cpus). Установите лимиты по результатам измерений при обычной и пиковой нагрузке. Слишком низкий лимит тоже опасен: при нехватке памяти контейнер может быть остановлен, а при жестком ограничении ресурсов процессора — работать медленно.

Ограничьте запись в файловую систему

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

Пример конфигурации

Вот пример части Compose-конфигурации. Он показывает пользователя без root-прав, корневую файловую систему, доступную только для чтения, удаление дополнительных возможностей, защиту от повышения привилегий и ограничение памяти. Это не готовый файл для любого приложения: замените образ и подберите UID и лимит для своей системы.

services:
  web:
    image: example/web@sha256:<digest>
    user: "10001:10001"
    read_only: true
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true
    mem_limit: 512m
    cpus: 1.0
    privileged: false

Числа 10001:10001 — пример идентификаторов пользователя и группы. Перед запуском проверьте, что приложение работает с ними и имеет доступ к каталогам, в которые должно записывать данные. Если вы включили read_only, предусмотрите отдельное место для необходимых записей. Лимиты 512m и 1.0 подберите по измерениям своего сервиса.

Короткий чек-лист

Это базовый список для проверки перед выпуском сервиса, который поможет найти распространённые ошибки. Проверьте:

  • Образ взят из понятного источника, обновляется и сканируется.
  • В образ не попали пароли, ключи и лишние файлы.
  • Приложение работает от обычного пользователя, если это возможно.
  • Нет режима --privileged и необоснованных дополнительных capabilities.
  • Контейнеру не переданы Docker-сокет и ненужные каталоги хоста.
  • Сеть, открытые порты и подключённые тома нужны этому сервису.
  • Для памяти и процессора заданы подходящие пределы.
  • Docker Engine и хост обновляются, доступ к демону ограничен.

Не переносите рекомендации из статьи в конфигурацию без проверки на своем сервисе. Например, режим только для чтения может помешать приложению сохранить временный файл, а жесткий лимит памяти — привести к остановке контейнера. Сначала проверьте изменения в тестовом окружении и объясните исключения, если они нужны сервису. 

Что делать дальше

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

Во второй части разберём сетевые связи между контейнерами, публикацию портов, безопасную передачу секретов и наблюдение за работой сервисов.

Этот материал был полезен?

Скидка новым клиентам
Закажите сервер сегодня и получите скидку на первый месяц аренды!
Наш сайт использует cookies Вы можете отключить их в настройках браузера, но это может ограничить функционал. Оставаясь на сайте, вы соглашаетесь с использованием cookies.