Перейти к основному содержимому

ADR: Подпись артефактов релиза контрибьютором по каждой платформе

  • Status: Accepted by Andrei Ganiushkin andrey@ganyushkin.ru
  • Date: 2026-07-26
  • Authors: Andrei Ganiushkin andrey@ganyushkin.ru
  • Related issues: -

Context​

  • Netgap — сетевой шлюз, зона ответственности которого доходит до безопасности изолируемых периметров. Скомпрометированный или подменённый релизный артефакт напрямую угрожает инфраструктуре конечного пользователя.
  • Артефакты для разных платформ собираются разными тулчейнами, на разном оборудовании и в разное время — они не идентичны и не могут быть покрыты одной общей подписью. Расхождение по одной платформе не должно обесценивать уже подписанные артефакты остальных платформ (поэтому файлы хешей и подписей не должны участвовать в подсчёте самих хешей).
  • CD не собирает релиз впервые, а лишь пересобирает уже собранный и подписанный maintainer релиз и проверяет, что пересборка побитово совпадает с тем, что было подписано — то есть CI/CD выступает контролирующей, а не удостоверяющей стороной.
  • Ключ подписи проверяется по pgp_key_id конкретного maintainer в CONTRIBUTORS.json — так же, как и при проверке подписи коммитов ADR 0004, что даёт единый источник доверенных ключей для всего проекта.

