IAM Identity CenterのGoogle Workspace連携における制限をTerraformでいい感じに管理する

2024/12/12に公開

概要

SRE Advent Calendar 2024の12日目の記事です。

この記事では、IAM Identity Center の概要と、 Terraform による管理方法の一例を紹介します。

prineNumber では Google Workspace を IAM Identity Center の IdP として利用しています。
特定の Google Group のみを Identity Center の User として provisioning する設定にしています。[1]

一方で、Google Workspace を IdP として利用する場合の制限として、Google Workspace 側で Group を作成しても、Identity Center 側では Group が作成されません。

Google Workspace からの SCIM 自動同期は、現在、ユーザープロビジョニングに限定されています。現在、自動グループプロビジョニングはサポートされていません
Google Workspace および IAM アイデンティティセンターによる SAML と SCIM の設定 - AWS IAM Identity Center

この Google Workspace 利用時の制限を Terraform で吸収しましょう、というのがこの記事の主題です。

この記事では触れないこと

初期連携周り、Google WorkspaceとIAM Identity Centerの連携のセットアップについてはこの記事では触れません。
少なくとも弊社の環境では公式ドキュメント通りに設定すればOKでした。

IAM Identity Center とは

  • 旧 AWS Single Sign-On の名の通りSSOをAWS上で行うためのサービス。
    • AWS Organization と組み合わせて、Organization 配下のアカウントに対する権限を制御します。
  • 詳しくは What is IAM Identity Center? - AWS IAM Identity Center を参照してください。

IAM Identity Center の登場人物

Application は使ってないので省略しています。

Organization Instance (Managing Instance)

  • Organization 単位で1つ作成される IAM Identity Center のインスタンス
  • おそらく1つの Organization で複数インスタンスは持てないと思うので、普段の運用で気にする必要はないですが、APIレベル(≒ Terraform)では紐づけが必要になってきます。

Identity Source

  • User と Group を持ってくる Source
  • ざっくりと以下の選択肢があります
    • Identity Center 組み込みの Identity Center directory
    • Azure AD
    • External identity provider (今回はこちら)

User

  • Identity Center 上のエンティティの最小単位
  • Identity Source からの provisioning 対象

Group

  • User の集合
  • 外部 IdP を利用している場合、コンソールから作成することはできません。
  • Identity Source からの provisioning 対象ですが、仕様上、Google Workspace を IdP として利用している場合、provisioning されません

Permission Set

  • 認可を行うためのリソース
  • AWS Managed Policy と Customer Managed Policy をアタッチできます。
    • AWS Managed Policy の場合は ARN を指定します。
    • Customer Managed Policy の場合はポリシー名を指定します。
      • このとき、Permission Set を紐づける AWS アカウント上に名前が完全一致する IAM Policy が存在している必要があります。
  • Permission Set は単体では AWS アカウントに紐づけることはできず、User または Group とセットで紐づける必要があります。

Terraform で IAM Identity Center のリソースを管理しよう

Group については IdP 側からの provisioning もできず、マネジメントコンソールからも作成できないようになっていますが、APIからは作成可能です。そのため、 Group 管理部分を Terraform で巻き取ります。

ディレクトリの概観としては、以下のような形を取っています。
それぞれについて、コード例を交えて説明していきます。

identity-center
├── iam.tf            # CI/CD向けの権限設定 (今回の記事のスコープ外)
├── organizations.tf  # Organizationの設定まわり(今回の記事の記事のスコープ外)
├── permission_set.tf # 複数アカウントに対して適用するPermissionSetを定義
├── users.tf          # provisiningされたUserをGroupにする定義
├── account_A.tf      # 各アカウントごとのPermissionSetとGroupの紐づけを定義
├── account_B.tf
├── account_C.tf
(snip)

locals

頻繁に参照する必要のある値は data ソースからローカル変数に入れて参照するようにしています。

data "aws_ssoadmin_instances" "instance" {}

