diff --git a/weed/s3api/s3tables/handler.go b/weed/s3api/s3tables/handler.go index 4092e6fa2e..742f94201d 100644 --- a/weed/s3api/s3tables/handler.go +++ b/weed/s3api/s3tables/handler.go @@ -402,6 +402,22 @@ func claimString(claims map[string]interface{}, key string) string { // 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 { + claims := requestClaims(r) + 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 +} + +// callerSubject returns the OIDC subject in the request identity's claims. +func callerSubject(r *http.Request) string { + return claimString(requestClaims(r), "sub") +} + +func requestClaims(r *http.Request) map[string]interface{} { identityRaw := s3_constants.GetIdentityFromContext(r) if identityRaw == nil { return nil @@ -413,14 +429,7 @@ func (h *S3TablesHandler) trustedIssuers(r *http.Request) []string { if val.Kind() != reflect.Struct { return nil } - 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 + return identityClaimsMap(val) } // normalizePrincipalID collapses ARN and identity strings to a key that is stable within a single account. @@ -596,6 +605,7 @@ func (h *S3TablesHandler) authorizeAbsentTarget(r *http.Request, filerClient Fil } ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespace, diff --git a/weed/s3api/s3tables/handler_bucket_create.go b/weed/s3api/s3tables/handler_bucket_create.go index 0370354798..3ddec4eab3 100644 --- a/weed/s3api/s3tables/handler_bucket_create.go +++ b/weed/s3api/s3tables/handler_bucket_create.go @@ -71,6 +71,7 @@ func (h *S3TablesHandler) handleCreateTableBucket(w http.ResponseWriter, r *http } if !CheckPermissionWithContext("CreateTableBucket", principal, owner, "", "", &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, IdentityActions: identityActions, DefaultAllow: h.defaultAllowFor(r), diff --git a/weed/s3api/s3tables/handler_bucket_get_list_delete.go b/weed/s3api/s3tables/handler_bucket_get_list_delete.go index 34b8b8ab04..1e5e92e687 100644 --- a/weed/s3api/s3tables/handler_bucket_get_list_delete.go +++ b/weed/s3api/s3tables/handler_bucket_get_list_delete.go @@ -72,6 +72,7 @@ func (h *S3TablesHandler) handleGetTableBucket(w http.ResponseWriter, r *http.Re identityActions := getIdentityActions(r) if !CheckPermissionWithContext("GetTableBucket", principal, metadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, IdentityActions: identityActions, @@ -197,6 +198,7 @@ func (h *S3TablesHandler) handleListTableBuckets(w http.ResponseWriter, r *http. identityActions := getIdentityActions(r) if !CheckPermissionWithContext("GetTableBucket", accountID, metadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: entry.Entry.Name, IdentityActions: identityActions, @@ -332,6 +334,7 @@ func (h *S3TablesHandler) handleDeleteTableBucket(w http.ResponseWriter, r *http identityActions := getIdentityActions(r) if !CheckPermissionWithContext("DeleteTableBucket", principal, metadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, IdentityActions: identityActions, diff --git a/weed/s3api/s3tables/handler_maintenance.go b/weed/s3api/s3tables/handler_maintenance.go index bd971e64cf..6004e7022f 100644 --- a/weed/s3api/s3tables/handler_maintenance.go +++ b/weed/s3api/s3tables/handler_maintenance.go @@ -361,6 +361,7 @@ func (h *S3TablesHandler) authorizeMaintenanceBucket(r *http.Request, filerClien principal := h.getAccountID(r) if !CheckPermissionWithContext(operation, principal, bucketMetadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, IdentityActions: getIdentityActions(r), @@ -410,6 +411,7 @@ func (h *S3TablesHandler) authorizeMaintenanceTable(r *http.Request, filerClient principal := h.getAccountID(r) ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, diff --git a/weed/s3api/s3tables/handler_namespace.go b/weed/s3api/s3tables/handler_namespace.go index f693ce833e..8981bf6d10 100644 --- a/weed/s3api/s3tables/handler_namespace.go +++ b/weed/s3api/s3tables/handler_namespace.go @@ -162,6 +162,7 @@ func (h *S3TablesHandler) handleCreateNamespace(w http.ResponseWriter, r *http.R glog.Infof("S3Tables: CreateNamespace permission check - principal=%s, owner=%s, actions=%v", principal, bucketMetadata.OwnerAccountID, identityActions) if !CheckPermissionWithContext("CreateNamespace", principal, bucketMetadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, @@ -388,6 +389,7 @@ func (h *S3TablesHandler) handleGetNamespace(w http.ResponseWriter, r *http.Requ identityActions := getIdentityActions(r) if !CheckPermissionWithContext("GetNamespace", principal, metadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, @@ -480,6 +482,7 @@ func (h *S3TablesHandler) handleUpdateNamespace(w http.ResponseWriter, r *http.R identityActions := getIdentityActions(r) if !CheckPermissionWithContext("UpdateNamespace", principal, metadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, @@ -600,6 +603,7 @@ func (h *S3TablesHandler) handleListNamespaces(w http.ResponseWriter, r *http.Re identityActions := getIdentityActions(r) if !CheckPermissionWithContext("ListNamespaces", principal, bucketMetadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, TableBucketTags: bucketTags, @@ -798,6 +802,7 @@ func (h *S3TablesHandler) handleDeleteNamespace(w http.ResponseWriter, r *http.R identityActions := getIdentityActions(r) if !CheckPermissionWithContext("DeleteNamespace", principal, metadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, diff --git a/weed/s3api/s3tables/handler_policy.go b/weed/s3api/s3tables/handler_policy.go index cf455df912..bb78476b3c 100644 --- a/weed/s3api/s3tables/handler_policy.go +++ b/weed/s3api/s3tables/handler_policy.go @@ -167,6 +167,7 @@ func (h *S3TablesHandler) handlePutTableBucketPolicy(w http.ResponseWriter, r *h identityActions := getIdentityActions(r) ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, TableBucketTags: bucketTags, @@ -267,6 +268,7 @@ func (h *S3TablesHandler) handleGetTableBucketPolicy(w http.ResponseWriter, r *h identityActions := getIdentityActions(r) if !CheckPermissionWithContext("GetTableBucketPolicy", principal, bucketMetadata.OwnerAccountID, string(policy), bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, IdentityActions: identityActions, @@ -345,6 +347,7 @@ func (h *S3TablesHandler) handleDeleteTableBucketPolicy(w http.ResponseWriter, r identityActions := getIdentityActions(r) if !CheckPermissionWithContext("DeleteTableBucketPolicy", principal, bucketMetadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, IdentityActions: identityActions, @@ -466,6 +469,7 @@ func (h *S3TablesHandler) handlePutTablePolicy(w http.ResponseWriter, r *http.Re identityActions := getIdentityActions(r) ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, @@ -591,6 +595,7 @@ func (h *S3TablesHandler) handleGetTablePolicy(w http.ResponseWriter, r *http.Re identityActions := getIdentityActions(r) ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, @@ -687,6 +692,7 @@ func (h *S3TablesHandler) handleDeleteTablePolicy(w http.ResponseWriter, r *http identityActions := getIdentityActions(r) ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, @@ -825,6 +831,7 @@ func (h *S3TablesHandler) handleTagResource(w http.ResponseWriter, r *http.Reque identityActions := getIdentityActions(r) ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: tableNamespace, @@ -988,6 +995,7 @@ func (h *S3TablesHandler) handleListTagsForResource(w http.ResponseWriter, r *ht identityActions := getIdentityActions(r) ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: tableNamespace, @@ -1117,6 +1125,7 @@ func (h *S3TablesHandler) handleUntagResource(w http.ResponseWriter, r *http.Req identityActions := getIdentityActions(r) ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: tableNamespace, diff --git a/weed/s3api/s3tables/handler_table.go b/weed/s3api/s3tables/handler_table.go index 5d9cb71d92..6d5e0525f6 100644 --- a/weed/s3api/s3tables/handler_table.go +++ b/weed/s3api/s3tables/handler_table.go @@ -501,6 +501,7 @@ func (h *S3TablesHandler) handleGetTable(w http.ResponseWriter, r *http.Request, metadata.OwnerAccountID, tablePolicy, tableARN, bucketMetadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespace, @@ -652,6 +653,7 @@ func (h *S3TablesHandler) handleListTables(w http.ResponseWriter, r *http.Reques nsMeta.OwnerAccountID, namespacePolicy, bucketARN, bucketMeta.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, @@ -705,6 +707,7 @@ func (h *S3TablesHandler) handleListTables(w http.ResponseWriter, r *http.Reques identityActions := getIdentityActions(r) if !CheckPermissionWithContext("ListTables", accountID, bucketMeta.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, TableBucketTags: bucketTags, @@ -1059,6 +1062,7 @@ func (h *S3TablesHandler) handleDeleteTable(w http.ResponseWriter, r *http.Reque metadata.OwnerAccountID, tablePolicy, tableARN, bucketMetadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, @@ -1409,6 +1413,7 @@ func tablePurgeDir(entry *filer_pb.Entry, tablePath, bucketName string) (string, func (h *S3TablesHandler) canReadEntry(readOp, principal, tableOwner, bucketOwner, tablePolicy, bucketPolicy, tableARN, bucketARN, bucketName, namespace, name string, bucketTags, tableTags map[string]string, identityActions []string, r *http.Request) bool { ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespace, @@ -1617,6 +1622,7 @@ func (h *S3TablesHandler) renameCatalogEntry(w http.ResponseWriter, r *http.Requ metadata.OwnerAccountID, tablePolicy, tableARN, bucketMetadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: srcNamespace, @@ -1686,6 +1692,7 @@ func (h *S3TablesHandler) renameCatalogEntry(w http.ResponseWriter, r *http.Requ destNamespaceMetadata.OwnerAccountID, destNamespacePolicy, bucketARN, bucketMetadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: destNamespace, @@ -2090,6 +2097,7 @@ func (h *S3TablesHandler) authorizeUpdateTable(w http.ResponseWriter, r *http.Re metadata.OwnerAccountID, tablePolicy, tableARN, bucketMetadata.OwnerAccountID, bucketPolicy, bucketARN, &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, @@ -2367,6 +2375,7 @@ func (h *S3TablesHandler) authorizeCreateTable(w http.ResponseWriter, r *http.Re identityActions := getIdentityActions(r) policyContext := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespaceName, diff --git a/weed/s3api/s3tables/handler_view.go b/weed/s3api/s3tables/handler_view.go index 7023518eb6..f60585821f 100644 --- a/weed/s3api/s3tables/handler_view.go +++ b/weed/s3api/s3tables/handler_view.go @@ -807,6 +807,7 @@ func (h *S3TablesHandler) authorizeViewOp(r *http.Request, operation, principal, identityActions := getIdentityActions(r) ctx := &PolicyContext{ TrustedIssuers: h.trustedIssuers(r), + Subject: callerSubject(r), IsLocalAccountID: h.localAccountIDs, TableBucketName: bucketName, Namespace: namespace, diff --git a/weed/s3api/s3tables/manager.go b/weed/s3api/s3tables/manager.go index 59a5820601..cc0edb1fc2 100644 --- a/weed/s3api/s3tables/manager.go +++ b/weed/s3api/s3tables/manager.go @@ -80,7 +80,7 @@ func (m *Manager) PrincipalFor(r *http.Request) string { // action scoped to that bucket apply, as the handlers do. func (m *Manager) PolicyContext(r *http.Request, bucket string) *PolicyContext { ctx := &PolicyContext{IsLocalAccountID: m.handler.localAccountIDs, IdentityActions: getIdentityActions(r), - TrustedIssuers: m.handler.trustedIssuers(r), TableBucketName: bucket} + TrustedIssuers: m.handler.trustedIssuers(r), Subject: callerSubject(r), TableBucketName: bucket} if m.handler.trusted || (m.handler.defaultAllow && s3_constants.GetIdentityNameFromContext(r) == "") { ctx.DefaultAllow = true } diff --git a/weed/s3api/s3tables/permissions.go b/weed/s3api/s3tables/permissions.go index efd8a32593..112793dbb7 100644 --- a/weed/s3api/s3tables/permissions.go +++ b/weed/s3api/s3tables/permissions.go @@ -159,6 +159,9 @@ type PolicyContext struct { // be re-anchored; a nil predicate exempts nothing beyond the built-in // reserved, ARN, and scoped-id checks. IsLocalAccountID func(string) bool + // Subject is the caller's OIDC subject, matched against subject patterns + // written before issuer scoping once it is shown to hash to the principal. + Subject string } // CheckPermissionWithContext checks permission with optional resource and condition context. @@ -565,10 +568,10 @@ func matchCaller(principalSpec interface{}, principal string, ctx *PolicyContext } switch p := principalSpec.(type) { case string: - return matchesLegacySubject(p, principal, ctx) + return matchesLegacyPrincipal(p, principal, ctx) case []interface{}: for _, item := range p { - if str, ok := item.(string); ok && matchesLegacySubject(str, principal, ctx) { + if str, ok := item.(string); ok && matchesLegacyPrincipal(str, principal, ctx) { return true } } @@ -604,6 +607,17 @@ func matchesLegacySubject(legacy, principal string, ctx *PolicyContext) bool { return sts.ComputeParentUser(legacy, ctx.TrustedIssuers[0]) == principal } +// matchesLegacyPrincipal is matchesLegacySubject for a policy principal, which +// may also be a subject pattern: it matches the caller's subject only when that +// subject itself resolves to the principal. +func matchesLegacyPrincipal(spec, principal string, ctx *PolicyContext) bool { + if matchesLegacySubject(spec, principal, ctx) { + return true + } + return ctx != nil && strings.ContainsAny(spec, "*?") && !strings.HasPrefix(spec, "arn:") && + wildcard.MatchesWildcard(spec, ctx.Subject) && matchesLegacySubject(ctx.Subject, principal, ctx) +} + // matchesPrincipal checks if the principal matches the statement's principal func matchesPrincipal(principalSpec interface{}, principal string) bool { return matchPrincipal(principalSpec, principal, false)