Conditions in depth
Lesson 3 of 5 in Policy Evaluation.
Conditions are where a broad statement becomes a safe one. A Condition block is a map of operator → key → value(s), and the evaluation rules are worth stating exactly because they are counter-intuitive in one specific way:
- Multiple keys under one operator, and multiple operators in one condition block, are combined with AND. All must be satisfied.
- Multiple values for one key are combined with OR. Any one matching is enough.
- A key absent from the request context makes the condition not match, so the statement does not apply at all. For an
Allow, that means no grant; for aDeny, that means no protection.
That last line is the single most important sentence about conditions, and it is the reason the [...IfExists](/certifications/glossary?term=ifexists-suffix) operators exist: appending IfExists to any operator makes the condition succeed when the key is missing. BoolIfExists, StringEqualsIfExists, ArnLikeIfExists.
The companion is the Null operator, which tests presence directly: "Null": {"aws:TokenIssueTime": "true"} matches when the key is absent (long-term credentials), and "false" matches when it is present.
| Operator family | Members you should recognise | Notes that decide questions |
|---|---|---|
String |
|
|
ARN |
| ARN-aware comparison, segment by segment — use these for |
Numeric / Date |
| Dates take ISO 8601 or epoch. |
Boolean |
| Values are the strings |
IP address |
| CIDR notation. |
Null |
| Tests whether the key exists at all. |
Set operators |
| For multi-valued keys such as |
…IfExists | Any operator plus the suffix | Succeeds when the key is missing. The fail-closed form for every Deny whose key may be absent. |
| Key | What it restricts | Phrasing in a question that points here |
|---|---|---|
| The organization the caller belongs to | "only principals in our organization", "no external accounts", "must keep working as accounts are added" |
| The OU path of the caller | "only accounts in the Production OU" |
| The exact calling identity | "only this specific role may…", used with |
| An attribute of the caller (role tag or session tag) | "engineers may only touch their own project’s resources" |
| A tag already on the target resource | "access is decided by the resource’s Environment tag" |
| A tag value being set by this request | "resources must be created with a CostCenter tag" |
| The set of tag keys in this request (multi-valued) | "users may only set an approved list of tag keys" — use |
| The caller’s public IP address — absent when the request goes through a VPC endpoint | "only from the corporate network", with the caveat below |
| The private IP of a request made through a VPC endpoint | "only from these subnets", for traffic that never leaves the VPC |
| The VPC, or the specific VPC endpoint, the request came through | "only reachable from our VPC", "only via our S3 endpoint" — the network half of a data perimeter |
| Whether the request used TLS | "reject any request not using HTTPS" — a |
| Whether MFA validated the temporary credentials | "destructive actions require MFA" — with |
| Whether an AWS service made the request on the principal’s behalf | "the restriction should apply only to direct calls", used to stop an IP or VPC condition breaking service integrations |
| Which services formed the call chain (multi-valued, ordered) | "this key may only be used through Athena", "only when called via CloudFormation" |
| The Region the request targets | "workloads may only run in eu-west-1 and eu-west-2" — the classic SCP |
| The AWS service making the KMS call for the principal | "this key may only be used by S3 in this account", a key-policy condition |
| The encryption header on a PutObject | "reject uploads that are not encrypted with SSE-KMS" |
Four traps, each of which has sunk a real policy.
aws:SourceIp does not mean what you want for service-mediated calls. When an AWS service makes a request on your behalf — CloudFormation calling EC2, Athena reading S3 — the target service sees the calling service's address, not your user's. The originating IP is not forwarded. So an identity policy restricting aws:SourceIp to the office range silently breaks every service integration. The fix is aws:ViaAWSService: apply the IP restriction only when the request is direct.
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"NotIpAddress": { "aws:SourceIp": ["203.0.113.0/24"] },
"Bool": { "aws:ViaAWSService": "false" }
}
}
The second trap in the same key: aws:SourceIp is not in the request context at all when the request goes through a VPC endpoint, so an IP-based policy stops applying for endpoint traffic. Use aws:VpcSourceIp, aws:SourceVpc, or aws:SourceVpce for that path.
aws:MultiFactorAuthPresent with plain Bool fails open. Covered in Directory Service, MFA, and the root user, and worth repeating because it appears in both tasks: the key is absent for long-term access keys and for federated identities, so a Deny with "Bool": {"...": "false"} does not match those requests. BoolIfExists is the only correct form.
ForAllValues is vacuously true for an absent key. "ForAllValues:StringEquals": {"aws:TagKeys": ["Project", "CostCenter"]} in a Deny-everything-else statement does nothing for a request that sets no tags at all, because the empty set satisfies "all". If untagged creation should fail, add a Null test requiring the key to be present.
Conditions are ANDed across keys, which makes "or" harder than it looks. Two keys under one operator must both match. Expressing "from the office network or with MFA" needs two statements, or a careful inversion — not two keys in one condition block.
Interactive sorting exercise: Match each requirement to the condition key that enforces it.
Key terms: Condition key, …IfExists condition operator, ForAllValues / ForAnyValue, aws:MultiFactorAuthPresent, aws:PrincipalOrgID
Interactive checkpoint quiz (2 questions) — open this page in a browser to take it.