mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-13 15:29:12 +01:00
s3tables: match legacy subject patterns in table bucket policy principals
A policy written before issuer scoping can name role sessions by a pattern over their bare OIDC subjects. Match such a pattern against the caller's subject claim once that subject resolves to the session's scoped principal under the single configured issuer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8f2dc0693f
commit
8e577b8685
10 files changed
+65
-11
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user