Decision​

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

  1. maintainer собирает релиз локально на машине (или под эмуляцией/кросс-сборкой) целевой платформы: make clean && make build-prod && make checks, make release-bundle;
  2. maintainer личным GPG-ключом подписывает файл хеш-сумм всех файлов релиза этой платформы: make release-sign GPG_KEY=<key-id> — получаются netgap-vX.Y.Z-<P>.sha256 и netgap-vX.Y.Z-<P>.sha256.asc;
  3. оба файла (хеши и подпись) коммитятся в release/* отдельным подписанным коммитом git commit -S -s -m "Sign release artifacts for vX.Y.Z (<P>)";
  4. процедура выполняется отдельно и полностью для каждой целевой платформы. Релиз допускается к слиянию в main только когда в release/* есть валидные подписи по всем целевым платформам;
  5. CD по релизному тегу пересобирает релиз для каждой платформы, пересчитывает хеши и сверяет их с закоммиченным файлом (sha256sum -c), а также проверяет подпись (gpg --verify) ключом из CONTRIBUTORS.json. Расхождение — провал релиза, а не повод перевыпустить подпись.

Приватный ключ подписи артефактов не покидает машину maintainer и не выдаётся CI/CD: сборка на стороне CD является проверяющей (sha256sum -c, gpg --verify), а не подписывающей операцией. После первого коммита платформенной подписи release/* замораживается: любое изменение кода или состава релиза делает подписи недействительными и требует нового -rc.X и повторной подписи всеми платформами.

Considered Alternatives​

Alternative 1: Автоматическая подпись артефактов сервисным ключом CI/CD​

CD после успешной сборки и прохождения тестов сам подписывает артефакты релиза служебным GPG-ключом или ключом HSM, без участия человека.

  • Pros: полностью автоматизированный процесс без ручных шагов на стороне maintainer; единообразная подпись для всех платформ из одного пайплайна. Не требует у maintainer локального окружения под каждую целевую платформу.
  • Cons: сервисный ключ подтверждает лишь то, что артефакт «прошёл через конвейер CI», а не то, что конкретный ответственный человек лично проверил и подтвердил именно этот релиз; компрометация единственного сервисного ключа/CI позволяет злоумышленнику подписать и выпустить вредоносный релиз автоматически, без какой-либо точки ручной остановки. CD в таком случае становится и собирающей, и удостоверяющей стороной одновременно, что убирает независимую проверку «пересборка совпадает с тем, что подписал человек» — по сути превращает подпись в самопроверку системы.
  • Reason for rejection: ключевая цель требования — подтвердить, что конкретный ответственный человек лично собрал и проверил релиз для платформы, а не что он прошёл автоматический конвейер. Автоматическая подпись переносит доверие с человека на CI/CD, создавая единую точку отказа.

Alternative 2: Публикация только хеш-сумм без криптографической подписи​

Публиковать sha256-суммы артефактов без GPG-подписи, полагаясь на защищённый канал доставки (HTTPS) как единственную гарантию целостности.

  • Pros: минимальные затраты — не нужна инфраструктура ключей и сверка с CONTRIBUTORS.json по pgp_key_id. Проще для конечных пользователей проверить хеш.
  • Cons: хеш-сумма без подписи доказывает лишь то, что скачанный файл совпадает с файлом на сервере в момент публикации, но не то, кто и с каким намерением его туда положил. При компрометации сервера раздачи или CI злоумышленник может подменить одновременно и артефакт, и файл хешей — без независимой криптографической подписи это не обнаруживается.
  • Reason for rejection: не даёт независимого от канала доставки доказательства подлинности релиза и не решает задачу подтверждения ответственного человека, только целостность при передаче.

Alternative 3: Платформенные сертификаты вендора (Apple notarization, Windows Authenticode) вместо личной подписи maintainer​

Использовать штатные механизмы подписи кода конкретной ОС (сертификат разработчика Apple, сертификат Authenticode для Windows) вместо GPG-подписи maintainer.

  • Pros: нативная интеграция с ОС — пользователь видит доверенного издателя в стандартном диалоге ОС без дополнительных действий.
  • Cons: такие сертификаты покрывают не все целевые платформы Netgap (например, у x86_64-unknown-linux-musl и других Linux-триплетов нет единого штатного механизма подписи кода уровня ОС) — потребовалась бы отдельная схема для части платформ. Сертификат обычно привязан к организации/аккаунту разработчика, а не к конкретному ответственному человеку, что хуже согласуется с моделью персональной ответственности, принятой для коммитов. Стоимость и процесс продления сертификатов вносят внешнюю зависимость от вендора ОС.
  • Reason for rejection: не покрывает все целевые платформы единообразно и заменяет персональную ответственность maintainer на организационный сертификат вендора. Может использоваться как дополнение к GPG-подписи для платформ, где это требуется, но не как её замена.

Alternative 4: Воспроизводимая сборка и публичный журнал прозрачности (SLSA/Sigstore, keyless-подпись) без ручного шага​

Подтверждать подлинность релиза через атрибуцию сборки и публичный лог (например, Sigstore/Rekor с keyless-подписью через OIDC), без обязательного ручного шага подписи конкретным человеком.

  • Pros: современный, индустриально признанный подход к цепочке поставок ПО. Не требует ручного управления долгоживущими GPG-ключами у каждого maintainer. Проверяемость через публичный журнал.
  • Cons: инфраструктура "публичный лог" и OIDC-провайдера — дополнительная внешняя зависимость (минимизация внешних доверенных сервисов и предположение о недоверенной или ограниченной внешней связности). Keyless-подпись удостоверяет личность через краткоживущую идентификацию в момент сборки CI, а не через осознанную ручную проверку релиза конкретным человеком — то есть по сути близка к Alternative 1 с тем же риском единой точки отказа через CI/OIDC. Не отменяет необходимость раздельной сборки и проверки для каждой платформы.
  • Reason for rejection: решает задачу прослеживаемости процесса сборки, но не задачу осознанного человеческого подтверждения конкретного релиза для конкретной платформы, что требуется данным ADR. Может рассматриваться в будущем как дополнительный слой прозрачности поверх, а не вместо личной подписи maintainer.

Consequences​

Positive​

  • Подпись артефактов криптографически привязана к конкретному ответственному человеку из CONTRIBUTORS.json, а не к результату работы автоматики — при инциденте можно однозначно установить, кто выпустил конкретный релиз для конкретной платформы.
  • Компрометация CI/CD не позволяет автоматически выпустить подписанный вредоносный релиз: без приватного ключа maintainer подпись невозможно получить, а CD выступает лишь проверяющей стороной (пересборка + sha256sum -c + gpg --verify).
  • Раздельная подпись по платформам изолирует проблему одной платформы: расхождение или задержка подписи для одной платформы не блокирует и не обесценивает уже подписанные артефакты других платформ.
  • Инвариант «пересборка CD побитово совпадает с подписанным» превращает саму пересборку в дополнительную проверку целостности процесса сборки, а не просто формальность.

Negative​

  • Повышаются трудозатраты maintainer: релиз должен быть локально собран и подписан отдельно на каждой целевой платформе (или под кросс-сборку/эмуляцию для неё), что требует доступа к соответствующему окружению.
  • Ручной шаг делает подпись релиза узким местом процесса: без доступного maintainer с личным ключом релиз для платформы не может быть выпущен.
  • Компрометация или потеря личного приватного ключа maintainer требует организационной процедуры отзыва и обновления pgp_key_id в CONTRIBUTORS.json, а также может задержать выпуск релиза до завершения процедуры.

Risks and Mitigations​

  • Risk: приватный GPG-ключ maintainer, используемый для подписи релизов, скомпрометирован или утерян.
    • Mitigation: организационная процедура отзыва ключа и обновления pgp_key_id в CONTRIBUTORS.json. При подозрении на компрометацию — блокировка приёмки релизов, подписанных этим ключом, до переоформления.
  • Risk: сборка на машине maintainer не воспроизводима, из-за чего пересборка CD не совпадает с подписанными хешами, и подпись становится невалидной уже после приёмки.
    • Mitigation: требования к воспроизводимой сборке из раздела 3.4.1 черновика: закреплённый toolchain, --locked, SOURCE_DATE_EPOCH, --remap-path-prefix, базовые образы по digest, отсутствие в артефактах времени сборки, абсолютных путей и имени хоста. Версия и метаданные сборки берутся только из Cargo.toml.
  • Risk: отсутствует доступный maintainer с личным ключом для одной из целевых платформ, что блокирует выпуск релиза целиком (из-за требования подписи по всем платформам).
    • Mitigation: несколько maintainer с правом подписи и зарегистрированными в CONTRIBUTORS.json ключами. Заблаговременное планирование релизного окна с учётом их доступности.
  • Risk: изменение кода или состава релиза после первого коммита платформенной подписи остаётся незамеченным, и релиз собирается/публикуется с частично устаревшими подписями.
    • Mitigation: заморозка release/* после первого коммита платформенной подписи на уровне процесса и защиты ветки. Обязательная проверка в MR release/* → main наличия валидных подписей по всем целевым платформам.

Verification​

  • Тест приёмки: релиз без валидной подписи хотя бы для одной целевой платформы не проходит проверку в MR release/* → main и не может быть слит.
  • Тест приёмки: CD по тегу пересобирает релиз, пересчитывает хеши и проверяет подпись для каждой платформы — при полном совпадении со всеми подписанными файлами релиз публикуется, при любом расхождении — падает.
  • Тест приёмки: подпись файла хешей ключом, отсутствующим в CONTRIBUTORS.json или не совпадающим с pgp_key_id заявленного автора коммита, отклоняется как невалидная.
  • Ручная проверка: изменение состава релиза после первого коммита платформенной подписи требует нового -rc.X и повторной подписи всеми платформами — старые подписи не переиспользуются.