Skip to main content
DevOps Insightsguides & answers

AWS IAM explained

Who can do what in an AWS account: users, roles and policies, the order in which AWS checks them, and how to find the one that said no.

Checked against the AWS documentation · 20 October 2026

On this page · 15 sections

The short answer

AWS Identity and Access Management (IAM) decides, for every request made to AWS, whether the caller may do it. Callers are principals: IAM users, and sessions of IAM roles. What they may do is written in policies, JSON documents that allow or deny actions on resources.

Three rules explain most of IAM. Everything is denied unless a policy allows it. An explicit Deny anywhere beats every Allow. And several kinds of policy can apply to one request, so the one you are looking at is not always the one that decided.

An analogy, and where it breaks

Think of an office building with key cards. A user is an employee with a permanent card. A role is a visitor card at reception: whoever is on the list (the role’s trust policy) can pick it up, and it stops working after a set time. The doors each card opens are its permissions policy. Some doors also carry their own list of who may enter (a resource-based policy, such as an S3 bucket policy). And head office can lock whole floors in every building it owns (service control policies).

Where it breaks:

  • A card opens nothing by default. A new user or role can do nothing at all until a policy allows it. The one exception is the account’s root user, which has full access.
  • A “no entry” sign wins. An explicit Deny in any applicable policy overrides every Allow, wherever the Allow is.
  • Head office never opens doors. An SCP grants nothing. It only sets the most that users and roles in an account can be allowed.

Users, groups, roles and the root user

  • The root user is the identity the account was created with, and it has full access. AWS recommends using it only for the few tasks that require it, never for everyday work, and protecting it with MFA.
  • An IAM user is an identity with long-term credentials: a console password, access keys, or both. AWS now recommends IAM users only for cases federation cannot cover; people should sign in through an identity provider and get temporary credentials instead.
  • An IAM user group is a set of users that share policies. A group is not an identity: it cannot sign in, and it cannot be named as a principal in a resource-based policy.
  • An IAM role is an identity with permissions but no password or access keys. Someone or something assumes it and receives temporary credentials for a role session. EC2 instances, Lambda functions, CI jobs and people from other accounts all use roles. Some AWS services create service-linked roles for themselves; you can view their permissions but not edit them.

Every identity has an ARN, and the ARN is what policies name:

arn:aws:iam::111122223333:user/exampleuser                                        # an IAM user
arn:aws:iam::111122223333:role/examplerole                                        # an IAM role
arn:aws:sts::111122223333:assumed-role/examplerole/examplerolesessionname         # a session of that role

Policies

