AWS CloudFormation StackSets を使ったマルチアカウントのスイッチロール実装

AWS

2023.8.30

Topics

はじめに

マルチアカウントの環境で、IAM User を使用してすべての環境にログインをしている場合は、管理する IAM User が大量になり管理が煩雑になります。
また、ログインする側としても毎回コンソールを切り替える必要があるためかなり大変です。

その課題を解決するために、スイッチロールが有用ですが、それだけではなく CloudFormation StackSets を使うことでより簡単に実装することができます。
本記事では、CloudFormation StackSets を使ってマルチアカウントのスイッチロールを実装について記述します。

まずは、2 つほど重要なキーワードがあるので以下にて軽く説明します。

  • スイッチロール
  • CloudFormation StackSets

スイッチロールとは

IAM Role を使ったログイン方法です。
認証された IAM Role を使って AWS アカウントにログインすることができます。

メリット

  • IAM User を作らなくても良い
  • 特定のアカウントだけが IAM Role にアクセスするように制御することができる
  • スイッチロール先のアカウント側で権限を管理できる

ロールの切り替え (コンソール) – AWS Identity and Access Management

CloudFormation StackSets とは

CloudFormation はリージョンサービスです。
マルチリージョンでデプロイしようとしたり、他の AWS アカウントに対してデプロイする場合は同じ作業を繰り返すことになるためかなり手間になります。
この StackSets を使うことで一つのテンプレートを使ってマルチリージョンやマルチアカウントのデプロイを簡略化できます。

メリット

  • マルチリージョン・マルチアカウントで CloudFormation をデプロイできる
  • 同じ作業を繰り返さなくてよくなる

StackSets の概念 – AWS CloudFormation

マルチアカウントの課題

AWS Well-Architected にはマルチアカウント戦略があります。
詳細は省きますが、環境に応じて AWS アカウントを分けましょうという話です。

SEC01-BP01 アカウントを使用してワークロードを分ける: – セキュリティの柱

そこで、実際に本番環境、ステージング環境、テスト環境などと AWS アカウントをただ分けると以下のような課題が浮き彫りになります。

  • マルチアカウントの IAM User の管理 → 管理者の課題
  • 環境ごとにログインを切り替える手間 → 開発者の課題

イメージとしては以下のような構成です。

IAM Userを使った場合マルチアカウントの接続構成図

「マルチアカウントの IAM User の管理」の課題は、どの AWS アカウントどの IAM User が存在して、どのロールを保持しているかを把握するときに、1 ユーザーに対して IAM User が複数ある場合調査対象がかなり多くなります。
また、人や AWS アカウントが増えていけばいくほど管理対象が増えるため管理コストが増加します。

「環境ごとにログインを切り替える手間」の課題は、原則として一つのブラウザでログインできる AWS アカウントは一つです。
そのため、複数環境に同時にログインしたい場合は別ブラウザを使います。
その際に、毎回ログインが発生して、ユーザー名やパスワードを入力しているとかなり手間が発生します。
さらに、セキュリティ向上のために各 AWS アカウントで、MFA を設定すると更にログイン時に手間が増えます。

これらの課題を解決するために、「マルチアカウントのスイッチロール」を実装します。
そうすることにより、管理アカウントで IAM User を一括管理することができ、複数環境に渡っていた IAM User が減るため管理コストを減らせます。

イメージとしては以下のような構成です。

スイッチロールを使った場合のマルチアカウント接続構成図

ただ、ログインする際の手間は減らせても、管理する側の手間はあまり減りません。
そのため、さらに手間を削減するために StackSets を使用して一元管理でデプロイを行います。

CloudFormation StackSets でスイッチロールを実装

じゃあ、実際にどう実装していくのかを本セクションで解説をします。

今回は以下の構成を実装いたします。
スイッチロール先の AWS アカウントは 1 つですが、これが 2 つ 3 つと増えてもやることは、該当のコード部分を増やすだけなのであまり変わりません。

本記事で実装するスイッチロール構成図

AWS アカウントが増えてもわかりやすいように、「CloudFormation テンプレートの構成」セクションでは複数のアカウントの場合を想定してテンプレート作成をしています。
実際のデプロイについては、上記構成の通りにテンプレートを作成しています。

