Home Resume
Homeβ€Ί Blogβ€Ί Week 7 - IAM Identity Center SSO: Multi-Account Pe…
AWS Weekly Lab AWS Terraform

IAM Identity Center SSO: Multi-Account Permission Sets

One set of groups and permission sets, defined entirely in Terraform, that AWS automatically turns into real IAM roles across every account in the Organization

AWS Platform Engineering Lab Β· Week 07

Why β€” The Problem This Solves

Week 6 built an Account Vending Machine β€” a ServiceNow ticket now produces a real AWS account inside the right Organizational Unit in minutes, with SCP guardrails attached automatically. That answers where the account lives. It says nothing about who can log into it.

Filled in by hand, that gap looks the same way it always does: someone creates an IAM user per person, per account, hands out an access key, and quietly loses track of which user has which permissions where. Multiply that across every account this lab has vended (and will keep vending), and the access model becomes the actual security problem β€” not the accounts themselves.

The Real-World Pain Point

IAM users with long-lived access keys, scattered one-per-account, are exactly what every AWS security review flags first. They don't expire, they're easy to forget about, and there's no single place to see who can get into what.

The Business Value

IAM Identity Center fixes this by inverting the model: define access centrally, once, and let AWS provision it everywhere it's needed.

1

Define Once

A permission set (e.g. Engineers) is written once in Terraform, backed by an AWS managed policy and its own session duration.

2

Group, Don't User

People join a group (Engineers, ReadOnlyAuditors, BreakGlassAdmins) instead of getting individual per-account permissions.

3

Assign Across Accounts

Terraform assigns each group's permission set to every target account in one pass β€” AWS auto-provisions the matching IAM role in each one.

4

Sign In Once

Users sign in to a single SSO portal and federate into whichever account/role they're assigned β€” temporary credentials only, never a static key.

Who Benefits

Security teams get one place to audit who can access what. Platform teams stop provisioning per-account IAM users by hand. New hires (or new vended accounts) inherit the right access automatically the moment they're added to a group or created.

What You Need to Know β€” Skills & Tools

This week is lighter on moving parts than Week 6 β€” no Lambda, no Step Functions β€” but it leans hard on Identity Center concepts that don't show up earlier in this series.

Required AWS Knowledge

πŸ” IAM Identity Center (permission sets, assignments)
πŸ‘₯ Identity Store (users, groups)
🏒 AWS Organizations (multi-account targeting)
πŸ”’ IAM (the roles Identity Center provisions per account)

Required Engineering Skills

πŸ—οΈ Terraform (modules, HCP Terraform)
πŸ”— Git/VCS-driven CI workflows
βš™οΈ HCP Terraform API (workspace/variable automation)

Concepts to Understand Before Starting

πŸ“

Permission Set

A reusable role template β€” a managed/inline policy plus a session duration β€” that exists independently of any one account until it's assigned.

πŸ‘₯

Account Assignment

The link between a principal (a group or user) and a permission set, scoped to one target account. This is the action that actually causes AWS to provision anything.

🧩

AWSReservedSSO_* Roles

The real IAM role Identity Center auto-creates inside a target account the moment an assignment is made β€” visible in that account's own IAM console, but never declared there directly.

🌐

AWS Access Portal

The single sign-in page every assigned user goes to. It only ever shows the accounts/roles that user's own group memberships actually grant β€” not everything assigned to the account.

Architecture β€” How It Fits Together

IAM Identity Center SSO Architecture Terraform defines groups, permission sets, and users, then assigns every group to every target account, causing AWS to auto-provision IAM roles that users access via the SSO portal terraform.tfvars: groups{} + users{} Terraform β€” identity-center module aws_ssoadmin_permission_set + managed_policy_attachment aws_identitystore_group one per permission set aws_identitystore_user + group_membership aws_ssoadmin_account_assignment setproduct(groups, target_account_ids) AWS auto-provisions Target Account (e.g. ent-wkld-dev-sandbox) IAM Role: AWSReservedSSO_<permission-set>_* never declared directly β€” created by Identity Center AWS Access Portal user signs in once, sees only their assigned account+role Federated console session temporary credentials, no static key Figure 1: Terraform defines identity once; Identity Center provisions access everywhere it's assigned

