From fc8f4c6ecb6ff7ff6bb550e4d2446dce9e90bdb3 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 13 Nov 2026 02:56:59 -0800 Subject: [PATCH] s3tables: re-anchor legacy owners for federated STS sessions A web-identity role session was keyed on its bare OIDC subject until owners became issuer-scoped. Its sessions now carry the scoped aws:userid but no asserted issuers, so the legacy subject check never applied and the session lost the table buckets and namespaces it had created. Read the configured issuer set for such sessions. Co-Authored-By: Claude Opus 5.5 --- weed/s3api/s3api_tables.go | 8 ++++++++ weed/s3api/s3tables/handler.go | 22 +++++++++++++++++++--- weed/s3api/s3tables/manager.go | 10 ++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/weed/s3api/s3api_tables.go b/weed/s3api/s3api_tables.go index cd2143fc31..2a9303e772 100644 --- a/weed/s3api/s3api_tables.go +++ b/weed/s3api/s3api_tables.go @@ -2,6 +2,7 @@ package s3api import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -63,6 +64,12 @@ func (st *S3TablesApiServer) SetEncryptionChecker(fn func() bool) { st.handler.SetEncryptionChecker(fn) } +// SetTrustedIssuerSource injects the configured OIDC issuer set for S3 Tables +// legacy-owner checks of federated STS sessions. +func (st *S3TablesApiServer) SetTrustedIssuerSource(fn func(context.Context) []string) { + st.handler.SetTrustedIssuerSource(fn) +} + // S3TablesHandler handles S3 Tables API requests func (st *S3TablesApiServer) S3TablesHandler(w http.ResponseWriter, r *http.Request) { st.handler.HandleRequest(w, r, st) @@ -85,6 +92,7 @@ func (s3a *S3ApiServer) registerS3TablesRoutes(router *mux.Router) { s3TablesApi.SetDefaultAllow(s3a.iam.iamIntegration.DefaultAllow()) if s3Integration, ok := s3a.iam.iamIntegration.(*S3IAMIntegration); ok && s3Integration.iamManager != nil { s3TablesApi.SetIAMAuthorizer(s3Integration.iamManager) + s3TablesApi.SetTrustedIssuerSource(s3Integration.trustedOIDCIssuers) } } else { // If IAM is not configured, allow all access by default diff --git a/weed/s3api/s3tables/handler.go b/weed/s3api/s3tables/handler.go index 84565439a5..4092e6fa2e 100644 --- a/weed/s3api/s3tables/handler.go +++ b/weed/s3api/s3tables/handler.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/iam/sts" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" ) @@ -79,6 +80,7 @@ type S3TablesHandler struct { trusted bool // Trusted local tooling (shell/admin) bypasses authorization iamAuthorizer IAMAuthorizer localAccountIDs func(string) bool + oidcIssuers func(context.Context) []string canEncrypt func() bool } @@ -123,6 +125,12 @@ func (h *S3TablesHandler) SetLocalAccountChecker(fn func(string) bool) { h.localAccountIDs = fn } +// SetTrustedIssuerSource injects the configured OIDC issuer set. A federated +// STS session carries no asserted issuers, so its legacy owner checks read it. +func (h *S3TablesHandler) SetTrustedIssuerSource(fn func(context.Context) []string) { + h.oidcIssuers = fn +} + // SetEncryptionChecker injects the predicate that tells whether the gateway // holds an SSE-S3 key, so a bucket whose declared AES256 the write path // applies is not created where every write would then fail. @@ -390,8 +398,9 @@ func claimString(claims map[string]interface{}, key string) string { return str } -// trustedIssuers returns the OIDC issuers the auth layer asserted into the -// identity's claims; they re-anchor legacy owner checks in checkPermission. +// trustedIssuers returns the OIDC issuers that re-anchor legacy owner checks in +// checkPermission: those the auth layer asserted into a bearer token's claims, +// else, for a federated STS session, the configured set. func (h *S3TablesHandler) trustedIssuers(r *http.Request) []string { identityRaw := s3_constants.GetIdentityFromContext(r) if identityRaw == nil { @@ -404,7 +413,14 @@ func (h *S3TablesHandler) trustedIssuers(r *http.Request) []string { if val.Kind() != reflect.Struct { return nil } - return strings.Fields(claimString(identityClaimsMap(val), "seaweed:oidc-issuers")) + claims := identityClaimsMap(val) + if _, asserted := claims["seaweed:oidc-issuers"]; asserted { + return strings.Fields(claimString(claims, "seaweed:oidc-issuers")) + } + if h.oidcIssuers != nil && sts.IsParentUserID(claimString(claims, "aws:userid")) { + return h.oidcIssuers(r.Context()) + } + return nil } // normalizePrincipalID collapses ARN and identity strings to a key that is stable within a single account. diff --git a/weed/s3api/s3tables/manager.go b/weed/s3api/s3tables/manager.go index c89b0349a3..59a5820601 100644 --- a/weed/s3api/s3tables/manager.go +++ b/weed/s3api/s3tables/manager.go @@ -47,6 +47,16 @@ func (m *Manager) SetTrusted(trusted bool) { m.handler.SetTrusted(trusted) } +// SetLocalAccountChecker sets the handler's configured local account predicate. +func (m *Manager) SetLocalAccountChecker(fn func(string) bool) { + m.handler.SetLocalAccountChecker(fn) +} + +// SetTrustedIssuerSource sets the handler's configured OIDC issuer source. +func (m *Manager) SetTrustedIssuerSource(fn func(context.Context) []string) { + m.handler.SetTrustedIssuerSource(fn) +} + // SetEncryptionChecker sets the handler's SSE-S3 availability predicate. func (m *Manager) SetEncryptionChecker(fn func() bool) { m.handler.SetEncryptionChecker(fn)