20.2
Conditions, Resource Policies, Boundaries and Full Evaluation Logic
Condition keys, S3 bucket vs object ARNs, roles vs resource policies, permission boundaries, and the complete decision flow.
28 min Reading + animations
Tip: tap or hover any dotted-underlined term to see what it means.
By the end of this lesson you will be able to
- Read and write any
Conditionblock: operators, keys, values, how several keys and several values combine, and what happens when a key is missing. - Use the global condition keys the exam asks about (
aws:SourceIp,aws:RequestedRegion,aws:MultiFactorAuthPresent,aws:PrincipalOrgID, tag keys,aws:SecureTransport) and the service keysec2:ResourceTag,s3:prefixandiam:PermissionsBoundary, and know where each one breaks. - Build attribute-based access control (ABAC) with tags, and protect the tags themselves.
- Write S3 policies that pair bucket-level and object-level actions with the right ARN.
- Choose between an IAM role and a resource-based policy for cross-account access, and explain why assuming a role gives up your own permissions.
- Use permissions boundaries to let developers create roles safely, and know how boundaries interact with SCPs, session policies and resource-based policies.
- Evaluate any request by hand, in one account or across two accounts, including RCPs, SCPs, boundaries and session policies, and test a policy before it reaches production.
#Where lesson 2.2 left off
Lesson 2.2 taught the three rules every request obeys: everything starts denied, an explicit Allow lifts the default, and an explicit Deny beats every Allow. The owner’s notes open with the example that proves it: a user in Group A (Allow S3 read and write) and Group B (Deny S3) is denied, because groups have no priority and one matching Deny wins; if Group B merely says nothing about S3, Group A’s Allow works. Lesson 2.2 also listed the six policy types, walked the single-account flow and previewed SCPs and boundaries.
This lesson goes deeper on the four things that turn that simple picture into real exam questions:
- Conditions: the part of a policy that decides when and how a statement applies.
- Resources: S3’s bucket-versus-object trap, and resource-based policies with
aws:PrincipalOrgID. - Roles versus resource-based policies: two ways across an account boundary that behave very differently.
- Limits: permissions boundaries, session policies, and how SCPs and RCPs (whose organization structure lesson 20.1 builds) fit into the complete evaluation flow, in one account and across two.
#Conditions in depth
The notes call the Condition block the most powerful part of a policy, and they are right. Users and resources say who may do what; conditions say when and how: only from the office network, only with MFA, only in one Region, only on resources tagged for your team.
#Anatomy: operator, key, values
Every condition has the same three parts (lesson 2.1 introduced them):
"Condition": {
"StringEquals": { "aws:RequestedRegion": ["eu-west-1", "ap-south-1"] },
"Bool": { "aws:SecureTransport": "true" }
}
- The operator (
StringEquals,Bool) says how to compare. - The key (
aws:RequestedRegion) names a fact in the request context: the bundle of facts AWS collects about every call (who, from where, when, with what credentials, on which resource, with which tags). - The values are what the fact is compared against.
The combining rules fit in one table:
Inside a Condition block | Logic | Example |
|---|---|---|
| Several operators | AND | Region matches and TLS was used |
| Several keys under one operator | AND | "StringEquals": {"aws:RequestedRegion": "eu-west-1", "aws:PrincipalTag/team": "data"} |
| Several values for one key | OR | Region is eu-west-1 or ap-south-1 |
| Several statements | each is evaluated on its own | one Allow statement for the office, another for the VPN |
If you need “A or B” across two different keys (from the office or with MFA), you cannot write it in one condition block: write two statements.
#The operators
| Family | Operators | Typical keys | Watch out for |
|---|---|---|---|
| String | StringEquals, StringNotEquals, StringEqualsIgnoreCase, StringLike (*, ?), StringNotLike | aws:RequestedRegion, aws:PrincipalTag/team, s3:prefix | Case-sensitive unless IgnoreCase; wildcards only with Like |
| Numeric | NumericEquals, NumericLessThan, NumericGreaterThanEquals… | aws:MultiFactorAuthAge, s3:max-keys | Values are written as strings: "3600" |
| Date | DateLessThan, DateGreaterThan… | aws:CurrentTime, aws:TokenIssueTime | ISO 8601 or epoch time |
| Boolean | Bool | aws:SecureTransport, aws:MultiFactorAuthPresent, aws:ViaAWSService | A missing key is not false |
| IP address | IpAddress, NotIpAddress | aws:SourceIp | Public addresses only; missing through VPC endpoints |
| ARN | ArnEquals, ArnLike, ArnNotLike | aws:SourceArn, aws:PrincipalArn | Each ARN part is matched separately |
| Existence | Null | any key | "Null": {"aws:TokenIssueTime": "true"} means “the key is absent” |
| Modifier | …IfExists on any operator except Null | keys that are present only sometimes | A missing key counts as a match |
| Set operators | ForAnyValue:, ForAllValues: before an operator | multi-valued keys: aws:TagKeys, aws:CalledVia, aws:PrincipalOrgPaths | Required for multi-valued keys; never for single-valued ones |
#When the key is missing
This rule explains most surprising condition results, so learn it word for word. From the IAM User Guide:
- A normal operator with a missing key is false.
IpAddresson a request with noaws:SourceIpdoes not match. - A negated operator (
NotIpAddress,StringNotEquals,ArnNotLike…) with a missing key is true. “The address is not the office” is considered true when there is no address at all. - An
…IfExistsoperator with a missing key is true: “check it if it is there, otherwise don’t worry”. Nulltests presence itself:true= absent,false= present.
Combine that with the effect of the statement and you get four behaviours to remember:
| Statement | Key missing | Effect on the request |
|---|---|---|
| Allow + normal operator | condition false | the Allow does not apply: often an implicit deny |
Allow + …IfExists | condition true | the Allow applies |
| Deny + negated operator | condition true | denied, often by surprise |
Deny + normal operator (Bool false) | condition false | the Deny does not apply: the guard is bypassed |
#The notes’ three exam scenarios
The notes give three phrases that should make you think “Condition element”, and the slides show the same keys:
| Exam phrase | Key | Shape of the answer |
|---|---|---|
| “Restrict API calls to the corporate IP range” | aws:SourceIp | Deny with NotIpAddress, or Allow with IpAddress |
| “Force MFA for deleting an S3 bucket” | aws:MultiFactorAuthPresent | Deny s3:DeleteBucket with BoolIfExists false (or Allow with Bool true) |
| “Allow EC2 creation only in eu-west-1” | aws:RequestedRegion | Deny with StringNotEquals the allowed Region, global services excluded (lesson 2.2’s Region guard) |
#aws:SourceIp, VPC endpoints and AWS services
aws:SourceIp holds the public address the request came from. Two common paths do not have one you control:
- Through a VPC endpoint (lesson 22.2): the key is not in the request context at all. With a Deny +
NotIpAddressguard, the negated operator on a missing key is true, so your own private servers are denied. Match them withaws:SourceVpce(the endpoint ID),aws:SourceVpc(the VPC ID) oraws:VpcSourceIp(the private address, always together with one of the other two). - An AWS service acting for you (CloudFormation creating resources, Athena reading S3 with your identity): the target service sees the calling service’s address. The IAM docs say plainly: do not use
aws:SourceIpfor such calls. Requests made this way (forward access sessions) carryaws:ViaAWSService=trueand list the services inaws:CalledVia, so add"Bool": {"aws:ViaAWSService": "false"}to the Deny to skip them. For service principals (a service acting as itself, such as CloudTrail writing logs), the matching key isaws:PrincipalIsAWSService.
The fixed office guard therefore looks like this. Read the three conditions as one AND: deny only if the call is not from the office, and not through our endpoint, and not made by a service on our behalf.
{
"Sid": "DenyOutsideOfficeAndEndpoint",
"Effect": "Deny",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::payroll-example", "arn:aws:s3:::payroll-example/*"],
"Condition": {
"NotIpAddress": { "aws:SourceIp": "203.0.113.0/24" },
"StringNotEquals": { "aws:SourceVpce": "vpce-0a1b2c3d" },
"Bool": { "aws:ViaAWSService": "false" }
}
}
One IP rule, five ways to call S3
The payroll bucket has a guard: deny every request whose aws:SourceIp is not the Motijheel head-office range 203.0.113.0/24. Watch which calls it lets through, and which ones it blocks by surprise.
Scenario
The normal case: a direct call from the office range.
Request context
aws:SourceIp
203.0.113.25
aws:SourceVpce
(missing)
aws:ViaAWSService
false
An accountant at head office downloads a payslip file. The request leaves through the office’s public address 203.0.113.25, so AWS puts that address in the request context as aws:SourceIp.
#Bool versus BoolIfExists: missing is not false
Lesson 2.3 showed the MFA table; here is why it works the way it does. aws:MultiFactorAuthPresent exists only for temporary credentials: the console (which uses temporary credentials behind the scenes), STS sessions, and the temporary credentials a service creates when it reuses your identity. For long-term access keys it is simply missing.
Deny+"Bool": {"aws:MultiFactorAuthPresent": "false"}: a normal operator on a missing key is false, so every script with an access key walks past the guard. This is the trap.Deny+"BoolIfExists": {"aws:MultiFactorAuthPresent": "false"}: missing counts as a match, so access keys are denied too. AWS recommends this one for “nothing without MFA”.Allow+"Bool": {"aws:MultiFactorAuthPresent": "true"}: only sessions that really used MFA.- “Recently” is
NumericLessThanonaws:MultiFactorAuthAge, for example"3600"seconds.
Try every combination in the tester below: pick where a request comes from, and watch which keys are present and how six common statements react.
Condition tester: is the key even there?
Pick where a request comes from. The lab lists the condition keys AWS puts in the request context, then evaluates six common statements against it. Watch the missing keys.
Request context
Allow: Allow only from the office
- IpAddress aws:SourceIp 203.0.113.0/24
203.0.113.25 → true
applies
A positive operator with a missing key is false, so the Allow simply does not apply.
Deny: Deny outside the office (naive)
- NotIpAddress aws:SourceIp 203.0.113.0/24
203.0.113.25 → false
does not apply
A negated operator with a missing key is true, so this Deny also hits VPC endpoint traffic and services acting for you.
Deny: Deny outside the office (fixed)
- NotIpAddress aws:SourceIp 203.0.113.0/24
203.0.113.25 → false - StringNotEquals aws:SourceVpce vpce-0a1b2c3d
key missing → negated operator is true - Bool aws:ViaAWSService false
false → true
does not apply
Three conditions in one statement combine with AND: the Deny needs “not the office” and “not our endpoint” and “not a service on your behalf”.
Deny: Deny without MFA, Bool (trap)
- Bool aws:MultiFactorAuthPresent false
true → false
does not apply
Long-term access keys have no MFA key at all, so Bool false never matches them and they walk straight through.
Deny: Deny without MFA, BoolIfExists
- BoolIfExists aws:MultiFactorAuthPresent false
true → false
does not apply
IfExists turns a missing key into a match, so access keys are denied too. This is the guard AWS recommends.
Allow: Allow only with MFA, Bool
- Bool aws:MultiFactorAuthPresent true
true → true
applies
Only sessions that really used MFA get this Allow; access keys and roles without MFA do not.
Signed in to the console at head office with a passkey. The console uses temporary credentials, so the MFA key is present and true.
#Multi-valued keys: ForAnyValue and ForAllValues
Most keys hold one value per request (one source IP, one Region). A few hold a set: aws:TagKeys (all tag keys in a tagging request), aws:CalledVia, aws:PrincipalOrgPaths. For those you must add a set operator:
ForAnyValue:StringEquals: true if at least one value in the request is in your list. False if the key is missing.ForAllValues:StringEquals: true if every value in the request is in your list, and also true if the key is missing or empty.
That last point is a real security trap: Allow + ForAllValues:StringEquals aws:TagKeys ["team", "env"] meant “you may only use these tag keys”, but a request with no tags matches too. AWS’s fix is to add "Null": {"aws:TagKeys": "false"} (the key must be present). The reverse mistake also exists: using a set operator on a single-valued key such as aws:ResourceTag/team can make a policy more permissive than intended.
#Service-specific keys
Every service publishes its own keys, prefixed with the service name. Four that appear on the exam and in real designs:
| Key | What it holds | Typical use |
|---|---|---|
ec2:ResourceTag/<key> | A tag on the EC2 resource being acted on | “Start and stop only instances tagged env=dev” (the slides’ example) |
s3:prefix | The folder prefix in a ListBucket request | “List only home/<your name>/” |
iam:PermissionsBoundary | The boundary ARN in a request that creates or changes a user or role | Force every new role to carry the approved boundary (below) |
iam:PassedToService | The service a role is being passed to | Allow iam:PassRole only to lambda.amazonaws.com |
The last one completes lesson 2.4’s iam:PassRole story: limit iam:PassRole to specific role ARNs, and with iam:PassedToService to the one service that should receive them, so nobody can hand an administrator role to a function they control.
#Policy variables
A value can contain a variable that AWS fills in from the request context: ${aws:username}, ${aws:PrincipalTag/team}, ${aws:PrincipalAccount}. One policy then behaves differently for each caller. You will see variables in the S3 home-folder pattern and in ABAC below. Variables need the 2012-10-17 policy version (the old 2008-10-17 treats ${…} as plain text).
#ABAC: permissions from tags
Attribute-based access control grants access by comparing tags instead of listing resources. Four keys make it work:
| Key | Holds | Role in ABAC |
|---|---|---|
aws:PrincipalTag/<key> | A tag on the calling user or role (or a session tag from the identity provider) | Who the caller is: team, project, cost centre |
aws:ResourceTag/<key> (or ec2:ResourceTag/<key>) | A tag on the existing resource | What the resource belongs to |
aws:RequestTag/<key> | A tag being set in this request | Force new resources to be created with the right tag |
aws:TagKeys | All tag keys in this request (multi-valued) | Control which tag keys may be added or removed |
One statement serves every team:
{
"Effect": "Allow",
"Action": ["ec2:StartInstances", "ec2:StopInstances"],
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringEquals": { "aws:ResourceTag/team": "${aws:PrincipalTag/team}" }
}
}
Why ABAC over role-based access control (RBAC): with RBAC each team needs its own policy listing its resources, and every new project means editing policies. With ABAC the payments team’s new instances are covered the moment they are tagged team=payments, and a new team needs no new policy at all. The price: tags become permissions, so changing tags must be controlled as tightly as changing policies (deny CreateTags/DeleteTags on the team key for everyone but the platform team), and new resources must be forced to carry the tag with aws:RequestTag. Lesson 20.3 shows how IAM Identity Center passes attributes from your identity provider as session tags, so a person’s department in the company directory becomes their aws:PrincipalTag.
ABAC: tags decide who touches what
A telecom has one policy for all developers: you may start and stop an instance only if its team tag equals your own team tag. No list of instance IDs anywhere.
Scenario
aws:PrincipalTag/team equals ec2:ResourceTag/team.
Rafi stops a payments instance at night to save money. His IAM role carries the tag team=payments.
#IAM for S3: bucket ARN versus object ARN
S3 permissions act on two kinds of resource, and the slides make the point with one bucket:
| Level | Actions | Resource ARN |
|---|---|---|
| Bucket | s3:ListBucket, s3:GetBucketLocation, s3:GetBucketPolicy, s3:PutBucketVersioning… | arn:aws:s3:::reports-example |
| Object | s3:GetObject, s3:PutObject, s3:DeleteObject, s3:GetObjectTagging… | arn:aws:s3:::reports-example/* |
A policy with only reports-example/* lets you read files whose names you already know, but ListBucket fails with AccessDenied, because the pattern bucket/* never matches the bucket itself. Granting s3:* does not help; adding the bucket ARN does:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListTheBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::reports-example"
},
{
"Sid": "ReadAndWriteObjects",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::reports-example/*"
}
]
}
To give each user a folder in a shared bucket, combine a ListBucket statement with a condition on s3:prefix and object statements on home/${aws:username}/*. The s3:prefix key exists only in list requests, which is exactly why it belongs on the bucket-level statement.
Bucket ARN or object ARN?
S3 actions act on two different resources: the bucket itself and the objects inside it. Watch what happens when a policy names only one of them.
Scenario
The policy allows s3:* on arn:aws:s3:::reports-example/* and nothing else.
Reading one known file. GetObject acts on the object ARN arn:aws:s3:::reports-example/q3.csv, which matches the pattern bucket/*.
#Resource-based policies and aws:PrincipalOrgID
A resource-based policy lives on the resource and names a Principal (lesson 2.1). Listing partner accounts one by one in a bucket policy breaks the day an account is added. The slides’ answer, and the exam’s, is aws:PrincipalOrgID: allow everyone in your AWS Organization, and nobody else.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "OrgMembersRead",
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::finance-data-example", "arn:aws:s3:::finance-data-example/*"],
"Condition": { "StringEquals": { "aws:PrincipalOrgID": "o-a1b2c3d4e5" } }
}]
}
"Principal": "*" looks frightening, but the condition narrows it to principals whose account is in o-a1b2c3d4e5; a user outside the organization has no such key and is not allowed. New member accounts are included automatically, removed ones lose access automatically (lesson 10.2 used the same key for bucket policies). The caller’s own identity policy must still allow the call, as in any cross-account request.
Related keys, each for a different question:
| Key | Asks | Typical use |
|---|---|---|
aws:PrincipalOrgID | Is the caller in this organization? | Bucket, queue or key policy open to the whole organization |
aws:PrincipalOrgPaths | Is the caller under this OU path? (multi-valued) | Open a bucket only to the Prod OU: ForAnyValue:StringLike o-a1b2c3d4e5/r-ab12/ou-ab12-cdef3456/* |
aws:ResourceOrgID | Is the resource in this organization? | SCP or identity policy: “our people may only write to our organization’s buckets” (stops data leaving to a personal bucket) |
aws:SourceArn, aws:SourceAccount | Which resource or account made an AWS service act? | Stop the confused deputy when a service principal writes to your resource |
The last row is the service-principal version of lesson 2.4’s external ID. When a queue policy allows sns.amazonaws.com to send messages, any SNS topic in any account could use that permission. Add "ArnEquals": {"aws:SourceArn": "arn:aws:sns:ap-south-1:111122223333:orders-topic"} (or aws:SourceAccount) and only your topic can.
#IAM roles versus resource-based policies
Both get a principal across an account boundary, as the slides show: account A’s user either assumes a role in account B (the role acts as a proxy), or B attaches a resource-based policy that names the user directly. The notes explain the difference that matters.
#Assuming a role swaps your identity
When a user, application or service assumes a role, it gives up its original permissions for that session and holds only the role’s permissions. Calls are made as assumed-role/<role>/<session>, not as the original user.
#A resource-based policy keeps your identity
With a resource-based policy the caller stays itself, with all its own permissions, and gains access to that one resource.
The slides’ example makes the difference concrete: a user in account A must scan a DynamoDB table in A and write the dump to an S3 bucket in B.
- With a role in B, the session can write to B’s bucket but can no longer read A’s table: the role in B has no permission there and the user’s own permissions are set aside. The job needs two identities and two steps.
- With a bucket policy in B naming the user, the user scans A’s table and writes to B’s bucket in one session, as themself.
Assume a role, or be on the guest list?
Nadia in account A must scan a DynamoDB table in account A and write the dump to a bucket in account B. Two ways to give cross-account access behave very differently.
Scenario
The role is a proxy: Nadia gives up her own permissions while she wears it.
B’s export role trusts account A, and Nadia’s policy allows sts:AssumeRole on it. She gets temporary credentials for the role (lesson 2.4).
The reverse edge case is just as common: Nadia assumes a role in account B to read a bucket there and copy the files back to her own bucket in A. The copy fails, because in the role she is no longer Nadia; it works only if A’s bucket policy trusts B’s role. Since 2024 DynamoDB also supports resource-based policies, so the slide’s scan could be fixed the same way, by letting the table’s policy allow B’s role.
#Which services support resource-based policies
Only some do, so the choice is not always yours:
| Resource-based policy available | Examples of when it is the natural choice |
|---|---|
| S3 buckets (bucket policy), S3 access points | Share data across accounts or with the whole organization |
| SQS queues, SNS topics, EventBridge event buses | Let another account or a service send messages or events |
| Lambda functions (function policy) | Let S3, SNS, API Gateway or another account invoke the function |
| KMS keys (key policy) | Every key has one; it must allow access (lesson 21.2) |
| Secrets Manager secrets, ECR repositories, Backup vaults, DynamoDB tables and streams, API Gateway REST APIs, OpenSearch domains, Glacier vaults | Cross-account access to that one resource |
| IAM roles (the trust policy) | Says who may assume the role |
Services without one (most EC2 resources, RDS instances, EBS volumes) are shared across accounts with roles, or with AWS Resource Access Manager for networking resources (lesson 20.1). The same split decides how EventBridge gets permission to reach a rule’s target: Lambda, SNS, SQS and the like through their resource-based policies, others through an IAM role, as lesson 19.5 explains.
#Which one to pick
| Situation | Pick | Why |
|---|---|---|
| “Access another account’s bucket without losing current permissions” (the notes’ exam phrase) | Resource-based policy | The caller keeps its identity and its own permissions |
| “Provide credentials to an EC2 instance dynamically” (the notes’ other phrase) | IAM role (instance profile) | Services get temporary credentials only through roles |
| A third-party vendor needs to work in your account | IAM role with an external ID | Short-lived credentials, audit trail, easy to revoke |
| Access to many resources or services in the other account | IAM role | One trust, one permission set; listing the user in dozens of resource policies does not scale |
| The resource type has no resource-based policy | IAM role | No other way |
| The whole organization must read one bucket | Resource-based policy with aws:PrincipalOrgID | One statement, no per-account roles |
#Cross-account means both sides must allow
Inside one account, an Allow in the identity-based policy or in the resource-based policy is enough (union). Across accounts, AWS runs two evaluations, one in each account, and the request is allowed only if both return Allow:
- The caller’s account (the trusted account) checks its identity-based policies and everything that limits them: SCPs, permissions boundary, session policy.
- The resource’s account (the trusting account) checks the resource-based policy and everything that limits it: RCPs.
So B’s bucket policy naming Nadia is useless if Nadia’s own policy in A does not allow s3:PutObject on that bucket ARN, and a generous identity policy in A is useless if B’s bucket policy does not name A. Two resource-based policies must allow even inside one account, because they decide who may use the resource at all: the role trust policy and the KMS key policy.
#Permissions boundaries in depth
A permissions boundary is a managed policy (AWS managed or customer managed) attached to an IAM user or role as its maximum permissions. It never grants anything itself; the slides draw it as “boundary + IAM policy”, where only the overlap is effective, and with no permission policy at all the result is no permissions.
- Boundaries apply to users and roles only, not to groups (the notes imply it; the slides state it).
- Effective permissions = identity-based policies ∩ boundary (and, when present, ∩ SCPs, ∩ RCPs, ∩ session policy).
- An explicit Deny in the boundary denies, like any Deny.
The notes’ example: a user’s boundary allows only S3 read and EC2 read, while their identity policy is AdministratorAccess. The user can do only S3 read and EC2 read: AdministratorAccess offers everything, the boundary keeps only those two.
#The delegation pattern
The notes give the exam’s favourite scenario: “Developers should be able to create IAM roles, but must not be able to give themselves administrator permissions through those roles.” The answer is to force a boundary on everything they create. The slides list the same use cases: delegate to non-administrators within their boundary, let developers manage their own permissions without escalating, and restrict one specific user without an account-wide SCP.
The developers’ own policy has four parts:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CreateRolesOnlyWithTheBoundary",
"Effect": "Allow",
"Action": ["iam:CreateRole", "iam:PutRolePermissionsBoundary",
"iam:AttachRolePolicy", "iam:DetachRolePolicy", "iam:PutRolePolicy"],
"Resource": "arn:aws:iam::111122223333:role/app/*",
"Condition": {
"StringEquals": { "iam:PermissionsBoundary": "arn:aws:iam::111122223333:policy/DevBoundary" }
}
},
{
"Sid": "PassOnlyTheirRolesToLambda",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::111122223333:role/app/*",
"Condition": { "StringEquals": { "iam:PassedToService": "lambda.amazonaws.com" } }
},
{
"Sid": "NeverRemoveABoundary",
"Effect": "Deny",
"Action": ["iam:DeleteRolePermissionsBoundary", "iam:DeleteUserPermissionsBoundary"],
"Resource": "*"
},
{
"Sid": "NeverEditTheBoundaryPolicy",
"Effect": "Deny",
"Action": ["iam:CreatePolicyVersion", "iam:SetDefaultPolicyVersion", "iam:DeletePolicy"],
"Resource": "arn:aws:iam::111122223333:policy/DevBoundary"
}
]
}
- They may create and edit roles under the path
app/only when the request names DevBoundary as the boundary. ACreateRolewithout it has noiam:PermissionsBoundarykey, the condition is false, and the request is implicitly denied. - They may pass those roles only to Lambda.
- They can never take a boundary off.
- They can never edit the boundary policy itself, or they would simply widen it.
Usually the developers carry the same boundary themselves, so they cannot use their IAM rights to do more than the roles they create could.
Developers create roles, but only inside the fence
Tanvir may create IAM roles for his Lambda functions without waiting for the security team, but every role must carry the approved boundary DevBoundary, which allows only S3, DynamoDB and CloudWatch Logs.
Scenario
The allowed path.
Tanvir runs aws iam create-role with --permissions-boundary pointing at DevBoundary.
#Boundary, SCP or session policy?
All three only limit. They differ in what they are attached to and who sets them:
| Permissions boundary | SCP | Session policy | |
|---|---|---|---|
| Attached to | One IAM user or role | Organization root, OU or account | One session, passed at AssumeRole/federation time |
| Set by | Whoever creates or administers that user or role | The organization’s administrators (lesson 20.1) | Whoever creates the session |
| Scope | That one identity, always | Every principal in the account (not the management account) | Only that session, until it expires |
| Typical use | Delegation; restrict one developer | Guard rails for whole accounts | A broker handing out narrower credentials per job |
The slides’ point “boundaries can be used in combination with SCPs” is just intersection: an action must be allowed by the identity policy and the boundary and the SCP.
#Boundaries and resource-based policies: the nuance
The IAM User Guide adds a detail many courses get wrong. Within the same account:
- A resource-based policy that grants access to an IAM user ARN is not limited by an implicit deny in that user’s identity policies or boundary. The bucket’s Allow stands on its own.
- A resource-based policy that grants access to an IAM role ARN is limited by an implicit deny in the role’s boundary and session policy.
- A resource-based policy that names a role session ARN directly grants to that session and is not limited by them.
- An explicit Deny in the boundary always wins.
Across accounts this nuance disappears: the caller’s account must allow the request through identity ∩ boundary ∩ session, whatever the resource policy says.
#Session policies
A session policy is a policy passed when a session is created: aws sts assume-role --policy (inline JSON) or --policy-arns (up to 10 managed policies), and the same parameters on AssumeRoleWithSAML, AssumeRoleWithWebIdentity and GetFederationToken. The session gets the intersection of the role’s (or user’s) policies and the session policy. It can never add anything.
Why use one: a single broad role serves many jobs, and each job receives credentials cut down to what it needs. A data-export service assumes ExportRole (read all buckets) but passes a session policy allowing only s3:GetObject on one customer’s prefix, so a bug in one job cannot read another customer’s files.
#The complete evaluation flow
#In one account
Lesson 2.2 walked the documented order step by step; in short (IAM User Guide, checked November 2026):
- Any explicit Deny in any policy type → Deny.
- RCPs must allow (in practice
RCPFullAWSAccessis always attached, so they stop requests only by Deny). - SCPs must allow (not for the management account).
- Resource-based policy allows? For an IAM user ARN that is enough → Allow. For a role ARN, continue but remember the Allow.
- Identity-based policies must allow (unless step 4 allowed).
- Permissions boundary must allow, if set.
- Session policy must allow, if it is a session with one → Allow.
#Across two accounts
For a cross-account request AWS runs the flow twice:
| Evaluation | Policies checked | Typical stop |
|---|---|---|
| Caller’s account (A) | explicit Denies in A’s policies, A’s SCPs, the caller’s identity-based policies, boundary, session policy | “no identity-based policy allows”; an SCP in A that keeps data inside A’s organization (aws:ResourceOrgID) |
| Resource’s account (B) | explicit Denies in B’s policies, B’s RCPs, the resource-based policy | “no resource-based policy allows”; an RCP in B that rejects principals outside B’s organization |
The request is allowed only if both say Allow. B’s SCPs play no part (they limit B’s principals, not A’s), and A’s RCPs play no part (they limit A’s resources).
Across accounts: two checks, both must pass
A partner company’s analytics role in account 444455556666 reads a shared bucket in your account 111122223333. AWS checks the request on the caller’s side and on the resource’s side.
Scenario
The working setup.
First evaluation, in the caller’s account: any Deny? Do its SCPs allow S3? Does the role’s identity policy allow GetObject on the bucket ARN? Does its boundary or session policy allow it?
#The evaluation lab
Set every layer yourself. Put the bucket in the same account or in another account, choose a user or a role session, require MFA in the identity policy, and switch SCPs, RCPs, the bucket policy, the boundary and the session policy between Allow, silent and Deny. The lab shows each evaluation’s gates and where the request stops.
Evaluation lab: one account or two
A principal asks for s3:GetObject on a bucket. Put the bucket in the same account or in another one, set every policy layer, and see where the request stops. Across accounts the lab runs two evaluations, one per account.
Account A: the caller’s side
1. Any Deny?
no Deny
2. SCP
allows
3. Identity-based
an Allow matches
4. Boundary
not set
5. Session
Allow
Account B: the bucket’s side
1. Any Deny?
no Deny
2. RCP
full access
3. Bucket policy
names the caller: Allow
Decision: Allowed
A partner’s analytics role reads your bucket. Its own policy allows the bucket ARN, your bucket policy names the role: both evaluations allow. Both evaluations returned Allow, so the request is allowed. If either had failed, the request would be denied.
#Worked example: the slides’ policy
The last slide asks three questions about one policy. Here is a policy of the same kind:
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Deny", "Action": "sqs:*", "Resource": "*" },
{ "Effect": "Allow", "Action": "sqs:DeleteQueue",
"Resource": "arn:aws:sqs:ap-south-1:111122223333:orders-queue" }
]
}
sqs:CreateQueue? The Deny onsqs:*matches: explicit deny.sqs:DeleteQueueonorders-queue? The Allow matches, but so does the Deny. A Deny is not beaten by being less specific: explicit deny.ec2:DescribeInstances? The Deny is only about SQS and nothing allows EC2: implicit deny.
Self-checkQuestion
An IAM user has this policy and no other: Deny sqs:* on *, and Allow sqs:DeleteQueue on one queue ARN. What happens when the user calls sqs:DeleteQueue on that queue?
#Testing a policy before it hurts
Conditions and boundaries are easy to get subtly wrong. Test them before attaching them:
- IAM policy simulator (
aws iam simulate-custom-policyfor a draft,simulate-principal-policyfor an existing user or role). Pass condition keys with--context-entriesto test the office IP, a home IP, or MFA true/false. It also accepts--permissions-boundary-policy-input-listand--resource-policy. Organization policies (SCPs, RCPs) are best tested in a sandbox account. - IAM Access Analyzer policy validation (
aws accessanalyzer validate-policy): more than 100 checks for errors (an invalid ARN likearn:awn), security warnings (iam:PassRolewith*,AllowwithNotAction), and suggestions, as you write. - Access Analyzer custom policy checks:
check-no-new-access(does a new version grant anything a reference policy does not?),check-access-not-granted(does it allowiam:CreateUserors3:DeleteBucket?),check-no-public-access(does a bucket or key policy make the resource public?). Put them in the CI pipeline so a risky change fails the build. Access Analyzer’s other features (external and unused access) are in lesson 2.4.
#What goes wrong
| Symptom | Cause | Fix |
|---|---|---|
| App on EC2 denied by an office-IP guard | Request came through a VPC endpoint; aws:SourceIp missing, NotIpAddress true | Add aws:SourceVpce / aws:SourceVpc to the Deny |
| CloudFormation or Athena fails under an IP guard | The service calls with its own address | Add "Bool": {"aws:ViaAWSService": "false"} |
| CLI users ignore the MFA rule | Deny + Bool false; key missing for access keys | Use BoolIfExists |
| A tag restriction allows untagged requests | ForAllValues on a missing key is true | Add "Null": {"aws:TagKeys": "false"} |
ListBucket → AccessDenied with s3:* | Resource is bucket/* only | Add the bucket ARN |
| Cross-account call denied though the bucket policy allows it | Caller’s identity policy is silent | Allow the bucket ARN in the caller’s account too |
| Assumed role cannot write back to its own account | A role session has only the role’s permissions | Let the home bucket’s policy trust the role, or use a resource-based policy instead |
| Developer cannot create a role | Missing or wrong --permissions-boundary | Pass the approved boundary ARN |
| An administrator still cannot do something | Boundary, SCP or session policy | Check every limit, not just the identity policy |
| Queue receives messages from a stranger’s SNS topic | Service principal allowed without aws:SourceArn | Add aws:SourceArn / aws:SourceAccount |
#How it is used in system design and on the exam
- Network perimeter: IP and VPC endpoint conditions on sensitive buckets, written as Deny guards that understand VPC endpoints and service calls.
- Identity perimeter:
aws:PrincipalOrgIDon resource policies and an RCP that rejects outsiders; resource perimeter:aws:ResourceOrgIDin an SCP so insiders cannot copy data to outside buckets. Together they are AWS’s “data perimeter” pattern. - Scale with ABAC: tag-based policies for many teams and projects, with tag changes locked down.
- Safe delegation: boundaries plus
iam:PermissionsBoundaryand narrowiam:PassRole, so teams move fast without a ticket queue. - Least privilege per job: session policies on a shared role.
#Try it yourself
With the CLI configured (lesson 1.3), try these against a test account. The simulator commands change nothing.
# 1. Test the office guard with different source addresses (nothing is attached)
aws iam simulate-custom-policy \
--policy-input-list file://office-guard.json \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::payroll-example/jan.csv \
--context-entries ContextKeyName=aws:SourceIp,ContextKeyValues=198.51.100.7,ContextKeyType=ip \
ContextKeyName=aws:ViaAWSService,ContextKeyValues=false,ContextKeyType=boolean \
--query 'EvaluationResults[].[EvalActionName,EvalDecision]' --output text
# s3:GetObject explicitDeny
# Repeat with 203.0.113.25: allowed (if the file also contains an Allow statement)
# 2. Bool vs BoolIfExists: leave the MFA key out to imitate a long-term access key
aws iam simulate-custom-policy \
--policy-input-list file://mfa-guard.json \
--action-names s3:DeleteBucket --resource-arns arn:aws:s3:::reports-example \
--context-entries ContextKeyName=aws:MultiFactorAuthPresent,ContextKeyValues=false,ContextKeyType=boolean \
--query 'EvaluationResults[0].EvalDecision' --output text
# explicitDeny (now delete the --context-entries line: Bool lets it through, BoolIfExists does not)
# 3. What can an existing role really do, boundary included?
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:role/app/orders-fn-role \
--action-names s3:GetObject iam:CreateUser \
--query 'EvaluationResults[].[EvalActionName,EvalDecision]' --output text
# s3:GetObject allowed
# iam:CreateUser implicitDeny
# 4. Create a role with the approved boundary, then put a boundary on an existing user
aws iam create-role --role-name app/orders-fn-role \
--assume-role-policy-document file://lambda-trust.json \
--permissions-boundary arn:aws:iam::111122223333:policy/DevBoundary
aws iam put-user-permissions-boundary --user-name tanvir \
--permissions-boundary arn:aws:iam::111122223333:policy/DevBoundary
aws iam put-role-permissions-boundary --role-name legacy-etl \
--permissions-boundary arn:aws:iam::111122223333:policy/DevBoundary
# 5. Validate a draft policy (finds the arn:awn typo and risky patterns)
aws accessanalyzer validate-policy --policy-type IDENTITY_POLICY \
--policy-document file://s3-policy.json \
--query 'findings[].[findingType,issueCode]' --output text
# one ERROR line for the invalid arn:awn prefix, then any SECURITY_WARNING,
# WARNING and SUGGESTION findings (for example a PassRole with a wildcard)
# 6. A session policy: assume a broad role, receive narrow credentials
aws sts assume-role \
--role-arn arn:aws:iam::111122223333:role/ExportRole \
--role-session-name job-4711 \
--policy '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::exports-example/customer-42/*"}]}'
# 7. Open a bucket to the whole organization
aws s3api put-bucket-policy --bucket finance-data-example --policy file://org-read.json
Then build the delegation pattern in a sandbox account: create DevBoundary, give a test user the developer policy above, and try aws iam create-role with and without --permissions-boundary, and then aws iam delete-role-permissions-boundary.
#Summary
Key points
- A condition is operator → key → values. Operators and keys combine with AND, values for one key with OR; “A or B” across keys needs two statements.
- A missing key makes a normal operator false, a negated operator true, and
…IfExiststrue. That single rule explains theaws:SourceIpsurprise (VPC endpoints, services on your behalf) and theBoolversusBoolIfExistsMFA trap. aws:SourceIpchecks API requests from public addresses; useaws:SourceVpce/aws:SourceVpcfor endpoint traffic andaws:ViaAWSServicefor service calls. It does not guard the console sign-in.- Multi-valued keys (
aws:TagKeys,aws:CalledVia,aws:PrincipalOrgPaths) needForAnyValue/ForAllValues;ForAllValueson a missing key is true, so add aNullcheck. - ABAC compares
aws:PrincipalTagwithaws:ResourceTag, forces tags withaws:RequestTagand must protect tag changes. - S3:
ListBucketon the bucket ARN, object actions onbucket/*. The slide’sarn:awnis a typo forarn:aws. aws:PrincipalOrgIDopens a resource to the whole organization;aws:SourceArn/aws:SourceAccountstop the confused deputy for service principals.- Assuming a role gives up your own permissions; a resource-based policy keeps them. Across accounts both the caller’s identity policy and the resource policy must allow.
- A permissions boundary (users and roles, never groups) is a ceiling. Delegation = allow
CreateRoleonly withiam:PermissionsBoundary= the approved boundary, deny removing or editing it, narrowiam:PassRole. - Full order: Deny → RCP → SCP → resource-based → identity-based → boundary → session. Across accounts the flow runs once per account and both must allow.
#Self-check
Answer on your own first, then read the explanation, even when you got it right.
Self-checkQuestion
A policy condition block contains two keys under StringEquals, and the first key lists two values. How does AWS combine them?
Self-checkQuestion
A bucket policy denies s3:* when NotIpAddress aws:SourceIp 203.0.113.0/24. Office users work fine, but an application on an EC2 instance in a private subnet, reaching S3 through a gateway VPC endpoint, gets AccessDenied. Why?
Self-checkQuestion
Same situation as the previous question. What is the smallest change that lets the application in through the endpoint while keeping home users blocked?
Self-checkQuestion
To force MFA, an administrator attached a Deny on sensitive actions with Bool aws:MultiFactorAuthPresent false. Console users without MFA are blocked, but scripts using long-term access keys can still run the actions. Which change closes the gap?
Self-checkQuestion
Which two statements about aws:MultiFactorAuthPresent are correct? (Choose two.)
Choose 2
Self-checkQuestion
An Allow statement lets users add tags only when ForAllValues:StringEquals aws:TagKeys is [team, env]. A user calls ec2:CreateTags with no tag keys at all (an empty set). What does the condition do?
Self-checkQuestion
You want a Deny that blocks any tagging request which includes the tag key team, even if other tag keys are present in the same request. Which operator do you use with aws:TagKeys?
Self-checkQuestion
A role has s3:* on arn:aws:s3:::reports-example/* and nothing else. Reading a known object works, but listing the bucket returns AccessDenied. What is the fix?
Self-checkQuestion
Which of these is NOT a resource-based policy?
Self-checkQuestion
A user in account A must write to a bucket in account B without losing any of her current permissions in A. What must be in place? (Choose two.)
Choose 2
Self-checkQuestion
Nadia assumes a role in account B, downloads files from a bucket in B, and tries to upload them to her own bucket in account A in the same session. The upload is denied, although Nadia herself can write to that bucket. What explains it?
Self-checkQuestion
In one account, a bucket policy allows s3:GetObject to the IAM user alice by her user ARN. Alice has no identity-based S3 permissions, and her permissions boundary allows only EC2. No policy denies anything. Can Alice read the object?
Self-checkQuestion
Same account. A bucket policy allows s3:GetObject to the role arn:aws:iam::111122223333:role/report. The role has no S3 policy of its own, and its permissions boundary allows only EC2. A session of that role reads the object. What happens?
Self-checkQuestion
A company wants developers to create IAM roles for their Lambda functions without a ticket, but they must never be able to create a role more powerful than an approved set of services. What is the best design?
Self-checkQuestion
Which two statements about permissions boundaries are correct? (Choose two.)
Choose 2
Self-checkQuestion
A user's identity policy is AdministratorAccess. Her permissions boundary allows s3:* and ec2:Describe*. No SCP or session policy applies. What can she do?
Self-checkQuestion
In a member account, one specific contractor must be limited to read-only S3, while everyone else in the account keeps their current permissions. Which control fits best?
Self-checkQuestion
A role in account A calls s3:GetObject on a bucket in account B. B's bucket policy allows the role. The role's identity policy in A says nothing about S3, and nothing denies. What is the result?
Self-checkQuestion
A security team requires that no principal outside the company's organization can ever read data from S3 buckets in any member account, even if a developer later writes an overly permissive bucket policy. What should they use?
Self-checkQuestion
Which two condition keys can limit a bucket policy to principals in your organization, or in one OU of it? (Choose two.)
Choose 2
Self-checkQuestion
An SQS queue policy allows sns.amazonaws.com to call sqs:SendMessage. A review warns that SNS topics in other customers' accounts could also deliver to the queue. What should be added to the statement?
Self-checkQuestion
An IP guard (Deny with NotIpAddress for the office range) breaks CloudFormation stacks that engineers launch from the office, because CloudFormation's own calls to other services are denied. What is the documented fix?
Self-checkQuestion
A batch job assumes a role whose permissions policy allows only dynamodb:*. It passes a session policy that allows s3:*. What can the session do?
Self-checkQuestion
A company wants developers to launch EC2 instances only in eu-west-1. Which approach matches the exam's expectation?
Self-checkQuestion
Which two AWS resources support resource-based policies? (Choose two.)
Choose 2
Self-checkQuestion
An IAM policy has two statements: Deny sqs:* on * and Allow sqs:DeleteQueue on one queue ARN. Which calls succeed?
Self-checkQuestion
The notes say a condition can make users “log in only from the company IP” with aws:SourceIp. What is the accurate statement?
Self-checkQuestion
A company wants one policy for 40 teams: each engineer may manage only the resources tagged with their own team, and new projects should need no policy change. Which keys form the core of that policy?
Self-checkQuestion
A team wants its CI pipeline to fail whenever a pull request changes an IAM policy so that it grants permissions the previous version did not. Which tool fits?
Self-checkQuestion
Developers may create Lambda functions. A security review finds that one of them attached an existing administrator role to a function and used it to change IAM. What is the most precise preventive control?