Repository navigation
fix(auth): follow IAM's rules for policy variables in condition values - #541
Merged
Merged
Conversation
LeeroyHannigan
requested review from
amrith,
c33howard,
jcshepherd,
pdf-amzn,
robinnsc and
yesyayen
as code owners
October 25, 2026 01:00
LeeroyHannigan
force-pushed
the
fix/condition-policy-variables
branch
from
October 28, 2026 23:36
8239ddd to
2f501a5
Compare
In a 2012-10-17 document, every ${key} in a Resource or NotResource
pattern is replaced by the request's value before ARN matching, with
${key, 'default'} falling back to its default and ${*}, ${?} and ${$}
producing literal characters. Text substituted from a value, a default
or an escape is matched literally; a wildcard written in the policy
still matches. A variable whose key the request does not carry, with no
default, makes the pattern match nothing. A 2008-10-17 document, or one
with no Version, matches the pattern as written, so PolicyDocument now
keeps an absent Version as None.
Condition values read the ${key, 'default'} form through the same module.
A multivalued key such as aws:TagKeys or dynamodb:LeadingKeys has no
value, default or not, as main already treats it in a condition value:
a condition value naming one stays as written, and a pattern naming one
matches nothing.
Measured with the IAM policy simulator. Closes #533.
… evaluator One test per measured row of issue #533, plus the escapes, the default form in a condition value, the 2008-10-17 and no-Version documents for NotResource, the permissions boundary and session policy paths, and explicit_deny_matches_arc. PolicyDocument::expands_policy_variables moves beside the expansion code so document.rs does not grow.
…in its field Two rules of IAM's policy variable expansion, measured with the policy simulator: - A multivalued condition key is never a policy variable. A Resource pattern naming one matches no resource and a condition value naming one is not expanded, with one value in the request or several. The resolver gave such a key no value, as main does in a condition value, but it also dropped a default written beside the key. It now reads the key as one the request does not carry, so the default form applies. - Only a colon written in the policy separates ARN fields. A colon inside a substituted value is a byte of its field, so a region-field value of us-east-1:123456789012 matches no table ARN, where it used to shift the fields and match a stream ARN. Tests pin both, variables in the region and account fields, an unresolved variable before a wildcard, and a policy-text ? beside a variable.
Text substituted into a Resource pattern or a condition value from a
variable value or a default is matched as text: it is not expanded a
second time, and a `*` or `?` in it is not a wildcard. An Allow whose
NotResource pattern holds an unresolved variable grants every table.
The `${key, 'default'}` form, and the rule that a multivalued key is
never a variable, hold under every condition operator form, not only
bare StringEquals.
New evaluator tests pin each rule. A resource tag value of
`${aws:username}` names only the table `${aws:username}`, and the same
text reached through `${aws:UserAgent}` is compared as written in a
condition. The defaults `'*'` and `'b?'` match only `table/*` and
`table/b?`. `Allow NotResource table/${aws:PrincipalTag/team}` with no
tag grants `table/blue` and `table/red`. `${aws:PrincipalTag/team,'none'}`
without a space denies `table/none`. A `team` value of `bl*` denies only
`table/bl*`. The default form expands under ForAnyValue, ForAllValues,
IfExists and Null, and `${dynamodb:LeadingKeys}` stays text under
ForAnyValue, ForAllValues and IfExists.
The NotResource Allow, no-space default and `bl*` cases repeat rows the
IAM policy simulator measured; the other cases follow from those rules
and are labelled as not measured.
A condition on a key the service populates but the context cannot
resolve fails closed: a Deny applies and an Allow does not grant. A
`${key}` variable naming such a key in Resource or NotResource was read
as an absent key instead, so `Deny table/${key}` did not apply and
`Allow NotResource table/${key}` granted every table.
`resolve` now reports such a key on its own, before the multivalued
rule and without using a default, and `resource_matches` fails closed
when no pattern matched and one names it. A pattern that matches, or
one whose variable names an absent key with no default, still decides
alone. In this build the context reports one such key,
`aws:RequestTag/<name>` when the request names the tag under two
spellings with different values, so a pattern naming that tag fails
closed for such a request. No key is listed in
`FAIL_CLOSED_GLOBAL_KEYS`; the comment on that list now names both
places that read it. Condition values do not change here: #522's check
fails a condition closed when a plain `${key}` in one of its values
names such a key, and a value with a default is compared as written.
The tests mark `aws:PrincipalOrgID` unresolvable through the test
context. Deny on `table/${aws:PrincipalOrgID}`, with and without a
default, and Deny NotResource block GetItem on `table/T`. Allow on
`table/${aws:PrincipalOrgID, 'T'}` and Allow NotResource do not grant
it. `table/T` in the same list, or an absent key in the same pattern
before or after the unresolvable one, still decides. The multivalued
`dynamodb:LeadingKeys`, marked unresolvable, fails closed the same way
in Resource and NotResource, with and without a default.
Comments in the policy variable code and tests cited cells of a table in an issue comment (C1, D1 and so on), which a reader of the tree cannot look up. Each label is replaced by the case it stood for, with the PR #535 body table as the source: `dynamodb:LeadingKeys` with one value and with two, `aws:RequestedRegion` and `aws:PrincipalAccount` in the region and account fields, a region tag value `us-east-1:123456789012` and `team=blue:x`, and `table/${aws:PrincipalTag/team}*` with no tag beside `table/b?${aws:PrincipalTag/team}`. Comments only.
The header of resource_variables.rs said every test was a measured row,
while the `${?}` and `${$}` escapes, NotResource in a 2008-10-17 or
no-Version document, the condition default form and the boundary,
session policy and explicit_deny_matches_arc tests were never measured.
The Deny NotResource test with no tag said "not measured" although the
simulator measured that case, and the `${*}` heading said "measured"
for both its checks although only `table/x` was measured.
The header now names the tests that repeat a simulator case, the checks
inside them that go beyond the measured target or context, and the
tests that follow from the measured rules. The fields file header names
its two derived checks. Comments only.
The policy write paths checked that a `Version` member exists but not its value, and the value now decides whether Resource and NotResource variables expand. A document whose `Version` was `"2012-10-18"`, `"2012-10-17 "`, `20121017`, `null`, `["2012-10-17"]` or `"2012-10-17T"` was stored, and every variable in its Resource patterns then matched as literal text, with no error at write. `from_json_for_write` now refuses a `Version` that is not the string `2012-10-17` or `2008-10-17`, the two values in IAM's policy grammar, with `invalid Version (must be the string 2012-10-17 or 2008-10-17):` followed by the value. The check sits beside `expands_policy_variables` so document.rs does not grow. It runs at write only, so a stored document with another value keeps parsing. A missing `Version` is still refused by each write path before parsing. The user, group and role policy, permissions boundary and console writes reach the check; an AssumeRole session policy is still checked only for the member. The message IAM returns for such a document was not measured. The test writes each of the six values and expects the refusal, parses each as a stored document, and writes both accepted values. The security model manual and the backlog entry on `Version` are updated.
The requirements, the two auth component designs and the backlog said that policy variables are deferred and that Resource and Condition values are literal strings, which the expansion in policy/variables.rs now contradicts. REQ-ABAC-006 states the requirement as met for Resource, NotResource and condition values, and the non-goals table drops its policy variables row. The policy variables note in 05-component-auth.md describes the expansion and points to the security model manual, the Mode 3 limitation names only the condition-value gaps, and the v3 note no longer calls the support deferred. The backlog entry keeps the two condition-value gaps filed as #534.
Stored policies change decision in three ways when policy variables
expand in Resource and NotResource, and operators had no note of it: a
pattern with a variable now matches the table it names, a condition
value naming a multivalued key is no longer expanded to its first
value, and the `${key, 'default'}` form now expands in condition
values. The entry names each change, including an Allow that now
grants and a Deny NotResource that now spares the named table, the
write-time refusal of a Version other than 2012-10-17 or 2008-10-17,
who is affected, and how to find affected policies. No catalog
migration is required.
A condition value `${key, 'default'}` whose key the context reports
unresolvable keeps its text as written: resolve returns
Missing::Unresolvable whatever the default, and expand_condition_value
falls back to the raw text. No existing test put such a key in a
condition value.
The new case_three test puts `${aws:PrincipalOrgID, 'blue'}` in a
StringEquals Deny on aws:PrincipalTag/team, for a caller tagged
team=blue. With the key unresolvable the Deny does not apply. With the
key absent the default is used and the Deny applies. With the key
present its value is used, so `blue` denies and `red` does not.
resource_pattern_is_unresolvable starts with `expand &&`, so a
`2008-10-17` document, or one with no Version, matches a pattern naming
a key the context cannot resolve as written and does not fail closed.
Every test of the fail-closed rule used a `2012-10-17` document, so no
test covered the gate.
The new case_three test puts `Deny table/${aws:PrincipalOrgID}` in each
of the two documents beside `Allow dynamodb:*`, with the key
unresolvable: GetItem on table `T` is allowed, and GetItem on the table
named `${aws:PrincipalOrgID}`, which the text matches, is denied.
The v0.1.15 upgrade entry and the policy variables paragraph of
05-component-auth.md said that a variable whose key the request does
not carry makes the pattern match nothing. That holds only with no
default. With a default, the default is used:
`table/${aws:PrincipalTag/team, 'none'}` matches `table/none` when the
request has no `team` tag, as measured with the IAM policy simulator
for the PR #535 measurements table. Both places now say so.
Item 2 of the v0.1.15 entry said a condition value naming a multivalued
key is compared as written, so such a condition no longer matches. That
holds for a positive operator. Under a negated operator the comparison
with the text now succeeds where the comparison with the first value
failed: `Allow` if `StringNotEquals` on aws:PrincipalTag/team with the
value `${dynamodb:LeadingKeys}`, for a caller tagged team=blue whose
request has the leading key `blue`, was denied before and is allowed
now, and the same condition in a Deny now applies.
The item now names both operator kinds with that example, and the
check-before-upgrading sentence names an Allow that now grants and a
Deny that now applies, for a Resource pattern with a variable and for
such a negated condition.
- variable_values: the multivalued condition-value test also covers
Null. With dynamodb:LeadingKeys holding `false`, expanding
`${dynamodb:LeadingKeys}` to its first value would make
`Null aws:PrincipalTag/team` match for a caller with a team tag, and
the Deny would apply; the test expects Allow. The module header
already listed Null for both rules.
- case_three: the header says its expectations are ExtendDB rules with
no service measurement. The Version write test says its message is
ExtendDB's own and that whether IAM refuses those values at write was
not measured; validate_version and the backlog row say the same.
- condition.rs: the expand_policy_variables comment says "unresolved"
for a key the request does not carry, and names the key the context
cannot resolve, which is left as written with or without a default.
- Upgrade manual, v0.1.15: a permissions boundary write refuses another
Version with `Invalid permissions boundary document: ...`; a session
policy is checked only for its Version and Statement members; item 3
names the present-key case of `${key, 'default'}`; the search advice
names get-user-boundary, get-role-boundary and list-roles for
boundaries and trust policies.
Measured with the IAM policy simulator on 2026-10-28: with aws:TagKeys
as [x, y] or [x], table/${aws:TagKeys, 'x'} in Resource and
${aws:TagKeys, 'x'} as a StringEquals condition value both answer
implicitDeny; with aws:TagKeys absent both answer allowed. The code
used the default whenever the key was multivalued, present or not. Now
a present multivalued key makes the pattern match nothing and keeps a
condition value as written, and the default is used only when the
request does not carry the key. Two evaluator tests pin the six cells
on dynamodb:LeadingKeys; the security model and the module comment say
the rule.
… key
A condition fails closed when one of its values reads, through a
${key} variable, a key the context reports unresolvable. In this build
that key is aws:RequestTag/<name> when the request names the tag under
two spellings with different values. The check took the whole text
between ${ and } as the key name, so ${aws:RequestTag/env, 'x'} named
`aws:RequestTag/env, 'x'`, which the context does not report. The value
was then compared as written: a StringEquals Deny did not apply and a
StringNotEquals Allow granted, where the plain form fails closed.
reads_unresolvable_variable now takes its names from
variables::variable_key_names, which parses each reference the way
expand_condition_value does and leaves the default out of the name.
Both forms fail closed: a Deny applies and an Allow does not grant,
whatever the operator. The default is not used for such a key, because
the service has a value for it. A key the request does not carry still
takes the default, and a present key still uses its own value. The
expand_policy_variables comment drops "(IAM behavior)": a variable left
as written gives IAM's answer except when the compared value holds the
variable's text.
The case_three test that pinned the old reading is renamed. It now
expects the Deny to apply under StringEquals and StringNotEquals, and
an Allow gated the same way not to grant under either operator. New
tests cover variable_key_names, the default form under three operators
in three_case, including as the second of two values, and an ambiguous
request tag with a default in request_tag_tests.
…osed
The FAIL_CLOSED_GLOBAL_KEYS comment said that a variable naming a
listed key inside a condition value is left as written, so listing a
key does not make such a condition fail closed. Main fails a condition
closed when a ${key} variable in one of its values names such a key,
and the previous commit does the same for ${key, 'default'}. The
comment now says both forms fail closed, as a Resource or NotResource
pattern does, and that a default written beside a listed key is not
used.
Three comments said an unresolved global key such as aws:PrincipalOrgID
or aws:SourceIp fails closed: in RequestContext::is_unresolvable_key,
on AssumeRoleContext and in its is_unresolvable_key. The list is empty,
so such a key keeps absent-key semantics. Each comment now says which
keys the context reports unresolvable: in RequestContext an ambiguous
aws:RequestTag/<name>, and in both contexts a key listed in
FAIL_CLOSED_GLOBAL_KEYS.
LeeroyHannigan
force-pushed
the
fix/resource-policy-variables
branch
from
October 31, 2026 16:59
d5c5722 to
57861f5
Compare
4 tasks done
…10-17 documents
A condition value in a 2008-10-17 document, or in one with no Version, is
compared as written: ${aws:username} is the literal text. Only a 2012-10-17
document expands variables, the same rule as Resource and NotResource.
In a 2012-10-17 document the escapes ${*}, ${?} and ${$} stand for the
literal characters. StringLike and ArnLike match the escaped character
literally while a * or ? written in the policy stays a wildcard, so a${*}
matches a* and not abc. The result of an escape is not expanded again.
A variable with no default whose key the request does not carry makes the
value match nothing: a positive operator is false against it and a negated
operator holds, except that every Arn operator is false against a context
value that is not an ARN, whatever the value (measured on 2026-10-31,
sixteen cells, one evaluator test each).
A variable naming a multivalued key the request carries, such as
${aws:TagKeys}, has no value either, as IAM evaluates it (measured on
2026-10-31). #522 treated it as literal text; its tests, wire test
docstrings and differences-from-dynamodb.md row now say it has no value.
The document's choice reaches condition evaluation through a flag on
evaluate_statement_conditions and evaluate_condition_match; the identity,
boundary and session policy paths pass their own document's Version. The
trust policy path keeps expanding whatever the Version.
Measured with the IAM policy simulator on 2026-10-24, nineteen cells, one
evaluator test each. Closes #534.
… for string and ARN operators
Two rules measured on IAM after the condition-variable change:
- A trust policy follows the same Version rule as an identity policy.
With AssumeRole on fresh roles, a 2008-10-17 trust policy, and one
with no Version, compares ${aws:PrincipalArn} in a condition as
literal text, and a 2012-10-17 one expands it. The trust evaluator
expanded variables in every trust policy.
- Only the string and ARN operators expand variables, with or without
a set qualifier or IfExists. NumericEquals and the date and address
operators compare a value written as a variable as written; Null and
Bool read it as a boolean, as below.
Tests pin both, and the measured cases for a wildcard inside a
substituted value, IfExists and the set qualifiers with an unresolved
variable. The security model states the operator rule, the trust
policy rule, and how a negated operator behaves for a caller without
the key.
The IAM policy simulator reads a Null or Bool value written as a
variable as the boolean false. A Deny
with Null aws:PrincipalTag/team: ${aws:PrincipalTag/flag} applies
whenever the team tag is present, whatever flag holds or defaults to.
A Deny with Bool aws:SecureTransport: ${aws:PrincipalTag/flag} applies
whenever SecureTransport is false. Before this change ExtendDB
expanded the variable under both operators and, with flag set to true,
allowed both requests.
A Null or Bool value that contains ${ is now read as a boolean, true
only when the text is true ignoring case. Null then requires the key to
be present and Bool matches a request value that is not true. Other
values compare as before. The tests pin the eleven measured cells, N1a
to N1f for Null (N1f is the Allow with the condition alone) and N2a to
N2e for Bool.
Two of #535's tests in variable_values.rs change with the Null
reading. the_default_form_expands_in_a_null_condition is renamed
a_null_value_written_with_a_default_reads_as_false. Its two decisions
still hold, because Null reads the text as false and needs the key
present; the default is not used. The Null assertion of
a_multivalued_key_in_a_condition_value_is_not_expanded_under_any_form
now expects Deny: the text reads as false and team is present, the
shape of measured cell N1a. The variable_values.rs module doc says
that Null expands neither form and that the default form was measured
under StringEquals.
A condition value naming a key the context reports unresolvable fails
the condition closed only under an operator that expands it. A
case_three.rs test pins that for ArnLike and ArnNotLike, with and
without a default, and that the same value in a 2008-10-17 document is
compared as written. The evaluate_condition_match doc says that a
value that also names an absent key with no default fails closed,
although it matches nothing.
A condition value such as ${aws:ResourceTag/blockedPrincipal} has no
":" written outside its reference. The ArnLike token matcher splits a
value into fields only at a written ":", so this value was one field
against the six of the request ARN: ArnLike never matched and
ArnNotLike always held. An ArnNotLike Allow on aws:PrincipalArn then
granted the principal that the tag names, and the ArnLike Deny written
the same way stopped applying to that principal.
The IAM policy simulator refuses a policy with such a value
(InvalidInput) and IAM Access Analyzer reports MISSING_ARN_FIELD, so
IAM gives no decision to match. In a document that expands variables,
an Arn* condition whose value holds a ${...} reference and fewer than
five ":" outside its references now evaluates as Unresolvable, with or
without a set qualifier or IfExists. A Deny with such a condition
applies and an Allow does not grant.
The tests pin the Deny and the Allow-only policies under ArnLike,
ArnNotLike, ArnEquals and ArnNotEquals and three qualified forms, and
that a value with five written colons and a variable in the resource
field still matches as before.
The doc on ConditionValue::Expanded::text listed the numeric, date, boolean and address operators as readers of the expanded text. Only the string and ARN operators expand a value, so the text is read by StringEquals, StringNotEquals and their IgnoreCase forms; the Like and Arn operators match the tokens. derived_variable_value_wildcards_are_literal_in_string_like sat under the derived heading of condition_variables.rs, but its cell was measured on 2026-10-25, and string_like_reads_a_substituted_star_as_a_character in condition_variables_operators.rs already pins it. The derived copy is removed and the module doc points at the measured one. The comment on operator_expands_variables said IAM documents variables for string comparisons and ARNs. IAM's operator reference also lists the Boolean operators and says a variable cannot be used with Null. The comment now gives the reference and what the IAM policy simulator measured for each operator family.
operator_expands_variables expands only the string and ARN operators,
and the security model says a date or address value written as a
variable is compared as written, but adding DateEquals or IpAddress to
the expanding operators failed no test.
The new test denies with DateEquals aws:CurrentTime against
${aws:PrincipalTag/when} when the tag holds the request time, and
evaluates IpAddress aws:SourceIp against ${aws:PrincipalTag/net} when
the tag holds a range containing the source address. Neither condition
matches. The IpAddress half goes through parse_conditions and
evaluate_statement_conditions, the trust policy path, because an
identity policy with that value does not parse: the value is not a
CIDR.
A stored policy whose condition values hold a policy variable can get a
different decision after the upgrade. The v0.1.15 entry gives each case
an example. The cases are the Version rule, the escapes, a wildcard
from a variable value, a variable with no value or default, numeric,
date and address values, Bool and Null values read as false, and an
Arn* value without written ARN fields, which now fails closed.
The entry names the commands that print stored policies, so an
operator can search them for ${ before upgrading. No catalog migration
is required.
It follows the v0.1.15 entry for Resource and NotResource variables
and corrects that entry's items 2 and 3, which described condition
values as compared before this change. In a 2012-10-17 document a
value naming a multivalued key the request carries now matches
nothing, and the default form expands only under the string and ARN
operators; Null and Bool read it as false, and the numeric and date
operators compare it as written. It also corrects the v0.1.15 entry for
the tag condition keys, which said a condition value ${aws:TagKeys}
stays literal text: the variable has no value, so a positive operator
is false against it and a negated one true.
An Arn* condition value that contains ${ and has fewer than five : written
outside its ${...} references cannot have an ARN's six fields. The rule that
makes such a condition Unresolvable read the document's expand_variables
flag, so in a 2008-10-17 or no-Version document the value went to the
ordinary comparison: an ArnLike Deny on ${aws:ResourceTag/blockedPrincipal}
applied to no request, and an ArnNotLike Allow on it granted every principal.
IAM checks a value's fields whatever the Version, so the rule no longer reads
the flag.
The new tests run the A7 and A8 policies in a 2008-10-17 document and in one
with no Version under ArnLike, ArnNotLike, ArnEquals and ArnNotEquals and
expect Deny. A second test pins that a value with its five : written is still
compared as written in those documents. The docs on evaluate_condition_match,
ConditionMatch::Unresolvable and arn_value_lacks_fields, the security model
and upgrade item 7 say the rule holds in any Version and applies to a value
that contains ${.
The v0.1.15 upgrade entry said every rule was measured with the IAM policy simulator. Its first sentence now names the measured cases: the string, ARN, numeric, Bool and Null operators in 2012-10-17 documents, and StringEquals in 2008-10-17 and no-Version documents. The date and address operators follow the numeric case. The docs on null_holds and bool_matches say that cells N1a to N2e are 2012-10-17 documents and that a 2008-10-17 or no-Version document is read the same way, because IAM reads a Null or Bool text without expanding it whatever the Version. That reading was not measured. No code path changes.
A Bool value written as a variable is read as false, and the request value is compared with true ignoring case, as a Bool value without a variable compares it. No test pinned the request side: comparing it with == "true" in that branch passed every test. The new test is cell N2c with aws:SecureTransport spelled TRUE and expects Allow. It is labelled not measured.
Upgrade item 4 said an unresolved variable changes the decision only for a
request value equal to its text. Under StringLike, ArnLike or ArnEquals a value
that also has a written * or ? changed for every request value the text matched
as a pattern, and the item says so. The "Who is affected" list names that
shape: a string or ARN value with a variable that has no default.
Item 7 says that only the ArnLike Deny and the ArnNotLike Allow in 2012-10-17
documents were sent to the IAM policy simulator, and that the ArnEquals,
ArnNotEquals, qualified, 2008-10-17 and no-Version forms follow the same rule
without a measurement.
The ${ branches of null_holds and bool_matches no longer compare the policy
text with true, which a text that contains ${ never is. They return
!key_absent and !context_value.eq_ignore_ascii_case("true"), with a comment
saying why. The decisions do not change.
A condition value now expands only in a 2012-10-17 document and only under the string and ARN operators, and Null and Bool read a variable value as false. A variable naming a key the context reports unresolvable fails the condition closed only where the value is expanded. A test comment and several docs from #535 described the earlier comparison. In case_three.rs the test for an unresolvable key in a condition value keeps its name and decisions, and its comment now names the string and ARN operators. The FAIL_CLOSED_GLOBAL_KEYS doc and the case_three.rs module doc say that a value naming such a key fails the condition closed only in a 2012-10-17 document under a string or ARN operator, and that a value that is not expanded reads no key. REQ-ABAC-006 and the Policy Variables note and limit 4 of 05-component-auth.md no longer list the #534 gaps, and the backlog item for them is removed, because the Version rule and the escape expansion close them. The security model sentence on multivalued keys says a written default is used only when the request does not carry the key.
LeeroyHannigan
force-pushed
the
fix/condition-policy-variables
branch
from
October 31, 2026 17:54
2f501a5 to
662f85b
Compare
LeeroyHannigan
requested review from
eliseong,
hitesh96db and
navareamol
as code owners
November 4, 2026 23:18
eliseong
previously approved these changes
Nov 5, 2026
Collaborator
There was a problem hiding this comment.
@LeeroyHannigan LGTM, other than needing a rebase.
This change makes a policy variable in a condition value follow IAM's rules. I confirmed each rule against live IAM.
- A document with
Version2012-10-17expands the variable. A document withVersion2008-10-17, and a document with noVersion, compares the value as written text. - The escapes
${*},${?}and${$}become the characters*,?and$. InStringLike,ArnLikeandArnEqualsthis character stays literal. A*or?that the policy writes stays a wildcard. - A GetItem against a running server gives the same decisions. A
2008-10-17Deny on${aws:username}does not apply to the caller's own table. The2012-10-17form of that Deny applies to it.
One separate point, not part of this change: the write path accepts a condition value with an unterminated ${, and the evaluator compares it as literal text. IAM refuses such a policy. This behaviour is present before this change. I filed it as #834, but if it's simple enoough to roll it in to this PR then we should do so.
crates/auth/src/policy/condition.rs: main passes the document's Version
into the condition functions as version_2012 and reads the default form
and a trimmed key name only in a 2012-10-17 document under a string or
ARN operator, while ${key} kept expanding everywhere. The branch expands
a value only in those places, returns a ConditionValue, matches a
substituted wildcard literally, fails Arn* values without written ARN
fields closed, and reads a Null or Bool variable as false. The merge
keeps main's signatures and names and applies the branch's rules: the
helper expands_variables replaces reads_iam_variable_syntax and gates
the whole expansion, and a value that is not expanded is compared as
written. Main's measured rows for the default form stay in its doc.
crates/auth/src/policy/variables.rs: main counts wildcards in UTF-16
code units, trims the code points up to U+0020 around a key name, and
added an iam_syntax switch under which ${key, 'default'} names the key
as written. The branch adds ConditionValue, arn_value_lacks_fields and
the expand switch on expand_condition_value. The merge keeps the UTF-16
tokens and the trimming and builds the branch's ConditionValue on them.
The iam_syntax switch is superseded: where it was false the branch does
not expand the value at all, so parts and variable_key_names take no
such argument.
crates/auth/src/policy/variables/tests.rs: main's expand_condition_value
tests compare a String and one test pins the iam_syntax false path. The
branch's tests compare a ConditionValue. The merge keeps the branch's
tests, drops the second argument of variable_key_names, and rewrites the
iam_syntax test as without_expansion_the_whole_value_is_literal_text.
Main's trimming and UTF-16 tests stay.
crates/auth/src/policy/evaluator.rs: both sides pass the document to the
condition helpers in the same way. The merge keeps main's text and says
that only a 2012-10-17 document expands the values.
crates/auth/src/policy/evaluator/tests.rs: main adds the modules
condition_variable_scope, empty_values and variable_spelling, the branch
the four condition_variables modules. The merge lists all seven.
crates/auth/src/policy/evaluator/tests/variable_values.rs: both sides
renamed the Null default-form test. The merge keeps both tests, main's
row N5 and the branch's cell N1f, and joins the module docs.
crates/auth/src/policy/evaluator/tests/case_three.rs,
crates/auth/src/policy/context.rs,
crates/auth/src/policy/condition/tests/three_case.rs and
crates/auth/src/policy/context/request_tag_tests.rs: main changed these
only through #535, whose commits this branch already carries, and the
branch's own commits changed them further. The merge keeps the branch's
versions.
crates/auth/src/policy/evaluator/tests/resource_variables_fields.rs:
the branch's own commits do not touch it and main adds a comment. The
merge keeps main's version.
crates/server/src/management/assume_role.rs: both sides pass the trust
policy's Version through trust_decision_for. The merge keeps main's
names, version_2012 and trust_policy_is_version_2012, says in the doc
that only such a policy expands its condition variables, and adds the
branch's trust_version_tests module, whose import takes main's name.
docs/backlog.md and docs/design/01-requirements.md: the branch's own
commits changed them and main did not beyond #535. The merge keeps the
branch's versions, which no longer list #534 as open.
docs/design/05-component-auth.md: main describes where the default form
is read; the branch describes where condition values expand at all. The
merge keeps the branch's paragraph.
docs/differences-from-dynamodb.md: main rewrote the backup ARN row; the
branch rewrote the ${aws:TagKeys} sentence of the tag condition keys
row. The merge keeps both.
docs/manuals/07-upgrade-manual.md: main added item 4 (trimmed key
names) and separators to the Resource entry; the branch corrected items
2 and 3 and added its own v0.1.15 condition values entry. The merge
keeps items 1 to 4 with the branch's wording of item 3 plus main's
sentences on IpAddress and trust policies, and places the branch's entry
between separators after the Resource entry.
docs/manuals/10-security-model.md: main added the trimming rule and the
operator list for the default form; the branch rewrote the paragraph on
condition values. The merge keeps the branch's paragraph with main's
trimming sentence and operator list.
Beyond the conflicts, the module doc and one comment of
crates/auth/src/policy/evaluator/tests/condition_variable_scope.rs now
describe the merged rule, since the file said that a Bool variable is
not read as false.
eliseong
approved these changes
Nov 5, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Nov 5, 2026
LeeroyHannigan
added a commit
that referenced
this pull request
Nov 5, 2026
Brings in #541, which applies IAM's rules for policy variables in condition values. No file conflicted. The auth and server crates pass fmt, clippy and the auth library tests on the merged tree.
8 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #534. Stacked on #535: this branch builds on
fix/resource-policy-variablesand reuses itsvariables.rsmodule, so the diff below is the eleven commits on top of it.Summary
Policy variables in condition values now follow IAM's rules, each measured with the IAM policy simulator or, for trust policies, with AssumeRole:
2012-10-17document expands variables in condition values. A2008-10-17document, or one with noVersion, compares${aws:username}as the literal text, and the default form too. This applies to identity policies, permissions boundaries, session policies and trust policies.${*},${?}and${$}expand to the literal characters. InStringLikeandArnLike(andArnEquals) the escaped character, and any character that comes from a substituted value, is matched literally, while a*or?written in the policy is a wildcard.${key, 'default'}expands todefaultwhen the key is absent.IfExistsand the set qualifiers following the same rule, except that everyArn*operator is false against a context value that is not an ARN, whatever the value. Before this change the variable was left as literal text and compared.BoolorNullvalue written as one is read as the booleanfalse, as the IAM policy simulator reads it, whatever the variable holds or defaults to:Nullholds when the key is present, andBoolholds when the request value isfalse(measured, rows N1a to N2e below). OtherBoolandNullvalues compare as before. IAM Access Analyzer reports a variable in either operator asINVALID_VARIABLE_FOR_OPERATOR; the simulator evaluates it anyway.Arn*operator, with or without a set qualifier orIfExists, a value that contains${and has fewer than five:written outside its${...}references, such as${aws:ResourceTag/blockedPrincipal}, fails the condition closed in a document of anyVersion: aDenyapplies and anAllowdoes not grant. The ARN matcher splits a value into fields only at a:written in the policy, so such a value cannot have an ARN's six fields. The IAM policy simulator refuses a2012-10-17policy with such a value (InvalidInput) and IAM Access Analyzer reportsMISSING_ARN_FIELD, so IAM gives no decision to match. IAM checks a value's fields whatever theVersion, so a2008-10-17or no-Versiondocument, which does not expand the variable, fails closed too; those documents were not sent to the simulator. A value with its five:written, such asarn:aws:iam::123456789012:user/${aws:username}, expands and matches as before in a2012-10-17document and is compared as written in the others.dynamodb:LeadingKeyshas no value when the request carries the key, and a written default is used when it does not (from fix(auth): expand policy variables in Resource and NotResource #535).evaluate_conditionkeeps its signature for its existing callers and expands as a2012-10-17document does;evaluate_condition_withandevaluate_statement_conditionstake the document's choice, which the evaluator passes from each identity policy, boundary and session policy and the trust evaluator from the trust policy'sVersion.Measurements
IAM policy simulator, 24 and 25 October 2026, action
dynamodb:GetItemonarn:aws:dynamodb:us-east-1:123456789012:table/blue,Allow dynamodb:*on*besideDeny dynamodb:GetItemon*with the condition below. Keys areaws:PrincipalTag/team(team),aws:ResourceTag/owner(owner) andaws:SourceArnunless named.StringLiketeama${*}abca*StringLiketeama*abcStringEqualsteam${?}?StringLiketeama${?}aba?StringEqualsteam${$}$StringEqualsteama${$}{x}a${x}StringEqualsteam${*}*${*}StringEqualsowner${aws:username}alice, owneralicealice, owner${aws:username}alice, owner${aws:username}ArnLikeSourceArnuser/a${*}user/abcuser/a*StringEqualsowner${aws:username, 'anon'}anonanonStringEqualsowner${aws:username}${aws:username}StringNotEqualsowner${aws:username}aliceStringLikeowner${aws:PrincipalTag/team}a*, ownerabca*, ownera*ArnLikeorArnEqualsSourceArnuser/${aws:PrincipalTag/name}alice, SourceArnuser/aliceal*, SourceArnuser/aliceStringEqualsIfExistsowner${aws:username}alice, no userStringNotEqualsIfExistssamealice, no userForAllValues:StringNotEqualsaws:TagKeys${aws:username}[a, b], no userForAnyValue:StringEqualssame[a, b], no userNumericEqualsaws:MultiFactorAuthAge${aws:PrincipalTag/age}300, MultiFactorAuthAge 300Boolaws:SecureTransport${aws:PrincipalTag/flag}true, SecureTransport trueIAM policy simulator, 28 October 2026, two samples each, the same two policies except that N1f and A8 are an
Allow dynamodb:GetItemwith the condition and nothing else. Keys areaws:PrincipalTag/team(team),aws:PrincipalTag/flag(flag) andaws:SecureTransport.Nullteam${aws:PrincipalTag/flag}blueNullteam${aws:PrincipalTag/flag, 'false'}blueNullteam${aws:PrincipalTag/flag}blue, flagtruetrueNullteam${aws:PrincipalTag/flag, 'false'}, Allow aloneblue, no flagBoolSecureTransport${aws:PrincipalTag/flag}falsetruetruefalseArnLikeaws:PrincipalArn${aws:ResourceTag/blockedPrincipal}ArnNotLike, same value, Allow aloneIAM Access Analyzer
ValidatePolicyon the same day: theArnLikevalue is an ERROR findingMISSING_ARN_FIELD, and a variable inNullorBoolis an ERROR findingINVALID_VARIABLE_FOR_OPERATOR("Policy variables can only be used with String and ARN operators.").Trust policies, AssumeRole on fresh roles whose trust policy allows the caller's role under the condition on
aws:PrincipalArnagainst${aws:PrincipalArn}:StringEqualsStringNotEqualsStringEqualsStringNotEqualsTesting
explicit_deny_matches_arcpaths. The simulator refuses rows A7 and A8, socondition_variables_arn.rspins their policies asDeny(failed closed) under all fourArn*operators, underArnNotLikeIfExists,ForAllValues:ArnNotLikeandForAnyValue:ArnLike, and in a2008-10-17document and one with noVersion. Only theArnLikeDeny and theArnNotLikeAllow in2012-10-17documents were sent to the simulator; theArnEquals,ArnNotEquals, qualified,2008-10-17and no-Versionrows were not. The same file pins that a value with five written:still matches in a2012-10-17document and is compared as written in the other two. A test withaws:SecureTransportspelledTRUEunder aBoolvariable value pins that the request value is read ignoring case; it is not measured. A derived test pins thatDateEqualsandIpAddressvalues with a variable are not expanded; itsIpAddresshalf drivesparse_conditionsandevaluate_statement_conditions, the trust policy path, because an identity policy with that value does not parse.662f85b3bwithcargo test -p extenddb-auth --lib: the document'sVersionignored by condition values (11 tests fail), escapes left as text (10), an escaped*or?read as a wildcard (6), an unresolved variable left as text (8), every operator expanding (7), theArn*fail-closed rule removed (3), theArn*rule applied only when the document expands variables (1), theNullandBoolreading of a variable asfalseremoved (4), a request value under a variableBoolvalue compared withtruecase-sensitively (1), andDateEqualsorIpAddressadded to the expanding operators (1 each). The trust policy'sVersionignored fails 2 of the 3 tests incrates/server'strust_version_tests.rs, whichcargo test -p extenddb-authdoes not run.cargo fmt --all --checkandcargo clippy --locked --workspace --all-targets -- -D warningson Rust 1.97.0,cargo check --locked --workspaceon Rust 1.88.0, andcargo test --locked -p extenddb-auth --liband--docon 1.97.0: 406 unit tests and 3 doc tests passed;cargo test --locked -p extenddb-server --lib: 238 passed. Each of the eleven commits passescargo test --locked -p extenddb-auth --lib. On a SQLite server and on a PostgreSQL server built from this head, the 72 tests oftests/test_tag_request_conditions.py,tests/test_tag_request_variable_conditions.py,tests/test_tag_request_repeated_keys.py,tests/test_tag_request_array_conditions.pyandtests/test_authz_arn_conditions.pypassed on each. CI runs the workspace tests.662f85b3b.Behaviour change for existing policies
2008-10-17document, or one with noVersion, no longer expands a variable in a condition value:${aws:username}is the literal text. This covers user, group and role policies, permissions boundaries, session policies and role trust policies. In such a document aDenywithStringEqualsonaws:ResourceTag/ownerand the value${aws:username}no longer applies to the caller's own table, and the sameDenywritten withStringNotEqualsnow applies to it.${*},${?}and${$}now stand for the characters*,?and$, soStringEqualswith the value${*}matches*where it matched the text${*}. InStringLike,ArnLikeandArnEqualsthe escaped character matches only itself.*or?that comes from a variable value is now a character inStringLike,ArnLikeandArnEquals, where it was a wildcard. ADenywithStringLikeonaws:ResourceTag/ownerand the value${aws:PrincipalTag/team}no longer applies to a caller taggedteam=a*on a table taggedowner=abc.${...}was compared, so the decision changes only for the request values that text matched: a positive operator no longer matches them and a negated operator now holds for them. UnderStringEqualsthose are the request values equal to the text; underStringLike,ArnLikeorArnEquals, when a*or?is also written in the value, such as${aws:username}*, they are every request value the text matches as a pattern.DenywithNumericGreaterThanonaws:MultiFactorAuthAgeand the value${aws:PrincipalTag/maxAge}, or withDateGreaterThanonaws:CurrentTimeand the value${aws:PrincipalTag/expiry}, no longer applies. An IP address value written as a variable still fails to parse outside trust policies (it is not a CIDR); in a trust policy it is no longer expanded and matches no request.BoolorNullvalue written as a variable is read asfalseinstead of being replaced by the variable's value (rows N1a to N2e). ADenywithNullonaws:PrincipalTag/teamand the value${aws:PrincipalTag/flag}now applies to every caller with ateamtag (N1a, N1c), and no longer applies to a caller without one whoseflagistrue(N1d). ADenywithBoolonaws:SecureTransportand the same value now applies to every request made without TLS (N2a, N2b, N2e) and no longer applies to a TLS request whoseflagistrue(N2c).Arn*value that contains${and has fewer than five written:now fails closed in a document of anyVersion. Before, the substituted ARN was compared as a whole, so aDenywithArnLikeonaws:PrincipalArnand the value${aws:ResourceTag/blockedPrincipal}applied only to the principal the tag named; it now applies to every principal. TheAllowwritten withArnNotLikegranted every other principal and now grants none. To compare a principal with an ARN stored whole in a tag, useStringEqualsin a2012-10-17document.${aws:TagKeys}in a condition value has no value, in identity and trust policies. Since fix(auth): resolve aws:TagKeys and aws:RequestTag for the tag operations #522,maincompares its text, and before fix(auth): resolve aws:TagKeys and aws:RequestTag for the tag operations #522 a trust policy expanded it to one session tag key. A positive operator is now false against it and a negated one true, so aDenywithStringNotEqualsonaws:PrincipalTag/envand that value now applies to every caller, and anAllowwritten that way grants every caller (IAM policy simulator, 31 October 2026).aws:RequestTag/<name>that the request names under two spellings with different values, fails the condition closed only in a2012-10-17document under a string or ARN operator. Since fix(auth): resolve aws:TagKeys and aws:RequestTag for the tag operations #522,mainfails it closed under any operator in a document of anyVersion. In a2008-10-17or no-Versiondocument, and under the numeric, date, address,NullandBooloperators, the value is compared as written, or read asfalseunderNullandBool.Rebase on #535 and main
This branch is rebased onto the rebuilt #535 (
57861f592), which ismainat1d0d8f4a2with #535's commits on top.mainnow has #471, which changes theArn*operators, and #522, which adds the tag condition keys. Both change code this branch changes, and in three places their rules and this branch's decide the same condition differently. The first two are resolved as IAM decides them, measured with the IAM policy simulator (SimulateCustomPolicy, read-only) on 31 October 2026: two samples per row, and both samples agreed on all 32 rows. Every policy in the two tables below is a2012-10-17Allow dynamodb:GetItemon*with the condition and nothing else. The third concerns a state the service does not have, so it follows this branch's rule for where a variable is expanded.1.
Arn*operators (#471)#471 makes a context value that is not an ARN match none of the four
Arn*operators, and evaluatesArnEqualsasArnLike. This branch makes a value whose variable has no value match nothing, so a negated operator holds against it. The two rules give different answers when such a value is compared with a context value that is not an ARN. The value isarn:aws:dynamodb:us-east-1:123456789012:table/${aws:PrincipalTag/team}onaws:SourceArn, and ARN x isarn:aws:dynamodb:us-east-1:123456789012:table/x.notanarnnotanarnArnLikeArnEqualsArnNotLikeArnNotEqualsSo the non-ARN rule is checked first.
compare_singlereturns false for everyArn*operator when the context value is not an ARN, and only after that gives a value with no value its answer: false forArnLikeandArnEquals, true forArnNotLikeandArnNotEquals. OtherwiseArnEqualsandArnLikecallvalue.arn_matches(context_value)and the negated pair its negation, soArnEqualsnow also matches a*or?from a substituted value or an escape as that character. AnArn*value without its written ARN fields still fails the condition closed.condition_variables_arn_context.rshas one test per cell. With the value's answer given before the non-ARN check, the tests of the twonotanarncells with noteamfail:ArnNotLikeandArnNotEqualsallow there, where IAM does not. With this branch's earlierArn*arms, which had no non-ARN check and comparedArnEqualsexactly, 24 tests fail: 20 of #471's tests and the four negatednotanarncells.2.
${aws:TagKeys}(#522)#522 left a condition value that names a multivalued key as text, and two of its tests said that text equals itself. This branch gives such a variable no value whether or not the request carries the key, as IAM does: IAM gives it no value whether the key holds two values, one, or none, and does not compare the variable's text. The value is
${aws:TagKeys}onaws:PrincipalTag/env.${aws:TagKeys}${aws:TagKeys}StringEqualsStringLikeStringNotEqualsStringNotLikepolicy_variable_naming_aws_tagkeys_is_left_literalinmultivalued.rsis renamedpolicy_variable_naming_aws_tagkeys_has_no_valueand checks these sixteen cells.trust_policy_variable_naming_aws_tagkeys_is_left_literalinrequest_tag_tests.rsis renamedtrust_policy_variable_naming_aws_tagkeys_has_no_value; it expectsStringEqualsto be false andStringNotEqualstrue, including for a principal tag that holds the variable's text. Two other #522 tests passed unchanged but had "left literal" in their names; they are nowpolicy_variable_naming_aws_tagkeys_has_no_value_for_every_tag_orderandpolicy_variables_naming_the_dynamodb_multivalued_keys_have_no_value. #522's v0.1.15 upgrade entry said the variable stays literal text in identity and trust policies, and so did its row indocs/differences-from-dynamodb.mdand the docstrings oftests/test_tag_request_variable_conditions.py. Each now says the variable has no value. The decisions of that wire file are unchanged, because no tag value in its requests equals the variable's text. With #522's rule put back,policy_variable_naming_aws_tagkeys_has_no_valueandtrust_policy_variable_naming_aws_tagkeys_has_no_valuefail.3. Keys the context reports unresolvable (#522 and #535)
#522, with #535's change for the default form, fails a condition closed when one of its values names a key the context reports unresolvable, through
${key}or${key, 'default'}. In this build that key isaws:RequestTag/<name>when the request names the tag under two spellings with different values. This branch decides where a variable is expanded, so the check now runs only there: in a2012-10-17document and under a string or ARN operator. In a2008-10-17or no-Versiondocument, and under the numeric, date, address,NullandBooloperators, the value is not expanded and reads no key. It is compared as written, or read asfalseunderNullandBool, and does not fail closed. A value that also names a key the request does not carry, with no default, fails closed too, although it matches nothing whatever the unresolvable key holds; aResourcepattern of that shape does not. This is an ExtendDB rule with no service measurement, because the service has one value for the key.Earlier, this description and the
expand_condition_valuedoc said such a value matches nothing and does not fail closed. The doc,ConditionValue::Unresolved, thevariables.rsmodule doc, theFAIL_CLOSED_GLOBAL_KEYScomment and thecase_three.rsmodule doc now state the rule above. #535'sa_condition_value_naming_an_unresolvable_key_fails_closed_with_or_without_a_defaultkeeps its name and decisions, so the last commit no longer renames it. Three tests are new: the same value in a2008-10-17document and in one with noVersionis compared as written, and so is the value underNumericLessThan, each with and without a default; and underArnLikeandArnNotLikethe value fails the condition closed with and without a default, and is compared as written in a2008-10-17document. Removing theVersiongate fails the2008-10-17test and theArn*test, removing the operator gate fails theNumericLessThantest, and removing the check fails five tests, among them the default form and theArn*test. Taking only theArn*operators out of the check fails theArn*test alone.4. The text of a variable with no value (#535)
On
mainand on #535, a variable with no default whose key the request does not carry is left as its text, and the text is compared. #535 measured that IAM does not compare it (IAM policy simulator, 30 October 2026, two samples per row, both samples agreeing on all 80 rows): withaws:PrincipalTag/envset to the exact text${aws:PrincipalTag/team}and noteamtag,StringEqualsis false. This branch gives such a variable no value, soStringEqualsis false here too, and the case needs no separate issue.Other changes in the rebase
ConditionValuedocs say thatArnEqualsandArnNotEqualsmatch the tokens and not the text.ArnEqualsbesideStringLikeandArnLikein items 2 to 4 and in the list of shapes that change, because fix(auth): match IAM wildcards in ArnEquals and ArnNotEquals #471 madeArnEqualshonour wildcards. The security model and thevariables.rsmodule doc also nameArnEqualsbesideStringLikeandArnLike, and the security model says that everyArn*operator is false against a request value that is not an ARN.expand_variablesargument. None of those tests builds a document, so each passestrue.Rust 1.97.0 on
662f85b3b:cargo fmt --all --check,cargo clippy --locked --workspace --all-targets -- -D warnings, andcargo test --locked -p extenddb-auth --liband--doc: 406 unit tests and 3 doc tests passed;cargo test --locked -p extenddb-server --lib: 238 passed. Rust 1.88.0:cargo check --locked --workspace. On a SQLite server and on a PostgreSQL server built from662f85b3b, the 62 tests oftests/test_tag_request_conditions.py,tests/test_tag_request_variable_conditions.py,tests/test_tag_request_repeated_keys.pyandtests/test_tag_request_array_conditions.pypassed on each backend, and so did the 10 oftests/test_authz_arn_conditions.py.Built on #535
This branch was rebased onto #535's tip (
57861f592). Beside the three resolutions above, the rebase made these changes:resolvereturnsResult<&str, Missing>on fix(auth): expand policy variables in Resource and NotResource #535.expand_condition_valuemaps bothMissing::AbsentandMissing::UnresolvabletoConditionValue::Unresolved, so a condition value naming a key the context cannot resolve matches nothing when it is compared; in a2012-10-17document under a string or ARN operator the condition fails closed before that comparison (section 3 above). The doc ofConditionValue::Unresolvednames the three ways to reach it: an absent key with no default, a multivalued key the request carries, and a key the context cannot resolve.variable_values.rschanged.the_default_form_expands_in_a_null_conditionwas renameda_null_value_written_with_a_default_reads_as_false; its decisions held, becauseNullreads the text${aws:PrincipalTag/flag, 'false'}asfalseand does not use the default. Ina_multivalued_key_in_a_condition_value_is_not_expanded_under_any_formtheNullassertion now expectsDeny: the text${dynamodb:LeadingKeys}reads asfalseandteamis present, the decision of row N1a. Its context carriesdynamodb:LeadingKeysastrue, so an expansion to the first value would readteamas absent and let the request through. The comments of both give that reason.FAIL_CLOSED_GLOBAL_KEYSincontext.rsand the module doc ofcase_three.rsnow say that a value naming such a key fails the condition closed only in a2012-10-17document under a string or ARN operator, and that a value that is not expanded reads no key;a_condition_value_naming_an_unresolvable_key_fails_closed_with_or_without_a_defaultkeeps its name and decisions. The module doc ofvariable_values.rsnames the measured cells behind itsNullcases. The backlog item "Policy-variable gaps in condition values (Policy variables in condition values: Version 2008-10-17 and the escapes #534)" was removed, and REQ-ABAC-006 indocs/design/01-requirements.mdand the Policy Variables note and limit 4 ofdocs/design/05-component-auth.mdno longer say that Policy variables in condition values: Version 2008-10-17 and the escapes #534 gaps remain.ResourceandNotResource, this branch's for condition values, and fix(auth): resolve aws:TagKeys and aws:RequestTag for the tag operations #522's for the tag condition keys. Two items of fix(auth): expand policy variables in Resource and NotResource #535's entry and one sentence of fix(auth): resolve aws:TagKeys and aws:RequestTag for the tag operations #522's were corrected. Item 2 now says that in a2012-10-17document a value naming a multivalued key the request carries matches nothing, and that a written default is used only when the request does not carry the key. Item 3 now says that the default form expands only under the string and ARN operators in a2012-10-17document, thatNullandBoolread it asfalse, and that the numeric and date operators compare it as written. fix(auth): resolve aws:TagKeys and aws:RequestTag for the tag operations #522's entry now says that the variable${aws:TagKeys}in a condition value has no value, in identity and trust policies, where it said the value stays literal text.dynamodb:LeadingKeysis not expanded" gained fix(auth): expand policy variables in Resource and NotResource #535's rule: when the request carries the key the value matches nothing, and a written default is used only when the request does not carry it.#535 checks
Versionon user, group and role policy, boundary and console writes, and the rebase does not extend that check to trust policies. A trust policy goes throughvalidate_trust_policy, which accepts anyVersion, and on this branch the trust policy'sVersiondecides whether its condition values expand. A trust policy stored with"2012-10-17 "or"2012-10-18"therefore compares its variables as text, with no error at write. WhetherCreateRolerefuses such a value was not measured.Related behaviour
Arn*value that contains${and has fewer than five:written outside its${...}references is not refused at write. The IAM policy simulator refuses it and IAM Access Analyzer reports an error for it; whetherPutUserPolicyandCreateRolerefuse it was not measured, so the value is stored and its condition fails closed when evaluated, in a document of anyVersion.NullandBoolvalues that are not variables compare as before:Nullmatches nothing for a value other than the exacttrueorfalse, andBoolcompares the request value with the text. How IAM reads a text that is not a boolean, such as"True"inNullor"no"inBool, was not measured.NullorBoolvalue written as a variable is read asfalsein a document of anyVersion. Rows N1a to N2e are2012-10-17documents. A2008-10-17or no-Versiondocument gets the same reading, without a measurement, because IAM reads the text without expanding it whatever theVersion.Checklist
cargo fmt,cargo clippycleandocs/manuals/10-security-model.md,docs/manuals/07-upgrade-manual.mdunder v0.1.15)