python

Безопасная работа с секретами при сборке в Docker Compose

  • среда, 13 мая 2020 г. в 00:29:04
https://habr.com/ru/company/otus/blog/501580/
  • Блог компании OTUS. Онлайн-образование
  • Разработка веб-сайтов
  • Python
  • Программирование


Перевод статьи подготовлен в преддверии старта курса «Web-разработчик на Python».



Когда вы собираете Docker-образ вам могут понадобиться секреты, например, пароль к приватному репозиторию пакетов. Вы не хотите, чтобы этот секрет в конечном итоге оказался в образе, потому что тогда любой, кто получит доступ к образу, получит доступ и к вашу приватному репозиторию.
Примечание: Если вы думаете «Почему бы просто не использовать переменные среды?», которые используются для секретов в рантайме при создании образа. Эта статья посвящена секретам сборки, которые используются при создании образа с помощью Docker-файла.
Более новые версии Docker поддерживают секреты с помощью экспериментального сервиса BuildKit, а в Docker Compose 1.25 и более поздних версиях уже можно создавать образы с помощью BuildKit. К сожалению, по состоянию на март 2020 года, возможность безопасной работы с секретами из Compose все еще находится в процессе разработки.

Так что же теперь делать?

В сегодняшней статье я покажу, как можно использовать один и тот же Dockerfile для безопасного создания образов с секретами, но при этом не упускать преимущества быстрой разработки с помощью Docker Compose.

Два варианта использования вашего Dockerfile


Очень удобно использовать один и тот же Dockerfile для продакшена и для локальной разработки с Docker Compose. Обычно вы используете Dockerfile вместе с функцией сборки из Compose:

version: "3.7"
services:
  yourapp:
    build:
      context: "."	

Затем вы можете сделать:

$ docker-compose up

С помощью этой команды вы можете (пере)собрать образ, а затем запустить его.
Для использования на продакшене, вы собираете образ и отправляете его с push:

$ docker build -t myimage .
$ docker push myimage

И пока все идет хорошо. Но что делать, если вам нужна сборка с секретом?

Первая попытка (небезопасная)


Допустим, у вас есть скрипт, которому нужна сборка с секретом, например, чтобы загрузить пакет Python из частного репозитория DevPI. Для простоты, мы будем просто выводить секрет с помощью use-secret.sh, чтобы показать, что он у нас есть.

#!/bin/bash
set -euo pipefail

echo "Secret is: $THEPASSWORD"

Можно простодушно передать секрет с помощью аргументов сборки Docker, поскольку они поддерживаются везде, в том числе и в Docker Compose.
Примечание: Выходя за пределы нашей дискуссии, хочу сказать, что использование Docker-файлов в этой статье – не лучшая практика, однако излишняя сложность может помешать донести основной смысл статьи.
Поэтому, если вы захотите запустить свое приложение на Python на продакшене с Docker, вот два хороших способа это сделать:


FROM python:3.8-slim-buster
# Using ARG for build secrets is INSECURE!
ARG THEPASSWORD
COPY use_secret.sh .
RUN ./use_secret.sh

Мы можем написать docker-compose.yml, который будет передан в секрете:

version: "3.7"
services:
  yourapp:
    build:
      context: "."
      args:
        THEPASSWORD: "s3kr!t"

Для локальной работы можно запустить или собрать образ с помощью Compose:

$ docker-compose build | grep Secret
Secret is: s3kr!t

И все хорошо.

А еще мы можем собрать образ с помощью Docker, в качестве подготовки к перемещению его в реестр образов:

$ docker build -t myimage --build-arg THEPASSWORD=s3krit . | grep Secret
Secret is: s3krit

Так делать небезопасно: никогда так не делайте. Если мы решим просмотреть слои образа, то увидим секрет, лежащий в нем!

