💪

ちゃんと理解したい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の基本要素から勉強したい方は公式ドキュメントやわかりやすい記事を参考にしてください。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/introduction.html
https://dev.classmethod.jp/articles/aws-iam-user-and-role-for-beginner/

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プロバイダーによって認証された外部ユーザー

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles.html

IAMロールは2種類のポリシーを使って定義します。

  • 信頼ポリシー:誰がこのロールを引き受けることができるのか
  • 権限ポリシー:このロールで何をすることができるのか

IAMポリシーというのは、「誰が、どのリソースに対して、どのような操作を(どのような条件の時に)許可・拒否するか」を定義することで、アクセス制御を行うことができる仕組みです。

ロールやポリシーについて詳しく知りたい方はこちらが参考になると思います。
https://blog.serverworks.co.jp/assume-role-divedeep

信頼ポリシーの例

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分

メリット

  1. クロスアカウントアクセスが簡単になる!
    開発者は開発アカウントのみでIAMユーザーやSSOユーザーを管理することができます。本番アカウントが複数あってもそこにIAMユーザーを作成せず、ロールを作成し、ロール連鎖を使うことで、アクセスの委譲ができるので、管理が楽になります。

  2. 最小権限の原則を段階的に実現できる!
    権限を段階的に付与することができるので、通常は読み取り権限だけを持たせて、必要時に限定的に権限昇格をさせることで最小権限の原則を守ることができます。

  3. セキュリティ境界を明確にできる!
    開発環境、検証環境、本番環境をアカウント単位で分離し、明示的に連鎖を設定し、さらにMFA制限や特定IPのみ許可などの条件をポリシーに付けてセキュリティ対策をすることができます。

  4. サードパーティアクセスを安全に管理できる!
    外部ベンダーに直接ユーザーを作るのではなく、外部IDや期限付きのロール連鎖でアクセス権を制御します。不要になればロール削除だけで権限を失効させることができるので、キーの漏洩対策ができます。

IAMロールの連鎖やってみた

実際にIAMロールの連鎖のデモをやってみました。
今回のデモでは、初期プリンシパルからA,B,Cのロールを順番に引き受けます。

初期プリンシパル → RoleA → RoleB → RoleC

各ロールには異なるS3バケットへのアクセス権限を付与し、権限の分離を確認します。
例えば、RoleBを引き受けた場合、BucketBは読み取ることができて、他の2つのバケットを読み取ることができません。

  • RoleA: BucketAへの読み取り権限
  • RoleB: BucketBへの読み取り権限
  • RoleC: BucketCへの読み取り権限

実際に手を動かして確認したい人のために、デモ環境も用意しました。
AWSアカウントとAWS CLIが使える状態であれば、すぐに試すことができます!

https://github.com/tmasud/role-chain-demo

環境構築

CloudFormationテンプレートの準備

3つのS3バケットとIAMロールを作成するCloudFormationテンプレートを作成しました。
先ほど示したリポジトリの中にあるcfn-role-chain.yamlです。

CloudFormationテンプレート
cfn-role-chain.yaml
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