IAM Identity Centerの設定をTerraformでやってみる
IAM Identity Centerの以下操作をやってみました。
- IAM Identity Centerの有効化
- IAM Identity Centerの管理の委任
- ユーザー/グループ/許可セットの作成
2024年1月時点では、以下は対応するTerraform Resourceがないため、手動で行なっています。その他の作業はTerraformを使いました。
- IAM Identity Centerの有効化
- IAM Identity Centerの管理の委任
Terraformコードは以下にあります。
msato0731/terraform-sample/iic
前提
この手順はOrganizationsを利用していることを前提にしています。
管理アカウントへのアクセスを最小限にするために、IAM Identity Center管理を他のアカウントに委任します。
そのため、管理アカウントの他にIAM Identity Center管理用のアカウントを用意する必要があります。
1. IAM Identity Centerを有効化する(管理アカウント)
Organizationの管理アカウントにログインし、マネジメントコンソールからIAM Identity Centerにアクセスします。
有効にする
を選択します。
有効化に成功すると、以下のようにダッシュボード画面が表示されます。
2. IAM Identity Centerの管理の委任(管理アカウント)
設定
-> 管理
-> 委任された管理者を登録
-> アカウントを登録
の順に選択します。
委任先AWSアカウントを選択し、`アカウントを登録`を選択します。
以降の作業は、委任先アカウントで行います。
3. ユーザー・グループ・許可セットの作成
AWSアカウントは以下の4つがあるものとします。
AWSアカウント | 説明 |
---|---|
Prd-HogeService | HogeService 本番環境 |
Stg-HogeService | HogeService STG環境 |
Prd-FugaService | FugaService 本番環境 |
Stg-FugaService | FugaService STG環境 |
ユーザーとグループは以下です。
ユーザー | 説明 | グループ |
---|---|---|
taro.test | インフラ管理責任者 | HogeAdministrator |
FugaAdministrator | ||
jiro.test | Hoge・Fuga Serviceの開発者 | HogeDevelopper |
FugaDevelopper | ||
saburo.test | Hoge Serviceの開発者 | HogeDevelopper |
グループに付与されている権限は以下です。
グループ | 説明 |
---|---|
*Administrator | 対象サービスの本番/STG: Admin |
*Developper | 対象サービスの本番:Read・STG:Admin |
AWSアカウント情報の設定
terraform.tfvarsを作成して、アカウントIDを記載します。
cp terraform.tfvars.sample terraform.tfvars
account_ids = { # アカウントIDを書き換える hoge_prd : "123456789012", hoge_stg : "555555555555", fuga_prd : "999999999999", fuga_stg : "012345678901", }
ユーザー・グループ・ユーザーとグループの関連付け
ユーザーとグループの記述は以下になります。
################################################################################ # Groups ################################################################################ groups = { hoge_admin = { name = "HogeAdministrator" description = "hoge service prd/stg:admin" } hoge_dev = { name = "HogeDevelopperTeam" description = "hoge servide prd:read, stg:admin" } fuga_admin = { name = "FugaAdministrator" description = "fuga service prd/stg:admin" } fuga_dev = { name = "FugaDevelopperTeam" description = "fuga servide prd:read, stg:admin" } } ################################################################################ # Users ################################################################################ users = { "[email protected]" = { name = { family_name = "Test" given_name = "Taro" } groups = [ "hoge_admin", "fuga_admin", ] } "[email protected]" = { name = { family_name = "Test" given_name = "Jiro" } groups = [ "hoge_dev", "fuga_dev" ] } "[email protected]" = { name = { family_name = "Test" given_name = "Saburo" } groups = [ "hoge_dev" ] } } ################################################################################ # Membership ################################################################################ # ユーザーとユーザーが属するグループの組み合わせを作成 users_groups_combined = [ for user, user_data in local.users : { for group in user_data.groups : "${user}_${group}" => { "user" = user "group" = group } } ] # ユーザーがブロックごとに分かれているため、ユーザーとグループの組み合わせを1つのブロックにまとめる users_groups_membership = zipmap( flatten( [for item in local.users_groups_combined : keys(item)] ), flatten( [for item in local.users_groups_combined : values(item)] ) )
################################################################################ # Group ################################################################################ resource "aws_identitystore_group" "this" { for_each = local.groups identity_store_id = local.identity_store_id display_name = each.value["name"] description = each.value["description"] } ################################################################################ # User ################################################################################ resource "aws_identitystore_user" "this" { for_each = local.users identity_store_id = local.identity_store_id display_name = join(" ", [each.value.name.given_name, each.value.name.family_name]) user_name = each.key name { family_name = each.value["name"]["family_name"] given_name = each.value["name"]["given_name"] } } ################################################################################ # Membership ################################################################################ resource "aws_identitystore_group_membership" "this" { for_each = local.users_groups_membership identity_store_id = local.identity_store_id group_id = aws_identitystore_group.this[each.value["group"]].group_id member_id = aws_identitystore_user.this[each.value["user"]].user_id }
ポイントは、aws_identitystore_group_membership
でユーザーとグループを関連づける必要があることです。
愚直に書くなら、このリソースはユーザーとグループの関連付けの数分書く必要があります。
local users_groups_membership
でユーザーとグループの関連付け用の配列を作っています。
users
配列から、group
とuser
だけの配列を作っています。
+ users_groups_combined = [ + { + "[email protected]_fuga_dev" = { + group = "fuga_dev" + user = "[email protected]" } + "[email protected]_hoge_dev" = { + group = "hoge_dev" + user = "[email protected]" } }, + { + "[email protected]_hoge_dev" = { + group = "hoge_dev" + user = "[email protected]" } }, + { + "[email protected]_fuga_admin" = { + group = "fuga_admin" + user = "[email protected]" } + "[email protected]_hoge_admin" = { + group = "hoge_admin" + user = "[email protected]" } }, ]
このままだと、user
ごとにブロックが分かれてしまっていて、for_each等を使いづらいです。
そのため、ユーザーとグループの組み合わせを1つのブロックにまとめます。
+ users_groups_membership = { + "[email protected]_fuga_dev" = { + group = "fuga_dev" + user = "[email protected]" } + "[email protected]_hoge_dev" = { + group = "hoge_dev" + user = "[email protected]" } + "[email protected]_hoge_dev" = { + group = "hoge_dev" + user = "[email protected]" } + "[email protected]_fuga_admin" = { + group = "fuga_admin" + user = "[email protected]" } + "[email protected]_hoge_admin" = { + group = "hoge_admin" + user = "[email protected]" } }
アクセス権限セット
アクセス権限セットの記述は以下になります。
######################## # Permissions ######################## permission_sets = { "admin" = { name = "AdministratorAccess" description = "Provides full access to AWS services and resources." managed_policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" }, "read_only" = { name = "ReadOnlyAccess" description = "Provides read-only access to AWS services and resources." managed_policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess" } } ################################################################################ # Account Assignments ################################################################################ account_assignments = [ # Hoge Service Production { account_id = var.account_ids.hoge_prd group = "hoge_admin" permission_set = "admin" }, { account_id = var.account_ids.hoge_prd group = "hoge_dev" permission_set = "read_only" }, # Hoge Service Staging { account_id = var.account_ids.hoge_stg group = "hoge_admin" permission_set = "admin" }, { account_id = var.account_ids.hoge_stg group = "hoge_dev" permission_set = "admin" }, # Fuga Service Production { account_id = var.account_ids.fuga_prd group = "fuga_admin" permission_set = "admin" }, { account_id = var.account_ids.fuga_prd group = "fuga_dev" permission_set = "read_only" }, # Fuga Service Staging { account_id = var.account_ids.fuga_stg group = "fuga_admin" permission_set = "admin" }, { account_id = var.account_ids.fuga_stg group = "fuga_dev" permission_set = "admin" } ] # アカウントID-グループ名-PermissionSet名をキーに設定 assignment_map = { for a in local.account_assignments : format("%v-%v-%v", a.account_id, local.groups[a.group].name, local.permission_sets[a.permission_set].name) => a } }
resource "aws_ssoadmin_permission_set" "this" { for_each = local.permission_sets name = each.value.name description = each.value.description instance_arn = local.instance_arn } resource "aws_ssoadmin_managed_policy_attachment" "this" { for_each = local.permission_sets instance_arn = local.instance_arn managed_policy_arn = each.value.managed_policy_arn permission_set_arn = aws_ssoadmin_permission_set.this[each.key].arn }
resource "aws_ssoadmin_account_assignment" "this" { for_each = local.assignment_map instance_arn = local.instance_arn permission_set_arn = aws_ssoadmin_permission_set.this[each.value.permission_set].arn principal_id = aws_identitystore_group.this[each.value.group].group_id principal_type = "GROUP" target_id = each.value.account_id target_type = "AWS_ACCOUNT" }
aws_ssoadmin_account_assignment
でアクセス許可セットとグループとアカウントIDを渡す必要があります。
上記の組み合わせをlocalaccount_assignments
で定義しています。
local変数account_assignments
を使ってfor_eachでリソースを作っても良いですが、以下の理由からlocal変数 user_group_membership
で加工しました。
- resource側のfor_eachの記述がシンプルになる
- リソースのキーが連番ではなく、ユニークな文字列になり分かりやすい
- 一部リソースの作成だけ失敗したときに、対象箇所を見つけやすい
resource "aws_ssoadmin_account_assignment" "this" { # そのまm for_each = { for idx, assignment in local.account_assignments: idx => assignment }
users_groups_membership
では、以下の[email protected]_fuga_dev
のようにユニークなIDが付きます。
+ users_groups_membership = { + "[email protected]_fuga_dev" = { + group = "fuga_dev" + user = "[email protected]" } + "[email protected]_hoge_dev" = { + group = "hoge_dev" + user = "[email protected]" } + "[email protected]_hoge_dev" = { + group = "hoge_dev" + user = "[email protected]" } + "[email protected]_fuga_admin" = { + group = "fuga_admin" + user = "[email protected]" } + "[email protected]_hoge_admin" = { + group = "hoge_admin" + user = "[email protected]" } }
おわりに
今回はカスタマー管理ポリシーやインラインポリシーは考慮せずに書きました。
追加したい場合は、Local変数permission_sets
に要素を足して、アタッチ用のresourceを定義すればできそうです。
以上、AWS事業本部の佐藤(@chari7311)でした。