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

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-gnu и других 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 и повторной подписи всеми платформами — старые подписи не переиспользуются.