All articles

// Knowledge.log — 技術記事

IfExists in an IAM policy: the Allow that passes when the context key is missing

A missing key under IpAddress is a mismatch, so the Allow grants nothing. With IfExists the condition is true. Three IAM policies show where Allow stays open.

A reviewer opens an identity policy and finds aws:SourceIp with 203.0.113.0/24 inside a Condition. They decide that s3:GetObject only works from that network and approve the change. Sometimes they're right. Other times the operator name ends in IfExists, and the same condition means something else.

A popular myth covers this case: "if the key isn't in the request, AWS ignores the condition and the Allow goes through." The documentation doesn't say that. With a normal operator, a missing key is a mismatch, so the condition is false and that Allow grants nothing. The thing that lets an Allow pass without the key is the ...IfExists suffix.

By the end you should be able to read three policies and say which one fails closed and which one lets GetObject through when the request carries neither aws:SourceIp nor aws:SourceVpc.

Everything here comes from the IAM User Guide, retrieved on September 29, 2026, and three local JSON files. Nothing was run against a real AWS account, in the Policy Simulator, or through a deploy.

What the User Guide says about a missing key

The condition operators page has an "Important" callout that clears up most of the confusion:

> If the key that you specify in a policy condition is not present in the request context, the values do not match and the condition is false.

The same callout adds two details. With a negated operator such as StringNotLike, a missing key makes the condition true. And this logic applies to every operator except ...IfExists and Null. The Condition element page says it more briefly: "A context key that is not present in the request is considered a mismatch." Neither page says the condition gets "ignored."

IfExists flips the missing-key case. From the operators page:

> If the condition key is present in the context of the request, process the key as specified in the policy. If the key is not present, evaluate the condition element as true.

Null does a different job: it checks whether the key exists. "true" means the key is absent. "false" means it's present and not null.

One more piece is how evaluation works. Every request starts as an implicit deny. It needs an explicit Allow, and an explicit Deny beats any Allow. An Allow whose condition is false just doesn't apply. That isn't the same as an explicit Deny, and the difference matters later.

OperatorKey present, matchesKey present, no matchKey missing
IpAddress on aws:SourceIpmatchno matchfalse: the statement does not grant
StringEqualsmatchno matchfalse (mismatch)
...IfExistsevaluates like the base operatorevaluates like the base operatortrue
Null with "false"truetruefalse

Policy 1: IpAddress on aws:SourceIp

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowGetObjectIfPublicSourceIp",
    "Effect": "Allow",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
    "Condition": {
      "IpAddress": { "aws:SourceIp": "203.0.113.0/24" }
    }
  }]
}

The operator is IpAddress, which the documentation uses to compare a CIDR range against aws:SourceIp. The policy uses the 2012-10-17 language version, which is the current one. All three policies in this post use it.

If the request's aws:SourceIp is inside the range, the Allow grants. If it's outside, it doesn't. If the key is missing, the value doesn't match, the condition is false, and this statement grants nothing.

There's a concrete case where the key goes missing, and the global condition keys page describes it:

> The aws:SourceIp key is always present in the request context, except when the requester uses a VPC endpoint to make the request. In this case, the condition returns false and the request is implicitly denied by this statement.

So a VPC endpoint silently drops SourceIp from the context, and the policy still looks like a padlock. The same page says aws:SourceIp only works for public IP ranges. A private CIDR here won't restrict anything the way the author intended.

Policy 2: the IfExists pair from the AWS docs

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowGetObjectIfExistsIpOrVpc",
    "Effect": "Allow",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
    "Condition": {
      "IpAddressIfExists": { "aws:SourceIp": ["203.0.113.0/24"] },
      "StringEqualsIfExists": { "aws:SourceVpc": ["vpc-1234567890abcdef0"] }
    }
  }]
}

This pair comes from the "IP range or VPC" example on the global condition keys page, and that page also explains the result:

> If either or both keys are not included in the request context, the condition still returns true. The values are only checked if the specified key is included in the request context.

Combine this with the two availability rules and the design makes sense. A request over the internet carries SourceIp, which is checked against the range. It doesn't carry SourceVpc, since that key only exists when the request uses a VPC endpoint, so that half evaluates true. A request through a VPC endpoint is the reverse: SourceIp is missing and evaluates true, while SourceVpc is present and gets checked. Each path is checked against the key it actually sends.

