This post original appeared on the CommonFate.io blog.

Still using IAM Users for AWS access? You're not alone. According to Datadog's 2024 State of Cloud Security report, 46% of organizations still rely on IAM Users for AWS access. However, with AWS Identity Center (formerly AWS SSO), there's a better way to manage access that improves security and reduces administrative overhead.

In a previous piece, we covered the planning process involved to move from using AWS IAM Users, to AWS IAM Identity Center. In this piece, we’ll show the exact AWS CLI commands needed to perform the migration steps.

Moving from IAM Users to Identity Center offers several key benefits:

  • Improved security through temporary credentials instead of long-lived access keys
  • Centralized identity management across multiple AWS accounts
  • Simplified user lifecycle management with IdP integration
  • Consistent security controls through centrally managed policies
  • Easier access auditing and reporting

The AWS Command Line Interface

The AWS CLI is a unified tool to manage your AWS services. To get started:

  1. Download and install from the official AWS CLI page
  2. Configure with aws configure or set up SSO with aws configure sso
  3. Verify your installation with aws --version

For detailed installation instructions for your operating system, visit the AWS CLI User Guide.

In these examples, the variables are given in $VARIABLE format, so that you can replace it with your own IDs and values.

Planning the Move

Timeline

Defining your timeline is a planning activity, not a technical one. It depends on your priorities, resources, and existing infrastructure, more than any specific CLI commands.

External Dependencies

If you don't have an identity provider (IdP) already available to store your identities, then now is the time to set one up. The process for choosing and setting up an IdP is more than technical, and will depend upon your choice. When working with IDC the available IdP options are:

  • Identity Center directory
  • Active Directory
  • External identity provider

Set-up Identity Center

With your planning and external dependencies in place, you can start configuring your AWS-based resources.

It is possible to create an IDC instance via the CLI, but not recommended. As detailed in the documentation, the command will fail if you try to create it in the organization management account, or if there is already an instance of IDC in the account. Since you should be using an organization instance of IDC, this unfortunately means you will need to use the AWS console to create your instance.

Once you have an IDC instance available, you can check its configuration with the list-instances command, because there will only ever be one instance in an AWS account, across all regions:

# Get the information for your IDC instance, and take note of InstanceArn
# for future commands
aws sso-admin list-instances

All IDC administration commands are under the sso-admin command namespace. The sso and identitystore command groups are used for logging in to IDC, and managing users if IDC is also your IdP, respectively.

Configure Your IdP

If your identity provider is newly set up, ensure it’s configured to work with IDC. The official AWS documentation includes step-by-step tutorials for the most popular enterprise IdPs in the market:

Define Your Policies

To have a successful migration, you need to first take stock of what you’re using in your environment.

Audit existing IAM users

Start by listing all your users in your environment, so you know exactly what you need to get rid of:

# List all IAM users
aws iam list-users

AWS IAM has a nice built-in feature to give you a summary report of all the long-term credentials being used in your environment:

# Generate and retrieve credential report
aws iam generate-credential-report

# Get the report, no ID needed since there can be only one report at a time
aws iam get-credential-report

The AWS documentation includes detailed explanation of the report fields, but at a high level it includes

  • User information, such as when they were created
  • Authentication method details, such as password age and if MFA is enabled
  • Access key information, such as their status, and when and where they were last used
  • If X.509 signing certificates have been active

For the access key usage, this information can be pulled directly from the API for a specific key, without needing to wait for a report to be generated with the command:

# View activity for a single access key
aws iam get-access-key-last-used --access-key-id $ACCESS_KEY_ID

If you use IAM groups, then they are a good starting point for roles to consider. Review your groups and the policies attached to them, so that you have a good starting point for the permissions your shared roles should have:

# List groups of users
aws iam list-groups

# Get all users in specific group
aws iam get-group --group-name $GROUP_NAME

# List inline policies
aws iam list-group-policies --group-name $GROUP_NAME

# List AWS and customer managed policies
aws iam list-attached-group-policies --group-name $GROUP_NAME

Review attached policies to find specific permissions that should be included in your new, role-based policies

# List AWS and customer managed policies
aws iam list-attached-user-policies --user-name $USERNAME

# List inline policies
aws iam list-user-policies --user-name $USERNAME

Use IAM Access Analyzer

AWS IAM Access Analyzer has many features, but the most relevant to your migration is the policy generation ability:

# Generate policy recommendations based on usage, returns a jobId
aws accessanalyzer start-policy-generation \\
  --policy-generation-details \\
    principalArn=arn:aws:iam::$ACCOUNT_ID:user/$USERNAME \\
  --cloud-trail-details \\
  '{
    "accessRole": "arn:aws:iam::$ACCOUNT_ID:role/service-role/AccessAnalyzerMonitorServiceRole",
    "startTime": "2024-11-22T00:30:00Z",
    "trails": [
        {
            "allRegions": true,
            "cloudTrailArn": "arn:aws:cloudtrail:$REGION:$ACCOUNT_ID:trail/$TRAIL_NAME"
        }
    ]
  }'

# Get the generated policy for the returned jobId
aws accessanalyzer get-generated-policy --job-id $JOB_ID

There is no charge for the policy generation functionality of Access Analyzer.

The JSON-based arguments can get unwieldy, so you can also put the contents in a JSON file, and use the file-based argument --cloud-trail-details file://$FILENAME.json instead of inline JSON as a string. The example above shows a user’s history being used to generate a policy, but Access Analyzer works with IAM roles too.

Create Permissions Sets