A policy is a JSON document of statements. Each statement has an Effect (Allow or Deny), the Actions it covers, the Resources it covers and, optionally, a Condition. Policies attached to a resource also have a Principal: who the statement is about. The Version is the policy language version, not a date you choose: use 2012-10-17.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadOneBucket",
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket1/*"
    },
    {
      "Sid": "ListThatBucket",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket1"
    }
  ]
}

Note the two resources: listing a bucket is an action on the bucket, reading an object is an action on the object, so one needs the bucket ARN and the other the ARN ending in /*. Getting that wrong is a classic implicit deny.

Policies attached to identities come in three kinds:

KindWho writes itEditableAttached to
AWS managed policyAWSNoMany identities
Customer managed policyYouYesMany identities
Inline policyYouYesExactly one user, group or role

The other policy types that can affect a request:

  • Resource-based policies, attached to a resource: S3 bucket policies, SQS queue policies, KMS key policies, and a role’s trust policy.
  • Permissions boundaries, a managed policy set as the most a user or role can ever be allowed. Used to let teams create roles without letting them create admins.
  • Service control policies (SCPs) and resource control policies (RCPs), from AWS Organizations. SCPs cap what principals in member accounts can do, including their root users; RCPs cap what can be done to resources in member accounts, from anywhere. Neither affects the management account or service-linked roles.
  • Session policies, passed when a role session is created, which can only narrow the role’s permissions for that session.

Roles and trust policies

A role has two policies that answer two different questions. Its trust policy says who may assume it. Its permissions policies say what a session of the role may do. Assuming a role needs both your own permission to call sts:AssumeRole on it and a trust policy that names you. A trust policy is one of the few resource-based policies that must always allow explicitly, even within one account.

A trust policy that lets the account’s own IAM policies decide who may assume the role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
      "Action": "sts:AssumeRole"
    }
  ]
}

Despite the :root, that principal is not the root user. Naming an account delegates to it: any user or role in 111122223333 whose own IAM policy allows sts:AssumeRole on this role can assume it. To trust one role or user instead, put its ARN there. When you save the policy, AWS stores that ARN as the principal’s unique ID, so a user or role deleted and recreated with the same name is not trusted any more.

How AWS decides: the order of checks

For a request inside one account, AWS goes through the applicable policies in this order:

  1. Explicit deny. If any applicable policy of any type has a Deny that matches, the answer is Deny.
  2. RCPs on the resource’s account must allow it. With RCPs enabled, the default RCPFullAWSAccess is always attached and cannot be detached, so in practice this step only fails on a Deny.
  3. SCPs on the principal’s account must allow it. If none allows it, the answer is Deny.
  4. Resource-based policy. Within one account, a resource policy that names the user or the role session directly is enough on its own. One that names the role’s ARN is still limited by the role’s permissions boundary and session policy. One that names the whole account only delegates to IAM policies.
  5. Identity-based policies must allow it, unless the resource policy already did.
  6. Permissions boundary, if set, must also allow it.
  7. Session policy, if one was passed, must also allow it.

Across accounts, AWS evaluates the request in both: the caller’s account (identity policies and what limits them) and the resource’s account (the resource’s policy and what limits it). Both must allow. That is why a bucket policy that names a role in another account is not enough by itself.

Explore a request

Pick who is asking and what each policy says, and the explorer walks the checks above for one request: reading an object from an S3 bucket. It shows where evaluation stops and the error S3 returns. It runs entirely in your browser.

Start from a common case

The request: s3:GetObject on one object

Allowed.

  1. continueAny explicit Deny?No policy has a Deny statement that applies.
  2. not setResource control policiesNo RCP applies to the bucket's account.
  3. not setService control policiesNo SCP applies: the account is not in an organization, or it is the management account, which SCPs do not affect.
  4. continueResource-based policyNo bucket policy allows this principal. Within one account that is fine as long as an identity-based policy does.
  5. continueIdentity-based policiesA policy attached to the principal allows the action.
  6. not setPermissions boundaryNo permissions boundary is set.
  7. not setSession policyNo session policy was passed when the role was assumed.

What the explorer leaves out: the root user, VPC endpoint policies, S3 ACLs and Block Public Access, conditions, and services whose resource policies must allow explicitly even in one account, such as KMS key policies and role trust policies. Each can change the outcome; the AWS pages listed under Sources cover them.

Reading an AccessDenied message

Most AccessDenied messages follow one format, which names the type of policy that denied the request with one of two phrases. “with an explicit deny in a … policy” means a Deny statement matched: find it and narrow it, because no Allow can override it. “because no … policy allows” means nothing of that type allowed the action: add or fix an Allow there.

The message saysWhere to look
because no identity-based policy allowsNothing attached to the user, its groups or the role allows the action on that resource. Add an Allow, or check the Resource and Condition of the one you think applies.
with an explicit deny in an identity-based policyA Deny statement in a policy attached to the user, its groups or the role. The message usually names the policy's ARN.
because no resource-based policy allowsThe bucket, queue, key or secret policy does not name you. Across accounts that is required even when your IAM policy allows it.
with an explicit deny in a resource-based policyA Deny in the resource's own policy, often a condition such as a required VPC endpoint, TLS or encryption.
because no permissions boundary allowsThe user or role has a permissions boundary, and the action is outside it. The boundary must allow it too.
because no session policy allowsWhoever created the session passed a session policy that leaves the action out. Common with tools that assume roles for you.
because no service control policy allowsAn SCP in AWS Organizations, on the account or an OU above it, does not allow the action. Only an organization administrator can change it.
with an explicit deny in a service control policyAn SCP denies it, often a Region or service guardrail. Ask whoever runs the organization.
with an explicit deny in a resource control policyAn RCP on the resource's account denies it, for example one that blocks access from outside the organization.
because no role trust policy allows the sts:AssumeRole actionThe role's trust policy does not name you, or its conditions do not match. You never got as far as the role's permissions.

The message names only one policy type, even when several deny the request. Fixing the one it names can simply reveal the next. And some services, and S3 requests between accounts in different organizations, still return a bare “Access Denied” with no context at all.

Try it with the AWS CLI

This creates a role that people in your own account can assume, gives it read-only access to S3 through an AWS managed policy, and uses it. Replace 111122223333 with your account ID, which the first command prints. These commands were checked against the AWS CLI’s own help (version 2.32.5); they were not run against a live account for this page.

# Who am I? Needs no permissions.
aws sts get-caller-identity
# The trust policy: let this account's IAM policies decide who may assume the role.
cat > trust.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:root" }, "Action": "sts:AssumeRole" }
  ]
}
EOF

aws iam create-role --role-name S3ReadOnlyDemo --assume-role-policy-document file://trust.json
aws iam attach-role-policy --role-name S3ReadOnlyDemo \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# A CLI profile that assumes the role, using your current credentials.
# Add to ~/.aws/config:
[profile s3-reader]
role_arn = arn:aws:iam::111122223333:role/S3ReadOnlyDemo
source_profile = default
aws sts get-caller-identity --profile s3-reader   # now an assumed-role ARN
aws s3 ls --profile s3-reader                      # allowed: s3:List*
aws s3 mb s3://amzn-s3-demo-bucket1 --profile s3-reader
# fails: the role may read, not create

Assuming the role needs your own identity to be allowed sts:AssumeRole on it. An administrator already is; anyone else needs a policy saying so. When you are done:

aws iam detach-role-policy --role-name S3ReadOnlyDemo \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam delete-role --role-name S3ReadOnlyDemo

Troubleshooting

Start from the message, then confirm who made the request, then test the decision without making the request again.

"AccessDenied", and you are not sure who you are

Half of all IAM debugging is discovering that the request was made by a different identity than you thought: a leftover environment variable, a default profile, an instance role.

aws sts get-caller-identity

The Arn field is who AWS thinks you are. It needs no permissions, so it works even when everything else is denied.

env | grep '^AWS_'

Credentials or AWS_PROFILE in the environment take precedence over the config file. Only an explicit --profile on the command line overrides them.

  • Unset the stray variables, or pass --profile explicitly.
  • Compare the ARN with the principal your policies actually name.

Your IAM policy looks right, but the request is still denied

Test the decision without making the request. The policy simulator evaluates the principal's policies, its permissions boundary and any SCPs, and says which statement decided.

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::111122223333:role/S3ReadOnlyDemo \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::amzn-s3-demo-bucket1/object-name

EvalDecision is allowed, explicitDeny or implicitDeny; MatchedStatements shows which policy decided. It does not fetch the resource's policy for you, and it does not evaluate RCPs.

  • implicitDeny: check the Resource ARN (bucket ARN for ListBucket, object ARN with /* for GetObject) and any Condition.
  • explicitDeny: find the Deny it names. Only removing or narrowing it helps; another Allow never does.

"is not authorized to perform: sts:AssumeRole"

Two policies must agree: your own identity policy must allow sts:AssumeRole on the role, and the role's trust policy must name you (or your account) as a principal, with every condition met.

aws iam get-role --role-name S3ReadOnlyDemo --query Role.AssumeRolePolicyDocument

The trust policy. Is your user, role or account in Principal? A unique ID such as AROADBQP57FF2AEXAMPLE where an ARN should be means the principal named there was deleted.

  • Add your principal to the trust policy, or your account if your own IAM policies should decide.
  • If the user or role was deleted and recreated with the same name, edit the trust policy and put the ARN back: it pointed at the old one's unique ID.
  • For GitHub Actions, the trust policy's sub condition is the usual culprit: check it with the OIDC trust policy checker linked below.

Works in your account, denied for the other account

Across accounts both sides must allow: an identity-based policy in the caller's account and the resource's policy in the owner's account. And S3 only explains the denial when both accounts are in the same organization; otherwise the message is a plain Access Denied.

aws s3api get-bucket-policy --bucket amzn-s3-demo-bucket1 --query Policy --output text

Run in the bucket owner's account. The Principal must name the caller's role, user or account.

  • Name the caller in the bucket policy, and allow the action in the caller's own IAM policy.
  • Encrypted with a customer managed KMS key? Reading also needs kms:Decrypt on that key, and the key policy must allow the caller. Encrypted with the AWS managed aws/s3 key? Then only the key's own account can read it.

Production practice

  • Temporary credentials everywhere. People sign in through an identity provider (AWS recommends IAM Identity Center) and assume roles. Workloads on EC2, Lambda or ECS use the role AWS delivers to them. CI systems outside AWS exchange their OIDC token for role credentials (AssumeRoleWithWebIdentity) rather than storing access keys.
  • Least privilege, in steps. AWS managed policies are a reasonable start; IAM Access Analyzer can then generate a narrower policy from what the role actually used.
  • Remove what nobody uses. Unused users, roles, keys and permissions are attack surface. Review last-accessed data regularly.
  • Guardrails above accounts. In an organization, SCPs and RCPs enforce rules no account administrator can lift, such as approved Regions or no access from outside the organization.
  • Protect the root user. MFA on, no access keys, used only for the tasks that require it.

Common mistakes

  • Adding an Allow to fix an explicit deny. It cannot work; the Deny has to change.
  • Using the bucket ARN for object actions, or the object ARN for s3:ListBucket.
  • Reading arn:aws:iam::111122223333:root in a trust policy as “only the root user”. It means the whole account, through its IAM policies.
  • Granting cross-account access in the bucket policy and forgetting the caller’s own IAM policy, or the reverse.
  • Deleting and recreating a role with the same name and expecting trust and bucket policies to follow it.
  • Leaving long-lived access keys in CI secrets when the CI system supports OIDC.
  • Debugging the wrong identity. Run aws sts get-caller-identity first, every time.

Interview questions

What is the difference between an IAM user and an IAM role?

A user has long-term credentials and belongs to one person or workload. A role has no credentials of its own: it is assumed, and the session gets temporary credentials. Roles are the default for workloads, cross-account access and federated people.

A policy allows an action and another denies it. What happens?

It is denied. An explicit Deny in any applicable policy overrides any Allow.

Does an SCP that allows everything give a user access?

No. SCPs grant nothing; they only cap what IAM policies in member accounts can grant. The user still needs an identity-based or resource-based policy that allows the action.

Why can a role in account A not read a bucket in account B, though B’s bucket policy names it?

Across accounts both sides must allow. The role’s own IAM policy in account A must also allow the action on that bucket.

What is a permissions boundary for?

Delegation. It caps what a user or role can ever be allowed, so you can let a team create roles for their applications without letting them create roles more powerful than the boundary.

Questions people ask

Should I use the root user for daily work if I am the only person in the account?

No. Create an identity for daily work, preferably through IAM Identity Center, and keep the root user for the tasks that require it, with MFA.

Inline or managed policies?

Managed policies, usually customer managed, because they can be reused, versioned and reviewed in one place. Inline policies make sense when a permission must never be attached to anything else.

Is there a way to test a policy without risk?

The IAM policy simulator (console, or aws iam simulate-principal-policy) evaluates policies without making the request. It includes permissions boundaries and SCPs, accepts one resource-based policy when simulating an IAM user, and does not evaluate RCPs.

Where to go next

Sources

Every fact on this page comes from these AWS pages, read on 20 October 2026.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it