Воспроизводимые сборки и подпись артефактов: контроль целостности от коммита до деплоя

Изображение: recraft
Особую роль в разработке программного обеспечения наравне с качеством написания кода играет безопасность цепочки его поставок (Software Supply Chain Security). Примером нарушения такой безопасности служит инцидент с Trivy – популярным open source-сканером уязвимостей, атакованным в марте этого года именно через цепочку поставок. Чтобы избежать таких инцидентов, необходимо руководствоваться правилами воспроизводимости сборок и криптографической подписи артефактов.

Воспроизводимость сборок
Для того, чтобы писать о том, «как это можно применить», нужно дать определение. Воспроизводимость сборок – это процесс, при котором многократная сборка одного и того же исходного кода при одинаковых инструкциях сборки приводит к созданию идентичных файлов сборки. Применяется это обычно для:
- обнаружения скрытых механизмов в программном обеспечении, которые позволяют обойти меры защиты (они же бэкдоры). Если привести пример, то злоумышленник собирает код на своём устройстве для подмены и моментально получает другой хэш. А этот «сигнал» скажет, что внутри кода есть что-то лишнее.
- детерминированной сборки – сборки одного и того же исходного кода с одной и той же средой и инструкциями сборки, при котором создаются одни и те же двоичные файлы в любом случае, даже если они сделаны на разных устройствах, в разных директориях, с разными именами и в разный диапазон времени.
- повышения доверия пользователей. Как ни странно, но сборка из исходного кода, где получается ожидаемый результат при множественном повторении, вне зависимости от сред приводит к тому, что пользователь доверяет данному программному обеспечению.
Этот фактор можно исключить, если следовать определённым правилам реагирования на сигналы:
- Временные метки (они же Timestamp). В компиляторах есть стандарт учёта времени сборки, а управлять им можно при помощи переменной SOURCE_DATE_EPOCH
- Пути к файлам. Никаких нестандартных абсолютных путей (пример: /custom/path/to/binary.bin), использовать только стандартизированные пути для каждого дистрибутива согласно иерархии файловой системы.
- Версионирование и фиксация зависимостей. Все зависимости должны фиксироваться в местах, которые использует тот или иной языка программирования (для NodeJs примером может послужить package-lock.json, для Golang – go.sum)
- Окружение сборки. Используя контейнеризированные среды, например Docker, необходимо фиксировать не только версии приложения, но также и версии используемых инструментов как при подготовке образа, так и при установке необходимых утилит внутри него.
Подпись артефактов
Помимо воспроизводимости сборок нужно также гарантировать конечному пользователю, что артефакты не только были благополучно загружены в реестр артефактов, но и не оказались подменены в процессе. На определенных уровнях управления можно получить гарантию сохранности от вмешательства в цепочку поставки:
- кода: при загрузке коммитов в веб-платформу для управления проектами и репозиториями программного кода (он же VCS) гарантируем, что код был подписан именно владельцем коммита;

- сборки: гарантирует, что артефакт был создал доверенным конвейером сборки;
- подписи образа: гарантирует целостность получаемого артефакта.
Для удовлетворения данных целей используются следующие инструменты:
- Подпись GPG/SSH на начальном (локальном) этапе разработки. Надежный способ избежать Man-in-the-Middle (MITM)-атаки, подмены хоста SSH (SSH Host Key Spoofing) или подмена на сервере управления репозиториями программного кода от внутренних сотрудников (Insider Threat).
- Sigstore/Cosign. Удобный инструмент с открытым исходным кодом для работы с подписью артефактов и верификации подписи напрямую в конвейере. Поддерживает подписывание артефакта, не используя ключи. Плюс от такого подхода в том, что ключ генерируется на лету, используется один раз и исчезает. Подпись привязывается к идентичности конвейера. Если применяются on-prem решения и не доступен сервис в белых листах, то нужно поднять свой или использовать вариант с ключами.
Полная цепочка доверия
Сформируем план проверки полной цепочки:
- Исходный код. Необходимо использовать систему контроля версий. Здесь мы защищаемся при помощи подписей коммитов разработчиками и безопасный ветвей.
- Сборки
- Должны происходить в изолированном окружении.
- Должны генерироваться метаданные сборки, которые отвечают на вопросы: Кто собрал? Из какого коммита? Кто автор коммита? Какие этапы сборки приложения? Данный шаг называется провенанс.
- Артефакты. Обязательное хранение в защищенном хранилище (Sonatype Nexus, Github releases, Harbor) с защитой от записи (это называют ещё иммутабельностью). В данном шаге подпись артефакта осуществляется после сборки.
- Процесс развертывания (Deploy). Контроль среды не допустит запуска не защищенного кода.
Практические примеры
1. Защита исходного кода на этапе разработки. Чтобы добиться данного эффекта, необходимо создать пару ключ публичный/ключ приватный -> в настройках VCS добавить публичную часть и далее следовать инструкции, приведенной ниже.
2. Сборка. Создадим тестовое приложение, чтобы проверить различие в хеше при сборке одного приложения и привязке к времени сборки.
Инициализируем приложение
Проверим сборку с объявленными локально параметрами
Повторим сборку без объявленных параметров
Как видим, хэш сумма изменилась. Именно такой принцип будет соблюдаться при сборке приложений. Также в зависимости от коммита и объявленных параметров хэш сумма продолжит и дальше изменяться.
Создадим Dockerfile для сборки приложения

Изображение 12. Локальная сборка образа.
3. Артефакты. Реализуем подписывание образа при помощи ключей. Генерируем ключ – пару cosign generate-key-pair – и заводим 3 переменные: COSIGN_PASSWORD COSIGN_PRIVATE_KEY COSIGN_PUBLIC_KEY
Далее реализуем CI/CD, в котором соберём образ, зальём его в реестр образов, подпишем и верифицируем его. Данный подход даст нам контроль над подменой в реестре образов.

4. Деплой. При использовании Kubernetes рекомендуется использовать Admission Controllers. Для поставки образов можно использовать внешний скрипт со встроенной проверкой, по аналогии выше, `cosign verify …`
Автор: Владислав Авдеев, DevOps, NGR Softlab.











