Командные сертификаты подписи на Cloud Mac: fastlane match и разблокировка Keychain без участия человека

Security ·~5 мин чтения

Командные сертификаты подписи на Cloud Mac: fastlane match и разблокировка Keychain без участия человека

Командные сертификаты подписи на Cloud Mac: fastlane match и разблокировка Keychain без участия человека

Два часа ночи, третий человек в команде запускает fastlane build на одном и том же облачном Mac — и CI тут же валится с ошибкой: сертификат недействителен. Все трое по отдельности гоняли match локально, и никто не заметил, что его собственный запуск вытеснил из системного дефолтного Keychain сертификат подписи, который только что импортировал коллега. Такая авария почти неизбежна, когда несколько человек собирают сборки на одном арендованном облачном Mac — в этой статье описано, как мы централизовали сертификаты через fastlane match и решили проблему с бесконечными запросами пароля от Keychain в headless-сессиях по удалённому доступу.

Проблема: почему на общем облачном Mac сертификаты постоянно ломаются

При локальной разработке Mac обычно обслуживает одного человека, и сертификаты в дефолтном Keychain никогда друг с другом не конфликтуют. Но арендованный посуточно облачный Mac часто используется командой совместно: сегодня вы подключаетесь и собираете сборку, завтра коллега логинится удалённо, меняет конфиг и запускает сборку снова. Если каждый по отдельности выполняет fastlane match development или вручную импортирует p12, записи сертификатов в дефолтном Keychain системы будут постоянно перезаписываться, удаляться и создаваться заново — итогом становится типичная ошибка «сертификат есть, но приватный ключ для подписи не найден».

Корень проблемы в том, что смешаны две разные вещи: хранение и распространение сертификатов и временная авторизация сертификата в локальном Keychain. Первое должно управляться централизованно, второе — изолироваться по сессиям.

Ключевая идея fastlane match

match хранит сертификаты разработки, продакшн-сертификаты и соответствующие profile-файлы в зашифрованном виде в отдельном Git-репозитории, и все участники команды (а также автоматизированные пользователи на облачном Mac) забирают один и тот же набор сертификатов из этого репозитория, а не генерируют их заново каждый в своём Apple Developer-аккаунте. Это гарантирует одинаковый результат подписи для одного и того же Bundle ID на любой машине и избавляет от старой проблемы «превышен лимит числа сертификатов Apple».

Ценность match не в том, что он «автоматически генерирует сертификаты», а в том, что «больше никому не нужно генерировать сертификаты самому» — именно эта формулировка определяет режим использования: операцию записи должен выполнять только один человек (или одна инициализирующая задача CI), во всех остальных случаях сертификаты забираются в режиме только для чтения.

Инициализация репозитория сертификатов на облачном Mac

В директории проекта на вашем облачном Mac сначала убедитесь, что Matchfile указывает на приватный репозиторий, а не на публичный адрес:

git_url("git@github.com:your-org/certs-private.git")
storage_mode("git")
type("appstore")

Первичная инициализация выполняется один раз только на одной доверенной машине:

fastlane match appstore --readonly false

После этого все операции получения сертификатов на облачных Mac должны выполняться с флагом --readonly true, чтобы гарантировать, что они только читают уже существующие сертификаты и не пытаются сгенерировать новые:

fastlane match appstore --readonly true

Для деплой-пользователя стоит выдать отдельный read-only Git deploy key, а не складывать на облачный Mac личные SSH-ключи всех участников команды — так границы прав становятся понятнее, и при смене состава команды не нужно отзывать ключи по одному.

Разблокировка Keychain без пароля в headless-сессиях

На облачный Mac в основном подключаются удалённо через SSH или VNC, а не физически сидя перед экраном и вводя пароль вручную, поэтому дефолтный логин-Keychain системы между сессиями часто оказывается заблокирован — при запуске codesign тут же выскакивает диалог авторизации, что в сценарии автоматического срабатывания CI без присутствия человека приводит к полному зависанию процесса.

Решение — создать отдельный Keychain специально для подписи, изолированный от логин-Keychain:

