Заметки по разработке

Проверка подписей Git в Cloud Mac CI

Проверка подписей Git в Cloud Mac CI

После отправки кода на облачный Mac система CI обычно сразу выполняет checkout, сборку и тестирование. Проблема в том, что имя и адрес электронной почты автора в Git-коммите — это всего лишь редактируемые текстовые поля. Они показывают, кто указан создателем коммита, но не доказывают, что объект действительно подписан этим разработчиком. Более надёжный подход — подписывать коммиты с помощью SSH, а затем поручить CI проверять весь диапазон от базового коммита слияния до текущего коммита по контролируемому списку открытых ключей.

Такая проверка не заменяет ревью кода и не определяет, безопасен ли сам код. Она решает более узкую, но важную задачу: подтверждает целостность Git-объектов, действительность подписей и принадлежность ключей тем, кому в данный момент разрешено отправлять код.

Сначала определите границы доверия при проверке

Проверка подлинности коммита включает как минимум три уровня:

  1. Соответствует ли подпись содержимому Git-объекта.
  2. Присутствует ли открытый ключ подписи в списке разрешённых подписантов.
  3. Сохранялись ли у этой личности права на отправку кода в момент создания коммита.

Первый уровень обеспечивается криптографической подписью, второй — поддерживаемым в репозитории списком, а третий по-прежнему зависит от принятого в команде процесса отзыва прав. Недостаточно просто выполнить git log --show-signature и увидеть сообщение «Good signature»: действующий, но не авторизованный ключ также способен создать корректную подпись.

Адрес электронной почты служит для отображения и уведомлений, открытый ключ — для проверки подлинности, а список разрешённых подписантов — для авторизации. Не следует считать их одним и тем же.

Список рекомендуется хранить в отдельном каталоге, изменения которого строго контролируются, например .ci/trusted_signers. Изменения самого списка должны проходить дополнительное ревью, чтобы автор не мог в рамках одного изменения добавить собственный открытый ключ и одновременно разрешить прохождение кода.

Настройка SSH-подписей коммитов в Git

Git версии 2.34 и новее поддерживает непосредственное подписание с помощью SSH-ключей. Сначала разработчик должен локально указать формат подписи и путь к открытому ключу:

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgSign true

В конфигурации указывается файл открытого ключа, но сама подпись создаётся соответствующим закрытым ключом. После создания коммита можно убедиться, что объект действительно содержит подпись:

git cat-file commit HEAD | sed -n '/^gpgsig /,/^[^ ]/p'

Каждая строка файла разрешённых подписантов содержит идентификатор, необязательные ограничения и открытый ключ. В качестве идентификатора следует использовать стабильное командное обозначение, а не часто меняемое отображаемое имя:

ci-release namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
ios-team namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5BBBB...

После checkout репозитория в CI укажите Git путь к этому файлу:

git config --local gpg.format ssh
git config --local gpg.ssh.allowedSignersFile .ci/trusted_signers
git verify-commit HEAD

Не помещайте закрытые ключи в репозиторий. Для проверки в CI нужны только открытые ключи; закрытый ключ необходим лишь в среде, где создаются подписанные коммиты или теги.

Проверка полного диапазона коммитов

Проверять только HEAD — распространённая ошибка. Вредоносное или неавторизованное изменение можно спрятать в предыдущем коммите, а последним подписанным коммитом замаскировать итоговое состояние. Проверка должна охватывать все коммиты после доверенной базовой точки.

#!/bin/bash
set -euo pipefail

base="${MERGE_BASE_SHA:?missing MERGE_BASE_SHA}"
head="${HEAD_SHA:?missing HEAD_SHA}"

git cat-file -e "${base}^{commit}"
git cat-file -e "${head}^{commit}"

count=0
while IFS= read -r commit; do
  git verify-commit "$commit"
  count=$((count + 1))
done < <(git rev-list --reverse "${base}..${head}")

printf 'verified_commits=%s\n' "$count"

Значение MERGE_BASE_SHA должно вычисляться CI по целевой и сливаемой веткам либо передаваться из надёжного источника. Нельзя просто задавать его как HEAD~1. Для merge-коммитов также необходимо убедиться, что выбор базовой точки соответствует политике команды, иначе можно пропустить объекты, добавленные через второго родителя.

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

Обработка неглубоких клонов, тегов и смены ключей

Неглубокие клоны часто приводят к ложным ошибкам скрипта: если базовый объект отсутствует локально, rev-list не сможет определить полный диапазон. Перед проверкой следует получить недостающую историю целевой ветки и явно убедиться в наличии базового объекта с помощью git cat-file -e. Если базовая точка не найдена, нельзя переходить к проверке только HEAD.

Сценарий Правильное действие Недопустимое упрощение
Базовой точки нет в неглубоком клоне Получить необходимую историю и заново вычислить диапазон Проверить только последний коммит
Релизный тег Использовать подписанный аннотированный тег и выполнить git verify-tag Проверить только имя тега
Переход со старого ключа на новый На короткий срок оставить оба открытых ключа Сразу заменить ключ, из-за чего старые задания перестанут проходить
Компрометация ключа Немедленно удалить ключ и повторно проверить подписанный им диапазон Изменить только отображаемую личность
Участник покинул команду Удалить его запись из списка разрешённых подписантов Только закрыть доступ для повседневного входа

Следует заранее решить, должны ли исторические коммиты продолжать проходить проверку после удаления ключа. Самый простой строгий режим — проверка по текущему списку; он подходит для контроля слияний. Если требуется долгосрочная проверка исторических релизов, необходимо хранить записи доверия с периодами действия, а также архивировать релизный тег, хеш коммита и использованную на тот момент версию списка.

Предварительная проверка и диагностика сбоев

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

При диагностике сначала выполните git verify-commit --raw <hash>, чтобы различить ситуации «объект не подписан», «подпись повреждена» и «открытого ключа нет в списке разрешённых». Затем проверьте настройки репозитория gpg.format и gpg.ssh.allowedSignersFile, а также пространство имён, тип ключа и переводы строк в списке. Это позволяет отделить проблемы авторизации личности от неполной истории Git и не переподписывать без конца один и тот же коммит.

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

Часто задаваемые вопросы

Достаточно ли правильного адреса автора для доверия коммиту?

Нет. Имя и адрес автора являются обычными текстовыми полями и могут быть заданы кем угодно. Подпись подтверждает владение закрытым ключом, а список подписантов связывает ключ с разрешённой личностью.

Можно ли проверять только последний коммит ветки?

Нет. Любой коммит после доверенной базы влияет на итоговое дерево, поэтому git verify-commit должен выполняться для всего диапазона. Подписанный тег выпуска отдельно проверяется через git verify-tag.

Как сменить ключ подписи без остановки сборок?

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

Выделенная среда разработки

Настройте облачный Mac для следующей задачи разработки

Выберите Oak M4, Oak M4 Plus или Oak M4 Pro и настройте тариф аренды на одном из пяти физических узлов с учетом расположения команды.

Настроить облачный Mac