* sts: recognize issuer-scoped parent user ids
ComputeParentUser output has a fixed shape (base64url SHA-256); let
callers distinguish it from bare subject strings.
* s3tables: keep legacy owner re-anchoring off local and scoped account ids
Re-anchoring a stored bare subject under the single trusted issuer must
only apply to strings that could actually be a legacy OIDC subject. A
stored owner naming the reserved or a configured local account, or an
already issuer-scoped id, can otherwise be claimed by any caller whose
token asserts that string as sub.
* s3tables: re-anchor legacy policy principals in map and array forms
A stored bare subject can appear as {"AWS": sub} or inside a principal
list, not only as a top-level string; re-anchoring only the string form
left legacy Deny statements unable to match the caller.
* s3tables: keep built-in legacy exclusions independent of the account checker
A missing local-account predicate must not disable re-anchoring entirely:
the reserved accounts, ARN-shaped values, and scoped-id shapes are never
OIDC subjects regardless, while legacy Deny statements for plain subjects
still need to resolve so a denied caller cannot fall through to an
identity grant.
* s3tables: exercise legacy Deny against an owner grant
The previous case denied the caller regardless of whether the statement
principal matched; make the caller the owner so a missed match shows.
Cover the array Deny form alongside the AWS map.
* s3api: test that a session token must not reveal its credential
* sts: derive secret access key with HMAC keyed on the signing key
* s3api: stop accepting STS session tokens as bearer credentials
* security: reject STS session tokens on filer and admin gRPC auth
* test: sign s3/iam framework requests with the session credential
* s3api: exercise the real auth pipeline in the end-to-end harness
* Add ResolveIdentityClaim helper for OIDC audit identity
ComputeParentUser derives a stable per-identity hash from (sub, iss) for
internal keying, but it is opaque and not human-readable. Audit logs for
STS-assumed OIDC sessions currently surface that opaque value (or the
random session id) as the requester, leaving no authoritative trace of the
federated user.
Add ResolveIdentityClaim next to ComputeParentUser to recover a
human-readable, server-asserted identity attribute from the STS request
context populated at federation time. It walks a priority list
(preferred_username, email, name, sub) so a federated session always
audits against a stable OIDC claim rather than a client-supplied role
session name.
For #11264
* Surface authoritative OIDC identity claim in S3 audit log
For STS-assumed sessions minted from an OIDC web identity, the audit log
requester field is the opaque session subject, which cannot be traced back
to the federated user who performed the operation. The OIDC identity claims
(preferred_username, email, sub) are already carried in the session request
context and reach the auth layer as identity.Claims, but they were never
surfaced to the audit log.
Add a requester_identity field to the S3 access audit log, populated from
the authoritative OIDC identity claim resolved via ResolveIdentityClaim.
The claim is propagated through the shared identity holder (the same
mechanism the requester name and principal ARN already use) so it survives
the request-context copy that hides auth-set values from the outer audit
middleware.
The existing requester field is left unchanged for backward compatibility;
requester_identity is empty for non-federated sessions, where requester
already carries the real username.
For #11264
* Gate OIDC audit identity on federation marker and harden resolver
Address review feedback (Devin Review, Greptile) on the initial
implementation:
- Non-federated STS sessions no longer gain a false requester_identity.
ValidateJWTWithClaims merges the JWT registered sub claim (the opaque
session id) into RequestContext for sessions without an explicit request
context, so the previous ResolveIdentityClaim fallback to sub surfaced
that session id as an authoritative identity. Resolution is now gated on
SessionInfo.ParentUser, which is set only for OIDC-federated sessions in
AssumeRoleWithWebIdentity. The claim is resolved from the original
sessionInfo.RequestContext (not the local claims map, whose sub the bearer
path overwrites with the session subject) so SigV4 and bearer sessions
surface the same identity.
- ResolveIdentityClaim now trims whitespace and treats whitespace-only
claims as absent, so a blank preferred_username no longer masks a usable
email or sub.
The resolved claim is carried on Identity.IdentityClaim (and IAMIdentity for
the bearer path) rather than re-derived in recordIdentityInContext, making
the federation gate explicit at the auth boundary.
For #11264
* Resolve OIDC identity claim for external bearer tokens
The external OIDC bearer path (a raw OIDC JWT presented directly, not via
STS) populates Claims with preferred_username/email/name/sub from the
validated token but did not set IdentityClaim, so requester_identity stayed
blank for that authentication path. Resolve the claim there too — sub is the
real OIDC subject on this path (not an STS session id), so no federation
gate is needed.
Also drop an ineffectual ctx assignment flagged by ineffassign in the audit
test.
For #11264
Without iss, the same `sub` from two different IDPs would collapse to
the same parent_user hash. Short-circuit to empty when either input
is missing so callers see "no identity" instead of a colliding hash.
Addresses coderabbit review on PR #9318.
* feat(iam): STS web-identity AWS-fidelity polish
- OIDC discovery via .well-known/openid-configuration; falls back to
/.well-known/jwks.json when discovery is absent. Reject discovery docs
whose issuer claim does not match the configured issuer to defend
against issuer-substitution.
- ComputeParentUser derives a stable per-identity hash from (sub, iss).
Surface as aws:userid in the request context and as a parent_user
claim in the session JWT so per-user state survives token rotation.
- Per-role MaxSessionDuration (3600..43200) clamps requested
DurationSeconds before the STS service applies its own caps.
- Tighten RoleSessionName to the AWS contract: 2..64 chars from
[\w+=,.@-].
- Populate PackedPolicySize in AssumeRole / AssumeRoleWithWebIdentity /
AssumeRoleWithLDAPIdentity responses as a percentage of the 2048-byte
inline session policy budget.
* fix(iam): leave omitted DurationSeconds nil so STS default applies
capDurationByRole was substituting the role's MaxSessionDuration
when the caller omitted DurationSeconds entirely. AWS returns the
configured default (typically 1 hour) in that case, not the role's
upper bound — a 12h MaxSessionDuration shouldn't silently make every
no-duration assume-role mint a 12h session.
Return nil when requested is nil; let the downstream
calculateSessionDuration in the STS service apply its TokenDuration
default. The role-max upper bound still clamps when the request
arrives with a concrete value above the cap.
Addresses gemini high-priority review on PR #9318.
* fix(iam): synchronize OIDCProvider JWKS cache fields
jwksCache, jwksFetchedAt, resolvedJWKSUri, and discoveryFailed are
mutated lazily on the first token-validate call and refreshed
afterwards on TTL expiry. Multiple S3 requests can land here in
parallel, so the writes were racing against subsequent reads on
every other goroutine. resolvedJWKSUri/discoveryFailed inherited
the same un-protected pattern when discovery shipped.
Add sync.RWMutex; getPublicKey takes the read lock for the
common cache-hit path and promotes to the write lock for misses
+ refreshes. fetchJWKSLocked / resolveJWKSUriLocked assume the
write lock is held by the caller; fetchJWKS keeps the
test-friendly entry point that acquires the lock itself.
Addresses gemini high-priority review on PR #9318.
* fix(iam): trim trailing slash + retry discovery after transient failure
Two OIDC discovery edge cases reviewers flagged:
1. Issuer comparison was sensitive to trailing slashes. resolveJWKSUri
trims them when building the discovery URL, but the doc.Issuer ↔
p.config.Issuer check did not, so an IDP whose issuer claim drops or
adds the slash relative to the configured value would be falsely
rejected. Trim a single trailing slash on each side before comparing.
2. discoveryFailed flipped to true on any error and stayed there for the
process lifetime. A transient 5xx at startup permanently locked the
provider into the /.well-known/jwks.json fallback. Reset the flag at
the top of fetchJWKSLocked when no URI has been cached yet, so each
JWKS refresh (typically once per TTL = 1h) reattempts discovery.
Successful discovery remains cached via resolvedJWKSUri so we don't
pay the discovery RTT on every refresh.
Addresses gemini security-medium + medium reviews on PR #9318.
* fix(iam): require non-empty issuer in OIDC discovery doc
The previous "doc.Issuer != "" && ..." guard let a discovery document
that omitted the issuer field bypass the issuer-mismatch check
entirely, letting the doc steer fetchJWKS at any URL it provided.
OIDC Discovery 1.0 §3 mandates the issuer field; treat missing as a
hard failure same as mismatched. Trailing-slash equivalence still
applies.
Adds TestDiscoveryRejectsMissingIssuer alongside the existing
TestDiscoveryRejectsIssuerMismatch via a new omitDiscoveryIssuer
toggle on fakeIDP.