Why Groups Instead of Per-User Assignments?

The Terraform assigns permission sets to groups, never directly to individual users. Adding someone to Engineers grants them every account that group is already assigned to, automatically β€” no new Terraform apply needed per person. Same idea as IAM groups, just at Organization scope instead of single-account scope.

Why a Cross-Product for Assignments?

Every group needs the matching permission set in every target account, so the module builds the assignment set as setproduct(keys(groups), target_account_ids) β€” add one more account to the target OU, and the same Terraform apply assigns every existing group to it with no additional code.

How We Built It β€” Step by Step

Step 1: The identity-center Terraform Module

One module, six resource types, looped over a single groups map:

HCL β€” Cross-Account Assignment Cross-Product
locals {
  assignments = {
    for pair in setproduct(keys(var.groups), var.target_account_ids) :
    "${pair[0]}-${pair[1]}" => {
      group_name = pair[0]
      account_id = pair[1]
    }
  }
}

resource "aws_ssoadmin_account_assignment" "this" {
  for_each = local.assignments

  instance_arn       = var.instance_arn
  permission_set_arn = aws_ssoadmin_permission_set.this[each.value.group_name].arn
  principal_id        = aws_identitystore_group.this[each.value.group_name].group_id
  principal_type      = "GROUP"
  target_id           = each.value.account_id
  target_type         = "AWS_ACCOUNT"
}

The target accounts themselves aren't hardcoded β€” they're looked up from Week 6's vended OUs, so adding a new account to Sandbox or Production automatically becomes a new assignment target on the next apply:

HCL β€” Reusing Week 6's Vended OUs
data "aws_organizations_organizational_unit_child_accounts" "target" {
  for_each  = toset(local.target_ou_ids)
  parent_id = each.value
}

locals {
  target_account_ids = distinct(flatten([
    for ou_id, result in data.aws_organizations_organizational_unit_child_accounts.target :
    [for acct in result.accounts : acct.id]
  ]))
}

Step 2: Enable IAM Identity Center (Manual, One-Time)

Prerequisite β€” No Terraform Resource Creates the Instance Itself

Same class of gap as Week 6's SCP policy-type enablement: IAM Identity Center has to be switched on once, manually, before any aws_ssoadmin_* resource can do anything. AWS Console (management account) β†’ IAM Identity Center β†’ Enable β†’ choose the Organization instance type, not Account β€” Organization is what lets assignments target every member account, not just the one you're logged into.

Step 3: Standing Up the HCP Workspace via API

Rather than click through the HCP UI, week-07-dev was created the same way week-06-dev was β€” via the HCP Terraform API, using the token already saved locally in credentials.tfrc.json. POST a workspace, PATCH/POST its variables, done:

Bash β€” Creating the Workspace via API
curl --header "Authorization: Bearer $TOKEN" \
     --header "Content-Type: application/vnd.api+json" \
     --request POST \
     --data '{
       "data": { "type": "workspaces", "attributes": {
         "name": "week-07-dev",
         "working-directory": "week-07-identity-center-sso/terraform/environments/dev",
         "vcs-repo": { "identifier": "katta698/AWS-Platform-Engineering-Lab",
                       "branch": "main",
                       "github-app-installation-id": "ghain-H7CUcPhefZdVqW8S" }
       }}
     }' \
     "https://app.terraform.io/api/v2/organizations/Katta/workspaces"

The OIDC trust policy on hcp-terraform-role already wildcards workspace:* from Week 6, so the new workspace inherited working AWS auth with zero IAM changes.

HCP Terraform apply run for week-07-dev showing 9 resources added
HCP Terraform β€” first apply on week-07-dev: 9 resources added (3 permission sets, 3 policy attachments, 3 groups)

Confirmed directly in the console β€” the three groups exist:

IAM Identity Center console Groups page showing Engineers, ReadOnlyAuditors, BreakGlassAdmins
Identity Center β†’ Groups β€” all three created exactly as defined in Terraform

And the permission sets, each backed by its managed policy:

IAM Identity Center console Permission sets page
Identity Center β†’ Permission sets β€” ReadOnlyAuditors, Engineers, BreakGlassAdmins, each with their attached AWS managed policy

