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

ADR: Подпись коммитов контрибьютором

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

Context

Netgap — проект с открытым исходным кодом, в котором вклад принимается только от контрибьюторов, прошедших процесс получения статуса (договор, передача прав — см. CONTRIBUTING.md и CONTRIBUTING-CAA.ru.md). Список авторизованных участников ведётся в CONTRIBUTORS.json, где для каждого контрибьютора зафиксированы email, role, permissions и pgp_key_id.

Netgap — это по сути сетевой шлюз с зоной ответственности вплоть до обеспечения безопасности изолируемых периметров. Ошибка в коде или намеренная закладка может иметь последствия для безопасности инфраструктуры конечных пользователей. Поэтому важна фиксация кто именно и с каким формальным согласием внёс изменение.

Ключевой вопрос ADR: подход к фиксации авторства изменений, вносимых контрибьюторами.

Decision

Каждый коммит контрибьютора обязан быть:

  1. подписан личным GPG-ключом контрибьютора (git commit -S), при этом подпись должна быть криптографически валидна и её ключ должен совпадать с pgp_key_id, зафиксированным за этим контрибьютором в CONTRIBUTORS.json.
  2. снабжён трейлером Signed-off-by: (git commit -s).

Рекомендуемая команда фиксации изменения:

git commit -S -s -m "Implement CSV export for reports"

Проверка авторства выполняется в CI автоматически (check-signed-off-by, check-gpg-signature, check-contributors).

Considered Alternatives

Alternative 1: Полагаться только на проверку по платформе (аккаунт, Verified badge)

Не требовать GPG-подпись, доверять авторству коммита полю author/committer и статусу верификации платформы (например, GitVerse, SourceCraft или GitLab «Verified», основанный на подписанном коммите через веб-интерфейс или привязанный SSH/GPG-ключ на уровне аккаунта).

  • Pros: ниже порог входа для контрибьюторов — не нужно генерировать и настраивать GPG-ключ локально. Проще онбординг.
  • Cons: поле author/email коммита в Git тривиально подделывается (git commit --author="...") и никак не подтверждает личность. Статус верификации платформы привязан к учётной записи на конкретном хостинге, а не к контрибьютору напрямую, и может быть утрачен или скомпрометирован при смене хостинга либо компрометации аккаунта. Отсутствует переносимая, независимая от платформы криптографическая привязка автора к содержимому коммита. При экспорте/зеркалировании репозитория на другой хостинг подтверждение личности теряется.
  • Reason for rejection: не даёт гарантии, что автор коммита — тот, кем он представлен, и оставляет проект уязвимым к подмене авторства и к атакам через компрометацию учётной записи на платформе, а не ключа конкретного человека.

Alternative 2: Подпись коммитов автоматизированным ключом CI/CD (боты, service account)

CI после прохождения проверок сам переподписывает или ставит коммит от имени сервисного ключа.

  • Pros: единообразная, полностью автоматизированная подпись. Не требует от контрибьюторов генерации и хранения личных ключей.
  • Cons: сервисный ключ подтверждает лишь факт «коммит прошёл через конвейер CI», а не то, кто именно и с каким согласием внёс изменение — то есть теряется главная функция: привязка к конкретному человеку и его формальному согласию (Signed-off-by). Компрометация единственного сервисного ключа обесценивает подписи сразу всех коммитов проекта.
  • Reason for rejection: заменяет проверку личности проверкой самого факта прогона конвейера, что не решает задачу — установить, кто именно нёс ответственность за изменение, и создаёт единую точку отказа безопасности (компрометация одного ключа CI).

Alternative 3: Только Signed-off-by без GPG-подписи

Требовать трейлер Signed-off-by:, но не требовать криптографическую GPG-подпись.

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

Alternative 4: Ревью и апрув maintainer как единственная гарантия личности автора

Не требовать GPG-подпись автора коммита, полагаться на то, что maintainer при апруве MR/PR лично удостоверяется в личности контрибьютора.

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

Consequences

Positive

  • Каждый коммит в истории Netgap криптографически привязан к конкретному человеку из CONTRIBUTORS.json, а не только к текстовому полю author, которое тривиально подделывается.
  • Компрометация одной учётной записи на платформе хостинга не позволяет злоумышленнику внести коммит от чужого имени: без приватного GPG-ключа контрибьютора подпись невалидна.
  • Появляется техническая основа для расследования инцидентов и разбора спорных ситуаций (кто, когда и с каким формальным согласием внёс конкретное изменение) — что критично для продукта с повышенными требованиями к прослеживаемости изменений.
  • Требование переносимо между платформами: подпись коммита не зависит от конкретного хостинга (GitVerse, SourceCraft или GitLab) и сохраняет силу при миграции или зеркалировании репозитория.
  • Автоматическая сверка (check-gpg-signature, check-signed-off-by, check-contributors) снимает нагрузку с maintainer по ручной проверке личности на каждый MR/PR, оставляя ему содержательное ревью кода.
  • Условие закладывает единый принцип для проекта: критичные для доверия действия подтверждаются подписью конкретного человека, а не автоматикой.

Negative

  • Повышается порог входа для новых контрибьюторов: требуется сгенерировать GPG-ключ, передать его отпечаток для внесения в CONTRIBUTORS.json и настроить локальное окружение (git config user.signingkey, commit.gpgsign).
  • Компрометация или потеря личного приватного ключа контрибьютора требует организационной процедуры отзыва и замены pgp_key_id в CONTRIBUTORS.json.
  • После rebase/squash GPG-подпись становится недействительной и должна быть проставлена заново автором — дополнительный шаг в рабочем процессе.

Risks and Mitigations

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

Acceptance Criteria

  • CI отклоняет любой коммит без GPG-подписи или с подписью, ключ которой не совпадает с pgp_key_id соответствующего автора в CONTRIBUTORS.json.
  • CI отклоняет любой коммит без трейлера Signed-off-by:.
  • CI отклоняет любой коммит, автор которого отсутствует в CONTRIBUTORS.json.
  • В CONTRIBUTING.md описан порядок генерации GPG-ключа, передачи его отпечатка maintainer и включения в CONTRIBUTORS.json.