Policy evaluation logic
How AWS combines identity, resource, boundary, session, SCP and RCP policies into one allow or deny, how cross-account requests differ, and the condition keys and operators the exam leans on.
Exam tasks: 4.2 (Skills 4.2.1 authorization controls and resource policies for cross-account access, 4.2.3 interpret and implement least-privilege policies, 4.2.4 analyze authorization failures)
The decision: given every policy that touches this request, is there an explicit deny, and if not, does each layer that must allow it actually allow it?
Policy types
| Type | Attached to | Grants? | Typical use |
|---|---|---|---|
| Identity-based (managed or inline) | User, group, role | Yes | What a principal may do |
| Resource-based | Bucket, key, queue, topic, secret, role trust policy | Yes, and names a Principal | Who may use this resource, including other accounts |
| Permissions boundary | User or role (one managed policy) | No, caps identity policies | Delegated admins, developer-created roles |
| Session policy | Passed when a session is created | No, caps the session | Narrowing a broad role for one task |
| SCP | Org root, OU, account | No, caps principals in member accounts | Org-wide guardrails on what people and roles can do |
| RCP | Org root, OU, account | No, caps resources in member accounts | Org-wide guardrails on who can reach your resources |
| VPC endpoint policy | Interface or gateway endpoint | No, caps traffic through the endpoint | Only company buckets through this endpoint |
| ACLs (S3, legacy) | Bucket or object | Yes, cross-account only | Avoid. Disable with Object Ownership |
Every request starts as an implicit deny. It needs at least one allow, and no explicit deny, in each layer that applies.
The evaluation flow
- Explicit deny is checked across everything first. A deny in an SCP, RCP, bucket policy, boundary or session policy ends it. No allow anywhere can override it.
- Guardrails need an allow too. SCPs and RCPs start with the AWS managed
FullAWSAccessandRCPFullAWSAccesspolicies. If you detachFullAWSAccesswithout adding your own allows, member accounts can do nothing.RCPFullAWSAccesscan't be detached. - The same-account shortcut. If a bucket policy (or another resource policy) in the same account grants access directly to the user ARN or role session ARN, an implicit deny from the identity policy, boundary or session policy doesn't stop it. If it grants to the role ARN, the boundary and session policy still limit it.
- Role trust policies and KMS key policies are exceptions. They must allow the principal explicitly, even in
the same account. An identity policy with
kms:*does nothing if the key policy doesn't delegate to IAM.
Exam signal
"The user has AdministratorAccess but still gets AccessDenied" means look at the layers that cap: SCP, RCP, permissions boundary, session policy, VPC endpoint policy, or an explicit deny in the resource policy. The error message usually names the policy type, as covered in least-privilege tooling.
Same account vs cross-account
| Same account | Cross-account | |
|---|---|---|
| Needs an allow in | Identity policy or resource policy | Identity policy and resource policy |
| SCPs from | That account | The caller's account |
| RCPs from | That account | The resource owner's account |
| Alternative | n/a | Assume a role in the resource account, so the request becomes same-account |
- Services without resource policies (most of EC2, for example) can only be reached cross-account by assuming a role in the owning account.
- With a resource policy, the caller keeps its own identity, so CloudTrail in the resource account records the external principal, and the caller doesn't give up its own permissions.
Only fixing one side
A partner account gets AccessDenied on your bucket even though your bucket policy names its role. The partner still needs an identity policy in its own account that allows the action on your bucket ARN, and its SCPs must allow it. Editing your bucket policy again won't help.
Condition keys that decide exam questions
| Key | What it checks | Classic use |
|---|---|---|
aws:PrincipalOrgID | Caller's account is in your organization | Bucket or key policy: allow anyone in the org, no account list |
aws:PrincipalOrgPaths | Caller's OU path | Only accounts under the prod OU |
aws:ResourceOrgID | Resource belongs to your org | Identity or endpoint policy: company buckets only |
aws:SourceVpce / aws:SourceVpc | Request came through this endpoint or VPC | Bucket reachable only from inside the network |
aws:SourceIp | Public IP of the caller | Office egress range. Not populated through VPC endpoints; use aws:VpcSourceIp there |
aws:MultiFactorAuthPresent | Session was created with MFA | Deny sensitive actions without MFA |
aws:MultiFactorAuthAge | Seconds since MFA | Require recent MFA for deletes |
aws:SecureTransport | Request used TLS | Deny HTTP on buckets and queues |
aws:RequestedRegion | Target Region | Region restriction SCPs |
aws:PrincipalTag/..., aws:ResourceTag/..., aws:RequestTag/..., aws:TagKeys | Tags on caller, resource and request | ABAC |
aws:SourceArn / aws:SourceAccount | Which resource made a service call on your behalf | Confused deputy protection in resource policies |
aws:PrincipalIsAWSService, aws:ViaAWSService, aws:CalledVia | The call came from, or through, an AWS service | Exempt service calls from network-perimeter denies |
Operators and multi-value logic
- Within a condition key, values are OR. Across keys and across operators, it's AND.
StringEqualsvsStringLike: onlyLikehonours*and?. The same goes forArnEqualsvsArnLike.IpAddress/NotIpAddresstake CIDRs.Booltakes"true"or"false"as strings....IfExistsmakes the condition match when the key is absent.Nulltests whether a key is present at all.ForAnyValue:andForAllValues:are for multi-valued keys such asaws:TagKeys.ForAllValuesreturns true when the key is missing, so pair it with aNullcheck if absence must fail.
The MFA deny done right
Long-term access keys never carry aws:MultiFactorAuthPresent. A deny with Bool false misses them, because the
key is absent. BoolIfExists catches both cases:
{
"Sid": "DenyDeletesWithoutMfa",
"Effect": "Deny",
"Action": ["s3:DeleteObject", "s3:DeleteBucket"],
"Resource": "*",
"Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" } }
}A network perimeter on a bucket
{
"Sid": "OnlyFromOurEndpointOrServices",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::lumen-ledger", "arn:aws:s3:::lumen-ledger/*"],
"Condition": {
"StringNotEqualsIfExists": { "aws:SourceVpce": "vpce-0a1b2c3d4e5f60718" },
"BoolIfExists": { "aws:PrincipalIsAWSService": "false" }
}
}Both conditions must be true for the deny to apply: the request didn't come through the endpoint and it isn't an AWS service principal (such as CloudTrail delivering logs).
aws:SourceIp behind an endpoint
A bucket policy that allows only aws:SourceIp 203.0.113.0/24 breaks as soon as instances reach S3 through a
gateway endpoint, because requests then carry private addresses and no public source IP. Use aws:SourceVpce or
aws:SourceVpc for traffic from your VPCs.
NotAction, NotResource and NotPrincipal
Allow+NotActiongrants every action except those listed, including services launched next year. It's almost never least privilege.Deny+NotActionwith a condition is the useful form: "deny everything except IAM and STS unless MFA is present", or "deny everything outside eu-west-1 except global services".NotResourcein an allow means "every resource except these". Same risk as above.NotPrincipalwithDenyis fragile: you must list the role and the assumed-role session ARN and the account, or you lock out the principal you meant to exempt. Prefer"Principal": "*"with anaws:PrincipalArncondition (ArnNotLike), which matches every session of the role.
Exam signal
If an answer uses NotPrincipal and another uses Principal: * with a StringNotEquals or ArnNotLike
condition on aws:PrincipalArn or aws:PrincipalOrgID, pick the condition version.
Scenarios
Cross-account access needs an allow on both sides, and the error says the caller's identity policy is the missing piece. An RCP never grants anything. An ACL grant is legacy and still wouldn't satisfy the caller-side requirement. Naming the account root only delegates the decision to 3333's IAM policies, which still don't allow the action.
With a plain Bool operator, a missing key makes the condition false, so the deny doesn't apply to access-key requests. BoolIfExists treats the missing key as a match. Explicit deny is always evaluated before any allow, and Deny with NotAction is a valid and common pattern. MultiFactorAuthAge is also missing on those requests, so it has the same problem.
aws:PrincipalOrgID checks the caller's organization, so new accounts are covered automatically. aws:SourceAccount describes the resource that made a service-to-service call, not the caller, and listing accounts is what the company wants to avoid. aws:ResourceOrgID describes the resource being accessed, which is always the company's key. An endpoint condition restricts the network path, not who is calling.
Further reading
Application identity and authorization
Amazon Cognito user pools and identity pools, guest access and token types, API Gateway authorizers, Amazon Verified Permissions with Cedar, and choosing RBAC or ABAC for application users.
Permissions boundaries and delegation
Using permissions boundaries to let teams create their own roles without escalating privilege, controlling iam:PassRole, and how service-linked roles fit in.