結論: 信頼ポリシーは参照先ロールの実在をチェックする
先に結論を書きます。
CloudFormationでクロスアカウントのAssumeRole構成を組むとき、監視対象アカウント側の信頼ポリシー(Trust Policy)は、参照先のIAMロールARNが実在するかどうかを検証します。つまり、AssumeRole元のロールが存在しない状態でスタックを作ると、Invalid principal in policy で弾かれます。
正しい作成順序はこうです。
graph TD
A[1. CIアカウントのIAMロールを作成/更新] --> B[2. ロールARNを確認]
B --> C[3. 監視対象アカウントAにクロスアカウントロールを作成]
B --> D[3. 監視対象アカウントBにクロスアカウントロールを作成]
B --> E[3. 監視対象アカウントCにクロスアカウントロールを作成]
C --> F[4. CI側のAssumeRole権限に各ロールARNを追加]
D --> F
E --> F
「当たり前じゃないか」と思うかもしれません。ただ、複数アカウントを行き来しながら作業していると、この順序を見失ってハマることがあります。今回の自分がまさにそうでした。
前提: やりたかったこと
CIサーバー(Jenkins)から、複数のAWSアカウントのリソースを監視する構成を作りたかった、というのが背景です。
[CIアカウント] [監視対象アカウント A, B, C]
Jenkins Agent 監視用ロール
(IAMロール) --- AssumeRole ---> (クロスアカウントロール)
ここで「AssumeRole」と「信頼ポリシー」について補足しておきます。
AWSでは、あるアカウントのIAMロールが別のアカウントのIAMロールを「引き受ける(AssumeRole)」ことで、クロスアカウントのアクセスを実現します。このとき、引き受けられる側のロールには**「誰からの引き受けを許可するか」を定義する設定が必要です。これが信頼ポリシー(Trust Policy)**です。
つまり、登場人物は2つです。
- IAMポリシー(Permission Policy): CIアカウント側に設定。「このロールは、どのリソースに対して何ができるか」を定義する。今回の場合、「監視対象アカウントのロールをAssumeRoleしてよい」という許可を書く
- 信頼ポリシー(Trust Policy): 監視対象アカウント側のロールに設定。「このロールを引き受けてよいのは誰か」を定義する。今回の場合、「CIアカウントのJenkinsロールからのAssumeRoleを許可する」と書く
この2つが揃って初めて、クロスアカウントのAssumeRoleが成立します。自転車の両輪みたいなものです。
CloudFormationテンプレートも2種類用意しました。
- CIアカウント側: Jenkinsエージェント用のIAMロール(IAMポリシーでAssumeRole権限を付与)
- 監視対象アカウント側: 監視用ロール(信頼ポリシーでCIアカウントのロールARNを指定)
それぞれのアカウントにデプロイすれば完成、のはずでした。
ハマり①: 既存ロールとの名前競合
結論: CloudFormationは同一アカウント・同一リージョン内で、異なるスタックから同名のIAMロールを作れません。
何が起きたか: スタック作成時にロールバックが発生しました。
Resource already exists in stack arn:aws:cloudformation:...
原因: 以前に別の目的で作ったスタックが、同じ名前のIAMロールをすでに持っていました。命名規則が統一されておらず、既存は {用途}-role-{環境} だったり {スタック名}-role-{環境} だったり。ここが曖昧だったことが根本原因です。
対処: ロール名のプレフィックスを変えて回避しました。この時点で「命名規則を標準化しないとまずいな」と気づき、後続の作業で統一することになります。
ハマり②: Invalid principal in policy
結論: 信頼ポリシーに指定したIAMロールARNが存在しないと、スタック作成が失敗します。
何が起きたか: 名前の競合を解消して再デプロイ。今度は別のエラーです。
Invalid principal in policy
原因: CIアカウント側のロール名を新しいものに変更する前に、監視対象アカウント側で新しいロール名を参照する信頼ポリシーを作ろうとしていました。まだ存在しないロールARNを指定していたので弾かれた、ということです。
正直ここで詰まりました。ARNのタイポを疑って何度も見直しましたが、問題はARNの正しさではなくロール自体が存在していなかったことでした。
sequenceDiagram
participant CI as CIアカウント
participant Target as 監視対象アカウント
Note over Target: ❌ 失敗パターン(ロールが未作成)
Target->>CI: 信頼ポリシーでCIのロールARNを参照
CI-->>Target: そのロール、まだ存在しません
Target->>Target: Invalid principal → ロールバック
Note over CI,Target: ✅ 成功パターン(ロールが先に存在)
CI->>CI: 1. CIアカウントにエージェントロールを作成
Target->>CI: 2. 信頼ポリシーでCIのロールARNを参照
CI-->>Target: ロールが存在する → 検証OK
Target->>Target: 監視用ロール作成成功
ポイント: ここで気づいた重要な非対称性があります。
- 信頼ポリシー(Trust Policy): プリンシパルに指定したARNの実在チェックあり。対象が存在しないと失敗する
- IAMポリシー(Permission Policy): ARNパターン(
arn:aws:iam::*:role/monitoring-*のようなワイルドカード)が使える。対象が存在しなくても設定できる
つまり、依存の方向が非対称なんです。CI側のポリシーは先に書けるけど、監視対象側の信頼ポリシーはCIのロールが先に存在しないと書けない。ここが今回の作業で一番の学びでした。
おまけ: 25ファイルの一括置換で二重置換が発生した話
ロール名を標準化した結果、そのロール名を参照しているファイルが25個ありました。設定ファイル、Jenkinsfile、Groovyスクリプト、ドキュメントなど。sed で一括置換しましたが、ここにも小さな罠がありました。
置換対象の文字列に新しいプレフィックスの一部が含まれていたため、二重置換が発生しました。
# 一括置換
find path/to/jobs -name "Jenkinsfile" -type f \
-exec sed -i "s/old-role-name/new-role-name/g" {} \;
# 期待: old-role-name → new-role-name
# 実際: old-role-name → new-new-role-name(二重置換)
対処は力技で、二重置換された文字列をさらに sed で修正しました。
find path/to/jobs -name "Jenkinsfile" -type f \
-exec sed -i "s/new-new-role-name/new-role-name/g" {} \;
本来は置換前に grep で対象を確認して、完全一致に近いパターンで書くべきでした。急いでいるときほど、こういう地味なところで時間を取られます。
まとめ
今回の学びを整理します。
- クロスアカウントAssumeRoleの信頼ポリシーは、参照先ロールの実在をチェックする。作成順序を間違えると
Invalid principalで弾かれる - 信頼ポリシーとIAMポリシーでは、依存の方向が非対称。IAMポリシー側はワイルドカードが使えるので対象の存在に依存しないが、信頼ポリシー側は実在チェックが入る
- マルチアカウント構成では、依存関係を図に描いてから作業すべき。頭の中だけで管理すると、アカウントを行き来するうちに順序を見失う
- 命名規則の統一は初期設計でやる。後から直すと影響範囲が広がる一方
特に2つ目の非対称性は、ドキュメントをちゃんと読めば書いてあることですが、実際にハマってみないと体感としては入ってこないものだなと思いました。どなたかの参考になれば。
参考ドキュメント
- AWS IAM: クロスアカウントアクセスのための IAM ロールの作成
- AWS IAM: IAM ロールの信頼ポリシー
- AWS CloudFormation: AWS::IAM::Role
- AWS IAM: 別の AWS アカウントへのアクセス権をユーザーに付与する
参考書籍
AWSの運用やインフラ自動化について、さらに学びたい方におすすめの書籍です。
[📦 商品リンク: moshimo-book-aws-operations]
[📦 商品リンク: moshimo-book-server-infra]