Conditions & evaluation
IAM condition keys, with real examples
Conditions turn a broad grant into a precise one. Learn the operators, the …IfExists and ForAnyValue/ForAllValues qualifiers, and the keys used most in production policies.
Published · Updated .
The Condition element is where most real-world policy precision lives. Actions and ARNs say what may be done; conditions say under which circumstances: over TLS, with MFA, in approved Regions, on resources tagged for your team. They are also where subtle mistakes hide, because a condition that does not match simply makes the statement not apply.
Anatomy of a condition block
{
"Condition": {
"StringEquals": {
"aws:RequestedRegion": [
"eu-west-1",
"eu-central-1"
],
"aws:PrincipalTag/Team": "payments"
},
"Bool": {
"aws:SecureTransport": "true"
}
}
}- Every operator in the block must match, and every key under an operator must match: these are combined with AND.
- Multiple values for one key are combined with OR: the Region may be either of the two listed.
- If a key is not present in the request context, a positive operator such as
StringEqualsorArnLikedoes not match. A negated operator such asStringNotEquals,StringNotLikeorArnNotLikedoes match: “not equal” is true when there is nothing to compare.…IfExistsoperators match when the key is absent, andNulltests for absence directly.
Operators you will actually use
- String:
StringEquals,StringNotEquals,StringLike(with*and?wildcards).StringEqualsIgnoreCaseexists; plainStringEqualsis case-sensitive. - ARN:
ArnEquals,ArnLike,ArnNotLike: prefer these to string operators for ARNs. - Boolean:
Bool, andBoolIfExistswhen the key can be absent. - Numeric and date:
NumericLessThan,DateLessThan(for example onaws:CurrentTimeto make a grant expire). - IP:
IpAddress,NotIpAddressonaws:SourceIp. - Null: tests whether a key is present (
"true"means absent).
Multivalued keys: ForAnyValue and ForAllValues
Some keys carry a set of values, such as aws:TagKeys (all tag keys in a request). ForAnyValue:StringEquals matches if at least one value matches; ForAllValues:StringEquals matches if every value is in your list. The trap: ForAllValues also matches when the set is empty, for example a request with no tags at all. When you use it in an Allow statement, add a Null check if empty must not pass.
Global condition keys worth knowing
| Key | What it contains | Typical use |
|---|---|---|
aws:SecureTransport | Whether the request used TLS. Always present. | Deny non-TLS access to buckets and queues. |
aws:MultiFactorAuthPresent | Whether the temporary credentials were MFA-authenticated. Absent for long-term access keys. | Require MFA for destructive actions (use BoolIfExists). |
aws:RequestedRegion | The Region the request was sent to. Requests to global services such as IAM are typically made to us-east-1 in the commercial partition, so exempt them in Region guardrails. | Region guardrails; exempt global services. |
aws:PrincipalOrgID | Organisation ID of the calling principal's account. | Organisation perimeters in resource policies. |
aws:PrincipalArn | ARN of the calling principal (for a role session: the role's ARN). | Exempt a break-glass role from a Deny. |
aws:SourceArn / aws:SourceAccount | The resource or account on whose behalf an AWS service principal calls. Only present for service principals. | Prevent the confused-deputy problem for service principals. |
aws:ResourceTag/key | A tag on the resource being accessed (where the service supports it). | ABAC: access only resources with a matching tag. |
aws:PrincipalTag/key | A tag on the calling user or role, or a session tag. | ABAC with policy variables. |
aws:RequestTag/key, aws:TagKeys | Tags sent in the request (for example when creating or tagging a resource). | Require tags on creation; stop changes to protected tag keys. |
Services add their own keys, such as s3:prefix for listing, iam:PassedToService for iam:PassRole, sts:ExternalId for third-party roles and iam:PermissionsBoundary for delegated administration. The Service Authorization Reference lists which keys each action supports; a key that an action does not support is never present for that action.
Worked example: MFA for destructive actions
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenySensitiveActionsWithoutMfa",
"Effect": "Deny",
"Action": [
"ec2:TerminateInstances",
"rds:DeleteDBInstance",
"rds:DeleteDBCluster",
"s3:DeleteBucket",
"iam:DeleteRole",
"iam:DeleteUser"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
},
{
"Sid": "DenyCredentialChangesWithoutMfa",
"Effect": "Deny",
"Action": [
"iam:CreateAccessKey",
"iam:UpdateAccessKey",
"iam:DeactivateMFADevice",
"iam:DeleteVirtualMFADevice",
"iam:CreateLoginProfile",
"iam:UpdateLoginProfile",
"iam:AttachUserPolicy",
"iam:PutUserPolicy",
"iam:AddUserToGroup"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}Why BoolIfExists and not Bool: requests signed with long-term access keys do not contain aws:MultiFactorAuthPresent at all. With plain Bool, “false” would not match for those requests, so the deny would not apply to exactly the credentials most likely to be leaked.
Worked example: tags and policy variables
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "OperateOwnTeamInstances",
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:RebootInstances"
],
"Resource": "arn:aws:ec2:eu-west-1:111122223333:instance/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Team": "${aws:PrincipalTag/Team}"
}
}
},
{
"Sid": "SeeInstances",
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:DescribeTags"
],
"Resource": "*"
},
{
"Sid": "LaunchSupportingResources",
"Effect": "Allow",
"Action": "ec2:RunInstances",
"Resource": [
"arn:aws:ec2:eu-west-1::image/*",
"arn:aws:ec2:eu-west-1:111122223333:subnet/*",
"arn:aws:ec2:eu-west-1:111122223333:network-interface/*",
"arn:aws:ec2:eu-west-1:111122223333:security-group/*",
"arn:aws:ec2:eu-west-1:111122223333:key-pair/*"
]
},
{
"Sid": "LaunchOnlyTaggedForOwnTeam",
"Effect": "Allow",
"Action": "ec2:RunInstances",
"Resource": [
"arn:aws:ec2:eu-west-1:111122223333:instance/*",
"arn:aws:ec2:eu-west-1:111122223333:volume/*"
],
"Condition": {
"StringEquals": {
"aws:RequestTag/Team": "${aws:PrincipalTag/Team}"
},
"ForAllValues:StringEquals": {
"aws:TagKeys": [
"Team",
"Name"
]
}
}
},
{
"Sid": "TagOnlyDuringLaunch",
"Effect": "Allow",
"Action": "ec2:CreateTags",
"Resource": "arn:aws:ec2:eu-west-1:111122223333:*/*",
"Condition": {
"StringEquals": {
"ec2:CreateAction": "RunInstances"
}
}
},
{
"Sid": "DenyTeamTagChangesAfterLaunch",
"Effect": "Deny",
"Action": [
"ec2:CreateTags",
"ec2:DeleteTags"
],
"Resource": "arn:aws:ec2:*:111122223333:instance/*",
"Condition": {
"ForAnyValue:StringEqualsIgnoreCase": {
"aws:TagKeys": [
"Team"
]
},
"StringNotEquals": {
"ec2:CreateAction": "RunInstances"
}
}
},
{
"Sid": "DenyDeletingAllTags",
"Effect": "Deny",
"Action": "ec2:DeleteTags",
"Resource": "arn:aws:ec2:*:111122223333:instance/*",
"Condition": {
"Null": {
"aws:TagKeys": "true"
}
}
}
]
}${aws:PrincipalTag/Team} is a policy variable: AWS substitutes the caller's tag value at request time. If the caller has no Team tag, the variable has no value. A positive operator such as StringEquals then does not match, so the Allow never applies and access fails closed. A negated operator would do the opposite: StringNotEquals matches a missing value, which is why AWS's own examples use it in Deny statements to block callers without the tag. The example's last statements stop anyone from retagging an instance into their team.
Pitfalls
aws:SourceIpand VPC endpoints. Requests through a VPC endpoint carry the private address inaws:VpcSourceIp, notaws:SourceIp. Calls that AWS services make on your behalf (for example CloudFormation creating resources) come from AWS addresses; an IP-based deny can break them unless you account foraws:ViaAWSService.- Service-principal keys.
aws:SourceArnandaws:SourceAccountare present only when an AWS service principal makes the call; they say nothing about IAM users or roles. - Negated operators in Allow statements.
StringNotEqualsin an Allow matches when the key has any other value and when the key is missing from the request, so it can grant far more than intended. Negations usually belong in Deny statements; if you need one in an Allow, pair it with aNullcheck that requires the key to be present.
Testing conditions
The IAM policy simulator accepts request-context values, so you can test conditions without making real calls:
aws iam simulate-custom-policy --policy-input-list file://policy.json --action-names ec2:TerminateInstances \
--context-entries ContextKeyName=aws:MultiFactorAuthPresent,ContextKeyValues=false,ContextKeyType=boolean