locals {
  identity_store_instance_arn = tolist(data.aws_ssoadmin_instances.instance.arns)[0]
  identity_store_id           = tolist(data.aws_ssoadmin_instances.instance.identity_store_ids)[0]
}

permission_set.tf

複数アカウントに対して適用する Permission Set を定義しています。
例えば、 AWSマネージドポリシーである ReadOnlyAccess をアタッチした Permission Set はどのアカウントでも利用したいのでこのファイルで定義しています。

resource "aws_ssoadmin_permission_set" "aws_readonly_access" {
  name             = "AWSReadOnlyAccess"
  description      = "This policy grants permissions to view resources and basic metadata across all AWS services"
  instance_arn     = local.identity_store_instance_arn
  session_duration = "PT1H"
}

resource "aws_ssoadmin_managed_policy_attachment" "readonly_access" {
  instance_arn       = local.identity_store_instance_arn
  managed_policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
  permission_set_arn = aws_ssoadmin_permission_set.aws_readonly_access.arn
}

特定のアカウントのみに適用するPermission Setに関しては、それぞれのアカウント名のファイルに定義するようにしています。

users.tf

チームなど権限をまとめて付与したい対象を local 変数で定義し、Group を作成しています。
例えば、新しい人に権限追加するだけであれば local 変数だけ修正して Pull Request を出してもらえば OK な形にしています。[2]

locals {
  team_cat = toset([
    "kuro@example.com",
    "shiro@example.com",
    "tama@example.com",
  ])
  team_dog = toset([
    "pochi@example.com",
    "taro@example.com",
  ])
  team_owl = toset([
    "omame@example.com",
  ])
  all_users = setunion([
      local.team_cat,
      local.team_dog,
      local.team_owl,
    ])
  groups = {
    "team_cat" = {
      users = local.team_cat,
    }
    "team_dog" = {
      users = local.team_dog,
    }
    "team_owl" = {
      users = local.team_owl,
    }
  }
  flatten_groups = flatten([
    for group, users in local.groups : [
      for user in users.users : {
        group_name = group
        user_name  = user
      }
    ]
  ])
}

data "aws_identitystore_user" "users" {
  for_each = local.all_users

  identity_store_id = local.identity_store_id
  alternate_identifier {
    unique_attribute {
      attribute_path  = "UserName"
      attribute_value = each.value
    }
  }
}

resource "aws_identitystore_group" "groups" {
  for_each          = local.groups

  identity_store_id = local.identity_store_id
  display_name      = each.key
}

resource "aws_identitystore_group_membership" "group_memberships" {
  for_each          = { for f in local.flatten_groups : "${f.group_name}_${f.user_name}" => f }

  identity_store_id = local.identity_store_id
  group_id          = aws_identitystore_group.groups[each.value.group_name].group_id
  member_id         = data.aws_identitystore_user.users[each.value.user_name].id
}

account_*.tf

AWS アカウントと Permission Set と Group を紐づけます。
全アカウントで利用する (permission_set.tfで定義した) Permission Set を使う場合は以下のような感じで定義します。

locals {
  account_A_account_id = aws_organizations_account.production_account["account-A"].id

  account_A_administrator_access_groups = toset([
    "team_cat"
  ])
  account_A_readonly_access_groups = toset(flatten([
    local.account_A_administrator_access_groups,
    "team_dog",
  ]))
}

resource "aws_ssoadmin_account_assignment" "account_A_administrator_access_groups" {
  for_each = local.account_A_administrator_access_groups

  instance_arn       = local.identity_store_instance_arn
  permission_set_arn = aws_ssoadmin_permission_set.aws_administrator_access.arn

  principal_id   = aws_identitystore_group.groups[each.value].group_id
  principal_type = "GROUP"

  target_id   = local.account_A_account_id
  target_type = "AWS_ACCOUNT"
}

