通常のiOSアップデートでも、ログイントークンが突然「存在しない」状態になることがあります。新版自体はクラッシュせず、APIも正常に動作しているにもかかわらず、実際の変更点がKeychainのservice、account、アクセスグループ、または保護属性に隠れているケースです。クラウドMac上でこうした問題を安定して検出するには、新規インストールだけをテストするのではなく、「旧版で書き込み、新版で読み取る」状態からテストを始める必要があります。
まず移行契約を定義する
Keychain項目を、互換性を維持すべきデータ構造として扱います。少なくとも、項目クラス、service、account、access group、kSecAttrAccessibleの5項目を記録してください。新版で名前を変更する場合も、検索条件をそのまま新しいものに置き換えてはいけません。まず旧条件で読み取り、新しい構造で書き込み、成功を確認してから旧項目を削除します。
移行ごとにkeychainSchemaVersion = 2のような整数バージョンを割り当て、完了フラグを通常の環境設定に保存することを推奨します。このフラグは移行の重複実行を防ぐためのものであり、Keychainデータが実際に存在することを保証するものではありません。アプリの起動時には引き続き移行先の項目を確認し、「フラグは存在するがデータは消去されている」状態にも対処する必要があります。
移行の成功基準は、関数が成功を返すことではありません。新版で旧データを読み取れること、繰り返し実行してもデータを壊さないこと、失敗しても復旧可能な旧項目が残ることが必要です。
まず、最小限の受け入れテストマトリクスを用意します。
| シナリオ | 初期状態 | 期待結果 |
|---|---|---|
| 新規インストール | 旧項目なし | 新版の項目を作成 |
| 上書きアップデート | 旧版の項目が完全な状態 | 移行後も読み取り可能 |
| 再起動 | 移行完了済み | 重複して書き込まない |
| 旧項目の破損 | データ形式が不正 | 現場を保持し、明確なエラーを返す |
| アクセスグループの変更 | 新版から旧グループにアクセスできない | リリースを阻止し、署名設定を確認 |
旧版と新版のテストフィクスチャを作成する
インストール可能なシミュレータ用.appを2つ保持します。旧版はデータの投入を担当し、新版は移行と検証を担当します。2つのビルド間でeraseやアンインストールを実行しないでください。テストの前提条件が変わってしまいます。-UITestSeedLegacyKeychainや-UITestVerifyKeychainV2のように、自動化環境でのみ有効になる起動引数をテスト用ビルドに追加します。製品版ビルドは、これらの引数に反応してはいけません。
set -euo pipefail
UDID="${SIMULATOR_UDID:?missing simulator id}"
OLD_APP="${OLD_APP_PATH:?missing old app path}"
NEW_APP="${NEW_APP_PATH:?missing new app path}"
BUNDLE_ID="com.example.product"
xcrun simctl bootstatus "$UDID" -b
xcrun simctl install "$UDID" "$OLD_APP"
xcrun simctl launch --terminate-running-process \
"$UDID" "$BUNDLE_ID" -UITestSeedLegacyKeychain
xcrun simctl install "$UDID" "$NEW_APP"
xcrun simctl launch --terminate-running-process \
"$UDID" "$BUNDLE_ID" -UITestVerifyKeychainV2
データ投入処理は検証可能にしておく必要があります。旧版で書き込んだ後、テストUIから機密値を含まないステータスファイルを出力し、項目のバージョン、書き込み結果、ランダムなテストケース番号だけを記録します。新版での検証完了後にも別の結果ファイルを書き出します。CIではステータスのみを比較し、トークンの内容は出力しません。
並行実行による汚染を防ぐ
同じシミュレータ上で、複数のKeychainテストを並行実行しないでください。ジョブごとに専用のシミュレータを作成するか、テストスイート単位で直列実行します。serviceとaccountにはランダムなテストケース接尾辞を追加し、前回の異常終了で残ったデータによって次のテストが誤って成功と判定されるのを防ぎます。
コード内に診断可能な結果を残す
すべての失敗をnilに変換してはいけません。OSStatusを保持すれば、未検出、権限不一致、パラメータエラーを区別できます。検索関数では、結果を明示的な型で返すことができます。
enum KeychainReadResult {
case found(Data)
case missing
case failed(OSStatus)
}
func readLegacyToken(service: String, account: String) -> KeychainReadResult {
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account,
kSecReturnData: true,
kSecMatchLimit: kSecMatchLimitOne
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
if status == errSecItemNotFound {
return .missing
}
guard status == errSecSuccess, let data = item as? Data else {
return .failed(status)
}
return .found(data)
}
移行時は、まず新版の項目を追加し、それを再度読み取って内容のダイジェストを比較した後、最後に旧項目を削除します。先に削除してから書き込んではいけません。権限やパラメータの問題で書き込みに失敗すると、ユーザーデータを復旧できなくなります。ログにはステータスコード、移行バージョン、テストケース番号だけを記録し、元の認証情報は記録しません。
署名とアクセスグループを確認する
コードを変更していないのに検索結果が突然errSecItemNotFoundになった場合は、新旧成果物のentitlementsを比較してください。アクセスグループのプレフィックス、アプリケーション識別子、Keychain access groupsのいずれかが変わると、新版から元の項目が見えなくなる可能性があります。
codesign -d --entitlements :- "$OLD_APP_PATH" > old-entitlements.plist
codesign -d --entitlements :- "$NEW_APP_PATH" > new-entitlements.plist
diff -u old-entitlements.plist new-entitlements.plist
差分ファイルはCIのアーティファクトとして保存しますが、署名情報をビルドログへコピーしてはいけません。アクセスグループを実際に変更する必要がある場合は、旧グループと新グループの両方へアクセスできる移行用バージョンを設計し、そのバージョンでデータを移してください。新版が任意の旧グループを直接読み取れると仮定してはいけません。
シミュレータは移行アルゴリズムやエラー分岐の検証には適していますが、署名やアクセス制御に関するすべての挙動を再現できるわけではありません。リリース前には、管理された実機でも同じ旧版のインストール、データ投入、上書きアップデートの手順を一度実行し、結果をシミュレータのテストとは分けて記録してください。
クリーンアップ、失敗時の調査、リリースゲート
成功したテストの終了後は、アプリ内のテスト用インターフェースを通じて、正確なserviceとaccountを指定してテスト項目を削除します。広範囲を対象とする削除クエリは使用しないでください。同じテストデバイス上にある別のデータまで誤って消去するおそれがあります。失敗したテストでは、まずシミュレータを保持し、新旧両バージョンのentitlements、OSStatus、移行手順、アプリログを収集してから、破棄するかどうかを判断します。
リリースゲートでは、次の3点を確認する必要があります。旧版のデータを新版で読み取れること、移行を連続して2回実行しても同じ結果になること、異常な入力によって旧項目が削除されないことです。いずれか1つでも失敗した場合は、成果物が次の配布段階へ進まないようにしてください。
最後に、移行スクリプトの入力を固定して保存します。対象は旧版のビルド識別子、新版のビルド識別子、シミュレータのOSバージョン、テストケース番号です。これにより、クラウドMacが再初期化された場合でも、アーカイブ済みの成果物から同じアップデート経路を再現できます。Keychain問題で最も難しいのは書き込みではなく、旧状態が残っていないことです。旧版のフィクスチャ、署名の差分、エラーコードを再現できれば、移行は本番環境のリスクではなく、通常の回帰テストとして扱えるようになります。
よくある質問
新規インストールのテストだけでは不十分なのはなぜですか?
現行版が新しい項目を書き込めることしか確認できず、旧版が保存したservice、account、アクセスグループ、保護属性を新版が読めるか検証できないためです。
Keychain移行に失敗したとき最初に確認する項目は何ですか?
SecItemCopyMatchingのOSStatusを保存し、新旧ビルドのentitlements、アクセスグループ、service、account、kSecAttrAccessibleを比較します。調査前に旧項目を削除してはいけません。
シミュレータだけで最終確認できますか?
できません。検索と移行ロジックの継続試験には適していますが、署名やアクセス制御を含む最終確認は管理された実機でも実施する必要があります。
開発パイプライン向けに固定クラウドMacを構成
MangoVMでは、M4とM4 Proの2種類のApple Silicon専有物理ノードを提供しています。利用期間、リージョン、ストレージの追加オプションを選択すると、注文内容をすべて確認できます。