AWS Organizations無しでユーザ権限を良い感じに管理する

:::note info
この記事は[ZOZO AdventCalender 2023](https://qiita.com/advent-calendar/2023/zozo)シリーズ9の15日目の記事です。
:::

# はじめに

- 本記事は以下のようなケースを想定しています。
  - 何らかの理由によってOrganizationsが使えない。
  - マルチアカウント構成におけるユーザ権限を良い感じに管理したい。
- Organizationsを使わないことを推奨するものではありません、使える場合には迷うことなく使いましょう。
  - 例えば、ルートユーザに対して制限を加えたい場合にはOrganizationsによるSCPが必要です。
- こういった作り方もあるよねという一例として見てもらえればと思います。

# 前提条件

本記事における前提条件を記載します。
- AWSアカウントは3つ（管理用アカウント、開発用アカウント1、開発用アカウント2）
  - 管理用アカウント
    - IAMユーザの管理
    - 後述するStackSetsの管理用アカウント
  - 開発用アカウント1、開発用アカウント2
    - 開発者の作業用
- 開発用アカウントには、以下3つの役割でアクセスする想定。
  - 管理者
    - 全ての権限を持つ（AdministratorAccess相当）
  - 開発者
    - 一部を除いて全ての権限を持つ（基本はAdministratorAccess相当）
    - CloudTrailに対する書き込み系の操作は禁止
    - 自身の権限を変更することは禁止（権限昇格の防止）
  - 監査者
    - 閲覧権限のみを持つ（ReadOnlyAccess相当）

# アーキテクチャ

アーキテクチャは以下の通りです。

![アーキテクチャ図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/26993348-6d18-32b3-9a61-a6ef592a915d.png)

IAMユーザは管理アカウントに作成します。
一方で、開発用アカウントには役割毎に適切なポリシーを付与したIAMロールを作成し、管理アカウントからスイッチロール出来るようにします。
IAMユーザにはスイッチロールするためのAssumeRole権限が必要となるため、役割毎に用意したIAMグループに対して、当該操作を許可するポリシーを割り当てます。

# 設定の配布方法

設定の配布は以下のイメージで行います。

![リソースの管理方法.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/ceac7f18-bd8f-bd2f-93da-52b6e8425f4a.png)

各AWSアカウントに設定を配布する際には、CloudFormation StackSetsを用いて一元管理します。
また、StackSetsで作成されたリソースには`ManagedResource`という名前のタグで目印をつけます（タグの名前は任意で良いです）。
タグがついたリソースに対するポリシーを各IAMロールへ適切に設定することで、勝手に権限が変更されないようにします。

# 作り方

まず、AWSアカウントを3つ作成します。
作成方法は以下をご確認ください。

https://aws.amazon.com/jp/register-flow/

続けて、以下の流れで設定を行なっていきます。

1. 開発用アカウントの設定（StackSets用）
2. 管理アカウントの設定（StackSets用）
3. 管理アカウントの設定（IAMグループの作成）
4. スイッチロール用IAMロールの作成
5. IAMユーザの作成

:::note warn
本手順で記載されている内容は最低限の設定のみを実施しています。
実際に運用する際にはセキュリティを考慮した上で適宜判断してください。
:::

## 1. 開発用アカウントの設定（StackSets用）

StackSetsの実行に使用するターゲットアカウント側のIAMロールを作成します。
このIAMロールは`AWSCloudFormationStackSetExecutionRole`という名前である必要があります。
詳細は、以下をご確認ください。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html

`ロールを作成`します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/e53fea49-1063-61bf-4986-ce76c458b291.png)

`信頼されたエンティティタイプ`として`AWSアカウント`を選択し、`別のAWSアカウント`に管理用アカウントのアカウントIDを入力します。

![スクリーンショット 2023-12-12 16.26.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/fd30bc74-d96f-0c91-d894-f96d1edd4378.png)

許可ポリシーとして`AdministratorAccess`を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/c8fb3211-2592-b0d7-829f-67962c757bf8.png)

ロール名を`AWSCloudFormationStackSetExecutionRole`として作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/8ec142ef-fb79-57f3-f551-a17944af80a1.png)

同様の手順を、もう一方の開発用アカウントに対しても実施します。

## 2. 管理アカウントの設定（StackSets用）

StackSetsの実行に使用する管理者アカウント側のIAMロールを作成します。
このIAMロールは`AWSCloudFormationStackSetAdministrationRole`という名前である必要があります。
詳細は、以下をご確認ください。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html

`ロールを作成`します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/c9c78205-72b2-70ac-3741-ee499c17e803.png)

`信頼されたエンティティタイプ`として`AWSのサービス`を選択し、`サービスまたはユースケース`としてCloudFormationを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/ab90fe30-4bac-2437-fca7-eb21ae2dc1ab.png)

`許可を追加`では何もせずに次へ進みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/68f3b884-b802-bc41-55c8-a64714f48085.png)

ロール名を`AWSCloudFormationStackSetAdministrationRole`として作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/42f2f810-a9c7-dc14-b3c1-e40b03840fa2.png)

