チームが外部コントリビューターからのPRを受け付けるようになると、クラウドMac CIで考慮すべき問題は、単に「コードをコンパイルできるか」だけではなくなります。ビルドスクリプト自体も審査対象となる入力です。環境変数の読み取り、ユーザーディレクトリの検索、共有キャッシュの変更、さらには後続ジョブで使用するツールの置き換えまで実行できる可能性があります。したがって、セキュリティ目標を明確にする必要があります。外部PRではコンパイルとテストを検証できる一方、リリース用の認証情報にはアクセスできず、生成物をそのまま正式リリースへ送ることもできない構成にします。
2つの信頼経路を分離する
ジョブを「シークレットなしの検証」と「信頼済みリリース」という2つの経路に分けます。前者は外部PRを受け入れ、依存関係の解決、コンパイル、静的解析、および実際の認証情報を必要としないテストだけを実行します。後者は、レビューとマージが完了した固定コミットのみを受け入れ、署名、アーカイブ、配布を担当します。
VMArmの専有物理ノードでは、この2つの経路に別々のmacOSシステムユーザーを割り当てられます。検証ユーザーは管理者グループに追加せず、リリース証明書を保存せず、リリースユーザーのホームディレクトリも読み取れないようにします。リリースユーザー側でも、検証ユーザーが書き込めるスクリプト、キャッシュ、ツールディレクトリを再利用してはいけません。
システムユーザーによる分離は、仮想マシンの境界と同等ではありません。通常のスクリプトが誤って認証情報を読み取ったり、ファイルを汚染したりする可能性は大幅に減らせますが、システムアップデート、最小権限、不審なコードに対する人手でのレビューを代替するものではありません。
推奨する境界は次のとおりです。
| 項目 | シークレットなしの検証 | 信頼済みリリース |
|---|---|---|
| コードの取得元 | 外部PRの固定コミット | レビュー済みブランチの固定コミット |
| コード署名 | 無効 | リリース手順に従って有効化 |
| ワークスペース | 毎回新規作成 | 専用ディレクトリ |
| キャッシュ | 破棄可能、信頼境界を越えて共有しない | 信頼済みジョブ間のみ共有 |
| ビルド成果物 | 検証のみに使用 | 再ビルド後に配布 |
| 認証情報 | 注入しない | 必要な間だけ一時的に注入 |
最小権限のワークスペースを用意する
runnerがユーザーのホームディレクトリ直下で継続的にビルドしないようにします。検証ジョブ専用のルートディレクトリを作成し、ほかのローカルユーザーが読み取れないようにしてください。ジョブ開始時には一時ディレクトリを使用し、成功・失敗にかかわらず終了時に削除します。
set -euo pipefail
umask 077
RUN_ROOT="$HOME/ci-untrusted"
mkdir -p "$RUN_ROOT"
chmod 700 "$RUN_ROOT"
WORK_DIR="$(mktemp -d "$RUN_ROOT/job.XXXXXX")"
cleanup() {
chmod -R u+rwX "$WORK_DIR" 2>/dev/null || true
rm -rf "$WORK_DIR"
}
trap cleanup EXIT INT TERM
cd "$WORK_DIR"
printf 'user=%s\nworkspace=%s\n' "$(id -un)" "$WORK_DIR"
rm -rfを実行する前に、対象パスが現在のジョブで作成した一時ディレクトリであることを必ず確認してください。ブランチ名を直接連結してパスを作ってはいけません。ブランチ名にはスラッシュ、空白、意図的に細工された文字が含まれる可能性があります。表示用テキストには使えても、未処理のままファイルパスに組み込むのは不適切です。
ソースコードをチェックアウトしたらコミットハッシュを記録し、ログにも出力します。後続の信頼済み経路では、この不変の識別子を使ってコードを改めて取得しなければなりません。その後も移動する可能性があるブランチ名だけに依存してはいけません。
ビルド実行前に環境内にシークレットがないことを確認する
「シークレットを明示的に使用していない」ことと、「ジョブからシークレットを読み取れない」ことは同じではありません。runnerサービスが起動時の環境を継承していたり、Shellの初期化ファイルがトークンを追加したりする可能性があります。プロジェクトのスクリプトを呼び出す前に環境変数名を検査し、ビルドコマンドには必要な変数だけを渡してください。
blocked='TOKEN|SECRET|PASSWORD|PRIVATE|SIGNING|AUTH|CREDENTIAL'
if env | cut -d= -f1 | grep -Eiq "$blocked"; then
printf '%s\n' "blocked environment variable detected" >&2
exit 70
fi
env -i \
HOME="$HOME" \
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
TMPDIR="$WORK_DIR/tmp" \
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Debug \
-derivedDataPath "$WORK_DIR/DerivedData" \
CODE_SIGNING_ALLOWED=NO \
build
実際のプロジェクトがパッケージマネージャーや独自ツールに依存する場合は、ログイン環境全体を継承するのではなく、レビュー済みのツールディレクトリを明示的にPATHへ追加します。変数名の検査は最低限の防御にすぎません。機密値は設定ファイル、キーチェーン、プロキシ設定、Git認証情報ヘルパーにも存在する可能性があります。そのため、検証ユーザーの環境は最初から空の状態に保ち、ビルドのたびに一部の変数だけを一時的に削除する運用は避けてください。
依存関係とキャッシュを扱う
外部コードがキャッシュへ内容を書き込み、その後に信頼済みジョブが同じキャッシュを読み取ると、汚染の影響を受ける可能性があります。最も安全な方法は、信頼ドメインごとにキャッシュを分離することです。外部PRについては、さらにリポジトリまたはジョブ番号ごとに分割します。検証経路から、リリースユーザーのSwiftPMキャッシュ、コンパイル済みモジュールのキャッシュ、独自バイナリツールのキャッシュへ書き込めるようにしてはいけません。
キャッシュを復元した後は、所有者と権限を確認し、ワークスペースの外部を指すシンボリックリンクを拒否します。実行可能ツールは、読み取り専用のベースラインディレクトリから取得するのが理想です。プロジェクトがビルド中に生成したスクリプトは、現在の一時ワークスペース内でのみ実行できるようにします。
成果物を信頼境界の外へ持ち出さない
外部PRのコンパイルに成功しても、そのコミットが検証条件下で合格したことしか証明できません。生成されたアーカイブがリリースに値することまでは保証できません。検証ジョブは、ビルドフェーズの置き換え、出力内容の変更、アーカイブへの追加ファイルの混入などを行う可能性があります。そのため、シークレットなしの経路で生成されたアプリケーションパッケージ、アーカイブ、エクスポートディレクトリを信頼済みリリースジョブへ渡してはいけません。
信頼済み経路では、次の手順を実行します。
- レビューシステムが承認したコミットハッシュを受け取る。
- リリースユーザー用の新規ワークスペースへ、そのコミットを改めてチェックアウトする。
- 現在のコミットが承認済みの値と完全に一致することを確認する。
- 信頼済みキャッシュまたは空のキャッシュから依存関係を再解決する。
- テストを実行し、ソースコードからアーカイブを再作成する。
- 署名が必要な短時間だけ認証情報を提供する。
- コミットハッシュ、依存関係ロックファイルのダイジェスト、最終成果物のダイジェストを記録する。
検証経路からテストレポートを渡す必要がある場合は、実行不可能なデータだけを渡し、信頼済み側で厳密な形式に従って解析してください。Shellスクリプト、プラグイン、コンパイルキャッシュ、アプリケーションパッケージへ直接組み込めるファイルは渡してはいけません。
失敗時のクリーンアップと監査を受け入れ条件に含める
分離設計は、失敗時の分岐で最も崩れやすくなります。ビルドのキャンセル、プロセスのタイムアウト、マシンの再起動が発生すると、一時ディレクトリ、バックグラウンドプロセス、ログが残る可能性があります。runnerは各ジョブの開始前に前回の残留物を確認し、終了処理ではそのジョブに属する子プロセスを停止する必要があります。
本番運用を開始する前に、次のチェックリストで検証できます。
- 検証ユーザーからリリースユーザーのホームディレクトリを一覧表示できない。
- 外部PRのログに、トークン、秘密鍵の内容、認証情報のパスが含まれていない。
- 検証経路で
CODE_SIGNING_ALLOWED=NOが有効になっている。 - 2つの経路で、書き込み可能なDerivedDataやツールキャッシュを共有していない。
- ジョブをキャンセルしても、一時ワークスペースが削除される。
- 信頼済みリリースでは検証ディレクトリを再利用せず、固定コミットを改めてチェックアウトする。
- リリース成果物から、コミットハッシュ、ロックファイルのダイジェスト、ビルド記録を追跡できる。
- runnerアカウントに、通常業務で不要な管理者権限が付与されていない。
最後に、能動的な演習を一度実施します。テスト用PRを作成し、ビルドスクリプトから一般的な機密環境変数の読み取り、リリースディレクトリへのアクセス、共有キャッシュへのファイル書き込みを試みます。正しい結果は、スクリプトが「何も見つけられなかった」ことではありません。権限とプロセスの設計によって、それらの経路自体へ到達できないことです。ここまで確認して初めて、外部コントリビューターのコード検証と正式リリースを実質的に切り離せます。
よくある質問
外部PRでコード署名を無効にすれば十分ですか?
十分ではありません。秘密の環境変数、認証情報、共有書き込みキャッシュを排除し、リリース用アカウントへのアクセスも遮断する必要があります。
外部PRの検証で作ったアーカイブをそのまま公開できますか?
推奨しません。承認後に信頼済み経路で固定コミットを取得し直し、ソースから再ビルドして不信なバイナリを境界の外へ持ち出さないようにします。
次回のXcodeビルドに、365日稼働のクラウドMacを選択
チップ、メモリ、ストレージ、請求期間、ノードのリージョンを確認して、設定に進みます。実際の利用可能状況はコンソールのリアルタイム表示をご確認ください。