IAM Identity Center: The Access Control Layer Your Multi-Account AWS Setup Actually Needs
The access management foundation every multi-account AWS setup needs, and the silent provisioning failures, Terraform gotchas, and group assignment patterns you'll hit building it.
Clean AWS access management is non-negotiable.
Every engineer has an IAM user in a central account.
You’ve thoughtfully created roles in each environment.
Cross-account assume-role is wired up and working.
Permissions are documented somewhere in a Confluence page nobody has updated since 2022.
Then you get to twenty engineers, six AWS accounts, three contractors, a compliance audit, and someone asks you to produce a report of exactly who can access what in production.
That’s when the house of cards starts to wobble.
Long-term IAM user credentials sitting in
.aws/credentialsfiles on laptops you don’t control.Access keys that were supposed to be temporary but have been rotated exactly zero times.
A junior engineer who left eight months ago and whose IAM user you’re not entirely sure got deactivated.
IAM Identity Center, which AWS used to call AWS SSO before deciding to give it a name that’s somehow harder to Google, is the right answer to this problem.
It’s not a perfect answer. But it’s the right one.
It gives you a central place to define who exists, what they can do, and which accounts they can do it in, without distributing long-term credentials to anyone.
When someone leaves, you deprovision them in one place and their access is gone everywhere.
When you onboard a new engineer, you assign them to a group and they get exactly what that group gets, nothing more.
For multi-account AWS architectures, it’s the access management foundation everything else should be built on. This article is about how the three core primitives actually work, where they get confusing, and what the Terraform implementation looks like in practice.
Users, Groups, and the Identity Store
IAM Identity Center has its own identity store, which is where it keeps user and group objects.
This is separate from IAM. The users that live here are not IAM users.
Say it with me:
These are not IAM users.
They don’t have access keys. They can’t be used to call AWS APIs directly.
They exist solely as identities that can be assigned to things inside Identity Center.
There are two models for where those users come from.
You create and manage them directly in the Identity Center identity store. You create a user, give them a name and email, send them an invitation, and they set up their credentials through the AWS access portal. This is fine for small teams or for AWS-native organizations that aren’t federating from an existing directory. It’s also what you’re working with if you’re building out an IdC setup from scratch before your organization has an external IdP story.
You connect an external identity provider: Microsoft Entra ID, Okta, Google Workspace, or anything else that supports SAML 2.0 and SCIM. The external IdP becomes the source of truth for who exists. Users and groups get synchronized into IdC via SCIM, and authentication is handled by the external IdP via SAML. You’re not managing credentials in AWS at all. This is the mature model for any organization that already has an identity system.
The important thing to understand about groups is that they exist in the identity store the same way users do, and they’re the thing you should be assigning access to.
Not individual users.
Groups.
Assigning a permission set directly to a user is mostly useless. Groups let you define access tiers once and manage membership. When an engineer gets promoted and needs broader access, you move them from one group to another. When a contractor finishes an engagement, you remove them from the relevant groups.
The assignments don’t change.
The IAM roles in each account don’t change.
Just the group membership changes.
What a Permission Set Actually Is
This is the concept that takes the longest to internalize if you’re coming from a traditional IAM background.
A permission set is not an IAM role.
It’s a template that IAM Identity Center uses to create IAM roles in your AWS accounts. When you assign a group to an AWS account with a permission set, IAM Identity Center materializes an IAM role in that target account, attaches the policies you defined in the permission set to that role, and configures a trust policy that allows IAM Identity Center to assume it on behalf of authenticated users.
The role name follows a predictable pattern:
AWSReservedSSO_<PermissionSetName>_<RandomSuffix>
You don’t manage that role directly.
You don’t modify its trust policy.
You don’t attach or detach policies from it.
You manage the permission set, and IAM Identity Center takes care of keeping the corresponding role in sync across every account where it’s assigned.
This means that when you update a permission set, IAM Identity Center has to re-provision it to every account where it’s assigned. In a small organization with five accounts, that’s fast. In an organization with a hundred accounts, that provisioning step takes meaningful time. IAM Identity Center will tell you the provisioning status is “In progress” and there’s not much you can do but wait.
A permission set can hold AWS managed policies, customer managed policies, and an inline policy. You can also attach a permissions boundary, which sets a ceiling on what the resulting role can do regardless of what the attached policies allow.
For most teams building least-privilege access, the combination of customer managed policies and a permissions boundary is the right model: you define exactly what each role tier can do, and the boundary ensures that even if someone adds a broader policy later, the effective permissions stay bounded.
One thing that trips people up: customer managed policies referenced in a permission set must already exist in every target account before the permission set can be provisioned there.
If you create a permission set that references my-redshift-analyst-policy and then assign it to an account that doesn’t have that policy, the provisioning will fail.
Not loudly and obviously.
It will fail in a way that shows up as a warning in the IAM Identity Center console, in the permission set’s provisioned status for that account. The access won’t work in that account and the error won’t surface to the person trying to use it.
Write that in your runbook.
The Account Assignment: Where It All Comes Together
The account assignment is the three-way binding that makes the whole thing work:
a principal (user or group)
a permission set
an AWS account
When all three are connected, the principal can sign into the AWS access portal, see the account listed under their access, and assume the role that corresponds to the permission set.
If any one of the three is missing, they can’t get in.
Not with an error message that explains what’s missing, usually with an error that just says access denied, or with the account not appearing in the portal at all.
This is the failure mode that generates the most support tickets. An engineer logs into the access portal and an account they expect to see isn’t there.
The answer is almost always one of three things:
✔ They’re in the right group but the group hasn’t been assigned to that account with the right permission set.
✔ They’re not in the group at all and someone forgot to add them.
✔ The permission set exists and the assignment exists but the permission set failed to provision in the target account because of the customer managed policy issue described above.
All three produce the same symptom from the user’s perspective, and none of them produce a useful error message in the portal.
CloudTrail will have the AssumeRoleWithSAML event and the failure reason if you dig into it, but most engineers aren’t going to do that themselves. This is worth knowing as the person who owns the IdC configuration.
The Terraform Story (And Where It Gets Messy)
Managing IAM Identity Center in Terraform uses the aws_ssoadmin_* resource family, and the identity store resources for users and groups live under aws_identitystore_*.
The structure roughly mirrors the logical model: you create permission sets, attach policies to them, look up your instance and identity store IDs via data sources, and create account assignments.
A minimal permission set with a managed policy looks like this:
data "aws_ssoadmin_instances" "this" {}
resource "aws_ssoadmin_permission_set" "analyst" {
name = "RedshiftAnalyst"
instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0]
session_duration = "PT8H"
}
resource "aws_ssoadmin_managed_policy_attachment" "analyst" {
instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0]
permission_set_arn = aws_ssoadmin_permission_set.analyst.arn
managed_policy_arn = "arn:aws:iam::aws:policy/AmazonRedshiftReadOnlyAccess"
}
This works cleanly.
Where Terraform starts to fight you is around customer managed policy attachments and account assignments at scale.
Customer managed policy attachments have an ordering dependency that Terraform’s dependency graph doesn’t always resolve cleanly. The policy must exist in the target account before you can attach it to the permission set for use in that account. If you’re managing both the customer managed policies (in each account) and the permission set attachments (in the IdC management account) in the same Terraform state, you need explicit depends_on to ensure the policies are created before the provisioning happens.
If they’re in separate states, you need to apply them in the right order.
Applying the permission set changes first, without the policies in place, will result in failed provisioning that Terraform quietly ignores on subsequent applies.
One more thing on customer managed policies before moving on: the policy name in your permission set reference must be an exact match to the policy name in the target account. Not the ARN, just the name.
If your naming convention drifts between environments (and it will, because it always does) you will spend time debugging provisioning failures where IAM Identity Center silently fails to attach the policy because the name doesn’t resolve. Consistency in policy naming across accounts is not optional when you’re using customer managed policies in permission sets.
There’s also a known issue with aws_ssoadmin_permission_set resources where if you try to create a permission set with the same name as an existing one, instead of returning an error, Terraform hangs. The underlying API returns a ConflictException, but the provider’s retry logic keeps waiting for a success response that never comes. The apply will sit at “Still creating...” until you kill it.
The fix is to check for existing permission set names before applying, and to be careful with for_each patterns that might generate duplicate names from your data structures.
For account assignments at scale, the pattern that actually holds up is using for_each over a flattened map of group-to-account-to-permission-set combinations.
Something like:
locals {
assignments = flatten([
for group, config in var.group_assignments : [
for account_id in config.account_ids : {
key = "${group}-${account_id}-${config.permission_set}"
group_name = group
account_id = account_id
permission_set = config.permission_set
}
]
])
}
resource "aws_ssoadmin_account_assignment" "this" {
for_each = { for a in local.assignments : a.key => a }
instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0]
permission_set_arn = aws_ssoadmin_permission_set.sets[each.value.permission_set].arn
principal_id = data.aws_identitystore_group.groups[each.value.group_name].group_id
principal_type = "GROUP"
target_id = each.value.account_id
target_type = "AWS_ACCOUNT"
}
This pattern scales because adding a new account to a group’s access list adds only the new assignment resources to state, rather than triggering a plan diff across every existing assignment.
Without the flattened map approach, you can end up in situations where modifying a group membership causes Terraform to refresh hundreds of account assignment resources, which can generate API throttling errors from the IAM Identity Center API.
Honest Assessment: What This Doesn’t Solve
IAM Identity Center doesn’t solve the problem of permissions being too broad. It solves the problem of credentials being unmanaged. Those are different problems.
You can build a permission set that gives everyone AdministratorAccess to production and manage it perfectly cleanly through IdC with great audit trails, and you still have a permissions problem. The tooling here is access management tooling, not least-privilege enforcement tooling.
The session duration deserves more thought than most teams give it, the default maximum is twelve hours. For production accounts, that might be too long. For developer sandbox accounts, eight hours is probably fine. For break-glass emergency access, you might want a much shorter session with mandatory MFA. IdC lets you configure this per permission set, which means you can have different session lengths for different access tiers. Use that.
There’s also the question of where IAM Identity Center lives in your account hierarchy.
By default it runs in your AWS Organizations management account, which is the account you want the fewest things running in. AWS supports delegating IAM Identity Center administration to a member account, and if you’re operating a mature multi-account setup, you should do this. The delegated admin account can manage permission sets and account assignments across the organization without needing access to the management account for day-to-day identity operations. This also means your Terraform for IdC resources can live in a dedicated identity account pipeline rather than requiring management account credentials to run.
The one limitation of the delegated administrator model is that it can’t manage permission sets provisioned by the management account, and AWS Control Tower creates its own permission sets in the management account that the delegated admin can’t touch. If you’re running Control Tower, those Control Tower-managed permission sets are off-limits to your delegation. Plan around that before you start building.
The big question: How the experience changes in very large organizations with hundreds of AWS accounts and permission sets being managed entirely in Terraform at scale? The API throttling behavior of IAM Identity Center when you’re running large account assignment applies is something I haven’t pushed to its limits, and I’d be curious to hear from people who have.
The prescriptive guidance from AWS suggests that in large environments, Terraform and the underlying SDK handle throttling automatically, but “handles throttling” can mean anything from “retries gracefully” to “takes an hour to apply.”
The tooling is also still maturing.
The Terraform provider for ssoadmin resources has a handful of known bugs around provisioning status detection and destroy operations that require running applies twice or manually resolving state. It works well enough that you should absolutely use it, but go in knowing that the error messages from failed provisioning are not always surfaced where you’d expect them.
What’s your experience managing IAM Identity Center at scale?
Are you using Terraform, CloudFormation, or something else to manage assignments?
I’d love to hear about it in the comments.
With Love and DevOps,
Maxine
Last Updated: March 2026
If you’re building the kind of AWS fluency that makes setups like this feel obvious rather than overwhelming, my learning library is designed for that.
LLMs for Humans: From Prompts to Production is for engineers who want to understand how language model systems are actually built and deployed, written by someone who works in production infrastructure, not marketing.
The AWS Solutions Architect Study Guide covers the AWS and IaC foundations that make multi-account architecture, identity management, and infrastructure-as-code practices make sense as a coherent system rather than a pile of disconnected tools.



