Секреты сборки и пересылка SSH в Docker 18.09 +19


image

Используя Dockerfile, всегда было сложно получить доступ к частным ресурсам. Хорошего решения просто не было. Использовать переменные среды или просто удалять секретные файлы после использования не годится: они остаются в метаданных образа. Пользователи порой шли на ухищрения: создавали многоступенчатые сборки, однако все равно приходилось соблюдать крайнюю осторожность, чтобы на финальном этапе не было секретных значений, а секретные файлы до отсечения хранились в локальном кэше сборки.


Команда сборки Docker 18.09 включает множество обновлений. Основная особенность — в том, что появился абсолютно новый вариант реализации серверной части, он предлагается в рамках проекта Moby BuildKit. Серверное приложение BuildKit обзавелось новыми функциями, среди которых — поддержка секретов сборки Dockerfile.


Использование секретов


В первую очередь нужно включить серверную часть BuildKit. BuildKit в версии 18.09 — это функция выбора, которую можно включить с помощью переменной среды DOCKER_BUILDKIT=1 перед запуском docker build. В следующей версии планируется сделать BuildKit серверной частью по умолчанию.


export DOCKER_BUILDKIT=1

Реализация секретов сборки основана на двух новых функциях BuildKit. Одна из них — возможность использования пользовательского интерфейса, загруженного из образов в реестре; вторая — возможность использования монтажных точек в командах RUN для Dockerfile.  Чтобы воспользоваться функцией реализации с поддержкой секретов (вместо стандартной), определите образ компоновщика с помощью синтаксической директивы в первой строке Dockerfile — указав на образ контейнера, который хотите использовать. Пока что секреты в стабильном канале внешних Dockerfile недоступны: потребуется одна из версий в экспериментальном канале, например, docker/dockerfile:experimental или docker/dockerfile/1.0.0-experimental.


# syntax=docker/dockerfile:1.0.0-experimental

Если вам как автору Dockerfile известно, что команда RUN, установленная в Dockerfile, требует секретного значения, используйте для нее метку --mount, указывая, какой секрет нужен команде, и где его следует монтировать. Метка --mount принимает разделенную запятой структуру как в --mount для docker run.


# syntax=docker/dockerfile:1.0.0-experimental
FROM alpine
RUN --mount=type=secret,id=mysite.key command-to-run

Эта метка указывает, что во время работы команда имеет доступ к секретному файлу по пути /run/secrets/mysite.key. Секрет доступен только команде с меткой mount, а не другим частям сборки. Данные в этом файле загружаются из хранилища секретов на основании указанного идентификатора «mysite.key». В настоящее время интерфейс командной строки Docker поддерживает раскрытие секретов из локальных файлов клиента с помощью метки --secret.


docker build --secret id=mysite.key,src=path/to/mysite.key .

Как описано выше, секреты по умолчанию устанавливаются в /run/secrets, однако можно указать любой путь с помощью ключа «target». Если «target» указано, а «id» — нет, то «id» по умолчанию становится базовое имя пути назначения.


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


Если автор Dockerfile указывает, что инструкция RUN может использовать секрет, а пользователь, вызывающий сборку, не предоставляет его, то секрет игнорируется, и по указанному пути никакой файл не устанавливается. Если подобная ситуация нежелательна, используйте ключ «required»: это покажет, что без значения произойдет сбой сборки.


# syntax=docker/dockerfile:1.0.0-experimental
FROM alpine
RUN --mount=type=secret,id=mysite.key,required <command-to-run>

Реализация


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


Секретные значения также исключаются из расчетов кэша сборки, чтобы нельзя было использовать кэш метаданных.


SSH


Чаще всего, наверное, пытаются получить доступ к частным хранилищам — через протокол SSH. Да, чтобы раскрыть личный ключ SSH для сборки, можно использовать секретные элементы, однако есть решение лучше. В протоколе SSH используется криптография с открытым ключом, и благодаря этой конструкции не нужно никому сообщать свой личный ключ. Например, если вы используете несколько компьютеров с SSH, не нужно передавать свой ключ — достаточно обеспечить соединение через протокол ssh-A.


Мы добавили аналогичную возможность в docker build, где можно использовать метку --ssh для направления существующего агентского соединения SSH или ключа в программу-компоновщик. Вместо того, чтобы передавать данные о ключе, Docker просто сообщит компоновщику о наличии такой возможности. Если компоновщику потребуется доступ к удаленному серверу через SSH, он обратится к клиенту и попросит подтверждения конкретного запроса, необходимого для соединения. Сам ключ не покидает программу-клиент, а после завершения программы, потребовавшей доступа, со стороны компоновщика не остается никаких данных для повторной установки удаленного соединения.


Доступ к передаче файлов по протоколу SSH получают только команды в Dockerfile, прямо запросившие доступ к SSH, указав блок type=ssh. Другие команды не имеют данных о доступном агенте SSH.


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


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


Для запроса доступа SSH к команде RUN в Dockerfile необходимо указать блок с типом «ssh». Тогда во время выполнения процесса будет установлен сокет с доступом к агенту SSH только для чтения. Это также настроит переменную среды SSH_AUTH_SOCK, чтобы программы, использующие протокол SSH, автоматически использовали этот сокет.


# syntax=docker/dockerfile:experimental
FROM alpine
# install ssh client and git
RUN apk add --no-cache openssh-client git
# download public key for github.com
RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
# clone our private repository
RUN --mount=type=ssh git clone git@github.com:myorg/myproject.git myproject

Со стороны клиента Docker необходимо с помощью метки --ssh указать, что пересылка по протоколу SSH разрешена для этой сборки.


docker build --ssh default .

Метка принимает пару ключевых значений, определяющую положение сокета локального агента SSH или закрытых ключей. Если хотите использовать значение default=$SSH_AUTH_SOCK, путь к сокету можно оставить пустым.


В блоке Dockerfile можно также использовать ключ «id» для разделения различных серверов, входящих в одну сборку. Например, доступ в различные хранилища в Dockerfile можно получить с различными ключами развертывания. В данном случае в Dockerfile Вы будете использовать:


…
RUN --mount=type=ssh,id=projecta git clone projecta …
RUN --mount=type=ssh,id=projectb git clone projectb …

и раскроете данные клиента с помощью docker build --ssh projecta=./projecta.pem --ssh projectb=./projectb.pem. Заметьте, что даже если указать фактические ключи, компоновщику сообщается только агентское соединение, а не фактическое содержание этих закрытых ключей.


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




К сожалению, не доступен сервер mySQL