[AWS][IAM][Terraform] ロール切替方式の権限設定一式を作成する

以下の記事の続き。

マルチアカウントにおけるユーザ管理を考える
https://qiita.com/batatch/items/425661dbffece3b981bf


## 概要

ここでは、ロール切替におけるグループ、ロール、ポリシーなど権限一式を実装していきます。
権限設定の全体像は下図のとおり。

![fig-switch-role-all-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558772/286793b1-4195-9e16-19ba-dab0593f11f6.png)


マスターアカウント、ターゲットアカウントに対して、それぞれ以下の項目を設定します。

**ターゲットアカウントへの設定項目**

| IAM ロール | ロール名 | 信頼関係のポリシー | IAM ポリシー |
|:--|:--|:--|:--|
| Administrator | 管理者 | 1111-1111-1111 からの AssumeRole 許可 | AdministratorAccess など (*) |
| Maintainer    | 作業者 | 1111-1111-1111 からの AssumeRole 許可 | PowerUserAccess など (*)     |
| Viewer        | 閲覧者 | 1111-1111-1111 からの AssumeRole 許可 | ViewOnlyAccess など (*)      |
|   :           |   :    |       :                              |    :                         |

> (*) 実際には独自に権限を整理してポリシーを定義しますが、ここでは AWS デフォルトの職務機能のポリシーを使って例示していきます。

**マスターアカウントへの設定項目**

| IAM ユーザ | IAM グループ | IAM ポリシー | 説明 |
|:--|:--|:--|:--|
| user001            | a2_AdministratorGroup | a2_AdministratorPolicy | 2222-2222-2222 の管理者へのロール切替 |
|                    | a2_MaintainerGroup    | a2_MaintainerPolicy    | 2222-2222-2222 の作業者へのロール切替 |
| user001<br>user002 | a2_ViewerGroup        | a2_ViewerPolicy        | 2222-2222-2222 の閲覧者へのロール切替 |
|                    | a3_AdministratorGroup | a3_AdministratorPolicy | 3333-3333-3333 の管理者へのロール切替 |
|                    | a3_MaintainerGroup    | a3_MaintainerPolicy    | 3333-3333-3333 の作業者へのロール切替 |
| user002<br>user003 | a3_ViewerGroup        | a3_ViewerPolicy        | 3333-3333-3333 の閲覧者へのロール切替 |

> ロール切替グループを作成し、ユーザへマッピングします。
> グループは、対象のアカウント x ロール数の組み合わせ だけ用意します。 


## ターゲットアカウントへの設定項目

ターゲットアカウントへの設定項目を実装します。
以下、Terraform スクリプトとして記述していきます。

### IAM ロールへ付与する信頼関係ポリシー定義

**スクリプト**

```hcl
data "aws_iam_policy_document" "assume_from" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type = "AWS"
      identifiers = [
        format("arn:aws:iam::%s:root", var.master_account_id)    # マスターアカウントID を指定
      ]
    }
  }
}
```

### IAM ロール本体の定義

上記で作成した信頼関係ポリシーを割り当てます。

**入力情報**

```yaml
- role_name: Administrator
    ...
- role_name: Maintainer
    ...
- role_name: Viewer
    ...
```

**スクリプト**

```hcl
resource "aws_iam_role" "this" {
  name               = var.role_name                                    # ロール名
  path               = var.path                                         # パス (デフォルトで "/")
  description        = var.desc                                         # ロール説明 (英数字のみ)
  assume_role_policy = data.aws_iam_policy_document.assume_from.json    # 信頼関係ポリシー (上記で定義)
}
```

### IAM ポリシーの独自定義

独自に権限設定を定義する場合は、IAM ポリシー定義を作成します。

**スクリプト**

```hcl
resource "aws_iam_policy" "this" {
  name        = var.name                                         # ポリシー名
  path        = var.path                                         # パス (デフォルトで "/")
  description = var.desc                                         # ポリシー説明 (英数字のみ)
  policy      = file(format("policy_defs/%s.json", var.name))    # ポリシー定義の本文、例は外部から "ポリシー名.json" ファイルを読み込み
}
```

### IAM ロールへの IAM ポリシー割当

IAM ロールへ権限本体の IAM ポリシーを割り当てます。
以下の例では var.policies として IAM ポリシーの ARN の配列を渡して、for_each のループで設定させています。

**入力情報**

```yaml
- role_name: Administrator
  policies:
    - arn:aws:iam::aws:policy/AdministratorAccess
- role_name: Maintainer
  policies:
    - arn:aws:iam::aws:policy/PowerUserAccess
- role_name: Viewer    
  policies:
    - arn:aws:iam::aws:policy/job-function/ViewOnlyAccess
```

**スクリプト**

```hcl
resource "aws_iam_role_policy_attachment" "this" {
  for_each = toset(var.policies)

  role       = aws_iam_role.this.name    # ロール名
  policy_arn = each.key                  # 割り当てるポリシーの ARN
}
```


## マスターアカウントへの設定項目

マスターアカウントへの設定項目を実装します。
以下、Terraform スクリプトとして記述していきます。

### ロール切替用の IAM ポリシー定義

ロール切替用のポリシー定義を作成します。
ターゲットアカウントの ID、ロール名がパラメータとなるので、テンプレート変換にすると便利です。

**入力情報**

```yaml
- target_name: a2
  target_id: 222222222222
  roles:
    - Administrator  #=> a2_AdministratorPolicy
    - Maintainer     #=> a2_MaintainerPolicy
    - Viewer         #=> a2_ViewerPolicy
```

**スクリプト**