resource "aws_ssoadmin_account_assignment" "account_A_readonly_access_groups" {
  for_each = local.account_A_readonly_access_groups

  instance_arn       = local.identity_store_instance_arn
  permission_set_arn = aws_ssoadmin_permission_set.aws_readonly_access.arn

  principal_id   = aws_identitystore_group.groups[each.value].group_id
  principal_type = "GROUP"

  target_id   = local.account_A_account_id
  target_type = "AWS_ACCOUNT"
}

特定のアカウントのみで使いたい Permission Set を定義する場合は account_*.tf 上で定義しています。
以下の例では、 CloudWatchLogsReadOnlyAccess と、ユーザー定義の Policy (super-readonly-policy) が付与された Permission Set を定義しています。

注意点として、AWS マネージドポリシーとカスタマーマネージドポリシーで Terraform Resource が別で用意されていること、Permission Set を紐づける AWS アカウント上に名前が完全一致する IAM Policy が存在している必要があることです。

locals {
  account_A_cwlogs_readonly_groups = toset([
    "team_owl",
  ])
  account_A_cwlogs_readonly_managed_policies = [
    "arn:aws:iam::aws:policy/CloudWatchLogsReadOnlyAccess",
  ]
  account_A_cwlogs_readonly_customer_managed_policies = [
    "super-readonly-policy", # この名前の IAM Policy がアカウントに存在する必要がある
  ]
}

resource "aws_ssoadmin_permission_set" "account_A_cwlogs_readonly" {
  name             = "CWLogsReadOnly"
  description      = "This policy grants permissions to use CW Logs"
  instance_arn     = local.identity_store_instance_arn
  session_duration = "PT1H"
}

resource "aws_ssoadmin_managed_policy_attachment" "account_A_cwlogs_readonly_managed_policies" {
  for_each = { for i, v in local.account_A_cwlogs_readonly_managed_policies : i => v }

  instance_arn       = local.identity_store_instance_arn
  managed_policy_arn = each.value
  permission_set_arn = aws_ssoadmin_permission_set.account_A_cwlogs_readonly.arn
}

resource "aws_ssoadmin_customer_managed_policy_attachment" "account_A_cwlogs_readonly_cosutomer_policy" {
  for_each = { for i, v in local.account_A_cwlogs_readonly_customer_managed_policies : i => v }

  instance_arn       = local.identity_store_instance_arn
  permission_set_arn = aws_ssoadmin_permission_set.account_A_cwlogs_readonly.arn
  customer_managed_policy_reference {
    name = each.value
    path = "/"
  }
}

resource "aws_ssoadmin_account_assignment" "account_A_cwlogs_readonly_groups" {
  for_each = local.account_A_cwlogs_readonly_groups

  instance_arn       = local.identity_store_instance_arn
  permission_set_arn = aws_ssoadmin_permission_set.account_A_cwlogs_readonly.arn

  principal_id   = aws_identitystore_group.groups[each.value].group_id
  principal_type = "GROUP"

  target_id   = local.account_A_account_id
  target_type = "AWS_ACCOUNT"
}

まとめ

IAM Identity Center の概要と、 Terraform による管理方法の一例を紹介しました。
Google Workspace 連携を利用した場合の仕様上の制限を Terraform で吸収することでいい感じに運用できることを目指しています。

We are hiring 🐈

TROCCO/COMETA を開発する primeNumber では、一緒に信頼性向上を実現してくれる SRE を絶賛募集中です! カジュアル面談もお待ちしています🙏

信頼性向上が選ばれる理由になる、裁量の大きいSRE【データ分析基盤総合支援SaaS TROCCO®/地方フルリモート有】 - 株式会社primeNumber


脚注
  1. 今のところ、全社員にAWSの権限が必要なわけではないためです。とはいえ利用する人の割合のほうが多いので、全員 provisioning だけしてしまってAWS側で権限を一切つけない、でもいいと思うのですが、制限した状態で始めてしまったので全解放のタイミングを失っているだけ説もあります。 ↩︎

  2. やや冗長かつ文字列で合わせないといけない形になっているのでもうちょっとシュッとさせたい… ↩︎

株式会社primeNumber

Discussion