Terraformで実践するAWS IAM Identity Center（AWS Single Sign-On）のユーザー管理戦略

# はじめに
AWS IAM Identity Center（AWS Single Sign-On）を使用して、ユーザー管理を考えていく上で、Terraformを使用して構成管理を実現しようと思います。


作成したコードはgithub上に上がっているので、ご参考ください。
（※`*.tfstate`は`.gitignore`に記載し、git管理外としています。）

https://github.com/parupappa/aws-sso-terraform


# 構成図
![スクリーンショット 2023-06-16 17.10.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/24095cec-534d-222d-8e5f-034ec4d4864b.png)


構成管理にはStateファイルの管理をサーバレスに行える点を考慮して[Terraform Cloud](https://app.terraform.io)を使用します

https://dev.classmethod.jp/articles/terraform_tfstate_management_tfc/


なお、今回は複数環境へのデプロイ含むCI/CDの設計などは考慮していません。

# 実装
## Terraform Cloud と Githubの接続
こちらを参考にVSC接続を行う

https://qiita.com/boccham/items/190f04bfbc9ffc0b5baf

## AWS SSO 有効化/ユーザー・グループの作成

最初に AWS SSO を有効にする必要がありますが、残念ながら 2023/6/21 時点で Terraformを使用したコードでの有効化設定は非対応です。 そのため、コンソール上から有効化したいリージョンを選択し、手動で有効にします


続いて、AWS IAM Identity Centerで`ユーザー/グループ`を作成しますが、こちらも Terraform で作成することはできません。 そのため、マネジメントコンソールで作成します。

また、グループにアタッチしたいユーザーもここでアタッチしておきます。

![スクリーンショット 2023-06-21 12.13.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/10b40b06-b4ab-c713-7e81-384545432d6c.png)

![スクリーンショット 2023-06-21 12.13.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/cf6ae996-b139-2318-9c60-3ab143263809.png)

## 方針
今回は、`Admin`,`Developer`,`Guest`,`System`と4つのグループを用意し、それぞれのグループにアタッチするポリシーをあらかじめ決定。


`Admin`: SREチームのユーザーが属するグループ。`AdministratorAccess`の権限を付与。
`Developer`: SREチーム以外の開発者用グループ。開発作業に必要最低限のアクセスを付与。
`Guest`: 開発チーム以外のグループ。`ReadOnly`が主となるアクセス。
`System`: システム系で使用するIAMポリシーをまとめたもの。

例：`Admin`グループには`Admin`のアクセス権限セットをアタッチするように設定。

## tarraform
実装したものが、以下。

※マネージドポリシーとカスタマー管理ポリシーでは作成に使用するterraform resourceが異なるので、variableを別々で定義した。

```tf:aws-sso.tf
########################################
# Terraformできないこと
# 下記に示すものはTerraformではできないので、手動でコンソール上（IAM Identity Center）から設定する必要がある

# 1. グループの作成・削除
# 2. ユーザーの追加・削除
# 3. グループにユーザーを追加・削除
########################################

data "aws_ssoadmin_instances" "example" {}

locals {
  groups = var.aws_sso_group
  policies = {
    Admin = {
      managed  = var.aws_sso_admin_managed_policies
      customer = var.aws_sso_admin_customer_policies
    }
    Guest = {
      managed  = var.aws_sso_guest_managed_policies
      customer = var.aws_sso_guest_customer_policies
    }
    Developer = {
      managed  = var.aws_sso_developer_managed_policies
      customer = var.aws_sso_developer_customer_policies
    }
    System = {
      managed  = var.aws_sso_system_managed_policies
      customer = var.aws_sso_system_customer_policies
    }
  }
  managed_policy_attachments = flatten([
    for group, policies in local.policies : [
      for policy in policies.managed : {
        group  = group
        policy = policy
      }
    ]
  ])
  customer_policy_attachments = flatten([
    for group, policies in local.policies : [
      for policy in policies.customer : {
        group  = group
        policy = policy
      }
    ]
  ])
}

########################################
# IAM Identity Center グループを取得
########################################
data "aws_identitystore_group" "group" {
  for_each = toset(local.groups)

  identity_store_id = tolist(data.aws_ssoadmin_instances.example.identity_store_ids)[0]

  alternate_identifier {
    unique_attribute {
      attribute_path  = "DisplayName"
      attribute_value = each.value
    }
  }
}
########################################
# アクセス許可セット作成
########################################
resource "aws_ssoadmin_permission_set" "PermissionSet" {
  for_each = toset(local.groups)

  name         = each.value
  description  = each.value
  instance_arn = tolist(data.aws_ssoadmin_instances.example.arns)[0]
}
locals {
  groups_to_permission_set = var.groups_to_permission_set
  assignment = [
    for tmp in setproduct(local.groups, values(data.aws_identitystore_group.group)) : {
      group_name          = tmp[0]
      group_id            = tmp[1].group_id
      permission_set_name = local.groups_to_permission_set[tmp[0]]
    }
  ]
}
########################################
# アクセス許可セットにポリシーをアタッチ
########################################
resource "aws_ssoadmin_managed_policy_attachment" "managed_policy_attachment" {
  for_each = { for item in local.managed_policy_attachments : "${item.group}.${item.policy}" => item }

  instance_arn       = aws_ssoadmin_permission_set.PermissionSet[each.value.group].instance_arn
  managed_policy_arn = each.value.policy
  permission_set_arn = aws_ssoadmin_permission_set.PermissionSet[each.value.group].arn
}

resource "aws_ssoadmin_customer_managed_policy_attachment" "customer_policy_attachment" {
  for_each = { for item in local.customer_policy_attachments : "${item.group}.${item.policy}" => item }

  instance_arn       = aws_ssoadmin_permission_set.PermissionSet[each.value.group].instance_arn
  permission_set_arn = aws_ssoadmin_permission_set.PermissionSet[each.value.group].arn
  customer_managed_policy_reference {
    name = split("/", each.value.policy)[1]
    path = "/"
  }
}
########################################
# IAM Identity Center グループにアクセス許可セットを関連付け
########################################
resource "aws_ssoadmin_account_assignment" "example" {
  for_each = data.aws_identitystore_group.group

  instance_arn       = aws_ssoadmin_permission_set.PermissionSet[each.key].instance_arn
  permission_set_arn = aws_ssoadmin_permission_set.PermissionSet[each.key].arn

  principal_id   = each.value.group_id
  principal_type = "GROUP"

  target_id   = var.account_id
  target_type = "AWS_ACCOUNT"
}

```


```tf:variables.tf
# variables.tfはvariable(変数)の宣言を行うところです。
variable "region" {
  description = "AWS region"
  default     = "ap-northeast-1"
}
# terraform cloudから取得
variable "AWS_ACCESS_KEY_ID" {}
variable "AWS_SECRET_ACCESS_KEY" {}

variable "account_id" {
  description = "The ID of the AWS account"
  type        = string
  # 12桁のアカウントID
  default     = "xxxxxxxxxxxx"
}


########################################
# IAM Identity Centerのグループ・許可セットを定義
# グループには同名の許可セットを関連付ける
########################################
variable "aws_sso_group" {
  description = "The list of identity store group"
  type        = list(string)
  default     = ["Admin", "Guest", "Developer", "System"]
  // Add more if needed
}

variable "groups_to_permission_set" {
  type = map(any)
  default = {
    Admin     = "Admin"
    Guest     = "Guest"
    Developer = "Developer"
    System    = "System"
    // Add more if needed
  }
}
########################################
# グループごとに使用するポリシーのarnを指定
# managedポリシーとカスタマー管理ポリシーでは作成するresourceが異なるので、それぞれvariableを用意
########################################
## Admin
variable "aws_sso_admin_managed_policies" {
  description = "The list of admin managed policies"
  type        = list(string)
  default = [
    "arn:aws:iam::aws:policy/AdministratorAccess"
  ]
}
variable "aws_sso_admin_customer_policies" {
  description = "The list of admin customer policies"
  type        = list(string)
  default     = []
}
## Guest
variable "aws_sso_guest_managed_policies" {
  description = "The list of guest managed policies"
  type        = list(string)
  default = []
}
variable "aws_sso_guest_customer_policies" {
  description = "The list of guest customer policies"
  type        = list(string)
  default = []
}
## Developer
variable "aws_sso_developer_managed_policies" {
  description = "The list of developer managed policies"
  type        = list(string)
  default = []
}
variable "aws_sso_developer_customer_policies" {
  description = "The list of developer customer policies"
  type        = list(string)
  default = []
}
## System
variable "aws_sso_system_managed_policies" {
  description = "The list of system managed policies"
  type        = list(string)
  default = []
}

variable "aws_sso_system_customer_policies" {
  description = "The list of system customer policies"
  type        = list(string)
  default = []
}

```

git pushをすればmainブランチへのマージをトリガーに`terraform plan` , `terraform apply`が実行される

※ git push する前に`terraform fmt`でフォーマットを整形しておくと良い（もしくはCI/CDに組み込む）

# さいごに
今回の実装を通して感じた気づき・学びをシェア

- Terraform　Cloudを使用するとstateファイルの管理がすごく楽
- Terraformで対応していないリソースのIaCをどのように管理・運用していくかチームで共通認識を作る必要がある
- どこまでを`variable`, `local`といった変数として扱うかの判断が難しい
- Terraformは楽しい😁


# 参考
## 【Terraform】

https://blog.serverworks.co.jp/SSO_Terraform

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_managed_policy_attachment

## 【Terraform Cloud】

https://zenn.dev/isosa/articles/ca44f2efbfe36b#cli%E3%81%8B%E3%82%89%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3

## 【AWS SSO】
https://blog.serverworks.co.jp/SSO_Terraform