Технологии контейнеризации сейчас являются неотъемлемой частью процессов DevOps. Контейнеры активно используются в микросервисных архитектурах для изоляции отдельных микросервисов. Наиболее распространенным решением по контейнеризации на сегодняшний день является Docker, и именно о небезопасной работе с контейнерами Docker мы и будем сегодня говорить.

Dockerfile как основа

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

На базе готового образа создается контейнер, который затем запускается на выполнение. По сути, образ — это основа, которая используется для создания контейнера.

Для построения собственного образа необходимо создать Dockerfile, определить необходимые шаги и запустить его. Каждая команда в Dockerfile создает слой в образе. При изменении Dockerfile и перестроении образа перестраиваются только те слои, которые изменились.

Ниже представлено содержимое образа Nginx на ресурсе Docker Hub. В разделе Layers представлены команды, которые выполняются для создания слоев данного образа.

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

Конфиденциальные данные в слоях

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

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

Представленный ниже набор команд Dockerfile создает образ на основе alpine, при этом в него сначала помещается, а потом удаляется файл с паролем:

FROM alpine
COPY pass.txt pass.txt
RUN rm /pass.txt 

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

Поэтому давайте сначала соберем данный образ, а потом запустим его.

Собрать образ можно с помощью команды:

docker image build -t layers .

А при запуске давайте сразу убедимся, что мы не можем прочитать файл pass.txt

С точки зрения разработчика все замечательно — конфиденциальные данные в контейнере отсутствуют. Однако на самом деле конфиденциальные данные все равно попали в образ.

Из контейнера в образ

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

Без прав root нельзя получить доступ к /var/lib/docker/… и прочитать содержимое файловой системы контейнера. Можно попробовать посмотреть историю образа с помощью команды docker history:

Мы видим упоминание некоторого файла pass.txt на промежуточном слое, но не видим его содержимого. Попробуем посмотреть, что в нем находится, другим способом.

Экспортируем образ в файл tar с помощью команды docker save и распакуем его:

docker save layers > layers.tar

tar -xf layers.tar

В итоге после распаковки мы увидим несколько файлов и каталог.

Здесь manifest.json — это файл верхнего уровня с описанием образа. В нем указывается, в каком файле находится конфигурация, описываются все теги для данного образа и перечисляются все слои. Этот файл нам и нужен. В нем упоминаются три слоя, используемые в нашем образе, а также указаны их размеры и формат tar.

Перейдем в каталог Blobs/sha256: здесь мы видим несколько файлов. Нас интересуют те три файла, которые были указаны в manifest.json. Каждый из них можно распаковать с помощью tar.

Файл 75654… является основным образом файловой системы. А вот в файле 70fbe… мы увидим наш файл pass.txt:

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

Методы защиты

Говоря о методах защиты стоит рассмотреть использование секретов в средах Docker Swarm. Также, в Kubernetes есть сущность secret, которая позволяет передавать в контейнеры конфиденциальные данные.

Так мы можем создать секрет с помощью kubectl, и затем использовать его непосредственно в контенейрах в качестве переменной среды или подмонтировав как внешний том.

Для создания секрета лучше сначала отключить запись истории команд (их можно посмотреть с помощью команды history).

set +o history

Далее создадим секрет mysecret, содержащий name=user

kubectl create secret generic mysecret --from-literal=name=user

далее запись истории можно вернуть.

set -o history

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

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      optional: true

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

Заключение

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

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

Комментарии (5)


  1. lexore
    22.11.2024 06:05

    Как думаете, стоит ли рассказать им про передачу секретов в контейнер в виде ENV переменных?


    1. baldr
      22.11.2024 06:05

      А также про то, что ENV-переменные для запущенных приложений видны прямо с хоста?


      1. lexore
        22.11.2024 06:05

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


  1. DarkHost
    22.11.2024 06:05

    Вот смотрите, файл добавили, удалили, а файл виден. Как защитить? А еще у нас есть kubernetes.

    Начал про распаковку слоев, а закончил непонятно чем.

    На самом деле способов "защиты" несколько:

    1. Самый деревянный. Добавляем и удаляем файл в одном и том же лэйере.

    2. Используем experimental features и опции для сборки --ssh или --secret.

    3. Не пробовал с целым файлом, но возможно --build-arg


    1. baldr
      22.11.2024 06:05

      Ещё есть -mount в Buildkit для COPY.