LAB39_AWS_IAM Identity Center: Multi-Account SSO with Permission Sets

IAM Identity Center: Multi-Account SSO with Permission Sets

☁ AWS 🔥 Advanced IAM Identity Center SSO Permission Sets AWS Organizations Multi-Account

Scenario

Your organisation has grown to 5 AWS accounts managed under AWS Organizations. Engineers currently have individual IAM users with long-term credentials in each account. Your CISO has mandated centralised SSO via IAM Identity Center (formerly AWS SSO) with role-based access: developers get ReadOnly in production and PowerUser in dev/staging. Ops engineers get AdministratorAccess in all accounts. No long-term credentials.

Your Objectives
  • Look up the IAM Identity Center instance using a data source (Identity Center is set up once in the management account — you reference it, not create it).
  • Create three Permission Sets: DeveloperReadOnly (ReadOnlyAccess managed policy, 4-hour session), DeveloperPowerUser (PowerUserAccess, 8-hour session), and OpsAdministrator (AdministratorAccess, 1-hour session with MFA enforced via inline policy).
  • Create two Identity Center groups: developers and ops-engineers.
  • Use a for_each-driven aws_ssoadmin_account_assignment to assign the correct permission set + group to each account.
  • Output the permission set ARNs and the SSO instance ARN.

Additional Context

IAM Identity Center is configured at the AWS Organizations management account level — there is exactly one Identity Center instance per organisation. You cannot create the instance itself with Terraform; use data "aws_ssoadmin_instances" to retrieve its ARN and Identity Store ID.

Permission sets define what access a user gets when they log in via SSO. They are templates — the actual access is granted via account assignments that link a permission set + principal (user or group) to a specific AWS account. Changes to a permission set automatically propagate to all account assignments using that set.

variables.tf

variable "aws_region"      { type = string; default = "us-east-1" }
variable "dev_account_id"  { type = string; description = "Development account ID." }
variable "staging_account_id" { type = string; description = "Staging account ID." }
variable "prod_account_id" { type = string; description = "Production account ID." }

main.tf

provider "aws" { region = var.aws_region }

# Look up the IAM Identity Center instance — created once per org, not by Terraform.
data "aws_ssoadmin_instances" "main" {}

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

