Policy Conditions
A Condition block is the part of an IAM (Identity and Access Management) policy that adds an “only if” test to a statement. The statement’s Allow or Deny takes effect only when the test passes, so you can write rules like “allow this action, but only from the office network” or “only if the user signed in with MFA.” This is how you turn a broad permission into a tightly scoped, context-aware one. This page covers how conditions are built, the most useful keys, a tag-based example, and a subtle gotcha that has caused real security incidents.
How a Condition block is structured
A Condition is a JSON object with three nested layers: an operator, a condition key, and a value (or list of values).
"Condition": {
"IpAddress": {
"aws:SourceIp": "203.0.113.0/24"
}
}
Read that as: using the IpAddress operator, check the aws:SourceIp key against the value 203.0.113.0/24. If the request came from that IP range, the test passes.
A few rules govern how multiple tests combine:
- Multiple operators in one
Conditionblock are joined with AND — every operator must pass. - Multiple keys under one operator are also joined with AND.
- Multiple values for one key are joined with OR — any one match passes.
Common condition operators
The operator decides how the key is compared. Pick the operator that matches the data type of the key.
| Operator | Use it for | Example |
|---|---|---|
StringEquals / StringNotEquals | Exact string match | Region, tag value, username |
StringLike / StringNotLike | String match with * and ? wildcards | ARN prefixes, bucket name patterns |
Bool | True/false keys | aws:MultiFactorAuthPresent |
IpAddress / NotIpAddress | Source IP in a CIDR range | Office or VPN network |
DateGreaterThan / DateLessThan | Time windows | Temporary access expiry |
ArnEquals / ArnLike | Comparing ARNs (Amazon Resource Names) | Source resource of a request |
Adding IfExists to an operator (for example StringEqualsIfExists) means “apply this test only if the key is present in the request; if it is missing, skip the test.” This matters a lot for the gotcha below.
Common condition keys
Condition keys come in two families. Global keys start with aws: and work across most services. Service-specific keys start with the service prefix (like s3: or ec2:) and only exist for that service.
| Key | Type | What it checks |
|---|---|---|
aws:SourceIp | IP | The public IP address the request came from |
aws:MultiFactorAuthPresent | Bool | Whether the caller used MFA (multi-factor authentication) when signing in |
aws:RequestedRegion | String | Which AWS Region the API call targets |
aws:PrincipalTag/<key> | String | A tag on the user or role making the request |
aws:ResourceTag/<key> | String | A tag on the resource being acted on |
aws:CurrentTime | Date | The time of the request |
aws:SecureTransport | Bool | Whether the request used HTTPS/TLS |
Tip: Not every key works on every action.
aws:SourceIpis meaningless for an action a service performs on your behalf (the request then comes from the AWS service, not your IP). Check the AWS “Actions, resources, and condition keys” reference for the exact keys each service supports before relying on one.
Lock access to MFA and the office network
This identity-based policy allows EC2 (Elastic Compute Cloud) instance termination only when the caller used MFA and is on the office network. Both operators must pass because they are joined with AND.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TerminateOnlyFromOfficeWithMFA",
"Effect": "Allow",
"Action": "ec2:TerminateInstances",
"Resource": "*",
"Condition": {
"Bool": { "aws:MultiFactorAuthPresent": "true" },
"IpAddress": { "aws:SourceIp": "203.0.113.0/24" }
}
}
]
}
When to use this: for sensitive, destructive actions where you want a second layer beyond “they have permission.” When not to: as your only guardrail — IP and MFA conditions can be bypassed or misconfigured, so pair them with least-privilege permissions and explicit Deny statements.
Tag-based access (ABAC)
ABAC stands for Attribute-Based Access Control: instead of writing a new policy for every team, you match a tag on the user against a tag on the resource. One policy then scales to any number of teams.
The policy below lets a user start or stop EC2 instances only when the instance’s team tag equals the user’s own team tag. The aws:PrincipalTag/team value is filled in from whoever is calling, so the same document works for everyone.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ManageOwnTeamInstances",
"Effect": "Allow",
"Action": ["ec2:StartInstances", "ec2:StopInstances"],
"Resource": "arn:aws:ec2:*:123456789012:instance/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/team": "${aws:PrincipalTag/team}"
}
}
}
]
}
To make this work, tag the user (or role) with a team tag, and tag each instance with a matching team tag.
# Tag the IAM user
aws iam tag-user \
--user-name dana \
--tags Key=team,Value=analytics
# Tag the EC2 instance
aws ec2 create-tags \
--resources i-0a1b2c3d4e5f \
--tags Key=team,Value=analytics
Output:
(no output on success; both commands return exit code 0)
Now Dana can start i-0a1b2c3d4e5f because both tags read analytics, but cannot touch an instance tagged team=payments.
Tip: ABAC keeps your policy count small and is the recommended pattern in AWS IAM Identity Center. The trade-off is that your security now depends on tags being correct — protect tag-changing actions (
ec2:CreateTags,iam:TagUser) tightly, or someone could grant themselves access by re-tagging.
The gotcha: a missing key is not “false”
This is the trap that catches people. When a request does not include a condition key, IAM does not treat it as false. Instead the whole condition evaluates differently depending on the effect:
- In an
Allowstatement, a missing key makes the condition fail, so access is not granted. Safe by default. - In a
Denystatement, a missing key makes the condition not match, so theDenydoes not fire — and access can slip through.
Consider a policy meant to deny everything unless MFA is present:
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"Bool": { "aws:MultiFactorAuthPresent": "false" }
}
}
Some requests (for example, calls made by an assumed role or certain service-linked calls) never include aws:MultiFactorAuthPresent at all. For those, the Deny does not match and the action is allowed — the opposite of what you wanted. The fix is to use BoolIfExists so the test still applies sensibly, and to verify the exact behaviour rather than assume it:
"Condition": {
"BoolIfExists": { "aws:MultiFactorAuthPresent": "false" }
}
Gotcha: Condition keys are populated only when the request actually carries that context, and the set of populated keys differs per service and per call path. A key the request never includes evaluates differently for
AllowversusDeny. Never ship a security-critical condition on assumption — run it through the IAM policy simulator first.
Test before you trust it
The IAM policy simulator lets you replay an action against a policy, including the condition context, and see “allowed” or “denied” with the reason.
- Open the IAM console and choose Policy simulator in the left nav (or go to the Policy simulator from a user’s page).
- Select the user, group, or role whose policies you want to test.
- Pick the service and action (for example,
EC2 > TerminateInstances). - Expand Condition keys and supply realistic values, such as
aws:SourceIp = 198.51.100.10andaws:MultiFactorAuthPresent = false. - Click Run simulation and confirm the result matches your intent.
The CLI can check policies in bulk, which is useful in CI:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/dana \
--action-names ec2:TerminateInstances \
--context-entries \
ContextKeyName=aws:MultiFactorAuthPresent,ContextKeyType=boolean,ContextKeyValues=false
Output:
{
"EvaluationResults": [
{
"EvalActionName": "ec2:TerminateInstances",
"EvalResourceName": "*",
"EvalDecision": "implicitDeny",
"MatchedStatements": []
}
]
}
An implicitDeny here confirms the condition blocked the action when MFA was absent — exactly what we wanted.
Best practices
- Match the operator to the key’s data type (
Boolfor booleans,IpAddressfor IPs,StringEqualsfor tags). - Prefer ABAC with
aws:PrincipalTag/aws:ResourceTagover writing one policy per team, and lock down tag-changing actions. - Use
...IfExistsoperators when aDenydepends on a key that some requests may not include. - Remember that multiple operators and keys are ANDed, while multiple values for one key are ORed.
- Never assume a missing key means “false” —
AllowandDenytreat it oppositely. - Always validate a security-critical condition in the policy simulator before relying on it.
- IAM, conditions, and the simulator are free, so there is no cost reason to skip testing.