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 StringEquals or ArnLike does not match. A negated operator such as StringNotEquals, StringNotLike or ArnNotLike does match: “not equal” is true when there is nothing to compare. …IfExists operators match when the key is absent, and Null tests for absence directly.

Operators you will actually use

  • String: StringEquals, StringNotEquals, StringLike (with * and ? wildcards). StringEqualsIgnoreCase exists; plain StringEquals is case-sensitive.
  • ARN: ArnEquals, ArnLike, ArnNotLike: prefer these to string operators for ARNs.
  • Boolean: Bool, and BoolIfExists when the key can be absent.
  • Numeric and date: NumericLessThan, DateLessThan (for example on aws:CurrentTime to make a grant expire).
  • IP: IpAddress, NotIpAddress on aws: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

Common global condition keys
KeyWhat it containsTypical use
aws:SecureTransportWhether the request used TLS. Always present.Deny non-TLS access to buckets and queues.
aws:MultiFactorAuthPresentWhether the temporary credentials were MFA-authenticated. Absent for long-term access keys.Require MFA for destructive actions (use BoolIfExists).
aws:RequestedRegionThe 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:PrincipalOrgIDOrganisation ID of the calling principal's account.Organisation perimeters in resource policies.
aws:PrincipalArnARN of the calling principal (for a role session: the role's ARN).Exempt a break-glass role from a Deny.
aws:SourceArn / aws:SourceAccountThe 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/keyA tag on the resource being accessed (where the service supports it).ABAC: access only resources with a matching tag.
aws:PrincipalTag/keyA tag on the calling user or role, or a session tag.ABAC with policy variables.
aws:RequestTag/key, aws:TagKeysTags 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

Deny sensitive actions without MFA · IAM identity-based policyOpen in Inline Policy Generator
{
  "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

Team ABAC permissions for EC2 · IAM identity-based policyOpen in Inline Policy Generator
{
  "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:SourceIp and VPC endpoints. Requests through a VPC endpoint carry the private address in aws:VpcSourceIp, not aws: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 for aws:ViaAWSService.
  • Service-principal keys. aws:SourceArn and aws:SourceAccount are present only when an AWS service principal makes the call; they say nothing about IAM users or roles.
  • Negated operators in Allow statements. StringNotEquals in 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 a Null check 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:

Simulate a request with and without MFA
aws iam simulate-custom-policy --policy-input-list file://policy.json --action-names ec2:TerminateInstances \
  --context-entries ContextKeyName=aws:MultiFactorAuthPresent,ContextKeyValues=false,ContextKeyType=boolean

Official AWS documentation