IAM Policy Evaluation and Authorization Strategies on AWS
IAM policy evaluation follows a fixed order: an explicit deny anywhere ends the request; otherwise the request must survive every applicable gate — service control policies, resource control policies, resource-based policies, identity-based policies, permissions boundaries, and session policies — with everything outside an explicit allow denied by default. Task 4.2 of the SCS-C03 exam, the heaviest-weighted topic on the paper, tests whether you can trace a request through that chain, explain why an action works same-account but fails cross-account, design ABAC and RBAC strategies, wield the condition keys that stop confused-deputy and data-exfiltration paths, and drive least privilege with IAM Access Analyzer and the policy simulator. This lesson works through the full evaluation chain with a gate-by-gate trace, the policy-language pitfalls that decide exam answers, and the AccessDenied triage tree you will use in production. Issuing credentials is Task 4.1's job; here we decide what an authenticated principal may do.
On this page8 sections
- The policy evaluation chain: every gate a request must pass
- The five policy types and when to use each
- Same-account OR versus cross-account AND
- A full evaluation trace, gate by gate
- Policy language pitfalls: Not-elements and condition operators
- Condition keys every security engineer must know
- ABAC versus RBAC, and S3 and KMS special cases
- Least-privilege tooling and the AccessDenied triage tree
- Trace any request through the IAM policy evaluation chain, applying explicit denies, SCPs, RCPs, resource policies, boundaries, and session policies in order
- Explain why same-account access needs only one allow while cross-account access must be allowed on both sides
- Select the right policy type — identity, resource-based, permissions boundary, SCP, or session policy — for each control objective
- Apply condition operators and security condition keys such as aws:PrincipalOrgID and aws:SourceArn without falling into NotAction, StringLike, or ForAllValues traps
- Design ABAC with matching principal and resource tags, and judge when RBAC permission sets are the better fit
- Diagnose AccessDenied errors systematically using Access Analyzer, the policy simulator, CloudTrail, and sts:DecodeAuthorizationMessage
The policy evaluation chain: every gate a request must pass
Every AWS request starts from implicit deny: nothing is permitted until some policy explicitly allows it. Evaluation then applies a fixed sequence, and internalizing it is the single highest-value preparation for this exam.
- Explicit deny check. AWS evaluates all applicable policies of every type first; a
"Effect": "Deny"statement matching the request in any of them ends evaluation immediately. No allow anywhere can override an explicit deny. - Service control policies. If the account belongs to an AWS Organization, the SCPs inherited along its OU path must allow the action. SCPs bound what identities in the account — including the root user of a member account — can ever do.
- Resource control policies. RCPs are the resource-side counterpart to SCPs: organization-managed ceilings on what can be done to supported resources (such as S3 buckets and KMS keys) in member accounts, regardless of who calls.
- Resource-based policy. If the target resource has one, it is evaluated next — and in the same account, its allow can be sufficient by itself.
- Identity-based policies. The policies attached to the calling user, group, or role.
- Permissions boundary. If the identity has one, the action must also be allowed inside the boundary.
- Session policy. If one was passed when the session was created, the action must also fall inside it.
The unifying rule: SCPs, RCPs, permissions boundaries, and session policies never grant anything. They are filters — ceilings the effective permission set is intersected with. Only identity-based and resource-based policies actually grant. When an exam option claims an SCP or boundary "gives" access, it is wrong by definition.
The five policy types and when to use each
Choosing the right policy type for a control objective is a recurring exam pattern. The capability matrix:
| Policy type | Attached to | Can grant? | Primary use |
|---|---|---|---|
| Identity-based | User, group, or role | Yes | Day-to-day permissions for principals |
| Resource-based | The resource (bucket, key, queue, topic, secret, role trust) | Yes — including to other accounts | Cross-account access; per-resource control that travels with the resource |
| Permissions boundary | User or role | No — ceiling only | Capping what delegated admins can grant |
| SCP / RCP | Organization root, OU, or account | No — ceiling only | Organization-wide guardrails on principals (SCP) or resources (RCP) |
| Session policy | Passed at AssumeRole time | No — narrows the session | Handing a broad role to a caller in scoped-down form |
Not every service supports resource-based policies: S3, KMS, Lambda, SQS, SNS, Secrets Manager, API Gateway, and IAM role trust policies are the ones the exam leans on; EC2 instances and DynamoDB tables, notably, have none, so cross-account access to them always routes through role assumption.
The permissions boundary earns its place through the delegated administration pattern. You want application teams to create their own roles without a central-IAM bottleneck, but nothing they create may exceed a defined ceiling. Give the team's admin role iam:CreateRole and iam:AttachRolePolicy — but condition those actions on iam:PermissionsBoundary requiring that every role they create carries the approved boundary. The delegated admin can grant anything they like; the effective permission of anything they create is intersected with the boundary. "Cap what delegated admins can grant" is the trigger phrase, and the boundary is the answer.
Same-account OR versus cross-account AND
This distinction decides more exam questions than any other single fact. Within one account, an identity-based allow or a resource-based allow is sufficient (assuming no deny and no failed ceiling). If the bucket policy on my-bucket allows a same-account role to call s3:GetObject, the role needs no S3 permissions in its own identity policy at all — and vice versa. The two grant paths are evaluated as a union.
Across accounts, the logic flips to an intersection: the resource's account must allow the access in a resource-based policy, and the caller's account must allow it in the caller's identity-based policy. Trust must exist on both sides of the account boundary. Hence the trigger phrase: when something "works from the same account but fails cross-account," the almost-certain cause is a missing grant on one side — usually the resource policy, because the caller's own policies were already in place.
Two refinements matter at Specialty level. First, when a resource policy grants to another account's root (the account principal, arn:aws:iam::111122223333:root), it delegates to the whole account — individual principals there still need identity-policy allows, which the account's admins control. Granting directly to a specific role ARN skips that delegation step. Second, cross-account role assumption is the alternative when a service has no resource policy: the caller assumes a role inside the resource's account, at which point everything becomes a same-account evaluation again. Choosing between "share the resource via its policy" and "assume a role in the owning account" — and knowing that only the second works for DynamoDB — is a classic question shape.
A full evaluation trace, gate by gate
Walk one request through every gate. A data analyst assumes the role AnalystRole in account 111122223333, which carries a permissions boundary allowing only s3:* and athena:*. The role's identity policy allows s3:GetObject on arn:aws:s3:::finance-data/*. The automation that created the session passed a session policy allowing s3:GetObject only on finance-data/reports/*. The account sits in an OU whose SCP allows all actions except a deny on S3 outside approved Regions. The bucket finance-data is in the same account, with a bucket policy that denies requests where aws:SecureTransport is false. The analyst requests s3:GetObject on finance-data/reports/q3.csv over HTTPS.
- Explicit denies: the bucket policy's
SecureTransportdeny does not match (the request is HTTPS); the SCP's Region deny does not match (the bucket is in an approved Region). No deny fires. - SCP: allows the action. Pass.
- Resource-based policy: the bucket policy grants nothing here — it only denies. No grant from this gate, but no block either.
- Identity-based policy: allows
s3:GetObjectonfinance-data/*, which covers the key. Grant found. - Permissions boundary:
s3:*covers the action. Pass. - Session policy:
reports/*covers the key. Pass.
Result: allowed. Now mutate it: request finance-data/raw/dump.csv and the session policy gate fails — AccessDenied even though the identity policy and boundary both allow it. Request over plain HTTP and the explicit deny ends evaluation before any allow is considered. Move the caller to another account and the bucket policy's lack of a grant becomes fatal, because cross-account requires both sides. One scenario, four different outcomes — this is exactly how the exam varies a single stem.
Policy language pitfalls: Not-elements and condition operators
The policy grammar's sharp edges are exam currency. NotAction with Allow grants everything except the listed actions — far broader than intended if you meant "allow these few things"; its safe use is inside a Deny (deny everything except approved actions) or paired with conditions. NotPrincipal with Deny, at concept level, denies everyone except the listed principals — an emergency lockdown pattern, but brittle: you must enumerate every ARN to exempt, including the account root and, for roles, the assumed-role session ARNs, and AWS recommends Condition with aws:PrincipalArn instead. NotResource mirrors NotAction's inversion risk.
Operators next. StringEquals is an exact, case-sensitive match; StringLike permits * and ? wildcards — use it for patterns like arn:aws:iam::*:role/Admin*, but remember that an over-broad wildcard in a trust or bucket policy is itself a finding. The Null operator tests key presence: "Null": {"aws:TokenIssueTime": "true"} matches requests made with long-term credentials, because temporary-credential requests carry that key. Most string operators also take an IfExists suffix that turns "key absent" into a match instead of a mismatch.
The multivalue trap: when a context key carries multiple values (like aws:TagKeys), you must use set operators. ForAnyValue matches if at least one value matches; ForAllValues matches if every value matches — and, crucially, it matches vacuously when the key is absent or empty. AWS explicitly warns against ForAllValues in an Allow statement for exactly this reason: a request that omits the key sails through. If the intent is "the key must be present and every value approved," pair ForAllValues with a Null check.
Condition keys every security engineer must know
A handful of global condition keys carry most of the security weight on this exam.
aws:PrincipalOrgID— in a resource policy, allows any principal from your AWS Organization and no one else. "Allow access only from accounts in our organization" has this one answer, and it beats maintaining account-ID lists forever.aws:SourceArn/aws:SourceAccount— the confused-deputy defense for service principals. When SNS, S3, or EventBridge invokes your Lambda function or writes to your bucket, the service acts as a deputy; without these conditions, anyone who can point that service at your resource inherits its access. Conditioning the resource policy on the expected source ARN or account pins the deputy to the resource you intended.aws:SourceIpandaws:SourceVpce— network provenance. A bucket policy denying requests unless they arrive via a specific VPC endpoint (aws:SourceVpce) is the standard data-perimeter move; rememberaws:SourceIpsees the caller's public IP and does not match traffic that reaches the service through a VPC endpoint.aws:SecureTransport— deny whenfalseto refuse plaintext HTTP; the canonical encryption-in-transit enforcement for S3.aws:PrincipalTag/aws:ResourceTag— the ABAC pair, matched against each other in the next section.aws:CalledVia— at mention level: constrains requests made through intermediary services such as Athena or CloudFormation, so a permission usable via Athena is not usable directly.
Read every exam condition twice for direction: a Deny with StringNotEquals on aws:PrincipalOrgID and an Allow with StringEquals achieve related but different postures — the deny blocks outsiders even when another statement would allow them.
ABAC versus RBAC, and S3 and KMS special cases
RBAC maps job functions to roles or permission sets; access changes mean editing policies or assignments. ABAC writes one policy that compares attributes at request time — the canonical pattern conditions on "StringEquals": {"aws:ResourceTag/project": "${aws:PrincipalTag/project}"}, so a principal tagged project=phoenix reaches only resources tagged project=phoenix. New project? Tag the people and the resources; no policy edits. ABAC scales where role explosion looms — many teams times many environments — but it demands disciplined tag governance: if users can retag resources or set their own session tags, they can rewrite their own access, so tag-modification APIs must themselves be tightly authorized. RBAC stays the better fit when permissions differ structurally per role rather than by attribute value, when services lack tag-based authorization support, and for coarse multi-account access through Identity Center permission sets. Real designs are hybrids: RBAC for the shape of access, ABAC for its scope.
Two services bend the general rules. S3: modern buckets disable ACLs (bucket-owner-enforced object ownership), making the bucket policy the single authorization surface — and Block Public Access sits above it, overriding any policy or ACL that would grant public access; "the bucket policy allows public read but access fails" means BPA is on. Access points, at concept level, give each consumer its own named endpoint and policy so one busy bucket's policy does not become an unmaintainable wall of statements. KMS: every key has a key policy, and it is the root of trust — unlike other resources, IAM identity policies grant nothing for a key unless the key policy itself enables IAM (the default policy's account-root statement does exactly that). A key whose policy omits that statement ignores your identity policies entirely. Depth on key management belongs to Domain 5.
Least-privilege tooling and the AccessDenied triage tree
Least privilege is a process, and the exam names its tools. IAM Access Analyzer does four jobs: external access findings (continuously flags resource policies granting access outside your zone of trust — the answer to "find buckets shared outside the org"); unused access findings (surfaces roles, keys, and permissions unused over a window); policy generation (drafts a least-privilege policy from the actions CloudTrail actually recorded for a role — the answer to "tighten this over-broad role based on real usage"); and policy validation (lints policies for errors and over-permissiveness before deployment). The IAM policy simulator tests "would this principal be allowed to do X?" without making real calls, and CloudTrail-driven pruning closes the loop: compare granted permissions against exercised ones and cut the difference.
When AccessDenied strikes, triage in order rather than guessing:
- Identify the triple. Which principal, which action, which resource? CloudTrail's event usually names all three — and modern AccessDenied messages often state which policy type denied.
- Hunt explicit denies across every layer: SCPs and RCPs on the account's OU path, the resource policy, boundaries, identity policies. A deny explains "it works in the dev account but not prod" instantly.
- Cross-account? Verify both sides grant. Same-account, verify at least one side does.
- Check the ceilings: a permissions boundary or session policy silently missing the action is the classic "the policy clearly allows it" mystery.
- Check conditions: wrong VPC endpoint in the path, KMS encryption context mismatch, missing MFA or tag context, HTTP instead of HTTPS.
- Decode when offered. Some services return an encoded authorization failure message;
sts:DecodeAuthorizationMessage(itself a permission) reveals the evaluated context.
Then reproduce the decision in the policy simulator before you touch a policy in anger.
Tip. This task is tested almost entirely through evaluation-chain scenarios: expect stems that hand you an SCP, a bucket policy, an identity policy, and a boundary, then ask why a request failed or succeeded. "Works in the same account but not cross-account" points to a missing resource-policy grant; "cap what delegated admins can grant" points to permissions boundaries; "allow only principals from our organization" points to aws:PrincipalOrgID; and a service acting on your behalf points to aws:SourceArn/aws:SourceAccount confused-deputy conditions. Remember that SCPs, RCPs, boundaries, and session policies never grant — any option saying otherwise is a distractor — and that explicit deny beats every allow.
- Evaluation order: explicit deny first, then SCPs, RCPs, resource-based policies, identity-based policies, permissions boundaries, session policies — with implicit deny for anything never allowed.
- Only identity-based and resource-based policies grant access; SCPs, RCPs, boundaries, and session policies are ceilings that can only take away.
- Same-account access needs an allow from either the identity policy or the resource policy; cross-account access needs both sides — 'works same-account but not cross-account' means a side is missing its grant.
- Permissions boundaries are the delegated-admin control: condition iam:CreateRole on the boundary's presence and teams can self-serve roles that never exceed the ceiling.
- aws:PrincipalOrgID restricts a resource to your organization; aws:SourceArn and aws:SourceAccount pin service principals and defeat the confused deputy.
- ForAllValues matches vacuously when the key is absent — never use it alone in an Allow; pair it with a Null presence check.
- ABAC matches aws:PrincipalTag against aws:ResourceTag so access follows tags instead of policy edits, but only with strict control over who can tag.
- KMS key policies are the root of trust — IAM policies are powerless for a key unless its key policy enables them; S3 Block Public Access overrides any policy that would grant public access.
Frequently asked questions
What is the IAM policy evaluation order?
AWS first checks every applicable policy for an explicit deny, which ends evaluation immediately. The request must then be allowed by the organization's SCPs and RCPs where they apply, and by at least one granting policy — resource-based or identity-based — and must also fall inside the permissions boundary and session policy if either exists. Anything not explicitly allowed is implicitly denied.
Does an explicit deny always override an allow in AWS?
Yes. An explicit deny in any applicable policy — identity-based, resource-based, SCP, RCP, permissions boundary, or session policy — overrides every allow, with no exceptions. That is what makes deny statements the right tool for guardrails: a deny with a condition, such as denying requests where aws:SecureTransport is false, holds no matter what other policies grant.
Why does cross-account access fail when the same policy works within one account?
Within one account, an allow from either the identity-based policy or the resource-based policy is sufficient. Across accounts, both are required: the resource owner must grant the caller (or its account) in a resource-based policy, and the caller's own identity policy must allow the action. If access works same-account but fails cross-account, one side — usually the resource policy — is missing its grant.
What is a permissions boundary and when should you use one?
A permissions boundary is a managed policy attached to a user or role that sets the maximum permissions that identity can have — its effective access is the intersection of the boundary and its identity policies, and the boundary itself grants nothing. Use it to delegate IAM administration safely: require that every role a team creates carries an approved boundary, and nothing they grant can exceed the ceiling.
What is the difference between ABAC and RBAC in AWS?
RBAC grants access through roles or permission sets mapped to job functions, so changing access means changing policies or assignments. ABAC grants access through attributes — typically a condition matching aws:PrincipalTag against aws:ResourceTag — so one policy scales across projects and new access is granted by tagging. ABAC suits large, dynamic environments with strong tag governance; RBAC suits structurally distinct job functions and services without tag-based authorization.
How do I troubleshoot an AccessDenied error in AWS?
Pin down the exact principal, action, and resource from the CloudTrail event, then hunt explicit denies across SCPs, RCPs, resource policies, and boundaries. Confirm a grant exists on both sides for cross-account calls, check that permissions boundaries and session policies include the action, and inspect condition mismatches such as VPC endpoint, encryption context, or missing HTTPS. Use sts:DecodeAuthorizationMessage on encoded failures and confirm your fix in the IAM policy simulator.
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.