У fastlane match есть как минимум четыре независимые точки отказа: доступ к хранилищу, расшифровка, установка полной подписывающей личности и соответствие Provisioning Profile целевому приложению — эти границы разделены в официальной документации match. Поэтому при ошибке подписи fastlane match не запускайте match nuke и не отзывайте сертификаты вслепую. На этой неделе сохраните обезличенный лог, последовательно проверьте хранилище, Keychain, Profile, entitlements и режим readonly, а затем подтвердите восстановление отдельным Archive.

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

Диагностическая карта отказа

Начните не с исправления, а с классификации. Оставьте в журнале дату запуска, код возврата команды, название Scheme и Configuration, но замените реальные значения на [REPO_URL], [TEAM_ID], [BUNDLE_ID], [KEYCHAIN_PATH] и [TOKEN]. Пароли, токены, deploy key, адреса приватных хранилищ и содержимое переменных среды не должны попадать в отчёт.

Слой проблемы Что проверяется Признак в журнале Первое действие
Хранилище и расшифровка Git или объектное хранилище, ветка, Team, пароль шифрования отказ авторизации, неверная расшифровка, отсутствие файлов проверить отдельные учётные данные и настройки match
Keychain сертификат, приватный ключ, доступ процесса найден сертификат без ключа или identity не определяется запросить список identities в ожидаемом Keychain
Profile и Target Bundle ID, Team, тип профиля, entitlements профиль не подходит текущему Target сопоставить Profile с настройками проекта
Верификация подписи Archive, экспорт, выбранная конфигурация архив собран, но не проходит проверку или экспорт повторить минимальную сборку с теми же параметрами

Если ошибка появляется до установки файлов, проблема находится не в Xcode-проекте. Если сертификат виден, но команда подписи не находит приватный ключ, переходите к Keychain. Если identity существует, проверяйте Profile и entitlements. Такой порядок не даёт принять сообщение No signing certificate за доказательство того, что сертификат нужно немедленно отзывать.

Официальная инструкция fastlane по устранению проблем с code signing также разделяет получение, импорт и использование подписывающих материалов. Это полезнее, чем искать одну универсальную причину по последней строке лога.

Минимальный набор доказательств

Соберите четыре независимых результата:

  • обезличенный вывод шага match;
  • статус доступа к хранилищу и расшифровки;
  • список подписывающих identities в конкретном Keychain;
  • результат минимального Archive и последующего экспорта с теми же параметрами.

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

Доступ к хранилищу и расшифровке

Когда fastlane match не находит сертификат

Сначала отделите отсутствие сертификата от невозможности получить хранилище. Исходный код может успешно скачиваться с одного сервера, тогда как репозиторий подписей использует другую учётную запись, отдельный deploy key, другую ветку или иной способ хранения. Успешный git clone проекта не доказывает наличие прав на хранилище, где лежат зашифрованные сертификаты и профили.

Проверьте следующие параметры, не раскрывая их значения:

  • URL или тип хранилища;
  • ветку, если она задана отдельно;
  • Team ID и идентификатор приложения;
  • переменную MATCH_PASSWORD;
  • срок действия токена или ключа доступа;
  • права процесса CI на чтение хранилища.

Не подставляйте пароль прямо в команду. Временный тест должен выполняться через защищённую переменную среды, а в логах должны появляться только [MATCH_PASSWORD] и [STORAGE_TOKEN]. Если хранилище доступно, но расшифровка завершается ошибкой, повторное создание сертификата не решит проблему: сначала нужно восстановить правильный секрет или проверить, что CI использует ожидаемую ветку.

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

Проверка Что считается положительным результатом Что не следует делать
Авторизация хранилища CI получает только чтение нужной ветки или объекта не копировать личный токен разработчика в общие секреты
Расшифровка файлы извлекаются без публикации секрета в логе не менять пароль наугад несколько раз подряд
Team и Bundle ID полученные материалы относятся к нужной команде и приложению не выбирать профиль другого приложения «для проверки»
Режим readonly задача использует уже созданные материалы не ожидать, что readonly создаст отсутствующий профиль

Несколько Bundle ID

Для нескольких приложений и расширений важна не только общая папка подписей, но и точное соответствие каждого Bundle ID. В одном запуске могут участвовать основное приложение, виджет, notification extension и отдельное macOS-приложение. У каждого Target должен быть понятный набор профилей, сертификатов и entitlements.

Составьте внутреннюю таблицу связи:

Target Bundle ID Тип Profile Конфигурация Способ подписи
Основное приложение [APP_BUNDLE_ID] [PROFILE_TYPE] [CONFIGURATION] ручная или автоматическая
Расширение [EXT_BUNDLE_ID] [PROFILE_TYPE] [CONFIGURATION] ручная или автоматическая
Второе приложение [SECOND_BUNDLE_ID] [PROFILE_TYPE] [CONFIGURATION] ручная или автоматическая

