developmentseed / developmentseed/multistore
Credential cache key (role ARN) doesn't capture the full minted identity (subject + extra_claims)
- Dominant language
- Rust
- Stars
- 19
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
The OIDC backend credential cache keys entries on the **role ARN alone**, while the credential that gets minted is a function of the full identity `(role_arn, subject, extra_claims)`. Today those always co-vary, so there is **no live vulnerability** — but the key does not enforce that invariant, leaving a latent cross-identity credential-reuse hazard for future callers.
Surfaced during the review of #61 (shared single-flight credential cache).
## Current behavior (not exploitable today)
- `OidcCredentialProvider::get_credentials(cache_key, exchange, subject, extra_claims)` caches on `cache_key` but federates using `subject` + `extra_claims` (`crates/oidc-provider/src/lib.rs`).
- The only production caller, `AwsBackendAuth::resolve_aws` (`crates/oidc-provider/src/backend_auth.rs`), derives **both** the key (`role_arn` from `oidc_role_arn`) and `subject` (from `oidc_subject`, default `"s3-proxy"`) from the **same `BucketConfig`**, and always passes `extra_claims = &[]`.
- `resolve_aws` never reads the inbound caller identity — the proxy federates **as itself** into the backend, and inbound authorization is enforced upstream. So two callers hitting the same OIDC bucket *correctly* share one backend STS session.
Because `(role_arn, subject, extra_claims)` move in lockstep per bucket config, the cache key is sufficient **in the current code**.
## The latent risk
Nothing in the type system or the cache enforces that the key captures everything the minted JWT encodes. If a future caller ever passes a **caller-derived `subject`** or a **non-empty `extra_claims`** (e.g. per-caller `source_identity` for STS session tagging) while keeping the `role_arn`-based key, the cache would serve identity A's backend session to identity B — a real cross-identity credential leak.
The pre-#61 cache docstring even hinted at the ambiguity ("an opaque key the caller chooses — e.g. a role ARN, *or the rendered OIDC subject*"), so this predates the merge.
## Recommended fix
Make the cache key derive from the **full minted identity** rather than trusting the caller to pass a sufficient key. E.g. have `get_credentials` compute the key internally from `(role_arn, subject, extra_claims)` (concatenated or hashed) instead of accepting a caller-supplied `cache_key`, so the key can never drift from what the JWT encodes.
## Severity
Minor / latent — defense-in-depth. Not reachable on `main` today; worth closing before any caller-specific `subject`/`extra_claims` is introduced.
## Pointers
- `crates/oidc-provider/src/lib.rs` — `get_credentials`
- `crates/oidc-provider/src/backend_auth.rs` — `resolve_aws`
- `crates/oidc-provider/src/cache.rs` — `CredentialCache::get_or_fetch`
Contributor guide
Research direction
Start with get_credentials in crates/oidc-provider/src/lib.rs, then trace resolve_aws in backend_auth.rs and get_or_fetch in cache.rs. Compare the cache key with the minted identity tuple of role ARN, subject, and extra_claims. The work is done when differing identity inputs cannot reuse one cached credential while the current caller behavior remains valid.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, rust
- Domain
- authentication, backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100