CloudFormation テンプレートの構成

先に今回使用する CloudFormation テンプレートの解説を行います。

使用するテンプレートは大きく 2 つに分けてます。

  1. 接続先アカウントにスイッチロールを作成するテンプレート
  2. 管理アカウントに IAM User とスイッチロール権限を付与するテンプレート

スイッチロール先アカウント用 IAM ロール作成用

本テンプレートでは各 AWS アカウントごとに Mappings を設定して環境名を変えています。

Mappings :キーと名前付きの一連の値
Mappings – AWS CloudFormation

本記事の例では、4 つの AWS アカウントで、2 つのプロジェクトを管理し、それぞれ本番環境とステージング環境が存在する設定とします。
以下、AWS アカウントのサンプル構造です。

  • ProjectA
    • 本番:A アカウント
    • ステージング:B アカウント
  • ProjectB
    • 本番:C アカウント
    • ステージング:D アカウント

今回は、上記 4 つの AWS アカウントに対して開発者権限のみを与えた IAM Role でスイッチロールすることを想定しています。

AWSTemplateFormatVersion: 2010-09-09
Description: SwitchRole CFn StackSets

Mappings:
  AccountMap:
    "AアカウントID":
      Env: Prod
      Prefix01: ProjectA
    "BアカウントID":
      Env: Stg
      Prefix01: ProjectA
    "CアカウントID":
      Env: Prod
      Prefix01: ProjectB
    "DアカウントID":
      Env: Stg
      Prefix01: ProjectB
Parameters:
  RootAccountId:
    Type: String
    Default: "管理アカウントID"
Resources:
  Developer:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub
        - "${Prefix01}${Env}SwitchDeveloper"
        - Prefix01: !FindInMap [AccountMap, !Ref "AWS::AccountId", Prefix01]
          Env: !FindInMap [AccountMap, !Ref "AWS::AccountId", Env]
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              AWS: !Sub arn:aws:iam::${RootAccountId}:root
            Action:
              - "sts:AssumeRole"
      Path: "/"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/ReadOnlyAccess
        - arn:aws:iam::aws:policy/AmazonEC2FullAccess

ポイント

管理アカウント用 IAM User・Group 作成用

このテンプレートでは、以下のリソースを作成します。

  • IAM Policy
    • 対象のスイッチロールに AssumeRole できる権限を付与
  • IAM Group
    • スイッチロールポリシーをアタッチし複数ユーザーで権限を共有
  • IAM User
    • 管理アカウントに接続するユーザー

一つのテンプレートにまとめると見づらいので、後述する例では 以下 2 つのテンプレートに区分しています。

  • IAM Group と IAM Policy
  • IAM User
IAM Group と IAM Policy

やっていることはスイッチロールテンプレートとあまり変わらないです。
注意点としては、IAM Policy でスイッチロールするロールと対象の AWS アカウントとを指定していることです。

AWSTemplateFormatVersion: 2010-09-09
Description: Group Policy

Parameters:
  Prj01:
    Type: String
    Default: ProjectA
  Prj02:
    Type: String
    Default: ProjectB
  Env01:
    Type: String
    Default: Prod
  Env02:
    Type: String
    Default: Stg
  ProjectAProdAccountID:
    Type: String
    Default: "AアカウントID"
  ProjectAStgAccountID:
    Type: String
    Default: "BアカウントID"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Prefix
        Parameters:
          - Prj01
          - Prj02
      - Label:
          default: Env
        Parameters:
          - Env01
          - Env02

Resources:
  ProjectAProdSwitchDeveloper:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Sub "${Prj01}${Env01}SwitchDeveloper"

  ProjectAProdSwitchDeveloperPolicy:
    Type: "AWS::IAM::ManagedPolicy"
    Properties:
      ManagedPolicyName: !Sub "${Prj01}${Env01}SwitchDeveloperPolicy"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - sts:AssumeRole
            Resource: !Sub "arn:aws:iam::${ProjectAProdAccountID}:role/${Prj01}${Env01}SwitchDeveloper"
      Groups:
        - !Ref ProjectAProdSwitchDeveloper
