Design AWS IAM Identity Center permission sets and account assignments
This guide is for developers and platform engineers who need AWS IAM Identity Center access to be predictable, auditable, and easy to operate across multiple accounts. You will create permission sets, attach policies and boundaries, assign users or groups to accounts, provision the changes, and verify the resulting AWS access from the CLI.
TL;DR — Build permission sets around job functions, assign groups instead of individual users, and keep one permission set per access pattern per account scope. The most common cause of "I can see the account but can’t do the thing" is that the permission set was changed but not reprovisioned to the target account, or the wrong group was assigned. Reading time: ~5 min
Goal
When you finish, your AWS organization will have reusable IAM Identity Center permission sets, those permission sets will be assigned to the correct users or groups in the correct AWS accounts, and a user will be able to sign in and obtain the expected AWS CLI role credentials in the target account.
Prerequisites
- AWS Organizations in use, with IAM Identity Center enabled in the management account or delegated admin account
- Permission to manage IAM Identity Center, permission sets, and account assignments; in practice, access equivalent to administrative control over IAM Identity Center and Organizations
- AWS CLI v2.15+ installed — check with:
aws --version
jqinstalled for parsing JSON — check with:
jq --version
- The AWS region where IAM Identity Center is configured; list regions if you are unsure:
aws ec2 describe-regions --query 'Regions[].RegionName' --output text
- The target AWS account IDs and the principal names you will assign, preferably groups from your identity source
- The policy ARNs or inline policy JSON you want in each permission set
- If you use a permissions boundary, the boundary policy ARN
Steps
Step 1: Find your IAM Identity Center instance ARN and identity store ID
Run:
aws sso-admin list-instances --output json
Example output shape:
{
"Instances": [
{
"CreatedDate": "2026-02-14T10:22:11Z",
"IdentityStoreId": "d-1234567890",
"InstanceArn": "arn:aws:sso:::instance/ssoins-1111222233334444",
"OwnerAccountId": "111122223333",
"Status": "ACTIVE"
}
]
}
Export the values you will reuse:
export INSTANCE_ARN="arn:aws:sso:::instance/ssoins-1111222233334444"
export IDENTITY_STORE_ID="d-1234567890"
You should see one ACTIVE instance and have both variables set.
Step 2: Create a permission set with a session duration and relay state
Create a permission set for a common job function, for example read-only access:
aws sso-admin create-permission-set \
--instance-arn "$INSTANCE_ARN" \
--name "ReadOnlyAccess-Standard" \
--description "Read-only access for engineers across non-prod accounts" \
--session-duration "PT4H" \
--relay-state "https://console.aws.amazon.com/"
Example output shape:
{
"PermissionSet": {
"Name": "ReadOnlyAccess-Standard",
"PermissionSetArn": "arn:aws:sso:::permissionSet/ssoins-1111222233334444/ps-aaaaaaaaaaaaaaaa",
"SessionDuration": "PT4H"
}
}
Export the permission set ARN:
export PS_ARN="arn:aws:sso:::permissionSet/ssoins-1111222233334444/ps-aaaaaaaaaaaaaaaa"
You should get a PermissionSetArn back with no error.
Step 3: Attach AWS managed policies or an inline policy
Attach a managed policy:
aws sso-admin attach-managed-policy-to-permission-set \
--instance-arn "$INSTANCE_ARN" \
--permission-set-arn "$PS_ARN" \
--managed-policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
If you need custom restrictions, attach an inline policy instead of stacking many customer-managed policies:
cat > inline-policy.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:Describe*",
"s3:Get*",
"s3:List*",
"cloudwatch:Get*",
"cloudwatch:List*",
"logs:Describe*",
"logs:Get*",
"logs:StartQuery",
"logs:StopQuery"
],
"Resource": "*"
}
]
}
JSON
aws sso-admin put-inline-policy-to-permission-set \
--instance-arn "$INSTANCE_ARN" \
--permission-set-arn "$PS_ARN" \
--inline-policy file://inline-policy.json
You should get exit code 0; put-inline-policy-to-permission-set returns no body on success.
Step 4: Add a permissions boundary if your org requires it
If your organization uses boundaries to cap privilege, attach one now:
aws sso-admin put-permissions-boundary-to-permission-set \
--instance-arn "$INSTANCE_ARN" \
--permission-set-arn "$PS_ARN" \
--permissions-boundary CustomerManagedPolicyReference='{Name=PermissionBoundary,Path=/}'
For an AWS managed boundary policy, use:
aws sso-admin put-permissions-boundary-to-permission-set \
--instance-arn "$INSTANCE_ARN" \
--permission-set-arn "$PS_ARN" \
--permissions-boundary ManagedPolicyArn=arn:aws:iam::aws:policy/PowerUserAccess
You should get exit code 0 and no JSON body.
Step 5: Look up the group or user principal ID in the identity store
List groups and pick the exact display name:
aws identitystore list-groups \
--identity-store-id "$IDENTITY_STORE_ID" \
--output json | jq -r '.Groups[] | [.DisplayName, .GroupId] | @tsv'
Example output shape:
Platform-Engineers 9067d123-aaaa-bbbb-cccc-1234567890ab
Security-Admins de45f678-1111-2222-3333-abcdefabcdef
Export the principal ID you want to assign:
export GROUP_ID="9067d123-aaaa-bbbb-cccc-1234567890ab"
You should have the exact GroupId; avoid guessing from display names later.
Step 6: Assign the permission set to the group in the target account
Create the account assignment:
export TARGET_ACCOUNT_ID="444455556666"
aws sso-admin create-account-assignment \
--instance-arn "$INSTANCE_ARN" \
--target-id "$TARGET_ACCOUNT_ID" \
--target-type AWS_ACCOUNT \
--permission-set-arn "$PS_ARN" \
--principal-type GROUP \
--principal-id "$GROUP_ID"
Example output shape:
{
"AccountAssignmentCreationStatus": {
"RequestId": "12345678-90ab-cdef-1234-567890abcdef",
"Status": "IN_PROGRESS"
}
}
Poll until it finishes:
export REQUEST_ID="12345678-90ab-cdef-1234-567890abcdef"
aws sso-admin describe-account-assignment-creation-status \
--instance-arn "$INSTANCE_ARN" \
--account-assignment-creation-request-id "$REQUEST_ID"
You should see "Status": "SUCCEEDED".
Step 7: Reprovision the permission set after any policy change
If you changed policies, session duration, or boundaries after assignments already existed, push the update to the account:
aws sso-admin provision-permission-set \
--instance-arn "$INSTANCE_ARN" \
--permission-set-arn "$PS_ARN" \
--target-type AWS_ACCOUNT \
--target-id "$TARGET_ACCOUNT_ID"
Example output shape:
{
"PermissionSetProvisioningStatus": {
"RequestId": "abcd1234-5678-90ab-cdef-111122223333",
"Status": "IN_PROGRESS"
}
}
Check status:
aws sso-admin describe-permission-set-provisioning-status \
--instance-arn "$INSTANCE_ARN" \
--provision-permission-set-request-id "abcd1234-5678-90ab-cdef-111122223333"
You should see "Status": "SUCCEEDED" before testing access.
Step 8: Test sign-in and AWS CLI access as the assigned user
On the user workstation, configure an SSO profile:
aws configure sso
Use the prompts with literal values matching your environment:
SSO session name (Recommended): corp
SSO start URL [None]: https://your-sso-portal.awsapps.com/start
SSO region [None]: us-east-1
SSO registration scopes [sso:account:access]:
Then log in and test credentials:
aws sso login --sso-session corp
aws sts get-caller-identity --profile your-profile-name
Expected output shape:
{
"UserId": "AROA...:botocore-session-...",
"Account": "444455556666",
"Arn": "arn:aws:sts::444455556666:assumed-role/AWSReservedSSO_ReadOnlyAccess_Standard_1234567890abcdef/user@example.com"
}
You should see the target account ID and an AWSReservedSSO_... role ARN.
Verify it works
List assignments for the account and permission set:
aws sso-admin list-account-assignments \
--instance-arn "$INSTANCE_ARN" \
--account-id "$TARGET_ACCOUNT_ID" \
--permission-set-arn "$PS_ARN" \
--output json
Expected output shape:
{
"AccountAssignments": [
{
"AccountId": "444455556666",
"PermissionSetArn": "arn:aws:sso:::permissionSet/ssoins-1111222233334444/ps-aaaaaaaaaaaaaaaa",
"PrincipalType": "GROUP",
"PrincipalId": "9067d123-aaaa-bbbb-cccc-1234567890ab"
}
]
}
Then verify the user can obtain credentials and perform an allowed action:
aws sts get-caller-identity --profile your-profile-name
aws ec2 describe-regions --profile your-profile-name --output table
The end-to-end result is correct when get-caller-identity returns the target account and the allowed API call succeeds without AccessDenied.
Common pitfalls
Changed the permission set but did not reprovision it
Mistake: You updated the inline policy, attached a new managed policy, or changed the boundary and stopped there.
Symptom: Existing assignees still get the old permissions; CLI calls fail with messages like:
An error occurred (AccessDeniedException) when calling the DescribeInstances operation: User: arn:aws:sts::444455556666:assumed-role/AWSReservedSSO_ReadOnlyAccess_Standard_... is not authorized to perform: ec2:DescribeInstances
Fix: Run aws sso-admin provision-permission-set ... --target-id "$TARGET_ACCOUNT_ID" and wait for SUCCEEDED.
Assigned a user instead of a group
Mistake: You created lots of per-user assignments because it was faster once.
Symptom: Access drift, hard-to-audit assignments, and people keeping access after team changes.
Fix: Create assignments with --principal-type GROUP and the group GroupId; reserve direct user assignments for exceptions with an expiration process.
Used the wrong identity store principal ID
Mistake: You copied a display name or external directory identifier instead of the IAM Identity Center GroupId or UserId.
Symptom: create-account-assignment fails with a not-found error similar to:
An error occurred (ResourceNotFoundException) when calling the CreateAccountAssignment operation: Principal not found
Fix: Re-run aws identitystore list-groups --identity-store-id "$IDENTITY_STORE_ID" and use the exact GroupId.
Designed permission sets per person instead of per job function
Mistake: You created Alice-Admin, Bob-ReadOnly, and similar one-off permission sets.
Symptom: Permission set sprawl, duplicate policy maintenance, and inconsistent session durations or boundaries.
Fix: Create reusable sets like ReadOnlyAccess-Standard, DeveloperPowerUser-NonProd, and BreakGlass-Admin, then assign groups to accounts.
Forgot that account assignment and permission boundaries intersect with SCPs
Mistake: You gave a broad permission set and expected it to override service control policies or a restrictive boundary.
Symptom: The user can assume the role but specific APIs still fail with AccessDenied.
Fix: Check all three layers: permission set policy, permissions boundary, and the account or OU SCP; the effective permission is the intersection, not the union.
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI