AWS IAM Policy Conditions Explained: Operators, Keys, Examples & Use Cases
AWS IAM Policy Conditions Explained
IAM permissions without conditions are binary: either you can do something or you cannot. Conditions change that. They let you say “allow this action, but only when the user authenticated with MFA” or “deny any API call outside these three regions.” Without conditions, enforcing rules like MFA-required destructive actions, IP-restricted console access, HTTPS-only S3 traffic, or region-locked deployments requires workarounds that are harder to maintain and easier to get wrong. Conditions give you a precise, declarative way to express these rules directly inside your IAM policy statements.
IAM policy conditions in simple terms
Think of it like a bouncer with a checklist
A normal IAM statement is like a door that is either locked or unlocked. A condition turns it into a door with a bouncer. The bouncer checks a list before letting you through: “Are you on the guest list? Did you show ID? Are you arriving before midnight?” The door is the same door. The permission is the same permission. The bouncer just adds rules about when and how you are allowed through.
A normal IAM statement answers two questions: what action and on what resource. A condition adds a third: under what circumstances.
You are not changing what the permission grants. You are controlling the context in which it applies. The action is the same. The resource is the same. But now the permission only takes effect when specific facts about the request are true: whether the caller used MFA, what IP they called from, which region the API targets, or whether the connection uses TLS.
If a condition is not met, the statement is skipped. An Allow statement with an unmet condition does not allow. A Deny statement with an unmet condition does not deny.
How IAM policy conditions work
A Condition block sits inside a policy statement alongside Effect, Action, and Resource. It contains one or more condition operators (like StringEquals or Bool), each containing one or more condition key-value pairs.
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}Four terms to know:
- Condition operator is the comparison type (
StringEquals,Bool,IpAddress, etc.) - Condition key is the variable AWS evaluates from the request (
aws:RequestedRegion,aws:SourceIp, etc.) - Request context is the set of facts AWS collects about the request at evaluation time (who, from where, when, how)
- Policy variable is a dynamic placeholder like
${aws:username}that AWS resolves to the actual value at request time
How AWS evaluates a condition
- AWS receives the API request and builds the request context (caller identity, IP, region, MFA status, timestamp, etc.).
- For each policy statement that matches the action and resource, AWS checks the
Conditionblock. - Each condition operator compares its condition key against the request context value using the operator’s comparison logic.
- If the condition key is present and the comparison is true, that condition passes.
- If the condition key is present and the comparison is false, that condition fails.
- If the condition key is missing from the request context, the condition fails. The exception is
IfExistsvariants, which treat a missing key as a pass. - All conditions in a statement must pass for the statement to apply.
The IfExists suffix (e.g. BoolIfExists, StringEqualsIfExists) changes behaviour when a key is absent. Without it, a missing key causes the condition to fail. With it, a missing key is skipped (treated as true). This is critical for keys like aws:MultiFactorAuthPresent, which is absent entirely for requests made with long-lived access keys.
AND vs OR logic
Condition logic follows two rules:
- Multiple values for the same key are OR’d. Any one match is enough. Example:
“aws:RequestedRegion”: [“us-east-1”, “eu-west-1”]matches either region. - Multiple keys or multiple operators are AND’d. All must be true. A statement with both
StringEqualson region andBoolon MFA requires both to pass.
{
"Condition": {
"StringEquals": {
"aws:RequestedRegion": ["us-east-1", "us-west-2"],
"ec2:ResourceTag/Environment": "dev"
},
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}This means: region is us-east-1 OR us-west-2, AND the instance tag is dev, AND MFA is present. All three lines must be satisfied.
Common condition operators
| Operator | Type | Use case |
|---|---|---|
StringEquals | String | Exact match: region, tag value, account ID |
StringNotEquals | String | Deny specific values: block a region, exclude a tag |
StringLike | String | Wildcard match with * and ?: ARN patterns, key prefixes |
ArnEquals | ARN | Exact ARN match |
ArnLike | ARN | ARN match with wildcards in resource portion |
Bool | Boolean | Check true/false keys: aws:SecureTransport, aws:MultiFactorAuthPresent |
BoolIfExists | Boolean | Same as Bool, but treats a missing key as a match. Essential for MFA deny patterns. |
IpAddress | IP | Match CIDR ranges: 10.0.0.0/8, 203.0.113.0/24 |
DateGreaterThan | Date | Time-based access windows: restrict to after a specific date |
Null | Null check | Test whether a key is present ("false") or absent ("true") in the request |
Every operator also has an IfExists variant (e.g. StringEqualsIfExists, IpAddressIfExists) that only applies the condition when the key exists in the request context.
Common condition keys
Condition keys come in two categories: global keys (prefixed with aws:) that are available for all services, and service-specific keys (prefixed with the service name) that only apply to that service’s API calls.
Global condition keys
| Key | Type | What it contains |
|---|---|---|
aws:RequestedRegion | String | The region targeted by the API call |
aws:MultiFactorAuthPresent | Boolean | Whether the session used MFA. Absent for access-key requests. |
aws:SourceIp | IP | The caller’s public IP address (not useful for VPC-internal traffic) |
aws:SourceVpc | String | The VPC ID the request came from (via VPC endpoint) |
aws:SourceVpce | String | The VPC endpoint ID the request came through |
aws:PrincipalArn | ARN | The ARN of the principal making the request |
aws:PrincipalAccount | String | The AWS account ID of the principal |
aws:CurrentTime | Date | The timestamp of the request (ISO 8601) |
aws:SecureTransport | Boolean | Whether the request used TLS |
S3-specific condition keys
| Key | Type | What it contains |
|---|---|---|
s3:prefix | String | The object key prefix in a ListBucket request |
s3:ExistingObjectTag/<key> | String | A tag value on the object being accessed |
Boolean keys like aws:MultiFactorAuthPresent and aws:SecureTransport must use Bool or BoolIfExists. String keys use StringEquals or StringLike. IP keys use IpAddress or NotIpAddress. Using the wrong operator type for a key is a common source of unexpected policy behaviour.
When to use IAM policy conditions
Require MFA for destructive actions. Deny deleting IAM users, detaching policies, or leaving an organization unless the session used MFA. This protects against compromised passwords that lack the second factor.
Restrict actions to approved regions. Prevent resource creation outside specific regions for compliance, data residency, or cost control. Often combined with Service Control Policies at the organization level.
Limit shared-bucket access by prefix. In a shared S3 bucket, give each team or user access only to objects under their own prefix using
s3:prefixand policy variables.Require HTTPS. Deny S3 or other API requests that do not use TLS, ensuring data in transit is always encrypted.
Restrict access by IP or VPC endpoint. Limit API access to requests from your corporate network or a specific VPC endpoint, preventing access from unexpected locations.
Tag-based access control. Use
ec2:ResourceTagors3:ExistingObjectTagconditions to allow actions only on resources tagged with specific values likeEnvironment=dev.Add constraints to trust policies for role assumption. Require that only principals from a specific account, with MFA, or from a specific source IP can assume a role.
When not to use IAM policy conditions
If a policy grants "Resource": "*" and you are adding conditions to limit which resources it affects, the real fix is to narrow the Resource ARN. Conditions should add context-based restrictions, not compensate for overly broad permissions. Apply the principle of least privilege at the resource level first.
When an SCP is the right tool. If you want to block all users across an entire organization or OU from using a region or service, that is a governance guardrail. Use a Service Control Policy, which applies to every principal in the account regardless of their individual policies.
When a bucket policy or trust policy is the right location. Conditions on an S3 bucket policy are evaluated at the bucket. Conditions on an IAM role trust policy control who can assume that role. Putting these conditions on a user’s identity policy instead means they can be bypassed if the user has other permissive policies. Choose the policy location that cannot be circumvented.
When the logic is getting complex enough to be fragile. If you are stacking five conditions with mixed AND/OR logic, you are likely better off restructuring your permissions into simpler, narrower policies. Complex conditions are hard to audit and easy to get wrong.
IAM policy conditions vs resource scoping
Address on the envelope vs ID at the door
Resource scoping is like writing a specific address on an envelope. The letter only goes to that address. A condition is like a security guard at the door who checks your ID before handing it over. Both control who gets access, but they work at different levels. Put the right address on the envelope first. Then add a guard if you need an extra check.
- Resource scoping means putting specific ARNs in the
Resourcefield. It is static, simple, and the most readable option. Use it whenever you know exactly which resources the principal needs. - Conditions evaluate dynamic context at request time. Use them when you need runtime checks that resource ARNs cannot express, like “only if the request comes from this VPC” or “only if the object is tagged production.”
Prefer resource scoping when you can. Add conditions when the restriction depends on context that the Resource field cannot capture. The two are complementary, not interchangeable.
IAM policy conditions vs Service Control Policies
- IAM conditions add requirements to one policy statement. They control when a specific allow or deny applies for whatever principals are subject to that policy.
- SCPs set maximum permission boundaries for an entire AWS account or OU. They apply to every principal in the account (except the management account root user). An SCP deny overrides any IAM allow.
Use conditions for fine-grained, per-principal context checks. Use SCPs for organization-wide guardrails. For region restrictions, SCPs with conditions are the standard pattern: a Deny with StringNotEquals on aws:RequestedRegion applied as an SCP.
SCPs and IAM conditions are not competing tools. The most effective setups use SCPs for broad guardrails (no unapproved regions, no disabling CloudTrail) and IAM conditions for per-principal context rules (require MFA for this role, restrict this team to their S3 prefix).
IAM policy conditions vs S3 bucket policies
- IAM policy with conditions is attached to the principal (user or role). It travels with the principal and applies to whatever buckets the policy’s Resource field covers.
- S3 bucket policy is attached to the bucket. It controls access to that specific bucket regardless of what IAM policies the caller has.
For bucket-specific rules like “deny all non-HTTPS requests to this bucket” or “restrict access to requests from this VPC endpoint,” a bucket policy is the right location. The restriction lives with the resource and cannot be overridden by a permissive IAM policy on the caller. Use IAM policy conditions when the restriction should follow the principal, not the bucket.
Examples
Require MFA for sensitive actions
Deny destructive IAM and billing actions when MFA is not present. This pattern uses a Deny statement with BoolIfExists so the deny fires both when MFA is explicitly false and when the key is absent (as happens with long-lived access keys).
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDestructiveWithoutMFA",
"Effect": "Deny",
"Action": [
"iam:DeleteUser",
"iam:DeleteRole",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"organizations:LeaveOrganization"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}The key aws:MultiFactorAuthPresent is a Boolean key, so it requires a Boolean operator. But it is only present in the request context when the caller used a console session or STS temporary credentials obtained with MFA. For requests made with long-lived access keys, the key is absent entirely. Bool would treat a missing key as “condition not met,” which means the Deny would not fire. That is the opposite of what you want. BoolIfExists treats a missing key as matching, so the Deny fires correctly in both cases.
Restrict actions to approved regions
Deny any API call targeting a region outside your approved list. This is most effective as an SCP applied at the organization or OU level.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyOutsideApprovedRegions",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": [
"us-east-1",
"us-west-2",
"eu-west-1"
]
}
}
}
]
}Any API call targeting a region not in the list is denied. See Restricting Resource Locations in AWS for the full pattern including global service exemptions.
Restrict S3 access to a prefix
Allow a user to list and read/write only objects under their own prefix in a shared bucket.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListOwnPrefix",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::shared-uploads",
"Condition": {
"StringLike": {
"s3:prefix": "${aws:username}/*"
}
}
},
{
"Sid": "AllowReadWriteOwnPrefix",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::shared-uploads/${aws:username}/*"
}
]
}The policy variable ${aws:username} resolves to the caller’s IAM username at request time. Each user gets access only to their own prefix without hardcoding names.
Require HTTPS for S3
Deny any S3 request that does not use TLS. This belongs on the bucket as a bucket policy, not on a user’s IAM policy, so the restriction applies to every caller.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::sensitive-data-bucket",
"arn:aws:s3:::sensitive-data-bucket/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}aws:SecureTransport is always present in the request context, so Bool (not BoolIfExists) is the right operator. Unlike aws:MultiFactorAuthPresent, this key never goes missing.
Restrict access by VPC endpoint or source IP
Deny S3 access unless the request comes through a specific VPC endpoint:
{
"Sid": "DenyOutsideVpce",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::internal-data",
"arn:aws:s3:::internal-data/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpce": "vpce-0abc1234def567890"
}
}
}Traffic routed through a VPC endpoint does not populate aws:SourceIp with the private IP. It is empty or contains the NAT gateway’s public IP, not the instance IP. For VPC-based restrictions, always use aws:SourceVpc or aws:SourceVpce. Reserve aws:SourceIp for restricting console access from a corporate IP range.
Use conditions in a trust policy
Conditions in a role trust policy control who can assume the role. This example requires MFA and restricts assumption to a specific account:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountWithMFA",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}In a trust policy, the principal has already been identified by the Principal field. The condition adds an extra gate. Here, the assuming user’s session must include MFA. Because sts:AssumeRole is called from an existing session (not from bare access keys), aws:MultiFactorAuthPresent is present in the request context, so Bool is appropriate.
Trust policy conditions are evaluated when someone tries to assume the role. Identity policy conditions are evaluated when the assumed role tries to do something. They protect different boundaries. For cross-account access, put MFA requirements in the trust policy so the check happens at the door, not after someone is already inside.
Common mistakes
Using the wrong operator type for a key.
aws:MultiFactorAuthPresentis a Boolean key. It must useBoolorBoolIfExists, notStringEquals. Similarly,aws:SourceIpmust useIpAddressorNotIpAddress, not string operators. Mismatched operators cause conditions to behave unpredictably.Misunderstanding missing keys. When a condition key is absent from the request context, standard operators treat the condition as not met. For Allow statements, the allow does not apply. For Deny statements, the deny does not apply, which may grant access you intended to block. Use
IfExistsvariants or theNulloperator when the key might be absent.Confusing allow-conditions with deny-conditions. A condition on an Allow statement restricts when the allow fires. A condition on a Deny statement restricts when the deny fires. The deny-without-MFA pattern uses a Deny with
“aws:MultiFactorAuthPresent”: “false”. The condition is met when MFA is absent, which triggers the deny. Getting this inverted is a common source of policies that do the opposite of what you intended.Using
aws:SourceIpwhen the right key isaws:SourceVpcoraws:SourceVpce. VPC-internal traffic routed through a VPC endpoint does not populateaws:SourceIpwith the private IP. Useaws:SourceVpcoraws:SourceVpcefor VPC-based restrictions.Not testing with the IAM Policy Simulator. Conditions interact in non-obvious ways, especially across multiple policies. The IAM Policy Simulator lets you test whether a specific API call would be allowed or denied under specific conditions before you apply the policy to production. See Fixing IAM AccessDenied Errors for more debugging strategies.
Using conditions instead of fixing overly broad resource scope. If a policy grants
"Resource": "*"and you are adding conditions to limit which resources it affects, the real fix is to narrow theResourceARN. Conditions add context checks. They are not a substitute for proper resource scoping.
How to test and troubleshoot condition-based policies
Use the IAM Policy Simulator. The simulator lets you test API calls against your policies with specific context values (region, IP, MFA status). It shows which statements matched, which conditions passed or failed, and the final allow/deny decision. Always simulate before deploying condition-heavy policies.
Read the AccessDenied message carefully. When a request is denied, AWS sometimes indicates which policy or condition caused the denial. Cross-reference the denied action with your condition keys. If you are debugging an unexpected denial, check whether the condition key is actually present in the request context for that call type.
Verify your assumptions about the request context. Not every key is present in every request.
aws:MultiFactorAuthPresentis absent for access-key calls.aws:SourceIpis empty for VPC endpoint traffic.aws:SourceVpceis only present when the request actually traversed a VPC endpoint. If your condition depends on a key, confirm that it is populated for the specific call pattern you are testing.Use the AWS CLI to test specific calls. Run the actual API call with the CLI using a test identity and check whether it succeeds or fails. Combine this with CloudTrail to see the full request context that AWS evaluated.
Check CloudTrail for evaluation details. CloudTrail logs show the request parameters, the calling identity, and the source IP. Comparing a CloudTrail event to your condition helps you identify exactly which condition key value did not match.
Summary
- Conditions add a “when / how / context” layer to Allow and Deny statements using operator-key-value logic.
- Global keys (
aws:) apply to all services. Service-specific keys apply only to their service’s APIs. - Multiple values for one key are OR’d. Multiple keys or operators in a block are AND’d.
aws:MultiFactorAuthPresentis a Boolean key. UseBoolorBoolIfExists, neverStringEquals.- Use
BoolIfExistsfor deny-without-MFA patterns because the key is absent for access-key requests. - Use
aws:SourceVpcoraws:SourceVpcefor VPC-based restrictions, notaws:SourceIp. - Prefer narrowing resource scope first. Add conditions when you need runtime context checks.
- SCPs enforce organization-wide guardrails. Bucket policies enforce bucket-level rules. IAM conditions enforce per-statement context rules. Choose the right tool.
- Test condition-heavy policies in the IAM Policy Simulator before deploying.
Frequently asked questions
What is an IAM condition key?
A condition key is a variable that AWS populates from the request context at evaluation time. Global keys like aws:RequestedRegion, aws:SourceIp, and aws:MultiFactorAuthPresent are available for every service. Service-specific keys like s3:prefix and ec2:ResourceTag apply only to the service they belong to. You reference condition keys inside the Condition block of a policy statement to allow or deny access based on context.
What is the difference between StringEquals and StringLike?
StringEquals requires an exact, case-sensitive match between the condition key value and the value you specify. StringLike supports wildcard characters. The asterisk (*) matches any combination of characters and the question mark (?) matches a single character. Use StringEquals when you know the exact value, such as a region or a tag. Use StringLike when you need pattern matching, such as an ARN path or a prefix with a wildcard.
When should I use BoolIfExists instead of Bool for MFA?
Use BoolIfExists when writing a Deny statement that blocks access without MFA. The key aws:MultiFactorAuthPresent is absent entirely when the request uses long-lived access keys instead of a console or STS session. Bool only evaluates when the key is present, so a Bool condition on a Deny statement would not deny access-key requests that lack MFA. BoolIfExists treats a missing key as a match, so the Deny fires both when MFA is false and when the key is absent.
Do IAM conditions work in SCPs and bucket policies?
Yes. The Condition block works the same way in identity-based policies, resource-based policies like S3 bucket policies, and Service Control Policies. The available condition keys may differ depending on context. For example, aws:SourceIp is not meaningful for VPC-internal traffic. But the syntax and evaluation logic are identical across all policy types.
What happens if a condition key is missing from the request?
If a condition key is missing and you use a standard operator like StringEquals or Bool, the condition evaluates to false. For an Allow statement, the allow does not apply. For a Deny statement, the deny does not apply. If you want the condition to match when the key is absent, use an IfExists variant like BoolIfExists or StringEqualsIfExists. The Null operator can also explicitly test whether a key is present or absent.