The Allow that slips through is the third case, where neither key arrives. Both elements evaluate true, the whole condition is true, and GetObject is granted without any network being checked. That's exactly how IfExists is documented to behave. Nobody noticed because the suffix sits in the operator name, not in the value the reviewer was reading.

One variant deserves a look. If only the first line were there, with IpAddressIfExists by itself, any request through any VPC endpoint would arrive without SourceIp and pass. That's the very path Policy 1 kept closed.

Policy 3: failing closed on purpose with Null

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowGetObjectOnlyWhenSourceIpPresentAndMatches",
    "Effect": "Allow",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
    "Condition": {
      "IpAddress": { "aws:SourceIp": "203.0.113.0/24" },
      "Null": { "aws:SourceIp": "false" }
    }
  }]
}

This JSON isn't copied from an AWS example. It combines two documented operators. The Null documentation uses a different key in its example, not SourceIp. Both operators are in the same Condition block, so both must evaluate true.

According to the table, Policy 1 already failed closed when the key was missing. Today, Null with "false" doesn't change that result. What it does is state the requirement that the key exist, and the evaluator enforces it. If a future PR adds IfExists to the IpAddress, the Null still blocks the request that has no SourceIp. It guards against a suffix pasted on without much thought.

When IfExists is just noise

Some keys are always in the context. The global condition keys page says aws:CurrentTime, aws:EpochTime and aws:ViaAWSService are "always included," and that aws:PrincipalAccount is in every request, anonymous ones included. For these keys IfExists never does anything, because the missing-key case never happens. All it does in practice is leave the next reviewer wondering why the suffix is there. aws:SourceIp isn't on that list: the sentence that calls it "always present" also carries the VPC endpoint exception.

Pitfalls worth knowing

  • StringEquals with a CIDR. The documentation uses IpAddress for CIDR ranges. StringEquals is a string operator, suited to keys like aws:SourceVpc or aws:SourceVpce. Don't expect it to understand a netmask.
  • Another Allow with no Condition. A false condition turns off only its own statement. If the same identity policy has another Allow for s3:GetObject on the same resource with no condition, the action is still granted. If you check the condition and skip the other statements, you've reviewed half the policy.
  • IfExists in a Deny with a negated operator. This is the opposite of the Allow case. The operators page says that with "Effect": "Deny" and something like StringNotEqualsIfExists, "the request is still denied even if the condition key is not present". The fail-open covered here only affects Allow statements.
  • Service-to-service calls. When an AWS service calls another service on the principal's behalf, the target service sees the calling service's IP, and some network context is removed. The source VPC and VPCE aren't preserved either. The documentation says not to use aws:SourceIp for these calls and to use aws:ViaAWSService or aws:CalledVia instead.

When DevDojo would use each form

If the goal is "only from this network," DevDojo would use IpAddress on aws:SourceIp, or StringEquals on aws:SourceVpc/aws:SourceVpce, with no IfExists. When the key's presence is a requirement that should be visible, it would add Null with "false", as in Policy 3.

IfExists on SourceIp or SourceVpc belongs where some legitimate clients sometimes don't send the key and the team has consciously chosen to leave that path open. That decision should be written down in the PR, not left as a suffix on an operator name.

The recommendation carries less weight in two situations. The first is when the question is what a real account grants right now, with SCPs, permissions boundaries and other policies in the same evaluation. The second is when the policy is a resource policy, which this post didn't evaluate. In both cases, this reading doesn't replace an evaluation in the account.

You also can't hand this check to a linter. Parliament and CloudFormation Guard analyze local JSON, but they don't evaluate request context or simulate a missing key. The difference between Policies 1 and 2 is beyond what they check.

Next step

Save the three JSON files and walk each one through the User Guide operator table in three cases: key present and inside the range, key present and outside the range, and key missing. For each case, note whether the statement grants. Policy 2 should be the only one that grants when the key is missing. If you have credentials, the Policy Simulator can confirm that reading.

awsiam

// Continue.training — 次のステップ

Knowledge only counts when it becomes practice.

Go back to the article, run the examples, and share what you learned.

Explore more articles