Summary
multistore federates identity inbound (callers → proxy, via multistore-sts + multistore-oidc-provider). The symmetric outbound half is missing: the proxy presenting its own identity to a backend cloud to obtain short-lived credentials for a private bucket.
Today a backend is either anonymous (anonymous_access / skip_signature) or configured with long-lived static keys in BucketConfig.backend_options. This issue tracks adding first-class outbound credential federation so deployments can serve private buckets without storing long-lived backend secrets. PRs below reference this issue.
Motivation
- Operators increasingly want to front customer-owned buckets. Long-lived keys at the edge are a liability: rotation, blast radius, secret-at-rest.
- With OIDC federation the bucket config holds only a role ARN; the proxy assumes a short-lived, optionally prefix-scoped role at request time. Trust and blast radius are governed by the bucket owner's IAM policies, not the proxy's correctness.
- multistore already has the building blocks — it can act as an OIDC provider and already does inbound STS — so this simply completes the symmetry.
Design
At bucket-resolution time, per backend:
- Mint a short-lived OIDC assertion with the proxy's signing key (
multistore-oidc-provider), scoped via its aud / sub claims.
- Exchange it at the backend cloud's STS — AWS
AssumeRoleWithWebIdentity, with Azure / GCS workload-identity analogs — for temporary credentials.
- Inject those into
BucketConfig.backend_options so the existing backend signs requests with them instead of going anonymous.
No long-lived secret is stored anywhere; credentials are short-lived and refreshed before expiry.
Constraints discovered while prototyping
- S3 needs no core change. object_store's
AmazonS3ConfigKey::Token accepts the token alias, and token is already in REDACTED_OPTION_KEYS, so temporary creds (access key + secret + session token) flow through and are redacted in logs.
- Azure / GCS need a bearer-credential source. Workload-identity federation yields an OAuth bearer token, but the Azure backend takes a shared
access_key and GCS a service_account_key — neither accepts a bearer token. Parity requires accepting bearer/access-token credentials (likely an object_store contribution).
- Caching matters and is subtle. Short-lived creds must be cached and refreshed per backend, independent of the caller. Anything varied at assume-time is baked into the credential and fragments the cache, so the cache key should track the credential identity (role / subject), not the caller. This is a generic concern every integrator would otherwise reinvent.
Roadmap
Out of scope
Application-specific policy — which role to use for which bucket, and how the OIDC sub is structured/scoped — stays in the integrator's BucketRegistry. multistore provides the mechanism, not the policy.
Summary
multistore federates identity inbound (callers → proxy, via
multistore-sts+multistore-oidc-provider). The symmetric outbound half is missing: the proxy presenting its own identity to a backend cloud to obtain short-lived credentials for a private bucket.Today a backend is either anonymous (
anonymous_access/skip_signature) or configured with long-lived static keys inBucketConfig.backend_options. This issue tracks adding first-class outbound credential federation so deployments can serve private buckets without storing long-lived backend secrets. PRs below reference this issue.Motivation
Design
At bucket-resolution time, per backend:
multistore-oidc-provider), scoped via itsaud/subclaims.AssumeRoleWithWebIdentity, with Azure / GCS workload-identity analogs — for temporary credentials.BucketConfig.backend_optionsso the existing backend signs requests with them instead of going anonymous.No long-lived secret is stored anywhere; credentials are short-lived and refreshed before expiry.
Constraints discovered while prototyping
AmazonS3ConfigKey::Tokenaccepts thetokenalias, andtokenis already inREDACTED_OPTION_KEYS, so temporary creds (access key + secret + session token) flow through and are redacted in logs.access_keyand GCS aservice_account_key— neither accepts a bearer token. Parity requires accepting bearer/access-token credentials (likely an object_store contribution).Roadmap
multistore-federationcrate: a runtime-agnosticAssumeRoleWithWebIdentityrequest builder + XML response parser (caller owns HTTP), andFederatedCredentials::apply_to(&mut BucketConfig). Additive, no core changes. Branch:feat/backend-federation. Validated end-to-end against real AWS STS in a downstream deployment (mint →AssumeRoleWithWebIdentity→ signedListObjectsV2, including asubtrust-policy condition).BackendCredentialProvidertrait + a caching wrapper (TTL, refresh-ahead, single-flight) so integrators get correct, non-thundering-herd refresh for free.Out of scope
Application-specific policy — which role to use for which bucket, and how the OIDC
subis structured/scoped — stays in the integrator'sBucketRegistry. multistore provides the mechanism, not the policy.