```hcl
resource "aws_iam_policy" "this" {
  for_each = toset(var.roles)

  name        = format("%s_%sPolicy", var.target_name, each.key)    # ポリシー名
  path        = var.path                                            # パス (デフォルトで "/")
  description = var.desc                                            # ポリシー説明 (英数字のみ)

  policy = templatefile("policy_defs/assume_to.tpl.json", {         # ポリシー定義の本文 (テンプレートをファイルから読み込み)
    target_account_id = var.target_id,                              # ターゲットアカウント ID を指定
    path              = var.path,                                   # パス (デフォルトで "/")
    role_name         = format("%s", each.key)                      # ターゲットアカウント上のロール名
  })
}
```

**テンプレートファイル(JSON)**

```policy_defs/assume_to.tpl.json
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": "sts:AssumeRole",
        "Resource": "arn:aws:iam::${target_account_id}:role${path}${role_name}"
    }
}
```

### ロール切替用の IAM グループ作成

ロール切替用のグループを作成し、上記で作成したポリシーを割り当てます。

**入力情報**

```yaml
- target_name: a2
  roles:
    - Administrator  #=> a2_AdministratorGroup
    - Maintainer     #=> a2_MaintainerGroup
    - Viewer         #=> a2_ViewerGroup
```

**スクリプト**

```hcl
resource "aws_iam_group" "this" {
  for_each = toset(var.roles)

  name = format("%s_%sGroup", var.target_name, each.key)    # グループ名
  path = var.path                                           # パス (デフォルトで "/")
}

resource "aws_iam_group_policy_attachment" "this" {
  for_each = aws_iam_group.this

  group      = each.value.name                              # グループ名
  policy_arn = aws_iam_policy.this[each.key].arn            # 割り当てるポリシーの ARN
}
```


## Terraform の構成

今回は以下のようにディレクトリを整理します。

```
versions.tf
components/
  account-master/
    main.tf
    outputs.tf
    versions.tf -> ../../versions.tf
  account-target/
    main.tf
    outputs.tf
    versions.tf -> ../../versions.tf
  policy_defs/
    assume_to.tpl.json
modules/
  group_assume_role/
    main.tf
    outputs.tf
    variables.tf
  policy/
    main.tf
    outputs.tf
    variables.tf
  role/
    main.tf
    outputs.tf
    variables.tf
```

ターゲットアカウントでは以下のようにコマンドを実行します。

```
$ cd components/account-target/
$ terraform init
$ terraform plan
$ terraform apply
```

マスターアカウントではこうなります。

```
$ cd components/account-master/
$ terraform init
$ terraform plan
$ terraform apply
```

module を使う/使わないなど、ベストプラクティスがいろいろあるようです。

作業者の役割によって、インフラ寄りだったり、アプリケーションの環境構築だったりと観点も異なるので、状況に合わせてよい形を見つけていくことが大事だと思います。
ディレクトリも適当な範囲で区切って、あまり大きくまとめない方がよさそうです。

個人としては、module を引き込む components 内の処理の記述が分かりやすくしたいです。
module は汎用性を高めるのではなく、components の記述がシンプルに仕様を表現できるように処理をまとめられるようにします。


**ターゲットアカウントの設定スクリプトのイメージ**

```components/account-target/main.tf
locals {
  master_account_id = "111111111111"                         # マスターアカウント ID

  policy_defs = {                                            # 個別に定義するポリシー設定
    SamplePolicy = "Policy of Sample"
  }

  assumed_role_defs = {                                      # 切替先となるロールと割り当てるポリシー設定
    "Administrator" = {
      policies = [
        "arn:aws:iam::aws:policy/AdministratorAccess"
      ]
    },
    "Maintainer" = {
      policies = [
        "arn:aws:iam::aws:policy/PowerUserAccess"
      ]
    },
    "Viewer" = {
      policies = [
        "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"
      ]
    }
  }
}

module "target_policy" {                                     # 個別ポリシーを定義する
  source = "../../modules/policy"

  for_each = local.policy_defs

  name = each.key                                            # ポリシー名
  path = "/"
  desc = each.value                                          # ポリシー説明
  body = file(format("../policy_defs/%s.json", each.key))    # ポリシー定義の本文をファイルから読み込み
}

module "target_role" {                                       # ロールを定義する
  source = "../../modules/role"

  for_each = local.assumed_role_defs

  master_account_id = local.master_account_id                # マスターアカウント ID

  name     = each.key                                        # ロール名
  path     = "/"
  policies = each.value.policies                             # ロールに割りてるポリシーの一覧(ARN)
}
```

**マスターアカウントの設定スクリプトのイメージ**

```components/account-master/main.tf
locals {
  target_defs = {                                            # 切替先ターゲットアカウントとロールの対応設定
    "a2" = {
      account_id = "222222222222",
      roles = [
        "Administrator",
        "Maintainer",
        "Viewer"
      ]
    },
    "a3" = {
      account_id = "333333333333",
      roles = [
        "Administrator",
        "Maintainer",
        "Viewer"
      ]
    }
  }
}

module "master_group_assume_role" {                          # ロール切替用のグループ、ポリシーを定義
  source = "../../modules/group_assume_role"

  for_each = local.target_defs

  assume_policy_template_file = "../policy_defs/assume_to.tpl.json"
                                                             # ロール切替用のポリシーのテンプレート定義ファイル
  target_name = each.key                                     # ターゲットアカウント名
  target_id   = each.value.account_id                        # ターゲットアカウント ID
  path        = "/"
  roles       = each.value.roles                             # ロール一覧
}
```

このように必要な情報だけに整理すると、仕様の見通しがよくなります。
例では設定内容は locals に集約されています。

あとは、先に説明した resource 定義をそれぞれ参照されている module に当てはめていくことになります。


今回はここまで。

// EOF