At this stage of migration, you should have a good idea of the policies that you need to be able to assign to your Identity Center-based users and groups. To assign them, the permissions need to be defined as IDC permissions sets

# Create a permission set, noting the returned PermissionSetArn
aws sso-admin create-permission-set \\
    --instance-arn $INSTANCE_ARN \\
    --name "ReadOnlyAccess" \\
    --description "Provides read-only access"

# Attach the ReadOnlyAccess managed policy to permission set
aws sso-admin attach-managed-policy-to-permission-set \\
    --instance-arn $INSTANCE_ARN \\
    --permission-set-arn $PERMISSION_SET_ARN \\
    --managed-policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess

aws sso-admin attach-customer-managed-policy-reference-to-permission-set \\
    --instance-arn $INSTANCE_ARN \\
    --permission-set-arn $PERMISSION_SET_ARN \\
    --customer-managed-policy-reference \\
    '{
      "Name": "MyCustomPolicy",
      "Path": "/"
    }'

# List permission sets for review
aws sso-admin list-permission-sets \\
    --instance-arn $INSTANCE_ARN

This example shows a permission set with the AWS managed policy ReadOnlyAccess being attached. Having a role that allows viewing, but not changing, your environment is one of the most common RBAC roles. AWS managed policies are useful because they are created by the services teams to encapsulate the permissions required to use the service with different levels of access. You can see the full list of AWS managed policies in the official documentation.

Assign Permissions Sets

To be used, permissions sets must be assigned to users or groups in your IdP, which are identified in IDC by a GUID:

# Create account assignment
aws sso-admin create-account-assignment \\
    --instance-arn $INSTANCE_ARN \\
    --permission-set-arn $PERMISSION_SET_ARN \\
    --principal-id $PRINCIPAL_GUID \\
    --principal-type USER \\
    --target-id $ACCOUNT_ID \\
    --target-type AWS_ACCOUNT

# List assignments for review
aws sso-admin list-account-assignments \\
    --account-id $ACCOUNT_ID \\
    --instance-arn $INSTANCE_ARN \\
    --permission-set-arn $PERMISSION_SET_ARN

In the create-account-assignment command example, the values “USER” and “AWS_ACCOUNT” are literal string values required for the command to work, and not placeholders for your own variables.

Safe, Staged Migration

By now, users logging in to your AWS environment should have access to start using the new IDC-based access on a regular basis. Even with careful planning, and services like Access Analyzer, it’s all too easy to miss permissions that are actually required.

Check for Permissions Errors

The AWS auditing service, CloudTrail, has a built-in event history functionality that you can use to look up specific user activity. This can help you ensure they have access to the right permissions, and address any permissions that might’ve been missed in the planning phase.

The CloudTrail event history only lasts for 90 days maximum, and it’s not possible to search for AccessDenied errors directly. For longer history, you will need to use an S3 or CloudWatch Logs-based trail, which you can then use Athena or another analytics service to find the most interesting events in your CloudTrail. If your CloudTrail logs to CloudWatch Logs, you can use this command to search for AccessDenied errors:

aws logs filter-log-events \\
  --region $REGION \\
  --start-time $EPOCH_TIME_MS \\
  --log-group-name CloudTrail/DefaultLogGroup \\
  --filter-pattern AccessDenied

Where $EPOCH_TIME_MS is the number of milliseconds since January 1, 1970 UTC.

Disable old Access

Disabling access is critical to actually benefiting from the migration process, but disable the wrong access, and you could impact running services, and cause systems outages. When IAM evaluates requests, any deny statements override allow statements. This means you can add a deny all policy to a user, and don’t have to remove existing policies to test what removing their access will do in your environment:

# Apply a deny all policy to IAM user
aws iam put-user-policy \\
    --user-name $USERNAME \\
    --policy-name DenyAll \\
    --policy-document \\
    '{
      "Version": "2012-10-17",
      "Statement": [
        {
            "Effect": "Deny",
            "Action": "*",
            "Resource": "*"
        }
      ]
    }'

Setting an access key to “Inactive” before you delete it means that if it disabling it causes issues, you can re-enable it by setting the status to “Active” and restore access quickly.

# Deactivate an access key
aws iam update-access-key \\
    --access-key-id $ACCESS_KEY_ID \\
    --status Inactive \\
    --user-name $USERNAME

Clean-up for Security

Congratulations! By this stage you and your teams are using IDC for AWS access on a daily basis. You’ve completed the hardest part of the migration, but there’s still more work to be done to realize the full value of migrating off users: Cleaning up unused resources. By removing old resources, you reduce the risk of them being used or accidentally exposed.

Delete old Access

# Remove a user's login profile, so they can't log in with a password
aws iam delete-login-profile --user-name $USERNAME

# Delete access keys, so they can't access AWS programmatically
aws iam delete-access-key --access-key-id $ACCESS_KEY_ID --user-name $USERNAME

# Remove a user from a group
aws iam remove-user-from-group --user-name $USERNAME --group-name $GROUP_NAME

# Finally, delete the user
aws iam delete-user --user-name $USERNAME

Review Access

Now that you’ve cleaned up old, unused access, all that’s left to do is to refine your access over time. There’s no fixed or “right” schedule for this, and the frequency you review will depend on the rate of change in your environment, your risk appetite, and compliance posture.

Some of the areas you should be focusing on include:

At the end of this migration, you'll have transformed your AWS access management from a potential security liability into a modern, secure foundation. Your users will have seamless access across accounts, your security team will have better visibility and control, and you'll have eliminated the risks of long-term credentials.