テスト中は常にAPIへ接続できていたのに、アーカイブをテストチームへ渡した途端、ネットワークエラーが発生することがあります。逆のケースはさらに危険です。証明書の問題を一時的に回避するために設定した NSAllowsArbitraryLoads がリリース構成に残り、Appは正常に戻ったように見えても、本来ATSが拒否すべき接続まですべて許可されてしまいます。この2種類の事故を防ぐには、リポジトリ内のInfo.plistだけでなく、クラウドMacで実際にコンパイルされたAppを検査する必要があります。
ゲートで検査する内容を定義する
実効性のあるATSゲートには、少なくとも静的設定、ホスト側のTLSハンドシェイク、アプリケーション層のリクエストという3つの検査が必要です。それぞれ確認できる内容が異なるため、1回の curl 成功だけですべての検証を代替することはできません。
| レイヤー | 検査対象 | 失敗時に優先して確認する項目 |
|---|---|---|
| 静的設定 | App内の最終的なInfo.plist | ビルド構成、スクリプトによる書き換え、ドメイン例外 |
| TLSプローブ | クラウドMacから接続先エンドポイントまで | DNS、証明書チェーン、プロトコルバージョン、リダイレクト |
| Appからのリクエスト | URLSessionの実際の挙動 | ATS、セッション設定、認証、レスポンス解析 |
まずポリシーを明確にします。リリース成果物では NSAllowsArbitraryLoads を禁止し、NSExceptionDomains には審査済みのドメインだけを記載します。一時的な例外には担当者と削除条件を設定し、ローカルデバッグ専用の設定をReleaseへ混入させてはいけません。
ATS例外は「ひとまず通信できるようにする」ための汎用スイッチではありません。適用範囲、理由、終了計画を明示すべきセキュリティ変更です。
最終ビルド成果物を監査する
ソース内のplistは、INFOPLIST_KEY_*、異なるxcconfig、ビルドスクリプトによって上書きされる可能性があります。まずReleaseビルドを完了し、生成された成果物のパスを取得します。
set -euo pipefail
xcodebuild \
-scheme "$SCHEME" \
-configuration Release \
-sdk iphonesimulator \
-derivedDataPath "$PWD/.derived-data" \
build
APP_PATH="$(find "$PWD/.derived-data/Build/Products" \
-type d -name '*.app' -path '*Release-*' -print -quit)"
test -n "$APP_PATH"
PLIST="$APP_PATH/Info.plist"
plutil -lint "$PLIST"
plutil -extract NSAppTransportSecurity json -o - "$PLIST" \
> "$PWD/ats-effective.json" 2>/dev/null || printf '{}
' > "$PWD/ats-effective.json"
ゲートでは、「ATS辞書が存在しない」場合と「空の辞書が存在する」場合のどちらも正常として扱い、エラーにしないでください。失敗とすべきなのは、広範な通信許可や未承認の例外です。次のスクリプトは許可リストを環境変数で受け取るため、チーム内部のドメインを公開スクリプトへハードコードせずに済みます。
import json
import os
import sys
with open(sys.argv[1], encoding="utf-8") as f:
ats = json.load(f)
if ats.get("NSAllowsArbitraryLoads") is True:
raise SystemExit("NSAllowsArbitraryLoads is forbidden")
approved = {
item.strip().lower()
for item in os.getenv("ATS_APPROVED_DOMAINS", "").split(",")
if item.strip()
}
exceptions = ats.get("NSExceptionDomains", {})
unknown = sorted(set(map(str.lower, exceptions)) - approved)
if unknown:
raise SystemExit("Unapproved ATS domains: " + ", ".join(unknown))
実行コマンドは python3 ci/audit_ats.py ats-effective.json です。CIログにはキー名と検査結果を残しても構いませんが、リクエストトークン、Cookie、完全な認証ヘッダーは出力しないでください。
審査可能な例外一覧を整備する
ドメイン名を比較するだけでは不十分です。例外ごとに、許可するATSキー、使用環境、理由、再確認または削除の条件を記録します。特に注意が必要なのは NSIncludesSubdomains です。この設定は影響範囲をすべてのサブドメインへ広げるため、現在使用しているAPIが1つだけという理由で安易に有効化してはいけません。
一覧はリポジトリ内のJSONまたはYAMLとして管理し、レビュー時に次の項目を確認することを推奨します。
- ドメインは正確に指定し、ワイルドカード形式の記述は認めない;
- プロジェクトの基準を満たさないTLSバージョンへ引き下げない;
- 1件のリダイレクトへ対処するために親ドメイン全体を許可しない;
- デバッグ用エンドポイントはDebug構成だけに含める;
- 例外を削除した後は、ビルドとリクエストのテストを再実行する。
構成間の設定混入を確認する
DebugとReleaseを個別にビルドし、2つの ats-effective.json をエクスポートして差分を比較します。パケットキャプチャやローカルサービスのためだけに用意したキーがReleaseに含まれている場合は、直ちに失敗させます。ソースファイルだけを比較してはいけません。同じplistでも、ビルド設定によって異なる値が注入される可能性があるためです。
TLSとリダイレクトチェーンを検査する
静的監査を通過したら、ビルドを担当するクラウドMacから接続先アドレスを検査します。nscurl はATSの診断マトリクスを出力できるため、プロトコル、証明書チェーン、前方秘匿性の問題を特定するのに適しています。
test -n "${API_URL:-}"
/usr/bin/nscurl --ats-diagnostics "$API_URL" \
> "$PWD/ats-diagnostics.txt" 2>&1
この出力は診断には有用ですが、ファイル内に「PASS」があるかどうかだけでゲートを判定する用途には適しません。診断モードでは、条件を段階的に緩和した複数の組み合わせが試されるためです。CIの合否判定では、プロジェクトが実際に使用するURLへ制御されたリクエストを送信し、タイムアウト、リダイレクト回数、レスポンスコードを制限します。
curl --fail --silent --show-error \
--proto '=https' \
--tlsv1.2 \
--max-time 15 \
--max-redirs 3 \
--output /dev/null \
"$API_URL"
curl の成功から分かるのは、ホスト側の通信経路が利用可能であることだけです。iOS AppのATS設定は適用されないため、Appのセッションデリゲート、リクエストヘッダー、認証ロジックが正しいことも証明できません。
アプリケーション層の回帰テストを追加する
最後に、本番用のネットワークスタックを使用する軽量なテストターゲットを追加します。テスト環境から注入されたURLを読み取り、URLSession でヘルスチェックを実行してください。そのうえで、リクエストが完了すること、ステータスコードが取り決めどおりであること、HTTPSではないアドレスへリダイレクトされないことを検証します。テストコードに固定の認証情報を書いてはいけません。
必要十分な証跡だけを保存する
失敗時にアーカイブするのは、最終的なATS辞書、ドメイン一覧の比較結果、nscurl の出力、リクエストのステータスコード、リダイレクト先のホスト名、Xcodeのビルド構成名で十分です。証明書の本文、アクセストークン、完全なレスポンス本文を長期ログへ保存する必要は通常ありません。
実行順序は「Releaseビルド → 最終plist監査 → TLSプローブ → URLSessionテスト」に固定します。これにより、設定のドリフトを検出できるだけでなく、Appのポリシーによる接続拒否なのか、接続先エンドポイントの証明書チェーン、DNS、リダイレクトが変化したのかを切り分けられます。
リリース前チェックリスト
マージ前に、リリース成果物で任意のネットワーク通信が許可されていないこと、すべてのドメイン例外が承認済み一覧に含まれていること、サブドメインの適用範囲が明示的にレビューされていること、TLSプローブが実際の接続先アドレスを使用していること、アプリケーション層のテストが実際のURLSession設定を通っていることを確認します。失敗が発生した場合は、設定を変更する前に成果物と診断ファイルを保存してください。証明書やリダイレクトの問題を、新しい例外の追加で覆い隠してはいけません。
このゲートの目的は、あらゆるネットワーク障害を自動的に解消することではありません。障害を対処可能なレイヤーへ切り分け、一時的なデバッグ設定が次回のリリースへひそかに混入するのを防ぐことです。
よくある質問
ソースのInfo.plistだけでは不十分なのはなぜですか?
ビルド設定やスクリプトで最終値が変わるためです。実際に配布されるAppバンドル内のInfo.plistを検査する必要があります。
nscurlが成功すればAppの通信も保証されますか?
保証されません。ホスト側のTLS確認に加えて、URLSessionでATSポリシー、リダイレクト、認証経路を検証します。
CIを失敗させるべきATS設定は何ですか?
NSAllowsArbitraryLoadsと、承認済みリストにないドメイン例外です。デバッグ専用の例外が配布版へ入った場合も失敗させます。
クラウドMac miniで次の開発・ビルドタスクを実行
2種類のM4構成、4つのレンタル期間、販売中の5つのノードから選択できます。実際の利用可能状況は、コンソールにリアルタイムで表示される情報をご確認ください。