fix(policy): resolve query-only condition keys from the query alone - #267
harshavardhana wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change marks five condition keys as query-only. Value lookup now skips canonical-header fallback for these keys. Tests verify query-name lookup, header-key behavior, and the documented key set. ChangesQuery-only condition handling
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to The query-only lookup behavior is covered by the described implementation and tests, with no actionable merge risk identified. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. A rabbit checks the query trail Comment |
getValuesByKey falls back to the canonical header spelling of a key's name,
which is how a header-sourced key such as s3:x-amz-server-side-encryption is
found: Name() gives the lowercase form while a server records the header
canonically.
Applied to a query-only key that fallback is a bypass. s3:prefix, s3:delimiter,
s3:max-keys, memory:prefix and memory:max-keys each document their value as
coming from a query parameter only, but CanonicalHeaderKey("prefix") is
"Prefix", so a caller could satisfy a condition on s3:prefix with a request
header while the listing itself ran with no prefix at all -- the condition
checking one value and the request being served with another.
Those keys now resolve from the exact name or not at all. Header-sourced keys
keep the fallback.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
932432b to
e51a0e6
Compare
|
Closing: not worth the blast radius. The behaviour is real -- a Narrowing |
Summary
A condition key documented as coming from a query parameter only could be
satisfied by a request header instead. Those keys now resolve from the exact
name or not at all.
What the fallback is for, and where it goes wrong
getValuesByKeylooks a key up byName()— the lowercase form left after theservice prefix is stripped — and falls back to the canonical header spelling:
That fallback is load-bearing for header-sourced keys.
s3:x-amz-aclbecomes
x-amz-acl, while a server records the header it came fromcanonically as
X-Amz-Acl, so without the fallback those keys never resolve.Applied to a query-only key it is a bypass. Five keys document their value as
coming from a query parameter only:
Name()s3:prefixprefixPrefixs3:delimiterdelimiterDelimiters3:max-keysmax-keysMax-Keysmemory:prefixprefixPrefixmemory:max-keysmax-keysMax-KeysA server merges request headers into the condition values, so a caller can
supply
Prefix: allowed/as a header with noprefixquery parameter atall. The condition is then evaluated against
allowed/while the listing runswith no prefix — the condition checking one value and the request being served
with another. Measured against a real
ListObjectsV2request and a policywhose
s3:ListBucketgrant is bounded byStringLike s3:prefix:A present-but-empty
?prefix=was already safe: the exact name is then foundand the fallback never runs. Only an entirely absent query parameter reached it.
Changes
KeyName.IsQueryOnly()plus thequeryOnlyKeysset naming the five keys.getValuesByKeyskips the canonical-header fallback for a query-only key.Header-sourced keys are untouched.
Testing
go build ./...andgo test ./...pass.TestQueryOnlyKeyIgnoresAHeaderValuecovers all five keys against a headervalue, each query-only key against its exact query name, and two
header-sourced keys in both spellings, so the fallback is pinned as still
working where it is needed.
TestQueryOnlyKeysAreTheDocumentedSetasserts the set both ways, so a keyadded to one and not the other fails.
Verified end to end against an unmodified AIStor build with a local
replace:the header-only row flips to denied and the query rows are unchanged.
Scope
This changes condition evaluation for every policy using these keys, not one
feature, which is why it is separate from #266 — it is reviewable and
backportable on its own. Say the word and I will fold it into #266 instead.
Summary by CodeRabbit