続けて、作成した`AWSCloudFormationStackSetAdministrationRole`ロールを選択し、`許可を追加`から`インラインポリシーを作成`します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/38d2de22-1853-ba4f-51eb-c83f685178b1.png)

以下のルールでポリシーを作成します（ポリシー名は任意です）。

```json:AWSCloudFormationStackSetAdministrationRolePolicy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "arn:aws:iam::*:role/AWSCloudFormationStackSetExecutionRole"
            ],
            "Effect": "Allow"
        }
    ]
}
```

## 3. 管理アカウントの設定（IAMグループの作成）

管理アカウントに対して、役割毎のIAMグループを作成します。
今回は、以下のCloudFormationテンプレートを用意します。

```yaml:create-iam-group.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: create iam group

Resources:
  # ---------------------------------------------
  # Group
  # ---------------------------------------------
  GroupAdministrator:
    Type: AWS::IAM::Group
    Properties:
      GroupName: group-administrator

  GroupDeveloper:
    Type: AWS::IAM::Group
    Properties:
      GroupName: group-developer

  GroupAudit:
    Type: AWS::IAM::Group
    Properties:
      GroupName: group-audit

  # ---------------------------------------------
  # Policy
  # ---------------------------------------------
  PolicyAdministrator:
    Type: AWS::IAM::Policy
    Properties:
      Groups:
        - !Ref GroupAdministrator
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - "sts:AssumeRole"
            Resource:
              - "arn:aws:iam::*:role/role-administrator"
      PolicyName: policy-administrator

  PolicyDeveloper:
    Type: AWS::IAM::Policy
    Properties:
      Groups:
        - !Ref GroupDeveloper
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - "sts:AssumeRole"
            Resource:
              - "arn:aws:iam::*:role/role-developer"
      PolicyName: policy-developer

  PolicyAudit:
    Type: AWS::IAM::Policy
    Properties:
      Groups:
        - !Ref GroupAudit
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - "sts:AssumeRole"
            Resource:
              - "arn:aws:iam::*:role/role-audit"
      PolicyName: policy-audit
```

CloudFormationから当該テンプレートを適用します。
`スタックの作成`を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/e6b06e52-e877-d50d-5d6e-b944eb63a233.png)

用意したテンプレートファイルを選択して次へ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/829bbee9-febf-1415-8f6b-82f173aefe04.png)

任意の名前を入力して次へ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/b8b4aac4-1b35-721f-042a-8c6e6c717b9d.png)

`スタックオプションの設定`では何をせずに次へ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/c8cf8c86-69a1-9131-e7d7-20ca07f1bec1.png)

設定内容を確認して`送信`を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/5079138e-8cba-d198-bbe3-ff5e84f130af.png)

スタックの作成が完了したら次の手順へ進みます。

## 4. スイッチロール用IAMロールの作成

管理アカウントから開発アカウントに対してスイッチロール用IAMロールを作成します。
今回は、以下のCloudFormationテンプレートを用意します。

```yaml:create-switchrole.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: create switchrole

Parameters:
  ManagementAWSAccountId:
    Type: String
    Description: AWS Account ID of Management Account

Resources:
  # ---------------------------------------------
  # Role
  # ---------------------------------------------
  RoleAdministrator:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Ref ManagementAWSAccountId
            Action:
              - 'sts:AssumeRole'
      Description: Administrator Role
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/AdministratorAccess
      RoleName: role-administrator

  RoleDeveloper:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Ref ManagementAWSAccountId
            Action:
              - 'sts:AssumeRole'
      Description: Developer Role
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/AdministratorAccess
      RoleName: role-developer

  RoleAudit:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !Ref ManagementAWSAccountId
            Action:
              - 'sts:AssumeRole'
      Description: Audit Role
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/ReadOnlyAccess
      RoleName: role-audit

  # ---------------------------------------------
  # Policy
  # ---------------------------------------------
  PolicyAdministratorRestriction:
    Type: AWS::IAM::Policy
    Properties:
      Roles:
        - !Ref RoleAdministrator
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Action:
              - "*"
            Resource:
              - "*"
            Condition:
              StringEquals:
                aws:ResourceTag/ManagedResource: "true"
      PolicyName: policy-administrator-restiction

  PolicyDeveloperRestriction:
    Type: AWS::IAM::Policy
    Properties:
      Roles:
        - !Ref RoleDeveloper
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Action:
              - "*"
            Resource:
              - "*"
            Condition:
              StringEquals:
                aws:ResourceTag/ManagedResource: "true"
          - Effect: Deny
            Action:
              - "cloudtrail:Cancel*"
              - "cloudtrail:Create*"
              - "cloudtrail:Delete*"
              - "cloudtrail:Enable*"
              - "cloudtrail:Put*"
              - "cloudtrail:Start*"
              - "cloudtrail:Stop*"
              - "cloudtrail:Update*"
              - "cloudtrail:Deregister*"
              - "cloudtrail:Register*"
              - "cloudtrail:Disable*"
              - "cloudtrail:Restore*"
            Resource:
              - "*"
      PolicyName: policy-developer-restiction

  PolicyAuditRestriction:
    Type: AWS::IAM::Policy
    Properties:
      Roles:
        - !Ref RoleAudit
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Action:
              - "*"
            Resource:
              - "*"
            Condition:
              StringEquals:
                aws:ResourceTag/ManagedResource: "true"
      PolicyName: policy-audit-restiction
```

