IAM Identity Center: Multi-Account SSO with Permission Sets
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.
- 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=..."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"