Outputs:
  ProjectAProdSwitchDeveloper:
    Description: Information about the value
    Value: !Ref ProjectAProdSwitchDeveloper
    Export:
      Name: ProjectAProdSwitchDeveloper

テンプレートを分けているので、IAM User テンプレートで値を参照するためオプションの Outputs セクションでグループ名を出力しています。
[Outputs] (出力) – AWS CloudFormation

IAM User

こっちはもっとシンプルで IAM User を作成して Outputs した IAM Group を参照します。

AWSTemplateFormatVersion: 2010-09-09
Description: user creation

Parameters:
  InitialPassword:
    Type: String
    NoEcho: True
Resources:
  User1:
    Type: "AWS::IAM::User"
    Properties:
      Groups:
        - !ImportValue ProjectAProdSwitchDeveloper
        - !ImportValue ProjectAStgSwitchDeveloper
      LoginProfile:
        Password: !Ref InitialPassword
        PasswordResetRequired: true
      UserName: sample.user

アクセスする環境を増やしたい場合は、権限を追加した Group に IAM User を追加していくだけです。

デプロイ手順

準備が整ったので作成したテンプレートを元にデプロイしていきます。

デプロイ手順は以下の通りです。

  1. 各アカウントで StackSets 用の IAM Role 作成
  2. IAM Group を作成 → CloudFormation を使ってデプロイ
    • 各アカウントの IAM Role にスイッチロールする権限をアタッチ
  3. 管理用アカウントに IAM User を作成 → CloudFormation を使ってデプロイ
    • 必要なメンバーをグループに追加
  4. StackSets で各アカウントに IAM Role を作成 → CloudFormation StackSets を使ってデプロイ

1. 各アカウントで CloudFormation StackSets 用の IAM Role 作成

はじめに、CloudFormation StackSets を使うには準備が必要です。
少し複雑な話ですが、CloudFormation StackSets は 2 つの以下方法でアクセス許可を与えてやる必要があります。

今回は弊社のリセール環境下で検証を行う都合上の理由で、「セルフマネージド型(IAM Role)のアクセス許可」で StackSets をデプロイします。
もう一つの「サービスマネージド型(AWS Organizations)のアクセス許可」を利用する場合は、本セクションの内容は不要です。
ただし、後述する手順に差異がある可能性がありますのでご了承ください。

「セルフマネージド型(IAM Role)のアクセス許可」を簡単に説明すると、スイッチロール先アカウントで CloudFormation を実行する権限を IAM Role を使って管理アカウントに委譲します。
そのために準備・作成する IAM Role は以下の 2 つです。

  • AWSCloudFormationStackSetAdministrationRole
  • AWSCloudFormationStackSetExecutionRole

なお、本記事では詳細な手順は記載いたしません。

作成するための CloudFormation テンプレートが以下ドキュメントにあるので詳細はそちらでご確認下さい。
セルフマネージド型のアクセス許可を付与する – AWS CloudFormation

この作成したロールを指定して後述の CloudFormation テンプレートをデプロイします。

2. IAM Group を作成

ここからが実際のデプロイ作業になります。
テンプレートを区分しているため先に IAM Group を作成します。

このテンプレートは、管理アカウントに対してデプロイするため StackSets ではなく CloudFormation でデプロイします。

上述したテンプレートに実際に値を設定しています。

AWSTemplateFormatVersion: 2010-09-09
Description: Group Policy

Parameters:
  Prj01:
    Type: String
    Default: ProjectA
  Env01:
    Type: String
    Default: Prod
  ProjectAProdAccountID:
    Type: String
    Default: "031125416455"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Prefix
        Parameters:
          - Prj01
      - Label:
          default: Env
        Parameters:
          - Env01

Resources:
  ProjectAProdSwitchDeveloper:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Sub "${Prj01}${Env01}SwitchDeveloper"

  ProjectAProdSwitchDeveloperPolicy:
    Type: "AWS::IAM::ManagedPolicy"
    Properties:
      ManagedPolicyName: !Sub "${Prj01}${Env01}SwitchDeveloperPolicy"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - sts:AssumeRole
            Resource: !Sub "arn:aws:iam::${ProjectAProdAccountID}:role/${Prj01}${Env01}SwitchDeveloper"
      Groups:
        - !Ref ProjectAProdSwitchDeveloper