CloudFormation StackSetsから当該テンプレートを適用します。
`StackSetsの作成`を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/071f9a52-9b06-597e-066e-265540689c96.png)

`IAM管理ロール`として、先に作成した`AWSCloudFormationStackSetAdministrationRole`を選択します。
また、実行ロール名にはターゲットアカウント側に作成した`AWSCloudFormationStackSetExecutionRole`を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/ec785efe-37e5-eb53-3b41-aac4ff465fb2.png)

用意したテンプレートファイルを選択して次へ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/8447e60e-dc1d-c6e8-f02f-a97ed4891bfb.png)

任意の名前を入力し、パラメータ（管理用アカウントのAWSアカウントID）を入力して次へ。
![スクリーンショット 2023-12-12 19.54.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/b0a29ce6-1bc7-a09e-f115-1b53a3572190.png)

`スタックオプションの設定`では、タグ（キー：ManagedResource、値：true）を指定します。
ここで指定したタグがCloudFormationで作成されるリソースに付与され、先に用意したIAMロールによる制御対象とすることが出来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/f2e2cb5c-b73d-62b8-d4ba-df4bfd33c04c.png)

`デプロイオプションの設定`では、デプロイ先のAWSアカウントIDとリージョンを指定します。
AWSアカウントIDには開発用アカウントのAWSアカウントID×2、リージョンは利用するリージョン（今回は東京リージョンとします）を指定します。

![スクリーンショット 2023-12-12 19.59.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/d2bfa6ee-8934-f396-27b6-d879d1c25c9b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/4705c042-78cf-772c-40e8-cfbc0eaa20d2.png)

設定内容を確認して`送信`を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/5e899ff2-866b-950c-368a-0752ee73699f.png)

スタックの作成が完了したら次の手順へ進みます。

## 5. IAMユーザの作成

管理用アカウントにIAMユーザを作成します。
今回は、例として先のアーキテクチャ図で記載した`saburo`さん（開発者ロール）を作成します。

`ユーザの作成`を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/2e10f027-65b2-a309-b466-e78e7b278176.png)

ユーザ名に`saburo`と入力し、AWSマネジメントコンソールへのアクセスにチェックを入れて次へ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/ac824f33-eab1-3cd9-b0bf-fa45c89837c9.png)

先に作成した開発者用のIAMグループ（group-developer）を選択して次へ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/8ca0fac5-761b-919c-a68e-4d043896a81a.png)

設定内容を確認して`ユーザーの作成`を押下。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/b1765bbb-6356-2f37-2565-461886d90657.png)

作成した`saburo`ユーザでAWSマネジメントコンソールへログインします。
画面右上の`ロールの切り替え`からスイッチロール出来ることを確認します。
![スクリーンショット 2023-12-12 20.14.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/48b2db9f-8354-fe70-25dd-5079a8e19386.png)

スイッチロール先のAWSアカウントIDとロール名を入力して、`ロールの切り替え`を押下。
![スクリーンショット 2023-12-12 20.16.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/d1ca640c-5576-5953-9bb6-e2980bb071c0.png)

ロールが切り替わったことが確認出来ます。
![スクリーンショット 2023-12-12 20.17.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/b695bf29-59a0-d56a-8764-45315453c0a8.png)

# 動作確認

前の手順でスイッチロールした状態で、許可されていない操作が出来ないことを確認します。

まず、自分の権限の変更を試してみます。
自分のIAMロール（role-developer）を参照しようとした時点で権限がないことが確認出来ます。
![スクリーンショット 2023-12-12 20.26.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/2247735d-93a9-17bc-3dd9-60d267f28465.png)

次に、StackSetsで作成されたCloudFormationスタックが削除出来るか確認します。
先の例と同じようにStackSetsで作成されたスタックを参照しようとした時点で権限がないことが確認出来ます。
![スクリーンショット 2023-12-12 20.28.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/1649bd9f-df00-56e9-6222-22cd670a33bb.png)

最後に、CloudTrail証跡の削除を試みました（記事には記載していませんが、あらかじめ有効にしておきました）。
上部に黄色く表記されていますが、削除権限が無いことが確認出来ます。
![スクリーンショット 2023-12-12 20.42.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483600/ee39bc9d-6cf7-3410-9a28-e4461be41d90.png)

このように、スイッチロール先のIAMロールに対してポリシーを適切に設定することで、OrganizationsにおけるSCPのように
ブラックリスト的な操作を指定することが出来ます。

# 最後に

- AWSを使ってマルチアカウント構成を取る場合にはOrganizationsを使うことが広く知られており、本記事のような手順を採用することはレアかと思いますが、敢えてそれを使わない方法をまとめてみました。
- 面倒な方法を試してみることで、サービスの持つ良し悪しがより分かる気がします。