Безопасность Docker

Начало небольшого цикла статей про особенности безопасности технологии контейнеризации, а именно – Docker

Docker

Первым делом надо понять, что такое контейнеры и контейнеры Docker в частности.

Попробовать Docker можно либо установив его себе на Linux/Windows систему: https://get.docker.com/ , или https://docs.docker.com/docker-for-windows/install/ ), либо воспользовавшись онлайн окружением: https://training.play-with-docker.com/ .

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

В отличие от виртуальных машин контейнеры – это абстракция на уровне приложений, в то время как в виртуальных машинах – абстракция на уровне hardware. Для выполнения какой-либо команды в случае с виртуальной машиной потребуется полноценный процесс загрузки ОС, подгрузки драйверов и выполнения команды, что требует продолжительного времени и больше ресурсов.

Базой для Docker служат контейнеры/jails – штатные средства в Linux, а сам Docker предоставляет просто удобный интерфейс для их использования. В Windows пока используется отдельная виртуальная машина, в рамках которой выполняются те же задачи, что и на нативном Linux Docker.

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

Во-вторых, это возможность средствами ядра ОС разграничить доступ к тем или иным ресурсам хоста. Для этого используются пространства имён, каждое из которых в той или иной мере поддерживается Docker-ом и позволяет разделить и изолировать те или иные нюансы взаимодействия с ОС на уровне контейнеров Docker. Иными словами каждый контейнер работает в своих собственных пространствах имён для различных ресурсов хоста:

Терминология Docker

Docker daemon – процесс, координирующий запуск и взаимодействие с контейнерами. Мы можем работать с ним через т.н. Docker Client – CLI интерфейс, который осуществляет запросы к REST API этого самого Docker daemon-а

Образ – это базовая сущность (суть шаблон), на основании которой создаются конкретные контейнеры (либо другие образы с некоторыми дополнительными модификациями). Соответственно из одного образа можно создать и запустить несколько контейнеров. Образы публикуются в публичных и приватных репозиториях (например Docker Hub – https://hub.docker.com/search?q=&type=image)

Контейнер – это конкретный работающий экземпляр всех процессов из образа, из которого он был создан. Его можно запускать/останавливать и даже создавать образы на базе текущего состояния. При необходимости он может сконфигурирован так, чтобы взаимодействовать с сетевым подключением/интерфейсами и файловой системой хоста.

Для создания собственного образа может использоваться Dockerfile – одноимённый файл, в котором перечисляются инструкции (например установка какого-либо пакета, либо какой базовый образ использовать), которые необходимо применить к базовому образу, чтобы сделать новый образ.
Также новый образ может быть импортирован в нашу систему из .tar архива, либо создан на основе существующего контейнера.

Volumes (Mounts) – тома для хранения данных приложений вне контейнера. Примитивно – «расшаренная папка» между хостом и контейнером. Могут быть как bind mount, когда томом является любой ресурс, заданный пользователем (например /var/log), либо docker managed – когда Docker создает ресурс и управляет им (например /var/lib/docker/volumes/…)

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

Перечень слоев можно получить либо через docker history либо при помощи сторонней утилиты Dive:


$ docker history <image id>
или
$ alias dive="docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
-e DOCKER_API_VERSION=1.37 wagoodman/dive:latest"
$ dive <image name:tag>

Основные операции с Docker

Скачать базовый образ ОС debian, помеченный тегом latest
$ docker pull debian:latest

Стартовать контейнер на базе образа Ubuntu (который будет автоматически скачан) и запустить в нем bash
$ docker run -it ubuntu: bash

Отобразить все созданные контейнеры и перечень всех образов в системе
$ docker ps -a && docker images

Стартовать простой контейнер с веб-сервером и подключенным томом для веб содержимого. При обращении по IP в браузере должен отобразиться index.html
$ docker run -d —rm -v ~/web-content:/usr/local/apache2/htdocs -p 80:80 httpd
$ sudo chown $(id -u) ~/web-content/ && echo «Ho-ho-ho!» > ~/web-content/index.html

Удалить контейнер или образ
$ docker rm -vf
$ docker rmi

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

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

Рассмотрим вкратце каждый из слоёв:

  1. Docker host security – всё, что связано с безопасностью самого хоста, на котором запущены контейнеры. Компрометация хоста с большой долей вероятностью приведёт к компрометации всех контейнеров на том хосте.
  2. Docker daemon config – специфичные настройки и особенности запуска демона Docker, который непосредственно обслуживает запущенные контейнеры.
  3. Docker images – вся поверхность атаки, которая относится к образам Docker как таковым. Проблемы в этот слой привносятся преимущественно на этапе сборки образа.
    a. OS Vulnerabilities – уязвимости базового образа и системных пакетов, включённых в этот образ
    b. Dependencies – уязвимости в сторонних зависимостях, которые притягиваются извне и впоследствии используются в приложениях, которые мы запускаем в контейнерах
    c. Software Vulnerabilities – конкретные уязвимости в приложениях, ради которых контейнеры и создавались. Сюда попадают все проблемы с кодом самих этих приложений.
    d. Dockerfile – некорректные и небезопасные инструкции, которые могут использоваться при сборке образа.
  4. Docker container run-time – все проблемы, которые связаны с run-time-конфигурацией контейнеров, то есть теми параметрами, которые применяются при запуске оных.
    a. Permissions – некорректное и избыточное выделение привилегий запускаемым контейнерам.
    b. Ports – необоснованно избыточно открытые сетевые порты.
    c. Volumes – небезопасное использование разделяемых томов.
  5. Docker image repository – все проблемы, связанные с безопасным хранением и доставкой образов из нашего репозитория к целевой системе. Репозитории могут быть сторонними, либо установленными в нашей инфраструктуре.
  6. Docker distributed systems – все проблемы, связанные с распределенным разворачиванием контейнеров и обеспечением безопасности систем, которые управляют этим разворачиванием.
  7. Docker container monitoring – меры, позволяющие оперативно следить за состоянием запущенных контейнеров и реагировать на аномальное поведение тех или иных параметров.
  8. Docker data backup – меры, позволяющие организовывать защиту хранимых данных, создаваемых и используемых в процессе работы приложений в контейнерах.

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