security create-keychain -p "$KEYCHAIN_PWD" signing.keychain
security set-keychain-settings -lut 21600 signing.keychain
security unlock-keychain -p "$KEYCHAIN_PWD" signing.keychain
security import cert.p12 -k signing.keychain -P "$P12_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PWD" signing.keychain
list=$(security list-keychains -d user | tr -d '"')
security list-keychains -d user -s signing.keychain $list

Шаг set-key-partition-list легко упустить из внимания, но без него codesign всё равно будет выдавать запрос авторизации, даже если Keychain уже разблокирован. В этом специальном Keychain рекомендуется держать только сертификаты, нужные для подписи, не смешивая их с обычными паролями — при завершении аренды или передаче проекта его гораздо проще и чище удалить целиком, чем чистить дефолтный Keychain вручную.

Получение сертификатов и очистка после срабатывания CI

Автоматизированный процесс рекомендуется выполнять в следующем порядке, и любая ошибка на каждом из шагов должна немедленно останавливать пайплайн, а не позволять ему продолжать выполнение:

Шаг Команда/действие Обработка при ошибке
Разблокировка отдельного Keychain security unlock-keychain Немедленная остановка, создание тикета вместо тихого повтора
Получение сертификатов (read-only) fastlane match … --readonly true Остановка с подсказкой сначала выполнить запись на основной машине
Выполнение сборки xcodebuild archive Сохранение логов сборки для последующего анализа
Удаление временного Keychain security delete-keychain signing.keychain (по необходимости) Логирование результата очистки

Если облачный Mac арендуется посуточно и после использования передаётся другой задаче или возвращается провайдеру, финальный шаг очистки особенно важен: отдельный Keychain для подписи, временно скачанные p12-файлы и profile-файлы в ~/Library/MobileDevice/Provisioning Profiles рекомендуется полностью удалять по завершении задачи — не стоит рассчитывать, что следующая аренда автоматически получит чистое окружение. Соответствие конкретной модели и узла требованиям вашей команды по параллельной сборке пакетов стоит проверять в панели управления перед планированием — скорость компиляции существенно отличается в зависимости от чипа, и это напрямую влияет на время ожидания в очереди при одновременной работе нескольких участников.

Командное взаимодействие и границы прав доступа

Чёткое разделение обязанностей экономит массу времени на последующий поиск причин сбоев:

Чек-лист перед релизом

После разделения управления сертификатами и разблокировки Keychain команда, поочерёдно собирающая пакеты на одном облачном Mac, практически перестаёт наступать друг другу на грабли, а новым участникам проекта достаточно один раз забрать репозиторий и выполнить read-only команду, без необходимости заново запрашивать сертификаты.

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

Можно ли хранить репозиторий сертификатов match в публичном Git-репозитории?

Нет. match шифрует p12-сертификаты и профили provisioning перед сохранением, но публичный репозиторий всё равно раскрывает структуру и метаданные. Используйте приватный репозиторий и выдавайте пользователю деплоя на Cloud Mac только токен с доступом на чтение.

Нужно ли вводить пароль Keychain после каждой перезагрузки Cloud Mac?

Нет. Создайте отдельный Keychain для подписи через security create-keychain с фиксированным паролем, отключите автоблокировку через security set-keychain-settings, разрешите codesign доступ через -A без подтверждений, и после каждого нового входа в сессию запускайте скрипт разблокировки один раз.

Будут ли конфликтовать сертификаты, если несколько человек собирают проект на одном Cloud Mac?

Да, если каждый запускает match отдельно, стандартный Keychain многократно импортируется и экспортируется, что создаёт конфликты. Выделите каждому отдельную директорию сборки и свой файл Keychain, а также используйте режим readonly в match, чтобы он только читал существующие сертификаты, а не создавал новые.

HireVPS Cloud Mac

Попробуйте выделенный облачный Mac mini уже сегодня

Аренда от одного дня, доступ по SSH/VNC за 2 минуты, конфигурацию можно повысить в любой момент.

Арендовать Mac mini