Step 4: Adding a Test User and Assigning to a Real Account

With the permission sets live, a second apply added a test user (jane.engineer, in the Engineers group) and the account assignments β€” five new resources: the user, the group membership, and three account assignments (one per group):

HCP Terraform second apply showing 5 resources added, including identitystore_user and three ssoadmin_account_assignment resources
HCP Terraform β€” second apply: user, group membership, and 3 account assignments created. Outputs confirm assignment_count = 3

The assignment is visible from the account side too β€” all three groups now show up against the target account:

IAM Identity Center AWS accounts tab showing assigned groups and permission sets for the target account
Identity Center β†’ AWS accounts β†’ target account β€” all three groups assigned, each to their own permission set

Step 5: Confirming AWS Auto-Provisioned the Roles

This is the part that makes Identity Center worth using: nothing was ever declared inside the target account's own IAM. Switching into it directly shows three roles that simply appeared the moment the assignment was made:

IAM Roles page inside the target account, filtered to AWSReservedSSO, showing three auto-provisioned roles
Target account β†’ IAM β†’ Roles, filtered to AWSReservedSSO β€” all three roles auto-provisioned by Identity Center, trusted entity is the Identity Center SAML provider

Step 6: Signing In as the Test User

Identity Center users created via Terraform have no password and no MFA device by default β€” there's no Terraform equivalent of "send invite email." An admin-initiated password reset (not the self-service "Forgot password?" flow, which got stuck on a stale MFA-required error during testing) got a usable credential. Signed in, the portal showed exactly one account and exactly one role:

AWS access portal showing one account assigned to Jane, with one role (Engineers) available
AWS access portal, signed in as jane.engineer β€” sees the target account with only the Engineers role, even though three permission sets are assigned to that account

That's the scoping working exactly as intended: the portal only ever shows what this user's own group membership grants, not everything assigned to the account. Picking the role federates straight into a live console session:

Federated AWS console session inside the target account
Federated console session in the target account β€” temporary credentials issued by Identity Center, no IAM user or access key involved anywhere

Challenges β€” What Actually Went Wrong

Challenge 1: Week 6's Sandbox/Production OUs Were Empty

What happened: The README's design targets Week 6's Sandbox/Production OUs by default, but no account had actually been vended through that pipeline yet β€” both were empty, so the assignment cross-product had zero accounts to target.

Fix: Overrode target_ou_names to ["Dev"] via an HCP workspace variable, pointing at an older Dev OU that already had one active account. This is a real, documented deviation β€” the default should be reverted to ["Sandbox", "Production"] once accounts actually exist there.

Challenge 2: Stuck "MFA Required" Error on a User With No MFA

What happened: Signing in as the Terraform-created test user via the portal's self-service "Forgot password?" flow returned "You are required to provide multi-factor authentication that you do not have" β€” even after relaxing Identity Center's MFA setting to "Allow them to sign in," and even after waiting for propagation. The exact root cause wasn't fully diagnosed; it looked like a stuck sign-in state from an earlier blocked attempt.

Fix: Used the admin-initiated Reset password button on the user's detail page in the console instead of the self-service "Forgot password?" portal flow β€” that path generated a fresh credential and the MFA block disappeared.

Challenge 3: Portal Only Showed One of Three Assigned Permission Sets

What happened: All three permission sets were assigned to the target account, but the test user's portal view showed only Engineers β€” momentarily looked like the other two assignments hadn't taken effect.

Not actually a bug: The portal scopes by the signed-in user's own group membership, not by everything assigned to the account. The test user was only in the Engineers group, so that's the only role she could see β€” confirmed by checking aws sso-admin list-account-assignments directly, which showed all three assignments present and correct.

Security β€” Controls at Every Layer

πŸ”’ Least-Privilege Permission Sets

Three distinct access levels (ReadOnlyAccess, PowerUserAccess, AdministratorAccess) instead of one shared admin role handed to everyone who needs any access at all.

⏳ Shortest Session for Highest Privilege

BreakGlassAdmins uses a 1-hour session β€” the same length as ReadOnlyAuditors, deliberately shorter than the 4-hour Engineers session. Full-admin access should expire fastest, by design.

