Repository navigation
test(auth): pin the order of the scope date and token expiry checks - #949
Open
LeeroyHannigan wants to merge 3 commits into
Open
LeeroyHannigan wants to merge 3 commits into
LeeroyHannigan wants to merge 3 commits into
Conversation
crates/auth/src/tests.rs held 613 lines. Lines 293 to 613 move to a new file, crates/auth/src/scope_tests.rs, which lib.rs declares beside mod tests. They hold nine tests of the request timestamp and credential scope checks and of the order of the checks. The helpers that only these tests use move with them: provider_without_credentials, make_date_stale, SCOPE_DATE_MESSAGE, yesterday, signed_with_scope and refusal. The helpers that both files use stay in tests.rs and become pub(super): MockCredentialStore and its fields, make_headers_with_auth, SECRET, signed_headers and provider. rustfmt wraps the signature of signed_headers, which is now longer than 100 columns. No test body, assertion or name changes. The moved lines are byte-identical to lines 293 to 613 of the old tests.rs. Before and after the move, cargo test for extenddb-auth lists and passes the same 515 unit tests, nine of them now under scope_tests. tests.rs now has 296 lines and scope_tests.rs has 331.
DynamoDB compares the credential scope date before it checks whether a session token has expired. Measured on 2026-11-10 in us-east-1 with signed ListTables requests and temporary credentials, two sends per case and the same answer each time: - expired token, scope date of the X-Amz-Date day: 400 ExpiredTokenException, "The security token included in the request is expired" - expired token, scope date one day earlier: 400 InvalidSignatureException, "Date in Credential scope does not match YYYYMMDD from ISO-8601 version of date from HTTP. " (the message ends with a space) ExtendDB answers both cases the same way. authenticate() runs check_scope before lookup_credential, and the lookup checks the session expiry. The new test in scope_tests.rs checks that order for both cases. It reads the session through CachedCredentialStore, so the ExpiredTokenException type and message come from the expiry check in CachedCredentialStore::lookup_credential. The same request with a matching scope date authenticates through a CachedCredentialStore built the same way when the session ends an hour from now. The expiry is the only fault in the matching case. The measurement recorded the error type after the "#" only, so the test does not compare the prefix. signed_with_scope_and_token is signed_with_scope with an optional session token, sent and signed. It sorts the signed header list, so x-amz-security-token comes before x-amz-target, as in the measured requests. signed_with_scope calls it with no token, so its existing callers sign the same requests as before. credential() in tests.rs becomes pub(super) for the new test.
The SigV4 section of the security model manual says its steps run in the order listed. It listed the access key lookup before the X-Amz-Date check and the credential scope check. BuiltinAuthProvider runs validate_timestamp, then check_scope, then lookup_credential, and its doc comment gives that as the service's order. The list now follows the code. The X-Amz-Date check is step 3, the scope check is step 4 and the key lookup is step 5. The scope step names the date comparison that check_scope makes. The lookup step says that the lookup checks the session expiry and refuses an expired session at once with ExpiredTokenException, before the signature check. The storage layer checks the expiry on a cache miss, and CachedCredentialStore checks it on a cache hit. The lookup step also says that the lookup reads whether the key is active, and that step 9 applies that decision. Step 9 now refers to steps 5 and 6 for the deferred decisions. DynamoDB checks the scope date before the session expiry (measured on 2026-11-10 in us-east-1). With an expired session token, a scope date one day earlier answers InvalidSignatureException with the scope date message. The same token with a matching scope date answers ExpiredTokenException. The order of the X-Amz-Date, scope and key lookup checks was measured on 2026-10-23, as the comment above validate_timestamp records.
LeeroyHannigan
requested review from
amrith,
c33howard,
eliseong,
hitesh96db,
jcshepherd,
navareamol,
pdf-amzn and
robinnsc
as code owners
November 11, 2026 01:09
This branch has not been deployed
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.
What
The three commits change no answer that ExtendDB gives.
crates/auth/src/tests.rsto the newcrates/auth/src/scope_tests.rs, with no change to a test. tests.rs keeps the shared helpers, nowpub(super). tests.rs held 613 lines before the move and holds 296 after it.scope_date_is_compared_before_the_session_token_expiryto scope_tests.rs, which then holds 429 lines. The test sends a temporary key with a signed session token. The store holds that session, and the provider reads it throughCachedCredentialStore, so the expiry refusal comes from the crate's own check.docs/manuals/10-security-model.mdin the order ofcrates/auth/src/lib.rs: theX-Amz-Datecheck, then the credential scope check, then the key lookup. Step 4 now names the scope date, and step 5 names the session expiry refusal. Step 9 points at steps 5 and 6.Why
Follow-up to #355 and #512. #512 moved the scope date check ahead of the key lookup. As a result, an expired session token with a stale scope date gets the scope date refusal. No test pinned that order, and the security model listed the key lookup first.
Measured on Amazon DynamoDB in us-east-1 on 2026-11-10, two sends of each request, with temporary credentials:
X-Amz-DateX-Amz-DateThe order of the
X-Amz-Date, scope and key lookup checks rests on the 2026-10-23 measurements that the comment abovevalidate_timestampincrates/auth/src/lib.rsrecords.Testing done
The runs used the same three patches on bf5ea8f, before they moved onto main 89b9427. Main's commits between the two change neither
crates/authnor the manual.cargo +1.97.0 test --locked -p extenddb-auth: 516 unit tests, 14sigv4_differentialtests and 3 doc tests passed. The base and the move alone each give 515, 14 and 3.check_scope, 515 passed and 1 failed. The failure is the new test's stale scope case, which got ExpiredTokenException where it expects InvalidSignatureException.cached_session_past_expires_at_returns_expired_token.cargo +1.97.0 clippy --locked --all-targets -- -D warningswith the two feature sets of CI: no warnings.cargo +1.97.0 fmt --all -- --check: clean.#, because the probe recorded only that part. The prefix that the service sends is not measured.Checklist
cargo test --workspace)cargo fmt --check)cargo clippy -- -W clippy::pedantic)Storagetrait, auth model, on-diskformat, or public CLI surface, an RFC has been accepted or is linked
below. Otherwise, an ADR captures the decision (link below).
Only the auth crate ran locally, and CI runs the workspace tests. Clippy ran with
-D warnings, without the pedantic group.ADR / RFC: n/a
By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache License 2.0 and I agree to the Developer Certificate of
Origin (DCO). See CONTRIBUTING.md for details.