Значения в таблице должны совпадать с проектом, а не только с переменными fastlane. Если один профиль случайно сопоставлен с несколькими Targets, локальная автоматическая подпись может скрыть ошибку, тогда как командный Archive выберет другой материал.

Полная подписывающая личность в Keychain

Сертификат сам по себе не является рабочей подписывающей личностью. Для подписи нужен сертификат вместе с соответствующим приватным ключом, доступным процессу, который выполняет Archive. Определения Apple о связи сертификатов, ключей и профилей изложены в документации Apple о распространении приложений.

Когда сертификат есть, а приватного ключа нет

Проверьте, в какой именно Keychain импортированы материалы. На удалённом Mac интерактивный сеанс разработчика и пользователь CI могут использовать разные хранилища, а команда может обращаться не к тому пути. Сопоставьте:

  • имя пользователя, запускающего задачу;
  • активный Keychain и его порядок поиска;
  • наличие сертификата;
  • наличие связанного приватного ключа;
  • возможность процесса прочитать ключ без диалога рабочего стола.

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

security find-identity -v -p codesigning "[KEYCHAIN_PATH]"
security list-keychains -d user
security default-keychain -d user

[KEYCHAIN_PATH] — это явная подстановка, а не команда для копирования без изменений. Не публикуйте полный вывод, если он содержит имена команды или внутренние пути.

Если после импорта сертификата identity не появляется, повторная загрузка одного и того же .cer не поможет: в нём нет приватного ключа. Нужен корректный экспорт подписывающей пары или повторная установка материала через утверждённый процесс match.

Перезапуск удалённого Mac

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

Вместо этого зафиксируйте безопасную процедуру:

  • отдельный Keychain для задачи, если это предусмотрено вашей моделью угроз;
  • защищённое хранение пароля Keychain;
  • явное открытие нужного хранилища перед match;
  • проверка доступа от имени пользователя CI;
  • повторная проверка после перезапуска;
  • удаление временных экспортов и секретов после выполнения.

Команды и пути зависят от настройки macOS и используемого процесса. Поэтому применяйте их только после проверки документации вашей версии инструментов и политики доступа. Цель — не сделать Keychain максимально открытым, а доказать, что конкретный процесс получает минимально необходимый доступ.

Provisioning Profile и настройки Target

Несовместимый Profile

Provisioning Profile связывает приложение, команду, тип использования, сертификаты и разрешённые entitlements. Подробное устройство этого файла описано в технической заметке Apple о Provisioning Profile. Проверьте каждую связь, а не только имя файла.

Измерение Что сравнить
Bundle ID идентификатор Profile и идентификатор текущего Target
Team команда в профиле и команда проекта
Тип development, distribution или другой требуемый тип
Сертификат профиль допускает используемую подписывающую личность
Entitlements разрешения Profile соответствуют настройкам Target
Срок и статус профиль не истёк и не удалён в кабинете

Profile может отсутствовать, быть недействительным или существовать, но не выбираться проектом. Это три разные ситуации. В справке Apple по управлению профилями описаны операции редактирования, загрузки и удаления; не удаляйте рабочий профиль до того, как установите, какой Target его использует.

Для расширений особое внимание уделите application-identifier, ключам доступа к группам, push-уведомлениям и другим entitlements, которые реально включены в проекте. Не переносите профиль основного приложения на расширение только потому, что у них похожее имя.

Режим readonly в CI

readonly означает, что автоматическая задача должна получить и установить уже существующие материалы, а не создавать или обновлять их. Если Profile отсутствует в хранилище, этот режим не заменит действие администратора. В рекомендациях fastlane по непрерывной интеграции разделяйте подготовку подписей и обычную сборку.

Рабочая схема выглядит так:

  1. Администратор или доверенный оператор создаёт либо обновляет нужный материал.
  2. Результат проходит проверку Bundle ID, Team, типа и entitlements.
  3. Материал добавляется в централизованное хранилище.
  4. CI запускает match в режиме чтения.
  5. Только после успешной установки начинается Archive.

Если readonly не находит Provisioning Profile, не отключайте его ради случайного создания профиля в CI. Сначала выясните, отсутствует ли файл, используется ли неправильная ветка или передан другой Bundle ID. Иначе разные задачи начнут создавать несовместимые варианты, а причина расхождения станет труднее отслеживаться.

Локальная сборка и удалённый Archive

Локальный Xcode может использовать автоматическую подпись и уже сохранённые материалы, а удалённая команда — явные настройки проекта, другой Scheme и другой пользовательский контекст. Поэтому сравнивайте не компьютеры в целом, а одну воспроизводимую цепочку.

Закрепите:

  • один и тот же commit;
  • одну Scheme;
  • одну Configuration;
  • один путь к активному Xcode;
  • одинаковый экспортный файл [EXPORT_OPTIONS_PLIST];
  • один Bundle ID и Team;
  • один порядок: сначала match, затем Archive.

Условный фрагмент lane должен использовать только заменяемые значения:

lane :archive_release do
  match(
    type: "[PROFILE_TYPE]",
    readonly: true,
    app_identifier: ["[BUNDLE_ID]"]
  )

  build_app(
    scheme: "[SCHEME_NAME]",
    configuration: "[CONFIGURATION]",
    export_options: "[EXPORT_OPTIONS_PLIST]"
  )
end

Это не готовая конфигурация для копирования: параметры нужно сверить с проектом и текущей документацией fastlane. Если локально включена автоматическая подпись, а CI ожидает ручное сопоставление, одинаковый исходный код не гарантирует одинаковый результат.

Сначала выполните минимальный Archive без публикации. Затем проверьте экспортированный пакет и подпись. Только после этого переходите к загрузке. Так вы отделите проблему создания архива от ошибки экспортных параметров или учётных данных публикации.

Исправление, ротация и восстановление

Когда достаточно исправить конфигурацию

Оставайтесь в текущем наборе активов, если причина подтверждена как:

  • неверный URL, ветка или секрет хранилища;
  • неправильный Team ID или Bundle ID;
  • отсутствующий доступ к нужному Keychain;
  • ошибка сопоставления Target и Profile;
  • случайное включение readonly до публикации нового профиля.

После изменения одной причины повторите тот же Archive. Не меняйте одновременно сертификат, профиль, Scheme и экспортные параметры — иначе вы не узнаете, что именно сработало.

Когда нужна контролируемая ротация

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

  • приложение и его Bundle ID;
  • Targets и расширения;
  • сертификаты и связанные профили;
  • рабочие задачи CI;
  • ветки и хранилища;
  • ближайшие релизные операции.

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

Почему match nuke — крайний вариант

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

Перед такой процедурой подтвердите одновременно:

  • активы действительно недоступны или скомпрометированы;
  • область затронутых приложений известна;
  • есть резервная карта зависимостей;
  • назначен ответственный за повторное создание;
  • есть окно для проверки Archive, экспорта и публикации.

Для обычной ошибки доступа, неправильного профиля или закрытого Keychain nuke не является ремонтом.

Контрольная временная шкала восстановления

Сделайте восстановление измеримым по этапам:

  • Этап диагностики: журнал обезличен, причина отнесена к одному слою, секреты не раскрыты.
  • Этап доступа: хранилище читается, расшифровка проходит, ветка и Team подтверждены.
  • Этап подписи: в нужном Keychain видна полная identity с приватным ключом.
  • Этап проекта: каждый Target получает подходящий Profile, а entitlements совпадают.
  • Этап Archive: используется тот же commit, Scheme, Configuration и export options.
  • Этап устойчивости: после перезапуска удалённого Mac процесс CI снова получает Keychain и завершает тестовую сборку.
  • Этап публикации: загрузка запускается только после успешной проверки архива и экспорта.

Если проблема возникла после временного сброса машины или Keychain не переживает перезапуск, сравните текущую среду с руководством по приёмке удалённой CI-среды Mac. Для самой миграции сертификатов полезно заранее пройти проверку переноса iOS-сертификатов, а цепочку автоматической публикации сверить с руководством fastlane для TestFlight. Эти ссылки стоит использовать как соседние материалы, но фактические параметры вашей среды всё равно нужно подтвердить собственными журналами.

Если текущая машина часто пересоздаётся, у вас нет гарантии сохранения Keychain, а задачи прерываются во время сборки, локальный ноутбук или случайный CI-агент становятся слабым звеном: состояние подписи теряется, ручной вход скрывает проблему, а повторное создание профилей увеличивает риск для нескольких приложений. В такой ситуации аренда удалённого Mac у VMSPIN имеет смысл как постоянная среда для проверки — возьмите отдельную неэкстренную ветку, выполните match, Archive и экспорт, перезапустите машину и убедитесь, что та же цепочка проходит без ручного входа. Если же вам нужен постоянный тяжёлый workload, физический USB-доступ или устройство для ежедневной локальной работы, покупка собственного Mac может оказаться более подходящей.

Для временного проекта, миграции CI или независимого разработчика, которому требуется Mac только для подписания и публикации, VMSPIN позволяет сначала проверить именно устойчивость цепочки, а не обещания конфигурации. Критерий принятия простой: после перезапуска удалённого Mac Keychain доступен разрешённому процессу, readonly получает ожидаемые материалы, а Archive и экспорт завершаются на том же commit.