AWS IAM Concepts Guide

Cheatsheet

AWS IAM Concepts Guide

last updated 2026-10-18Daniel Corneschi18 min read

IAM fundamentals — roles, policy types, evaluation logic, Identity Center (SSO), federation, temporary credentials, permission boundaries, and root account vs admin user.

Best Practices

  • Never use the root account for day-to-day tasks
  • Enable MFA for all human users (especially root)
  • Apply least privilege: grant only the permissions needed
  • Use groups to assign permissions instead of attaching policies directly to users
  • Rotate access keys regularly and remove unused credentials
  • Use roles for applications and services (never embed long-term credentials in code)
  • Use AWS Organizations SCPs for guardrails across accounts

Roles

A role is an AWS identity with permission policies. Unlike users, roles don’t have permanent credentials — they’re assumed temporarily by:

  • AWS services (EC2, Lambda, ECS)
  • Users from your account or other accounts
  • Federated users from external identity providers

Think of a role as a “hat” that grants specific permissions when worn. When you assume a role, you get temporary security credentials.

Common use cases:

  • EC2 instance accessing S3 buckets
  • Lambda function writing to DynamoDB
  • Cross-account access between AWS accounts

Policy Types

Inline Policies

Embedded directly in a single user, group, or role. Useful for strict 1:1 relationships.

aws iam put-user-policy \
  --user-name jsmith \
  --policy-name AllowS3ReadOnly \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "s3:GetObject",
          "s3:ListBucket"
        ],
        "Resource": [
          "arn:aws:s3:::my-bucket",
          "arn:aws:s3:::my-bucket/*"
        ]
      }
    ]
  }'

AWS Managed Policies

Created and maintained by AWS. Good starting point but can be overly broad.

Customer Managed Policies

Created by you, reusable across multiple identities. Preferred for most use cases.

# Create the policy
aws iam create-policy \
  --policy-name AllowS3ReadOnly \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "s3:GetObject",
          "s3:ListBucket"
        ],
        "Resource": [
          "arn:aws:s3:::my-bucket",
          "arn:aws:s3:::my-bucket/*"
        ]
      }
    ]
  }'

# Attach it to a user (reusable — can also attach to roles or groups)
aws iam attach-user-policy \
  --user-name jsmith \
  --policy-arn arn:aws:iam::123456789012:policy/AllowS3ReadOnly

A managed policy is a standalone object with its own ARN, versioning, and can be attached to many identities. An inline policy lives inside a single identity and is deleted when that identity is deleted.

Inline vs Managed — Comparison

AspectManaged PolicyInline Policy
ReusabilityCan attach to multiple identitiesTied to one identity
LifecycleIndependentDeleted with identity
Version controlSupports up to 5 versionsNo versioning
Size limit6,144 characters per policyTotal of all inline policies: 2,048 (user), 5,120 (group), 10,240 (role) characters
Max per identityUser 10 (quota up to 20), role 20 (up to 25), group 10No count limit, only the total size above
ScopeCustomer managed: one account. AWS managed: every accountOne identity
Best forCommon, reusable permissionsUnique, one-off permissions

When to use which:

  • Use managed policies when permissions are reusable across multiple identities
  • Use inline policies when permissions are unique to one identity and should never be accidentally applied elsewhere

Identity-based policies: attached to IAM users, groups and roles; a statement has Effect, Action, Resource and an optional Condition but no Principal; a user gets its own and its groups’ policies, allows add up and an explicit Deny wins; managed policies (AWS managed or customer managed) have their own ARN and attach to many identities, inline policies live inside one identity

