skip to content

An IAM role carries two separate policy documents: a trust policy and one or more permissions policies. What does each control, and how do you tell from an AccessDenied error which one is at fault?

level: middleimportance: must knowfreq 70%

answer

  1. two documents, two questions
  2. who may become it versus what it may do
  3. Principal lives on only one of them
  4. read the failing action in the error
  5. cross-account needs both sides

basics

~20 s

The trust policy is the role's resource policy: it names the principals allowed to call sts:AssumeRole on it. The permissions policies say what the resulting session may do. A denial on sts:AssumeRole points at the trust policy; any later denial points at the permissions policies.

solid answer

~50 s

Every IAM role has an *assume role policy document* — the trust policy — and separately attached identity policies. The trust policy is the only document on the role that carries a `Principal` element, and it answers one question: who may turn into this role. Its `Action` is `sts:AssumeRole` (or `sts:AssumeRoleWithWebIdentity` / `sts:AssumeRoleWithSAML`, and `sts:TagSession` when you pass session tags), and it is where conditions like requiring MFA belong. The permissions policies answer the other question: what the session may do once it exists. The split is also your diagnostic. An error naming `sts:AssumeRole` means you never became the role, so look at the trust policy — and if the caller is in another account, also at the caller's own identity policy, because cross-account assumption needs an allow on both sides. Any denial on a service action such as `s3:GetObject` means you did become the role and its permissions policies are too narrow.

go deeper

for a junior

Remember that a role has two documents and be able to say which is which: the trust policy names who may assume, the permissions policy names what the session may do.

for a middle

Explain the anatomy — that the trust policy is a resource policy, that it is the only place Principal appears, and that its Action must match the STS call being made. Be ready to read an AccessDenied message and say which document to open.

for a senior

Demonstrate the review instinct: spot wildcard principals, insist on specific principal ARNs plus conditions such as MFA or organization ID, and reason about the caller-side allow that cross-account assumption also requires.

for a principal

Own the pattern across accounts — who is allowed to author trust policies at all, how role trust is standardised so it can be audited, and how you keep a fleet of roles from drifting into overly broad trust.

## A role is two documents that answer two different questions When people say "the role's policy" they usually mean only half of it. An IAM role has: 1. **A trust policy** (the API calls it the `AssumeRolePolicyDocument`) — *who may become this role*. 2. **Permissions policies**, managed or inline, attached like they would be to a user — *what this role may do*. Keeping these straight is the single most useful mental model in IAM, because almost every "my role doesn't work" ticket resolves to having edited the wrong one. ## The trust policy A trust policy is a **resource-based policy** attached to the role. That is why it has a `Principal` element, which identity policies attached to users and roles never do: ```json { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:role/BuildRunner" }, "Action": "sts:AssumeRole" }] } ``` The `Principal` can be an IAM principal ARN, an account (`"AWS": "arn:aws:iam::111122223333:root"`), or an AWS service principal, or — for federation — a `Federated` entry naming a SAML provider or an OIDC provider. The `Action` must be the matching STS action: `sts:AssumeRole` for IAM principals, `sts:AssumeRoleWithWebIdentity` for OIDC, `sts:AssumeRoleWithSAML` for SAML. Getting that pairing wrong is a classic silent failure: an OIDC-federated caller cannot assume a role whose trust policy only allows `sts:AssumeRole`. Naming a whole account as the principal is a **delegation**, not a grant. It says "account 1111… may decide who among its principals gets to assume me", and those principals still need `sts:AssumeRole` on this role ARN in their own identity policy. Naming a specific principal ARN is the tighter form, and it is what you want in production. Conditions belong here too, and this is where the good ones live: requiring `aws:MultiFactorAuthPresent` for a human break-glass role, pinning `sts:ExternalId` for a third-party vendor, restricting by `aws:PrincipalOrgID` so only accounts in your organization qualify. ## The permissions policies These are ordinary identity policies — `Effect`, `Action`, `Resource`, `Condition`, no `Principal`. They are evaluated for every API call the *session* makes, exactly as they would be for a user. The role's permissions have nothing to do with the permissions of whoever assumed it; the caller's rights do not travel into the session. ## Reading the failure The two-document split gives you a free bisection. - The error mentions **`sts:AssumeRole`** ("User: arn:aws:iam::…:user/dana is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::…:role/ReportsReader"). You never became the role. Check the trust policy's `Principal`, that its `Action` matches the STS call you actually made, and that no condition in it is failing. If the caller lives in a different account, also check the caller's own identity policy, because cross-account assumption needs an allow on the caller's side as well as in the trust policy. - The error mentions a **service action** (`s3:GetObject`, `dynamodb:Query`) and the principal in the message is `arn:aws:sts::…:assumed-role/ReportsReader/…`. You *did* become the role — assumption succeeded — and the gap is in the permissions policies, or in a resource policy on the target, or in something above the role such as an organization-level guardrail. That principal ARN in the error message is the tell. If it names an assumed-role session, the trust policy already did its job; stop editing it. ## The misplacements people actually make **Putting service actions in the trust policy.** Adding `s3:GetObject` to a trust policy does nothing useful — the document is only consulted for STS assumption calls, so the statement never matches anything. The role still cannot read S3. **Trying to put a `Principal` in a permissions policy.** Identity-based policies do not accept `Principal`; the document fails validation. If your instinct is to write one, you are describing trust, and it belongs in the other document. **Forgetting the caller side.** People edit the trust policy in the target account, see it correctly naming the caller, and are baffled that it still fails from another account. The caller's identity policy also has to allow `sts:AssumeRole` on that role ARN. **Leaving `"AWS": "*"` in the trust policy.** A wildcard principal with no condition means any AWS principal anywhere can assume the role. This is one of the highest-severity findings a reviewer or an automated analyzer can raise, and it usually got there as a temporary debugging step nobody removed.

  • Your trust policy names the whole account 111122223333 as the principal. Practically, what does that mean for who can assume the role?
    It delegates the decision to that account's administrators. Nobody in it can assume the role purely on the strength of your trust policy — each principal also needs `sts:AssumeRole` on your role ARN in its own identity policy. It is broader than naming a specific role ARN, because any principal that account chooses to authorize qualifies.
  • An OIDC-federated caller keeps failing to assume a role whose trust policy looks correct. What would you check in the document itself?
    The `Action`. Federated assumption uses `sts:AssumeRoleWithWebIdentity`, not `sts:AssumeRole`, and a trust policy that only allows the latter will never match. Check that the `Principal` is a `Federated` entry naming the OIDC provider, and that any subject or audience conditions match the token actually presented.
  • Why is a trust policy with "Principal": {"AWS": "*"} treated as a critical finding?
    With no condition narrowing it, every AWS principal in every account can assume the role and inherit its permissions. It converts an internal role into a publicly assumable one. If a wildcard is unavoidable, it must be paired with a hard condition such as `aws:PrincipalOrgID` or a specific `sts:ExternalId`.

saying these in an interview costs you the question

  • Adding service actions like s3:GetObject to the trust policy
  • Believing an account principal alone lets that account's users assume
  • Trying to write a Principal element in an identity policy
  • Editing the trust policy when the error names s3:GetObject
  • Leaving a wildcard principal in place after debugging

context