πŸ”‘ No Static Credentials, Anywhere

Every session is federated and temporary. No IAM user, no access key, created in any target account β€” not even for the test user used to validate this.

πŸ’Ύ Real Emails Stay Out of Git

terraform.tfvars (containing real user emails) is gitignored; the committed .example file only shows the shape, not real data.

What's Missing for Production
  • MFA enforcement was relaxed to "Allow them to sign in" purely to unblock testing β€” must be reverted to requiring MFA registration before any real user gets one of these permission sets
  • No automated lifecycle for removing a user's group membership when they leave a team or the org
  • BreakGlassAdmins has no approval workflow or alerting on use β€” a real break-glass process should notify someone the moment that permission set is actually assumed
  • Permission sets use broad AWS managed policies; a production rollout should move toward narrower customer-managed or inline policies per role

Cost

IAM Identity Center, permission sets, groups, users, and account assignments are all free β€” so are the IAM roles Identity Center provisions into target accounts.

ResourceUsageCost
IAM Identity Center1 Organization instanceFree
Permission sets / groups / users / assignments3 / 3 / 1 / 3Free
Auto-provisioned IAM roles3 per target accountFree
Destroyed: $0. Nothing in this week's build carries a per-resource cost β€” the only thing worth cleaning up deliberately is the test deviations (MFA setting, OU override), not billable infrastructure.

Cleanup β€” Tearing It Down

week-07-dev is VCS-connected, same restriction as Weeks 5–6 β€” destroy is confirmed from the HCP UI, not local CLI.

ResourceDestroyed by Terraform?Manual step needed?
Permission sets, policy attachmentsYesAWS refuses to delete a permission set with live assignments anywhere β€” remove assignments first
Identity Store groups, users, membershipYesNone
Account assignmentsYesNone
IAM Identity Center instance itselfNo β€” never in stateDisable manually via console, if desired, the same way it was enabled
Test deviations to revert, not just destroy
The target_ou_names = ["Dev"] workspace variable override and the relaxed MFA setting were both introduced purely to validate this module end-to-end. Reverting them is a settings/variable change, separate from a Terraform destroy.

References

Takeaways β€” What I'd Do Differently

πŸ‘₯ Groups Scale, Per-User Assignments Don't

Assigning permission sets to groups instead of individual users means onboarding someone is a group-membership change, not a new Terraform apply β€” the access model scales independently of headcount.

🧩 Trust, But Verify, the Auto-Provisioning

Identity Center's account-side IAM roles are real, auditable resources β€” checking them directly inside the target account, not just trusting the Terraform plan output, is what actually confirmed the pipeline worked end-to-end.

❗ "Not Showing Up" Isn't Always a Bug

The portal scoping by group membership looked like a missing assignment at first glance. Checking the assignment API directly before assuming something broke saved a wrong fix.

⚠️ Document Deviations, Don't Hide Them

Testing against a live account meant diverging from the README's stated design twice (target OU, MFA setting). Writing both into the README as explicit, reversible deviations keeps the project honest about what was actually exercised.

IAM Identity Center β€” What's New (2025–2026)

Three updates since this post was published:

  • Multi-Region replication β€” IAM Identity Center configurations can now be replicated across AWS regions. Previously it was a single-region service; this matters for multi-region workloads where you need consistent access in each region.
  • Trusted identity propagation β€” new capability that creates identity-enhanced IAM role sessions when accessing AWS services (e.g. Redshift, S3 Access Grants). The downstream service sees who the user is, not just which role they assumed β€” enables row/column-level security tied to actual user identity.
  • Vanity domain support in AWS CLI (July 2026) β€” the CLI now supports custom access portal domains, so users can authenticate via your-company.awsapps.com rather than the auto-generated URL.
Week 07 complete. IAM Identity Center SSO: permission sets, groups, users, and cross-account assignments, entirely in Terraform β€” validated end-to-end against a real account, with AWS auto-provisioning the IAM roles on the other end. Next up: Week 8’s S3 Intelligent Storage Platform.

Comments

How was your experience?
Your feedback helps improve this site.
PoorExcellent