Policy JSON Structure

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::my-app-bucket/*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::my-app-bucket"
    }
  ]
}

Identity-Based vs Resource-Based Policies

TypeAttached ToDefinesPrincipal element
Identity-basedUsers, groups, rolesWhat the identity can doNo — the principal is the identity the policy is attached to
Resource-basedResources (S3, SQS, KMS)Who can access the resourceYes, required — names who the statement applies to

Resource-based policies support cross-account access without assuming a role.

Identity-based vs resource-based policies: role AppRole calls s3:GetObject on my-bucket; the identity-based policy on the role has no Principal and answers what the identity can do, the bucket policy names the role as Principal and answers who can use the bucket; in the same account an Allow in either is enough, across accounts both must allow, an explicit Deny in either wins; comparison of attachment, Principal and Resource elements, kinds, cross-account use, size limits and CLI commands

A resource-based policy must say who it applies to, so it uses the Principal element: an account, IAM user, role, role session, federated user, AWS service or "*" (everyone, including anonymous users). An IAM group can’t be a principal. An identity-based policy can’t use Principal at all, because its principal is the user, group or role it is attached to. A bucket policy that lets another account read objects:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::222222222222:root" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}

arn:aws:iam::222222222222:root means the whole account 222222222222, not its root user: that account’s administrators still decide which of their users and roles get s3:GetObject through their own identity-based policies.

The Principal Element

Why it is required. An identity-based policy lives on the caller, so AWS already knows who it is about. A resource-based policy lives on the bucket, key, queue or role being accessed, and the same resource is called by many identities, so each statement has to name the callers it covers. Without Principal the statement wouldn’t say who it grants (or denies) access to, so AWS requires the element in every resource-based policy.

When it is needed:

PolicyPrincipal?Example of what it names
Role trust policyRequiredWho may assume the role: an account, a role, an AWS service, an OIDC or SAML provider
S3 bucket policyRequiredAccounts, roles or services that may use the bucket
KMS key policyRequiredWho may use and manage the key (including your own account)
SQS queue / SNS topic policyRequiredServices or accounts that may send or publish
Lambda function policyRequired (aws lambda add-permission --principal)Services or accounts that may invoke the function
ECR repository, Secrets Manager secret, other resource policiesRequiredAccounts or roles from other accounts
VPC endpoint policyRequired; must be "*" for gateway endpoints (S3, DynamoDB)Narrowed with an aws:PrincipalArn condition instead
Identity-based policy, permission boundary, session policyNot allowedThe principal is the identity or session
SCPNot allowedUse the aws:PrincipalArn condition key instead

Principal formats:

WhoPrincipal value
An AWS account (its admins decide further){ "AWS": "arn:aws:iam::222222222222:root" } or { "AWS": "222222222222" }
An IAM role{ "AWS": "arn:aws:iam::222222222222:role/ProdDeployRole" }
One role session{ "AWS": "arn:aws:sts::222222222222:assumed-role/ProdDeployRole/deploy" }
An IAM user{ "AWS": "arn:aws:iam::222222222222:user/jsmith" }
An AWS service{ "Service": "lambda.amazonaws.com" }
An OIDC provider (role trust only){ "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" }
A SAML provider (role trust only){ "Federated": "arn:aws:iam::123456789012:saml-provider/ADFS" }
Everyone, including anonymous users"*" or { "AWS": "*" }

Several principals of the same type go in an array: { "AWS": ["111111111111", "222222222222"] }. They are alternatives (OR): the request comes from one principal at a time.

Example: let Lambda assume a role

The trust policy of a Lambda execution role. Without it, Lambda can’t use the role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "lambda.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}

Example: GitHub Actions (OIDC) assumes a role

The principal is the OIDC provider. The conditions decide which repository and branch may use it; without the sub condition, any GitHub repository could assume the role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
        }
      }
    }
  ]
}

Example: S3 may publish to an SNS topic

An AWS service as the principal. aws:SourceAccount and aws:SourceArn stop other accounts’ buckets from using your topic (the confused deputy problem):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "s3.amazonaws.com" },
      "Action": "sns:Publish",
      "Resource": "arn:aws:sns:us-east-1:123456789012:uploads",
      "Condition": {
        "StringEquals": { "aws:SourceAccount": "123456789012" },
        "ArnLike": { "aws:SourceArn": "arn:aws:s3:::my-bucket" }
      }
    }
  ]
}

Example: let S3 invoke a Lambda function

For Lambda, the CLI writes the statement for you; --principal becomes its Principal:

aws lambda add-permission \
  --function-name my-function \
  --statement-id s3-invoke \
  --action lambda:InvokeFunction \
  --principal s3.amazonaws.com \
  --source-arn arn:aws:s3:::my-bucket \
  --source-account 123456789012

Example: only my organization may use the bucket

"*" with a Deny and a condition: everyone outside the organization is refused, whatever their own policies say. AWS services acting for you are left out of the deny:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "OnlyMyOrganization",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ],
      "Condition": {
        "StringNotEquals": { "aws:PrincipalOrgID": "o-a1b2c3d4e5" },
        "Bool": { "aws:PrincipalIsAWSService": "false" }
      }
    }
  ]
}

Principal gotchas

  • No partial wildcards. arn:aws:iam::123456789012:role/dev-* is not a valid principal. Use "Principal": "*" plus an ArnLike condition on aws:PrincipalArn.
  • "*" with Allow means public, anonymous users included. Always narrow it with a condition.
  • Account vs role. Naming an account delegates to that account’s admins, who must still allow the action in an identity-based policy. Naming a role (in the same account) grants access by itself.
  • Deleted and recreated roles or users. IAM stores a role or user named in a trust policy as its unique ID. Recreate it with the same name and the old trust policy no longer matches; edit the policy and save the ARN again.
  • Groups can’t be principals. Name the roles or users, or the account with a condition.
  • Prefer roles over role sessions. A role session ARN only matches one session name; AWS recommends the role ARN, or aws:PrincipalArn in a condition.
  • Avoid NotPrincipal. AWS recommends "Principal": "*" with a Deny and an aws:PrincipalArn condition instead.

Trust Policies vs Permission Policies

PolicyAttached ToPurpose
Trust policyRoleDefines who can assume the role (the principal)
Permission policyRoleDefines what the role can do once assumed

Permission Boundaries

  • Sets the maximum permissions an identity can have
  • Effective permissions = identity-based policy AND permission boundary (intersection)
  • Useful for delegating user/role creation safely (letting developers create roles without escalating privileges)

Policy Evaluation Logic

AWS evaluates a request (within one account) in this order:

  1. Explicit Deny — if any policy explicitly denies, the request is denied (always wins)
  2. RCPs (Organizations) — resource control policies must allow access to resources in member accounts
  3. SCPs (Organizations) — service control policies must allow the action for principals in member accounts
  4. Resource-based policies — an Allow here can grant access on its own, even without an identity-based Allow
  5. Identity-based policies — otherwise, at least one must explicitly allow the action
  6. Permission boundaries — if a boundary is set, it must also allow the action
  7. Session policies — for sessions created with a session policy, it must also allow the action
  8. Implicit Deny — if nothing explicitly allows it, the request is denied by default

Cross-account requests need an Allow on both sides: the identity-based policy in the caller’s account and the resource-based policy (or role trust policy) in the other account.

IAM policy evaluation flowchart: an explicit Deny in any policy denies; then resource control policies and service control policies must allow; a resource-based policy Allow allows; otherwise an identity-based policy must allow, and a permission boundary and session policy, if set, must allow too; anything not allowed is an implicit deny

Conditions

Policies support conditions to restrict access further:

Condition KeyUse Case
aws:SourceIpIP restriction
aws:MultiFactorAuthPresentMFA enforcement
aws:CurrentTimeTime-based access
aws:RequestTag / aws:ResourceTagTag-based access control
aws:RequestedRegionRegion restriction

Example:

"Condition": {
  "Bool": { "aws:MultiFactorAuthPresent": "true" },
  "IpAddress": { "aws:SourceIp": "203.0.113.0/24" }
}

aws:SourceIp is the public IP of the caller. Requests that come through a VPC endpoint carry the private address in aws:VpcSourceIp instead, so a private range such as 10.0.0.0/8 in aws:SourceIp never matches.

Service-Linked Roles

  • Pre-defined roles created by AWS services (e.g., AWSServiceRoleForECS)
  • Cannot modify the permission policy — AWS manages it
  • Automatically created when you enable certain service features
  • Can only be deleted after removing the dependent resources

IRSA (IAM Roles for Service Accounts)

  • Allows Kubernetes pods in EKS to assume IAM roles without node-level credentials
  • Uses an OIDC provider to federate trust between Kubernetes service accounts and IAM roles
  • The role trust policy references the OIDC provider ARN and the service account name/namespace
  • Newer alternative: EKS Pod Identity (an EKS add-on), which needs no OIDC provider and uses the trust principal pods.eks.amazonaws.com

IAM Identity Center (AWS SSO)

Recommended for human users. Authenticate via a central portal and get short-lived credentials for any account/role you’re granted.

How It Works

  1. Admin enables IAM Identity Center in the management account
  2. Users are defined in a directory (built-in, Active Directory, or external IdP like Okta/Azure AD)
  3. Permission sets define what users can do in each account (maps to IAM roles behind the scenes)
  4. Users log in via a portal URL, pick an account + permission set, and get temporary credentials

Key Benefits Over Access Keys

  • No long-lived credentials on disk — credentials auto-expire (1–12 hours)
  • No access keys to rotate or leak
  • Centralized access management across all accounts
  • MFA built-in — enforced at the IdP level
  • Full audit trail in CloudTrail

CLI Setup

# One-time configuration (interactive)
aws configure sso --profile my-sso-profile

Creates a profile and an sso-session section in ~/.aws/config:

[profile my-sso-profile]
sso_session = mycompany
sso_account_id = 123456789012
sso_role_name = AdministratorAccess
region = eu-west-1
output = json

[sso-session mycompany]
sso_start_url = https://mycompany.awsapps.com/start
sso_region = eu-west-1
sso_registration_scopes = sso:account:access

Older guides put sso_start_url and sso_region directly in the profile. That legacy format still works, but it can’t refresh the token automatically.

Daily Usage

# Login (opens browser for IdP + MFA)
aws sso login --profile my-sso-profile

# Use normally
aws s3 ls --profile my-sso-profile
aws ec2 describe-instances --profile my-sso-profile

# Check identity
aws sts get-caller-identity --profile my-sso-profile

Multiple Accounts / Roles

[profile dev-readonly]
sso_session = mycompany
sso_account_id = 111111111111
sso_role_name = ReadOnlyAccess
region = eu-west-1

[profile prod-admin]
sso_session = mycompany
sso_account_id = 222222222222
sso_role_name = AdministratorAccess
region = eu-west-1

A single aws sso login authenticates once — all profiles that point to the same sso_session use the cached token (aws sso login --sso-session mycompany logs in without naming a profile).

Session Duration

TypeDefaultConfigurable
Portal session8 hours15 minutes – 90 days
Permission set session1 hour1 – 12 hours

SSO vs Access Keys

AspectAccess Keys + AssumeRoleIAM Identity Center (SSO)
Credentials on diskYes (~/.aws/credentials)No (cached token, auto-expires)
MFAMust pass manually per assume-roleHandled at IdP login (once per session)
Key rotationManual responsibilityNot needed
Multi-accountOne assume-role call per accountOne login, switch profiles freely
RevocationDeactivate key + revoke sessionsDisable user in directory (instant)
Best forAutomation, legacy setupsHuman users, daily CLI/console access

GetSessionToken (MFA-Protected)

Exchange long-lived keys + MFA code for temporary credentials:

aws sts get-session-token \
  --serial-number arn:aws:iam::123456789012:mfa/jsmith \
  --token-code 123456 \
  --duration-seconds 3600

Useful when you can’t eliminate the IAM user yet but want MFA on every action. --duration-seconds accepts 900 (15 minutes) to 129,600 (36 hours) for IAM users, default 43,200 (12 hours); root user sessions are capped at 3,600 (1 hour).

OIDC Federation (AssumeRoleWithWebIdentity)

For CI/CD pipelines (GitHub Actions, GitLab CI) and Kubernetes (IRSA). No stored secrets.

aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::123456789012:role/GitHubActionsRole \
  --role-session-name github-run \
  --web-identity-token "$OIDC_TOKEN"

SAML Federation (AssumeRoleWithSAML)

For corporate SSO (Active Directory, Okta) federated to AWS. Users authenticate against the IdP, receive a SAML assertion, and exchange it for temporary credentials.

Instance / Task / Execution Roles

For workloads on AWS (EC2, ECS, Lambda, EKS). The service automatically provides and rotates temporary credentials via the metadata service — no configuration beyond assigning the role.

All Authentication Methods

Every way to prove who you are to AWS, grouped by what you end up holding. Access key IDs tell them apart: AKIA… is a long-term key, ASIA… is a temporary key that only works together with a session token.

Long-Term Credentials

Valid until someone deletes or rotates them, so they are what leaks.

MethodUsed byWhat you presentLifetimeNotes
Root user sign-inAccount owner, root-only tasksEmail + password + MFAConsole sessionEnable MFA; with centralized root access, member accounts have no root password at all
Root access keysNobodyAKIA… + secret keyUntil deletedNever create them; delete any that exist
IAM user passwordPeople without Identity CenterUser name + password (create-login-profile) + MFAConsole session up to 12 hSign in at https://<account-id>.signin.aws.amazon.com/console
IAM user access keysLegacy scripts, tools outside AWSAKIA… + secret keyUntil rotated or deletedMax 2 per user; check use with aws iam get-access-key-last-used
Service-specific credentialsCodeCommit over HTTPS, Amazon KeyspacesGenerated user name + passwordUntil deletedaws iam create-service-specific-credential, only valid for that one service
SSH public keyCodeCommit over SSHSSH key uploaded to an IAM userUntil deletedaws iam upload-ssh-public-key
X.509 signing certificateLegacy SOAP APIsCertificate uploaded to an IAM userUntil deletedRarely needed today

Temporary Credentials for People

MethodUsed byWhat you presentLifetimeNotes
IAM Identity Center (aws sso login)People, across many accountsPortal sign-in: Identity Center directory or external IdP, with MFAPermission set 1–12 h; portal session 15 min – 90 daysThe recommended way for people
aws loginConsole users (root, IAM user or federated) who also need the CLIBrowser sign-in with the console credentialsRefreshed every 15 min, up to the session duration of the principal (max 12 h)AWS CLI 2.32.0+; needs the SignInLocalDevelopmentAccess policy; Identity Center users use aws sso login instead
sts get-session-tokenIAM user that must use MFA on the CLIAccess key + MFA code15 min – 36 h (root: 1 h)Can’t call IAM APIs unless MFA was included; of STS only assume-role and get-caller-identity
sts assume-roleCross-account access, switching to a stronger roleExisting credentials (optionally + MFA, external ID)15 min – role’s MaxSessionDuration (max 12 h); 1 h when chainedThe role’s trust policy decides who may assume it
sts assume-role-with-samlCorporate IdP (AD FS, Okta, Entra ID)SAML assertion from the IdP15 min – 12 h, capped by the assertion’s SessionNotOnOrAfterConsole or CLI via the IdP
sts get-federation-tokenOld custom identity brokersIAM user’s access keys15 min – 36 h (root: 1 h)Prefer roles; without --policy / --policy-arns the session has no permissions; can’t call IAM or STS (except get-caller-identity)
sts assume-rootAdmins in the management or delegated administrator accountIAM user or role with sts:AssumeRoot (not root credentials) + a root-task/… task policyMax 15 minOnly for root-only tasks in member accounts; regional STS endpoint only

Temporary Credentials for Workloads

MethodUsed byWhat you presentLifetimeNotes
EC2 instance profileApps on EC2Nothing: the SDK reads the instance metadata service (use IMDSv2)Rotated automaticallyRole attached to the instance
ECS task roleContainers on ECS / FargateNothing: container credentials endpointRotated automaticallySeparate from the task execution role (pulls images, writes logs)
Lambda execution roleLambda functionsNothing: environment variablesManaged by LambdaRole set on the function
EKS Pod IdentityPods on EKSNothing: Pod Identity Agent on the nodeRotated automaticallyAssociation between a service account and a role, no OIDC provider
IRSA (sts assume-role-with-web-identity)Pods on EKSService account token (OIDC JWT)15 min – 12 h, refreshed by the SDKNeeds the cluster’s OIDC provider in IAM
OIDC federation (sts assume-role-with-web-identity)GitHub Actions, GitLab CI, other CIOIDC token (JWT) from the CI provider15 min – 12 hNo secrets stored in the CI system
IAM Roles AnywhereOn-premises servers, other cloudsX.509 certificate from your CA (trust anchor)15 min – 12 h, default 1 haws_signing_helper as credential_process
Cognito identity poolsUsers of mobile and web appsToken from a user pool or social IdP, or guest access1 hGetCredentialsForIdentity

Which Temporary Credential Method to Use

ScenarioSolution
Human console/CLI accessIAM Identity Center (SSO)
CI/CD pipelinesOIDC federation
Cross-account accesssts assume-role
App on EC2/ECS/LambdaInstance/task/execution role
Existing IAM user + MFAsts get-session-token
Corporate SSOSAML federation

Key Points

  • Temporary credentials cannot be revoked individually — revoke all sessions via IAM console (inline deny on aws:TokenIssueTime)
  • Maximum AssumeRole session: the role’s MaxSessionDuration (1–12 hours, default 1 hour); role chaining (assuming a role from a role session) is limited to 1 hour
  • Maximum GetSessionToken session: 36 hours for IAM users
  • Always prefer roles and federation over IAM users — the goal is zero long-lived credentials

Root Account vs IAM User with AdministratorAccess

The root account is tied to the email that created the AWS account. It exists outside IAM — no identity-based policy or permission boundary can restrict it. SCPs do apply to the root user of member accounts in AWS Organizations, but never to the root user of the management account.

With centralized root access (aws iam enable-organizations-root-credentials-management and enable-organizations-root-sessions), the management account can remove the root credentials of member accounts and run the few root-only tasks through aws sts assume-root.

Actions Only Root Can Perform

  • Change account name, email, or root password
  • Close the AWS account
  • Change the AWS support plan
  • Enable MFA Delete on S3 buckets
  • Create a CloudFront key pair (legacy)
  • Configure billing (activate IAM access, modify payment methods)
  • Restore IAM permissions when all access is locked out
  • Edit or delete an S3 bucket policy or SQS queue policy that denies all principals
  • Register as a seller in the Reserved Instance Marketplace
  • Sign up for GovCloud
  • View certain tax invoices

Root vs Admin — Comparison

AspectRoot AccountIAM User + AdministratorAccess
Restricted by IAM policiesNoYes
Restricted by SCPsOnly in member accountsYes (in member accounts)
Restricted by permission boundariesNoYes
Account-level operationsFull accessCannot perform
MFA enforcementSelf-managedEnforced via policies/SCPs
Blast radius if compromisedUnrestricted, no guardrailsCan be limited after the fact
Recommended for daily useNeverYes (with MFA + temporary credentials)

Root is the only identity that can recover an account where all IAM access has been removed. This is why it should never be used for daily tasks — unrestricted blast radius with no guardrails.

Migrating Inline to Managed Policies

  1. Audit — identify common permission patterns across inline policies
  2. Create managed policies — develop standardized policies for common patterns
  3. Gradual migration — replace inline policies with managed policies incrementally
  4. Test — verify permissions still work after each change
  5. Document — record the purpose and scope of each managed policy

IAM Access Analyzer

Identifies resources shared with external entities and finds unused permissions. Can also generate least-privilege policies from CloudTrail activity and validate policy documents for errors.

Credential Report

A CSV report of all IAM users showing access key status, MFA enabled, password last used/rotated, and creation time. Useful for auditing compliance.

Last Accessed Information

Shows which AWS services each identity has accessed and when. Use it to find unused permissions and tighten policies — services never accessed are candidates for removal.

Cross-Account Role Patterns

External ID (Confused Deputy Prevention)

When granting a third party access to your account, use an external ID to prevent the confused deputy problem :

Trust policy on the role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::999999999999:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "unique-id-from-third-party"
        }
      }
    }
  ]
}

Cross-Account Access (Your Own Accounts)

Trust policy allowing a role from another account you own:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::222222222222:role/ProdDeployRole"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

With this trust policy on CrossAccountRole in account 111111111111, a ProdDeployRole session in account 222222222222 can assume it:

AssumeRole flow: a ProdDeployRole session in account 222222222222 calls sts:AssumeRole, STS checks the trust policy of CrossAccountRole in account 111111111111, returns temporary credentials (ASIA access key, secret key, session token, expiration), the caller signs API calls with them as the assumed-role session, and the role’s permission policies decide the request

See Also