# Permission Set: Developer ReadOnly — for production access.
resource "aws_ssoadmin_permission_set" "dev_readonly" {
  name             = "DeveloperReadOnly"
  instance_arn     = local.sso_arn
  session_duration = "PT4H"  # ISO 8601 — 4 hours
  description      = "Read-only access for developers viewing production resources."
}
resource "aws_ssoadmin_managed_policy_attachment" "dev_readonly" {
  instance_arn       = local.sso_arn
  permission_set_arn = aws_ssoadmin_permission_set.dev_readonly.arn
  managed_policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

# Permission Set: Developer PowerUser — for dev and staging.
resource "aws_ssoadmin_permission_set" "dev_poweruser" {
  name             = "DeveloperPowerUser"
  instance_arn     = local.sso_arn
  session_duration = "PT8H"
  description      = "Power user access for development and staging accounts."
}
resource "aws_ssoadmin_managed_policy_attachment" "dev_poweruser" {
  instance_arn       = local.sso_arn
  permission_set_arn = aws_ssoadmin_permission_set.dev_poweruser.arn
  managed_policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
}

# Permission Set: Ops Administrator — short-lived sessions, all accounts.
resource "aws_ssoadmin_permission_set" "ops_admin" {
  name             = "OpsAdministrator"
  instance_arn     = local.sso_arn
  session_duration = "PT1H"  # 1 hour — short-lived for admin access
  description      = "Administrator access for ops engineers. Requires MFA via identity provider."
}
resource "aws_ssoadmin_managed_policy_attachment" "ops_admin" {
  instance_arn       = local.sso_arn
  permission_set_arn = aws_ssoadmin_permission_set.ops_admin.arn
  managed_policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

# Identity Center Groups.
resource "aws_identitystore_group" "developers" {
  identity_store_id = local.identity_store_id
  display_name      = "developers"
  description       = "Software engineers — read prod, power-user dev/staging."
}

resource "aws_identitystore_group" "ops" {
  identity_store_id = local.identity_store_id
  display_name      = "ops-engineers"
  description       = "Operations engineers — admin access across all accounts."
}

# Account assignments — who gets what in which account.
locals {
  assignments = {
    # Developers: ReadOnly in prod, PowerUser in dev and staging
    "dev-poweruser-developers" = {
      account_id       = var.dev_account_id
      permission_set   = aws_ssoadmin_permission_set.dev_poweruser.arn
      principal_id     = aws_identitystore_group.developers.group_id
      principal_type   = "GROUP"
    }
    "staging-poweruser-developers" = {
      account_id       = var.staging_account_id
      permission_set   = aws_ssoadmin_permission_set.dev_poweruser.arn
      principal_id     = aws_identitystore_group.developers.group_id
      principal_type   = "GROUP"
    }
    "prod-readonly-developers" = {
      account_id       = var.prod_account_id
      permission_set   = aws_ssoadmin_permission_set.dev_readonly.arn
      principal_id     = aws_identitystore_group.developers.group_id
      principal_type   = "GROUP"
    }
    # Ops: Admin in all three accounts
    "dev-admin-ops" = {
      account_id       = var.dev_account_id
      permission_set   = aws_ssoadmin_permission_set.ops_admin.arn
      principal_id     = aws_identitystore_group.ops.group_id
      principal_type   = "GROUP"
    }
    "staging-admin-ops" = {
      account_id       = var.staging_account_id
      permission_set   = aws_ssoadmin_permission_set.ops_admin.arn
      principal_id     = aws_identitystore_group.ops.group_id
      principal_type   = "GROUP"
    }
    "prod-admin-ops" = {
      account_id       = var.prod_account_id
      permission_set   = aws_ssoadmin_permission_set.ops_admin.arn
      principal_id     = aws_identitystore_group.ops.group_id
      principal_type   = "GROUP"
    }
  }
}

resource "aws_ssoadmin_account_assignment" "main" {
  for_each           = local.assignments
  instance_arn       = local.sso_arn
  target_id          = each.value.account_id
  target_type        = "AWS_ACCOUNT"
  permission_set_arn = each.value.permission_set
  principal_id       = each.value.principal_id
  principal_type     = each.value.principal_type
}

outputs.tf

output "sso_instance_arn"         { value = local.sso_arn }
output "identity_store_id"        { value = local.identity_store_id }
output "dev_readonly_ps_arn"      { value = aws_ssoadmin_permission_set.dev_readonly.arn }
output "dev_poweruser_ps_arn"     { value = aws_ssoadmin_permission_set.dev_poweruser.arn }
output "ops_admin_ps_arn"         { value = aws_ssoadmin_permission_set.ops_admin.arn }
output "developers_group_id"      { value = aws_identitystore_group.developers.group_id }
output "ops_group_id"             { value = aws_identitystore_group.ops.group_id }

Workflow Commands

terraform init
terraform apply \
  -var="dev_account_id=111111111111" \
  -var="staging_account_id=222222222222" \
  -var="prod_account_id=333333333333"

# List all account assignments for a permission set
aws sso-admin list-account-assignments \
  --instance-arn $(terraform output -raw sso_instance_arn) \
  --permission-set-arn $(terraform output -raw ops_admin_ps_arn) \
  --account-id 111111111111

# Add a user to the developers group
aws identitystore create-group-membership \
  --identity-store-id $(terraform output -raw identity_store_id) \
  --group-id $(terraform output -raw developers_group_id) \
  --member-id '{"UserId":"xxxx-xxxx-xxxx"}'

terraform destroy -var="dev_account_id=..." -var="staging_account_id=..." -var="prod_account_id=..."
✓ Expected Output After Apply
Apply complete! Resources: 10 added, 0 changed, 0 destroyed.

Outputs:

dev_poweruser_ps_arn  = "arn:aws:sso:::permissionSet/ssoins-xxxx/ps-poweruser"
dev_readonly_ps_arn   = "arn:aws:sso:::permissionSet/ssoins-xxxx/ps-readonly"
developers_group_id   = "9088xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
identity_store_id     = "d-xxxxxxxxxxxx"
ops_admin_ps_arn      = "arn:aws:sso:::permissionSet/ssoins-xxxx/ps-admin"
ops_group_id          = "9088yyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
sso_instance_arn      = "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxx"