ちゃんと理解したいIAMロールの連鎖
この記事は、ヤプリ&フラー 合同アドベントカレンダー Advent Calendar 2025 の7日目の記事です。
はじめに
初めまして、フラーでエンジニアしてます。masuです。
最近はAWSの資格試験を頑張ってます。
つい先日、AWS Certified Security - Specialtyを取りました。
全冠まであと7個です、先は長い...
試験や業務をやっていく中で理解した気になっていたIAM(AWS Identity and Access Management)について、本当に理解できているのか?と不安になったので取り上げます。
AWS資格試験や業務でやってて、通常のAssumeRoleとロールの連鎖って何が違うんだ?とか、そもそも何が嬉しいんだ?とかあると思います。(同じように悩んだことがある人にちょっとでも刺さると良いな)
拙い文章ですが、最後まで読んでいただけるとありがたいです。
そもそもIAMとは?
IAM (Identity and Access Management) は、AWSリソースへのアクセスを
安全に管理するためのサービスです。
AWSの世界においては、「力」そのものだと思います。
誰がどんな「力」を持って何ができるかを決めるルールブックみたいなものです。
「力」は使い方を間違えると大変なことになるからね...
きちんと設計して安全に使ってあげる必要があるよねっていつも思ってます。
IAMの3つの基本要素
さっとIAMの3つの基本要素についてさらっとおさらいしておきます。
ここは、本当にさらっと書いたので、ちゃんとIAMの基本要素から勉強したい方は公式ドキュメントやわかりやすい記事を参考にしてください。
1. IAMユーザー
- 個人や特定のアプリケーションを表す永続的なID
- 例: 開発者の山田さん、本番環境のバッチアプリ
2. IAMグループ
- 複数のユーザーをまとめて権限を一括で管理
- 例: 開発者グループ、管理者グループ
3. IAMロール
- 一時的に引き受けることができる権限のセット
- 例: EC2インスタンスロール、Lambda実行ロール
本記事は3のIAMロールを使って、権限(力)の受け渡しについて理解を深めていきます。
IAMロールの連鎖をざっくり理解
RPGのジョブとかで例えるとわかりやすいかもしれないですが、もっと身近にするために普段着る服について想像してみてほしいです。
パジャマ(ロールA)
- 通気性がよく家でリラックスできる
- 外には出られない
スーツ(ロールB)
- 会社やフォーマルな場に行ける
- ジムには入れない
ジャージ(ロールC)
- 動きやすいので、運動するのに向いてる
- 会社には行けない
ジャージでも家でリラックスできるじゃんとか思うことはあると思うのですが、一旦続きを読んでください。
ここで、前提とさせて欲しいのは、重ね着ができないことです。(パジャマの上にスーツは着ないし、ジャージをスーツの上から着ないでしょ)
スーツを着るためには、パジャマを脱がなくてはいけない。
パジャマからスーツに着替えて、ジムでジャージに着替えたとします。最後に着ているのは、ジャージだけです。
ジャージが持つポテンシャル(運動ができる)以上のことはできないのです。
つまり、IAMロールの連鎖でも同じで、最後に持っているロールにある権限が使えます。
しっかり理解する
IAMロールについて
先述した通り、IAMロールは一時的に引き受けることができる権限セットのことで、IAMロールを引き受けることができる対象は以下のようになります。
- 同じAWSアカウント/別のAWSアカウントのIAMユーザー
- 同じAWSアカウントのIAMロール
- AWSサービスや機能(EC2とかS3)
- SAML2.0, OIDCと互換性のある外部IDプロバイダーによって認証された外部ユーザー
IAMロールは2種類のポリシーを使って定義します。
- 信頼ポリシー:誰がこのロールを引き受けることができるのか
- 権限ポリシー:このロールで何をすることができるのか
IAMポリシーというのは、「誰が、どのリソースに対して、どのような操作を(どのような条件の時に)許可・拒否するか」を定義することで、アクセス制御を行うことができる仕組みです。
ロールやポリシーについて詳しく知りたい方はこちらが参考になると思います。
信頼ポリシーの例
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: arn:aws:iam::123456789012:user/your-name # このユーザーが引き受けることができる
Action: sts:AssumeRole
権限ポリシーの例
Policies:
- PolicyName: S3BucketAReadOnly
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:ListBucket # バケット一覧取得ができる
- s3:GetObject # オブジェクト読み取りができる
Resource:
- arn:aws:s3:::bucket-a
- arn:aws:s3:::bucket-a/*
IAMロールの連鎖
まずは、通常のAssumeRoleとの違いについて記載します。
通常のAssumeロールでは、IAMユーザーやサービスが1つのIAMロールを直接引き受けることを指します。
IAMロールの連鎖は、1つ目のロールを引き受けた後、そのロールの権限を使ってさらに2つ目、3つ目のロールを連続して引き受けることを指します。
ロールの連鎖になった時点で、セッションが最大1時間になるという制約があります。
| 項目 | 通常のAssumeRole | ロール連鎖 |
|---|---|---|
| 引き受け元 | ユーザー/サービス | ロール |
| 用途 | 基本的な権限委譲 | 多段階の権限委譲 / クロスアカウント |
| 最大のセッション有効期限 | 12時間 | 1時間 |
| デフォルトのセッション有効期限 | 1時間 | 1時間 |
| 最小のセッション有効期限 | 15分 | 15分 |
メリット
-
クロスアカウントアクセスが簡単になる!
開発者は開発アカウントのみでIAMユーザーやSSOユーザーを管理することができます。本番アカウントが複数あってもそこにIAMユーザーを作成せず、ロールを作成し、ロール連鎖を使うことで、アクセスの委譲ができるので、管理が楽になります。 -
最小権限の原則を段階的に実現できる!
権限を段階的に付与することができるので、通常は読み取り権限だけを持たせて、必要時に限定的に権限昇格をさせることで最小権限の原則を守ることができます。 -
セキュリティ境界を明確にできる!
開発環境、検証環境、本番環境をアカウント単位で分離し、明示的に連鎖を設定し、さらにMFA制限や特定IPのみ許可などの条件をポリシーに付けてセキュリティ対策をすることができます。 -
サードパーティアクセスを安全に管理できる!
外部ベンダーに直接ユーザーを作るのではなく、外部IDや期限付きのロール連鎖でアクセス権を制御します。不要になればロール削除だけで権限を失効させることができるので、キーの漏洩対策ができます。
IAMロールの連鎖やってみた
実際にIAMロールの連鎖のデモをやってみました。
今回のデモでは、初期プリンシパルからA,B,Cのロールを順番に引き受けます。
初期プリンシパル → RoleA → RoleB → RoleC
各ロールには異なるS3バケットへのアクセス権限を付与し、権限の分離を確認します。
例えば、RoleBを引き受けた場合、BucketBは読み取ることができて、他の2つのバケットを読み取ることができません。
- RoleA: BucketAへの読み取り権限
- RoleB: BucketBへの読み取り権限
- RoleC: BucketCへの読み取り権限
実際に手を動かして確認したい人のために、デモ環境も用意しました。
AWSアカウントとAWS CLIが使える状態であれば、すぐに試すことができます!
環境構築
CloudFormationテンプレートの準備
3つのS3バケットとIAMロールを作成するCloudFormationテンプレートを作成しました。
先ほど示したリポジトリの中にあるcfn-role-chain.yamlです。
CloudFormationテンプレート
AWSTemplateFormatVersion: "2010-09-09"
Description: "IAMロール連鎖のデモ: RoleA → RoleB → RoleC"
Parameters:
InitialPrincipalArn:
Type: String
Description: "ロールAを引き受けることができる初期プリンシパルARN"
Resources:
# RoleAがアクセスできるS3バケット
BucketA:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${AWS::AccountId}-role-chain-bucket-a"
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# RoleBがアクセスできるS3バケット
BucketB:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${AWS::AccountId}-role-chain-bucket-b"
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# RoleCがアクセスできるS3バケット
BucketC:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${AWS::AccountId}-role-chain-bucket-c"
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# ロールA:
# 初期プリンシパルが引き受けることができるロール
# RoleBを引き受ける権限とBucketAへの読み取り権限を持つ
RoleA:
Type: AWS::IAM::Role
Properties:
RoleName: RoleA-Demo
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Ref InitialPrincipalArn
Action: sts:AssumeRole
Policies:
- PolicyName: AssumeRoleB
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: sts:AssumeRole
Resource: !Sub "arn:aws:iam::${AWS::AccountId}:role/RoleB-Demo"
- PolicyName: S3BucketAReadOnly
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetObject
Resource:
- !GetAtt BucketA.Arn
- !Sub "${BucketA.Arn}/*"
# ロールB:
# RoleAが引き受けることができるロール
# RoleCを引き受ける権限とBucketBへの読み取り権限を持つ
RoleB:
Type: AWS::IAM::Role
DependsOn: RoleA
Properties:
RoleName: RoleB-Demo
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/RoleA-Demo"
Action: sts:AssumeRole
Policies:
- PolicyName: AssumeRoleC
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: sts:AssumeRole
Resource: !Sub "arn:aws:iam::${AWS::AccountId}:role/RoleC-Demo"
- PolicyName: S3BucketBReadOnly
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetObject
Resource:
- !GetAtt BucketB.Arn
- !Sub "${BucketB.Arn}/*"
# ロールC:
# RoleBが引き受けることができるロール
# BucketCへの読み取り権限を持つ
RoleC:
Type: AWS::IAM::Role
DependsOn: RoleB
Properties:
RoleName: RoleC-Demo
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/RoleB-Demo"
Action: sts:AssumeRole
Policies:
- PolicyName: S3BucketCReadOnly
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetObject
Resource:
- !GetAtt BucketC.Arn
- !Sub "${BucketC.Arn}/*"
Outputs:
BucketAName:
Value: !Ref BucketA
BucketBName:
Value: !Ref BucketB
BucketCName:
Value: !Ref BucketC
RoleAArn:
Value: !GetAtt RoleA.Arn
RoleBArn:
Value: !GetAtt RoleB.Arn
RoleCArn:
Value: !GetAtt RoleC.Arn
デプロイ
初期プリンシパルのARNを取得し、CloudFormationスタックをデプロイします。
PRINCIPAL_ARN=$(aws sts get-caller-identity --query Arn --output text)
aws cloudformation deploy \
--template-file cfn-role-chain.yaml \
--stack-name role-chain-demo \
--parameter-overrides InitialPrincipalArn=$PRINCIPAL_ARN \
--capabilities CAPABILITY_NAMED_IAM
デプロイが完了したら、作成されたリソースを確認します。(マネジメントコンソールでもOK)
aws cloudformation describe-stacks \
--stack-name role-chain-demo \
--query 'Stacks[0].Outputs' \
--output table
ロール連鎖を実行
RoleAを引き受ける
ロールAを引き受けます。
※認証情報はダミーの値に置き換えてます。
コマンド
aws sts assume-role \
--role-arn arn:aws:iam::${ACCOUNT_ID}:role/RoleA-Demo \
--role-session-name session-A
出力
{
"Credentials": {
"AccessKeyId": "****************",
"SecretAccessKey": "****************************************",
"SessionToken": "IQoJb3JpZ2luX2VjEE8a...",
"Expiration": "2025-12-02T16:10:00+00:00"
},
"AssumedRoleUser": {
"AssumedRoleId": "****************:session-A",
"Arn": "arn:aws:sts::123456789012:assumed-role/RoleA-Demo/session-A"
}
}
出力された認証情報を環境変数に設定します。
export AWS_ACCESS_KEY_ID="****************"
export AWS_SECRET_ACCESS_KEY="****************************************"
export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2VjEE8a..."
3つのS3バケットに対して動作確認を行います。
AWS CLIでS3に対して、バケット配下のオブジェクト一覧を取得してみます。
現在は、ロールAを引き受けている状態なので、BucketAへのアクセスのみが成功し、BucketAの中にあるオブジェクトの一覧が確認できます。
aws s3 ls s3://${ACCOUNT_ID}-role-chain-bucket-a/ # 成功 ✅
aws s3 ls s3://${ACCOUNT_ID}-role-chain-bucket-b/ # AccessDenied ❌
aws s3 ls s3://${ACCOUNT_ID}-role-chain-bucket-c/ # AccessDenied ❌
まだ、引き受けたロールが1つ目なので、ロールの連鎖ではありません。
ここからRoleB、RoleCを連鎖していきます。
RoleBを引き受ける
ここから同じ作業を繰り返します。
退屈かもしれませんがしばらくお付き合いください。
RoleAの認証情報を使用して、RoleBを引き受けます。
aws sts assume-role \
--role-arn arn:aws:iam::${ACCOUNT_ID}:role/RoleB-Demo \
--role-session-name session-B
同様に、返された認証情報を環境変数に設定し、動作確認を行います。
RoleA → RoleB で引き受けたので、RoleBに権限として与えられているBucketBへのアクセスのみが成功していることがわかります。
先ほどまでは、BucketAにもアクセスできていましたが、アクセスができなくなりました。
aws s3 ls s3://${ACCOUNT_ID}-role-chain-bucket-a/ # AccessDenied ❌
aws s3 ls s3://${ACCOUNT_ID}-role-chain-bucket-b/ # 成功 ✅
aws s3 ls s3://${ACCOUNT_ID}-role-chain-bucket-c/ # AccessDenied ❌
RoleCを引き受ける
RoleBの認証情報を使用して、RoleCを引き受けます。
aws sts assume-role \
--role-arn arn:aws:iam::${ACCOUNT_ID}:role/RoleC-Demo \
--role-session-name session-C
同様に、返された認証情報を環境変数に設定し、動作確認を行います。
BucketCの中にあるオブジェクトのみアクセスできることがわかりました。
aws s3 ls s3://${ACCOUNT_ID}-role-chain-bucket-a/ # AccessDenied ❌
aws s3 ls s3://${ACCOUNT_ID}-role-chain-bucket-b/ # AccessDenied ❌
aws s3 ls s3://${ACCOUNT_ID}-role-chain-bucket-c/ # 成功 ✅
気になる人は公開されているデモ環境で試してみてください!
終わりに
全部が思い通りに動きました、やったね。
IAMロールはいくつ連鎖しても権限は最後に引き受けたロールが持つ権限になります。
もっと詳しく連鎖について知りたい方は、たくさん連鎖させてみた方の記事を見てみると良いかもしれません。凄すぎる。
Discussion