Outputs:
  ProjectAProdSwitchDeveloper:
    Description: Information about the value
    Value: !Ref ProjectAProdSwitchDeveloper
    Export:
      Name: ProjectAProdSwitchDeveloper

デプロイした後の出力結果です。
この値を次の IAM User のテンプレートで参照します。

IAM Groupのスタック出力結果

3. 管理用アカウントに IAM User を作成

続いては IAM User 作成して、先程出力した IAM Group 名を参照しています。
こちらのテンプレートも先程同様に、管理アカウントに対してデプロイするため StackSets ではなく CloudFormation でデプロイします。

AWSTemplateFormatVersion: 2010-09-09
Description: user creation

Parameters:
  InitialPassword:
    Type: String
    NoEcho: True
Resources:
  User1:
    Type: "AWS::IAM::User"
    Properties:
      Groups:
        - !ImportValue ProjectAProdSwitchDeveloper
      LoginProfile:
        Password: !Ref InitialPassword
        PasswordResetRequired: true
      UserName: sample.user

しっかりとポリシーがアタッチされています。

作成されたIAM Policyの画面

4. StackSets で各アカウントに IAM Role を作成

では、最後に大本命の CloudFormation StackSets 作業を行います。

AWSTemplateFormatVersion: 2010-09-09
Description: SwitchRole CFn StackSets

Mappings:
  AccountMap:
    "031125416455":
      Env: Prod
      Prefix01: ProjectA
Parameters:
  RootAccountId:
    Type: String
    Default: "375845453107"
Resources:
  Developer:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub
        - "${Prefix01}${Env}SwitchDeveloper"
        - Prefix01: !FindInMap [AccountMap, !Ref "AWS::AccountId", Prefix01]
          Env: !FindInMap [AccountMap, !Ref "AWS::AccountId", Env]
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              AWS: !Sub arn:aws:iam::${RootAccountId}:root
            Action:
              - "sts:AssumeRole"
      Path: "/"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/ReadOnlyAccess
        - arn:aws:iam::aws:policy/AmazonEC2FullAccess

上記のテンプレートでデプロイを行います。

StackSetsの設定画面

設定を進めていくとまずは、アクセス許可の設定があり一番最初に作った IAM Role をここで選択します。

StackSetsのアクセス許可でIAM Roleを指定する

その後必要な値を入力していきます。
ここは CloudFormation スタックと変わりないです。

StackSetの詳細を入力する画面

ここは StackSets 特有の設定になってます。
デプロイする AWS アカウントやリージョンをここで選択します。
ただ、リージョンについては今回は IAM リソースなのでどこでも問題ないです。

StackSetのリージョンとアカウントの設定画面

設定が完了したので実行します。
オペレーションタブで今回実行したログを確認できます。

オペレーションの実行ログ画面

スタックインスタンスでどこのリージョンやどの AWS アカウント単位の実行を確認できます。

スタックインスタンス画面

実行が完了したらスイッチロール先の AWS アカウントにログインして IAM Role の確認をします。
するとしっかりと名前が設定され、管理アカウントとの信頼関係になっていることが確認できます。

作成されたIAM Roleの画面
IAM Roleの信頼ポリシーで管理アカウントを許可している

接続確認

では、実際にスイッチロールを使って接続します。
IAM User のログインやパスワード変更は割愛します。

ログインができたら、AWS コンソールの右上から「ロール切り替え」を選択します。

スイッチロールを行う画面

スイッチロール先アカウントの情報を入力します。
今回の入力する値はロールは「ProjectAProdSwitchDeveloper」になります。

スイッチロール入力画面

入力内容が間違っていなければスイッチロールできます。
右上のログイン情報が変わっています。

接続情報がIAM Roleに切り替わった画面

あとはこのまま操作が可能です。

まとめ

  • アカウントが増えれば増えるほど管理は煩雑になる
  • スイッチロールを使うと マルチアカウントの接続が楽になる
  • StackSets を使うとマルチアカウントデプロイが楽になる
Cold-Airflow

2021年新卒入社。インフラエンジニアです。RDBが三度の飯より好きです。 主にデータベースやAWSのサーバレスについて書く予定です。あと寒いのは苦手です。

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら