Предварительная проверка прав подписи iOS в облачном Mac
Архивация в Xcode может завершиться успешно, а при загрузке возникнет ошибка из-за несовпадения среды push-уведомлений, связанных доменов или идентификатора приложения. Обычно причина не в компиляции, а в рассогласовании трёх источников: прав, заявленных в проекте, прав, разрешённых профилем подготовки, и прав подписи, фактически записанных в приложение. Облачные Mac часто используются повторно через сценарии автоматизации, и один узел может обслуживать несколько веток и окружений. Поэтому недостаточно проверять файл .entitlements в репозитории — нужно анализировать непосредственно предназначенный для поставки архив .xcarchive.
Сначала определите объект проверки
Ниже предполагается, что архив уже создан. Сначала найдите основное приложение. Не проверяйте временные артефакты из DerivedData напрямую: они могут быть ещё не подписаны окончательной подписью.
ARCHIVE_PATH="$HOME/builds/MyApp.xcarchive"
APP_PATH="$ARCHIVE_PATH/Products/Applications/MyApp.app"
test -d "$APP_PATH"
test -f "$APP_PATH/embedded.mobileprovision"
codesign --verify --strict --verbose=2 "$APP_PATH"
Если приложение содержит расширения, каждое .appex также необходимо проверить отдельно. Успешная проверка основного приложения не гарантирует, что расширение уведомлений, виджет или расширение общего доступа используют совместимые идентификаторы приложений и права.
find "$APP_PATH/PlugIns" -type d -name '*.appex' -print0 2>/dev/null |
while IFS= read -r -d '' item; do
codesign --verify --strict --verbose=2 "$item"
done
Предварительная проверка должна основываться на артефакте, который будет загружен, а не на том, что настройки проекта выглядят корректно. Первое — результат сборки, второе — лишь входные данные.
Извлеките два фактических набора прав
Команда codesign позволяет получить entitlements из окончательной подписи, а security cms — декодировать встроенный в приложение профиль подготовки. Сохраните оба результата в формате plist, чтобы при последующем сравнении не зависеть от интерфейса Xcode.
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
codesign -d --entitlements :- "$APP_PATH" \
> "$WORK_DIR/app-entitlements.plist" 2>/dev/null
security cms -D -i "$APP_PATH/embedded.mobileprovision" \
> "$WORK_DIR/profile.plist"
plutil -lint "$WORK_DIR/app-entitlements.plist"
plutil -lint "$WORK_DIR/profile.plist"
Не сравнивайте приложение напрямую с полями верхнего уровня профиля подготовки. Разрешённые права находятся в словаре Entitlements; такие поля, как время создания и имя, полезны только для дополнительной диагностики и не отражают результат подписи.
Автоматически сравните ключевые поля
Следующий сценарий проверяет пять наиболее распространённых типов проблем. Для строк учитывается область действия с подстановочным знаком * в профиле подготовки; для прав в виде массива требуется, чтобы профиль разрешал каждый элемент, заявленный приложением. При несовпадении сценарий возвращает ненулевой код завершения, поэтому его удобно запускать сразу после архивации.
python3 - "$WORK_DIR/app-entitlements.plist" "$WORK_DIR/profile.plist" <<'PY'
import fnmatch
import plistlib
import sys
with open(sys.argv[1], "rb") as f:
app = plistlib.load(f)
with open(sys.argv[2], "rb") as f:
profile = plistlib.load(f)["Entitlements"]
keys = [
"application-identifier",
"com.apple.developer.team-identifier",
"aps-environment",
"com.apple.developer.associated-domains",
"get-task-allow",
]
def allowed(actual, expected):
if isinstance(actual, list) and isinstance(expected, list):
return all(any(fnmatch.fnmatch(str(v), str(rule)) for rule in expected) for v in actual)
if isinstance(actual, str) and isinstance(expected, str):
return fnmatch.fnmatch(actual, expected)
return actual == expected
failed = []
for key in keys:
if key not in app:
continue
if key not in profile or not allowed(app[key], profile[key]):
failed.append(key)
if failed:
print("Entitlement mismatch:", ", ".join(failed))
raise SystemExit(1)
print("Entitlement preflight passed")
PY
Сценарий сравнивает только поля, которые фактически заявлены приложением. Если правила проекта требуют обязательного наличия определённого права — например, производственная сборка должна содержать aps-environment, — добавьте список обязательных полей и завершайте проверку с ошибкой, если какое-либо из них отсутствует.
Определите источник настройки по симптомам
| Симптом | Что проверить в первую очередь | Распространённая причина |
|---|---|---|
| Ошибка push-уведомлений | aps-environment |
Окружение архива не соответствует назначению профиля подготовки |
| Не работают универсальные ссылки | com.apple.developer.associated-domains |
Домены не попали в окончательную подпись или расширение настроено иначе |
| При загрузке возникает ошибка несовпадения идентификатора | application-identifier |
Смешаны Bundle Identifier, префикс команды или конфигурации Target |
| Релизную сборку по-прежнему можно отлаживать | get-task-allow |
Использована конфигурация сборки, не предназначенная для поставки |
| Не удаётся установить расширение | Права основного приложения и .appex |
Несовместимы идентификатор расширения, группа приложений или набор профилей подготовки |
При ошибке сначала сохраните архив и оба файла plist, а затем проверьте CODE_SIGN_ENTITLEMENTS, PRODUCT_BUNDLE_IDENTIFIER и текущую конфигурацию сборки. Не переподписывайте приложение сразу же: это изменит исходный артефакт и уничтожит наиболее ценные свидетельства расхождений.
Связанные домены и группы приложений представлены массивами. Порядок элементов обычно не имеет значения, поэтому следует проверять включение множеств. Логические поля нужно читать как настоящие логические значения: строку "false" нельзя считать значением false. Именно поэтому для проверки используется plistlib, а не grep.
Включите проверку в приёмку облачной сборки
Сохраните сценарий как Scripts/verify_entitlements.sh и запускайте его после успешного выполнения xcodebuild archive, но до экспорта или загрузки. Для каждого задания используйте отдельный каталог архива, а результаты проверки, сводку подписи приложения и список полей с ошибками сохраняйте как вложения сборки. В журнал записывайте только имена полей и обезличенные идентификаторы; не выводите содержимое закрытых ключей или полные учётные данные доступа.
Рекомендуется разделить приёмку на четыре этапа: проверка структуры пакета, извлечение прав подписи, декодирование профиля подготовки и сравнение с правилами. При сбое на любом этапе дальнейшая поставка должна быть остановлена, но .xcarchive следует сохранить для последующего анализа. В проектах с несколькими Target проверяйте основное приложение и каждое расширение отдельно — использовать для них результаты проверки основного приложения нельзя.
В завершение проведите ручную выборочную проверку: убедитесь, что текущее задание использует ожидаемые Scheme и Configuration, значение get-task-allow в производственном артефакте равно false, среда push-уведомлений соответствует цели поставки, среди связанных доменов нет тестовых адресов, а все расширения прошли независимую проверку подписи. Так ошибки, которые прежде обнаруживались только при загрузке, превращаются в воспроизводимые и отслеживаемые проверки на этапе сборки.
Часто задаваемые вопросы
Почему недостаточно проверить файл entitlements в проекте?
Это только входные данные сборки. Настройки, профиль и процесс подписи могут изменить итоговые права, поэтому проверять нужно приложение внутри архива.
Какие права стоит автоматизировать в первую очередь?
Сначала проверяйте application-identifier, идентификатор команды, aps-environment, связанные домены и get-task-allow.
Запустите следующую сборку в облачном Mac.
Сравните три конфигурации Apple Silicon и выберите подходящий для текущего рабочего процесса регион среди пяти зарубежных узлов.