Существует множество вариантов использования секретов во время сборки, но мы остановимся на самом распространенном, например, на установке пакета из частного репозитория.
Это может быть частное git-репозиторий или частный репозиторий пакетов, например, самостоятельная версия PyPI, RubyGems и т. д..
Это также относится к использованию коммерческих пакетов, где у вас может быть токен доступа для установки пакета из их частного репозитория.
Основная идея заключается в том, что вам нужен только секретный API-ключ или токен для получения доступа к частному репозиторию.
После того как пакет(ы) был(и) загружен(ы) и установлен(ы), нет причин держать этот секретный ключ при себе.
Есть несколько способов сделать это, но некоторые из них могут привести к тому, что вы случайно (или сознательно) навсегда включите свои секреты времени сборки в образ, что не является идеальным вариантом.
Использование ARG небезопасно
Давайте быстро рассмотрим, как использование ARG для сборки является небезопасным способом решения этой проблемы:
FROM debian:stable-slim
ARG ACCESS_TOKEN
RUN echo ./private-install-script --access-token "${ACCESS_TOKEN}"
./private-install-script – это место для команды, которую вы запустите для установки ваших пакетов.
Он не существует.
Вот почему я использовал echo, чтобы убедиться, что образ успешно соберется и без этого скрипта.
Затем при сборке образа вы можете сделать что-то вроде этого:
export ACCESS_TOKEN="supersecretvalue123"
docker image build --build-arg ACCESS_TOKEN="${ACCESS_TOKEN}" . -t myimage
При желании можно использовать Docker Compose или настроить чтение токена из файла .env, чтобы не экспортировать его вручную каждый раз при сборке образа.
Кажется разумным, верно?
Вы добавили секрет времени сборки, использовали его, и теперь в образ установлен ваш приватный пакет(ы).
Проблема в том, что слой RUN содержит ваш ACCESS_TOKEN в виде простого текста.
Если бы вы собирали образ таким образом, то смогли бы увидеть секретное значение в выходных данных:
Вы также можете запустить docker image history myimage и найти его в результатах:
Это свидетельствует о том, что ваш секрет постоянно хранится в вашем образе.
Если вы сами размещаете все на хостинге, вы можете подумать, что это не конец света, но, на мой взгляд, все же стоит защищать свои секреты.
Что, если вы добавите инструмент для сканирования уязвимостей в системе безопасности, и он просканирует ваше образ?
Теперь у них есть доступ к вашему секрету.
Бездумное копирование секретов облегчает случайную утечку информации.
Не расстраивайтесь, много лет назад, когда еще не было доступа к монтируемым секретам, я поступал именно так.
Я знал, что это проблема в долгосрочной перспективе, но это было простое решение, которое, по крайней мере, позволяло избежать фиксации секрета в вашей истории git.
Кроме того, я использовал его только для токенов доступа к приватным репо, которые достаточно легко переделать.
Сегодня мы можем получить лучшее из обоих миров. Простота использования и безопасность.
Безопасное монтирование секретов
Прежде всего, стоит отметить, что у вас должен быть включен BuildKit.
Если вы используете Docker v23.0+ (февраль 2023 года) на любой платформе, то он включен по умолчанию.
Если вы используете более старую версию Docker, вы можете включить его несколькими способами, об этом говорится в документации Docker.
Немного измененный Dockerfile из приведенного выше примера:
FROM debian:stable-slim
RUN --mount=type=secret,id=ACCESS_TOKEN \
echo "./private-install-script --access-token $(cat /run/secrets/ACCESS_TOKEN)"
- Вместо ARG мы используем –mount в нашей инструкции RUN.
- Этот секрет будет доступен только для этой конкретной инструкции RUN
- type=secret говорит, что мы хотим, чтобы это был секрет, другие типы указаны в документации Docker
- id=ACCESS_TOKEN позволяет нам назвать наш секрет как угодно (мы будем ссылаться на него позже)
- $(cat /run/secrets/ACCESS_TOKEN) позволяет нам получить доступ к секрету по его id
- Смонтированные секреты попадают в /run/secrets, catпозволяет нам получить их значение
Тогда мы сможем создать наш образ:
- –secret «id=ACCESS_TOKEN» должен совпадать с id в Dockerfile
- Опционально мы можем задать id=x,env=ACCESS_TOKEN, чтобы иметь идентификатор, отличный от имени env var, в этом случае вы будете ссылаться на id=x в вашем Dockerfile
- Лично я стараюсь не задавать env=, поскольку использование одного и того же имени интуитивно понятно.
Обратите внимание, что само секретное значение нигде не выводится
А вот и история:
Вы можете подумать, что я вру, потому что значение усечено.
Вы можете запустить приведенную выше команду с параметром –no-trunc, чтобы избежать обрезания вывода, это будет выглядеть некрасиво, но вот первая строка вывода без обрезания, которая включает нашу приватную команду RUN /bin/sh -c echo «./private-install-script –access-token $(cat /run/secrets/ACCESS_TOKEN)».
Действительно ли это работает?
Учитывая, что мы ничего не делаем в нашей инструкции RUN (это фиктивная команда echo), как мы можем убедиться, что все действительно работает?
Я не советую использовать этот Dockerfile для чего-либо, кроме локального тестирования, но вот быстрый способ убедиться, что все действительно работает.
Это полностью уничтожит цель использования секретного монтирования:
FROM debian:stable-slim
RUN --mount=type=secret,id=ACCESS_TOKEN \
cat /run/secrets/ACCESS_TOKEN > /tmp/dontdothisnormally
CMD ["cat", "/tmp/dontdothisnormally"]
Пересоберите с помощью docker image build –secret «id=ACCESS_TOKEN» . -t myimage и затем:
Да, он действительно устанавливается!
В качестве эксперимента вы можете попробовать просмотреть /run/secrets/ACCESS_TOKEN в CMD, и вы будете рады узнать, что он недоступен.
Вы также не можете использовать –mount с CMD, что вполне логично, поскольку монтирование доступно для RUN.
Что, если мой инструмент ожидает установки переменной окружения?
В нашем примере мы выполнили ./x –access-token $(cat /run/secrets/ACCESS_TOKEN), поэтому ее установка в качестве переменной окружения ничем не отличается от того, как вы обычно устанавливаете ее, например ACCESS_TOKEN=$(cat /run/secrets/ACCESS_TOKEN) ./x.
Вы также можете сделать его более красивым, отформатировав его следующим образом:
RUN --mount=type=secret,id=ACCESS_TOKEN \
ACCESS_TOKEN=$(cat /run/secrets/ACCESS_TOKEN) \
./x
А как насчет нескольких секретов?!
Нет проблем, Docker позаботится об этом:
RUN \
--mount=type=secret,id=ACCESS_KEY \
--mount=type=secret,id=ACCESS_TOKEN \
ACCESS_KEY=$(cat /run/secrets/ACCESS_KEY) \
ACCESS_TOKEN=$(cat /run/secrets/ACCESS_TOKEN) \
./x
Тогда вы сможете собрать его с помощью:
Как насчет монтирования файлов, а не переменных окружения?
Это может быть удобно, если у вас есть что-то вроде файла ~/.pypirc или какого-то конфигурационного файла, содержащего чувствительные API-ключи или токены, которые используются для команды:
RUN --mount=type=secret,id=pypirc,target=/root/.pypirc \
echo twine upload dist/*
- target= позволяет нам определить, где этот файл будет находиться в нашем изображении.
- Эта строка echo демонстрирует загрузку пакета Python, здесь она не важна.
Затем мы можем собрать его с помощью:
- src= – это место, где файл лежит на вашем хосте Docker при выполнении команды сборки.
Если вы хотите убедиться в том, что все работает, вы можете применить ту же тактику, которую мы использовали в разделе «Действительно ли это работает?» этого руководства.
Вы также можете подключить несколько файлов таким же образом, как мы подключали несколько переменных окружения.
Просто задайте каждому из них уникальный идентификатор, а также src и target, и все готово.
🐳 Docker secret – как использовать в Docker Swarm и Docker Compose
Docker Compose
В большом проекте у вас может быть несколько секретов, и вы используете Docker Compose.
Вот несколько примеров, которые преобразуют вышеописанное в формат Docker Compose.
Доступ к переменным окружения
Во-первых, вот пример Dockerfile.
Возвращаясь к тому, что мы делали ранее, мы приводим этот файл только для того, чтобы продемонстрировать использование наших секретов.
Не делайте этого в реальном проекте!
Кстати, в этом файле нет ничего специфичного для Docker Compose:
FROM debian:stable-slim
RUN --mount=type=secret,id=ACCESS_KEY \
--mount=type=secret,id=ACCESS_TOKEN \
echo "$(cat /run/secrets/ACCESS_KEY) | $(cat /run/secrets/ACCESS_TOKEN)" \
> /tmp/dontdothisnormally
CMD ["cat", "/tmp/dontdothisnormally"]
Затем мы настраиваем docker-compose.yml, чтобы установить наши секреты.
Обратите внимание на отступ свойства services.myservice.build.secrets, важно, что это свойство сборки:
services:
myservice:
build:
context: "."
secrets:
- "ACCESS_KEY"
- "ACCESS_TOKEN"
secrets:
ACCESS_KEY:
environment: "ACCESS_KEY"
ACCESS_TOKEN:
environment: "ACCESS_TOKEN"
Здесь мы используем преимущество определения секретов на основе окружения, которое находится в самом низу файла.
Свойство окружения – это имя переменной env, которая будет считываться с вашего хоста Docker.
Имя ключа словаря – это идентификатор секрета.
Наконец, по желанию создайте файл .env, и Docker Compose будет автоматически считывать данные из него.
Если вы не выполните этот шаг, то вам нужно будет экспортировать эти env-вары в текущую оболочку, иначе ваши секреты будут пустыми:
Теперь вы можете увидеть, как все это работает:
Доступ к файлам
Это тот же Dockerfile, что и в нашем примере с файлами:
FROM debian:stable-slim
RUN --mount=type=secret,id=pypirc,target=/root/.pypirc \
echo twine upload dist/*
Затем мы настраиваем docker-compose.yml, чтобы установить наш секрет:
services:
myservice:
build:
context: "."
secrets:
- "pypirc"
secrets:
pypirc:
file: "${HOME}/.pypirc"
Единственное реальное отличие от другого примера Docker Compose заключается в том, что мы выполняем поиск по файлу в нижней части вышеуказанного файла.
Как и в команде Docker, этот путь ищется на вашем хосте Docker.
Теперь вы можете увидеть, как все это работает:
$ docker compose build && docker compose run myservice
root@e491336de8f2:/#
Технически здесь нечего смотреть, и вы попадёте в сеанс командной строки, так как именно это делает образ Debian по умолчанию.
Вы всегда можете отменить CMD и вывести файл, чтобы убедиться, что он работает (он работает).
Здесь я рассмотрел только несколько распространённых случаев использования.
Не стесняйтесь проверить документацию для других вариантов.
Вы даже можете подключить SSH-ключ.
Я не использовал этот способ, потому что обычно использую персональные токены доступа для образов, которые нужно клонировать в частные репозитории, и в этом случае описанные выше решения работают.
см. также:
- 🌐 Управление заданиями Cron на нескольких серверах с помощью Dkron
- 🐳 Импорт SSL-сертификатов в контейнер Docker
- 📜 Ansible Security: Обеспечение безопасности инфраструктуры с помощью Ansible
- ☁️ Генерация безопасных пользовательских паролей с помощью Terraform
- 🐳 Защита паролей в Docker
- ☸️ Как обновить секрет Kubernetes
- ☸️ Как расшифровать секрет Kubernetes
- 🐳 Как средства Docker защищают контейнерные среды
- ☸️ Реализация неизменяемых ConfigMaps в Kubernetes: Практическое руководство