Asterrr's Handbook

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

TypeAttached toGrants?Typical use
Identity-based (managed or inline)User, group, roleYesWhat a principal may do
Resource-basedBucket, key, queue, topic, secret, role trust policyYes, and names a PrincipalWho may use this resource, including other accounts
Permissions boundaryUser or role (one managed policy)No, caps identity policiesDelegated admins, developer-created roles
Session policyPassed when a session is createdNo, caps the sessionNarrowing a broad role for one task
SCPOrg root, OU, accountNo, caps principals in member accountsOrg-wide guardrails on what people and roles can do
RCPOrg root, OU, accountNo, caps resources in member accountsOrg-wide guardrails on who can reach your resources
VPC endpoint policyInterface or gateway endpointNo, caps traffic through the endpointOnly company buckets through this endpoint
ACLs (S3, legacy)Bucket or objectYes, cross-account onlyAvoid. 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 FullAWSAccess and RCPFullAWSAccess policies. If you detach FullAWSAccess without adding your own allows, member accounts can do nothing. RCPFullAWSAccess can'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 accountCross-account
Needs an allow inIdentity policy or resource policyIdentity policy and resource policy
SCPs fromThat accountThe caller's account
RCPs fromThat accountThe resource owner's account
Alternativen/aAssume 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

KeyWhat it checksClassic use
aws:PrincipalOrgIDCaller's account is in your organizationBucket or key policy: allow anyone in the org, no account list
aws:PrincipalOrgPathsCaller's OU pathOnly accounts under the prod OU
aws:ResourceOrgIDResource belongs to your orgIdentity or endpoint policy: company buckets only
aws:SourceVpce / aws:SourceVpcRequest came through this endpoint or VPCBucket reachable only from inside the network
aws:SourceIpPublic IP of the callerOffice egress range. Not populated through VPC endpoints; use aws:VpcSourceIp there
aws:MultiFactorAuthPresentSession was created with MFADeny sensitive actions without MFA
aws:MultiFactorAuthAgeSeconds since MFARequire recent MFA for deletes
aws:SecureTransportRequest used TLSDeny HTTP on buckets and queues
aws:RequestedRegionTarget RegionRegion restriction SCPs
aws:PrincipalTag/..., aws:ResourceTag/..., aws:RequestTag/..., aws:TagKeysTags on caller, resource and requestABAC
aws:SourceArn / aws:SourceAccountWhich resource made a service call on your behalfConfused deputy protection in resource policies
aws:PrincipalIsAWSService, aws:ViaAWSService, aws:CalledViaThe call came from, or through, an AWS serviceExempt 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.
  • StringEquals vs StringLike: only Like honours * and ?. The same goes for ArnEquals vs ArnLike.
  • IpAddress / NotIpAddress take CIDRs. Bool takes "true" or "false" as strings.
  • ...IfExists makes the condition match when the key is absent. Null tests whether a key is present at all.
  • ForAnyValue: and ForAllValues: are for multi-valued keys such as aws:TagKeys. ForAllValues returns true when the key is missing, so pair it with a Null check 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 + NotAction grants every action except those listed, including services launched next year. It's almost never least privilege.
  • Deny + NotAction with 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".
  • NotResource in an allow means "every resource except these". Same risk as above.
  • NotPrincipal with Deny is 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 an aws:PrincipalArn condition (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

Scenario
A data team in account 3333 runs a role, orbit-analytics, that must read objects from the bucket tidal-exports in account 4444. The bucket policy in 4444 allows s3:GetObject for the orbit-analytics role ARN. Requests still fail with AccessDenied, and the error says no identity-based policy allows the s3:GetObject action. What fixes it with the least change?
Scenario
A security engineer wants to force MFA for all actions except managing one's own MFA device and calling sts:GetSessionToken. The first draft denies everything with NotAction for those exceptions when aws:MultiFactorAuthPresent is false. Testing shows that CLI calls with a long-term access key and no MFA still succeed. What is the cause?
Scenario
A company wants a KMS key policy that lets any role in any of its 140 accounts use the key for decryption, without listing accounts, and with no access from outside the company. Which condition should the key policy statement use?

Further reading

On this page