$ docker history myimage
IMAGE               CREATED              CREATED BY                                      SIZE
c224231ec30b        47 seconds ago       |1 THEPASSWORD=s3krit /bin/sh -c ./use_secre…   0B
6aef62acf0db        48 seconds ago       /bin/sh -c #(nop) COPY file:7aa28bbe6595e0d5…   62B
f88b19ca8e65        About a minute ago   /bin/sh -c #(nop)  ARG THEPASSWORD              0B
...

Любой, кто получит доступ к этому образу, узнает ваш пароль. Что в таком случае можно сделать?

Секреты BuildKit (частичное решение проблемы)


BuildKit – это новое (и все еще экспериментальное) решение для создания Docker-образов, которое помимо всего прочего добавляет поддержку безопасного использования секретов при сборке. В Docker Compose есть поддержка BuildKit с v1.25.

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

Поэтому мы собираемся объединить два этих подхода:

  • Docker Compose продолжит пользоваться аргументами при сборке, чтобы передавать секреты;
  • Для образа на продакшене, собранного с помощью docker build, мы используем BuildKit для передачи секретов.

Так мы сможем использовать один и тот же Dockerfile для работы локально и на продакшене.
BuildKit работает с секретами следующим образом: файл с секретами монтируется во временную директорию, пока выполняется команда RUN, например, в /var/secrets/thepassword. Поскольку он монтируется в течение выполнения команды RUN, он не будет добавлен в конечный образ.

Мы изменим файл use_secret.sh, чтобы он проверял, существует ли такой временный файл. Если существует, он использует его установки переменной среды $THEPASSWORD. Если файла не существует, то мы вернемся к переменной среды. То есть $THEPASSWORD может быть установлена с помощью BuildKit или аргументов сборки:

#!/bin/bash
set -euo pipefail
if [ -f /run/secrets/thepassword ]; then
   export THEPASSWORD=$(cat /run/secrets/thepassword)
fi

echo "Secret is: $THEPASSWORD"

Затем мы изменим Dockerfile, чтобы добавить BuildKit и смонтировать секрет:

# syntax = docker/dockerfile:1.0-experimental
FROM python:3.8-slim-buster
# Only use the build arg for local development:
ARG THEPASSWORD
COPY use_secret.sh .
# Mount the secret to /run/secrets:
RUN --mount=type=secret,id=thepassword ./use_secret.sh

Файл docker-compose.yml мы не меняем:

version: "3.7"
services:
  yourapp:
    build:
      context: "."
      args:
        THEPASSWORD: "s3kr!t"

Теперь нужно определить две переменные среды, одна из которых будет говорить Docker, что нужно использовать BuildKit, вторая, что Compose нужно использовать CLI-версию Docker и, следовательно, BuildKit. Также мы запишем секрет в файл:

$ export DOCKER_BUILDKIT=1
$ export COMPOSE_DOCKER_CLI_BUILD=1
$ echo 's3krit' > /tmp/mypassword

С Compose используем аргументы для сборки:

$ docker-compose build --progress=plain \
    --no-cache 2>&1 | grep Secret
#12 0.347 Secret is: s3kr!t

Обратите внимание, что --no-cache нужен, чтобы понять, что образ действительно пересоберется, если вы сами запустите все вышеприведенное. В реальной жизни этот параметр можно опустить. 2>&1 перенаправит stderr на stdout для корректной работы grep.

Когда мы будем готовы к сборке на продакшен, мы используем docker build с функционалом секретов из BuildKit:

$ docker build --no-cache -t myimage \
    --secret id=thepassword,src=/tmp/mypassword \
    --progress=plain . 2>&1 | grep Secret
#12 0.359 Secret is: s3krit

Безопасно ли это?


Давайте убедимся, что секрет не виден:

$ docker history myimage
IMAGE               CREATED             CREATED BY                                      SIZE
a77f3c32b723        25 seconds ago      RUN |1 THEPASSWORD= /bin/sh -c ./use_secret.…   0B
<missing>           25 seconds ago      COPY use_secret.sh . # buildkit                 160B
...

Ура! Мы передали секрет в один и тот же Dockerfile, используя Compose и docker build, и в последнем случае не раскрыли секрет из сборки.



Узнать подробнее о курсе.