s3: read bucket owner and crtime from the entry-free bucket config

The owner index read the raw filer entry off BucketConfig, which no
longer carries one. Cache Crtime alongside IdentityId, and pass the
owner id rather than the entry through the ListBuckets visibility
checks so the scan and granted-bucket paths share one implementation.
This commit is contained in:
Chris Lu committed 2026-07-02 21:27:58 -07:00
1 parent 17af32f3ff
commit 98e49d2d42
3 files changed
+19 -28

No files matched your search

+5
View File
@@ -38,6 +38,7 @@ type BucketConfig struct {
ACL []byte ACL []byte
Owner string Owner string
IdentityId string // identity that created the bucket IdentityId string // identity that created the bucket
Crtime int64 // bucket creation time, unix seconds
IsPublicRead bool // Cached flag to avoid JSON parsing on every request IsPublicRead bool // Cached flag to avoid JSON parsing on every request
CORS *cors.CORSConfiguration CORS *cors.CORSConfiguration
ObjectLockConfig *ObjectLockConfiguration // Cached parsed Object Lock configuration ObjectLockConfig *ObjectLockConfiguration // Cached parsed Object Lock configuration
@@ -404,6 +405,10 @@ func (s3a *S3ApiServer) newBucketConfigFromEntry(bucket string, entry *filer_pb.
return config return config
} }
if entry.Attributes != nil {
config.Crtime = entry.Attributes.Crtime
}
if entry.Extended != nil { if entry.Extended != nil {
if versioning, exists := entry.Extended[s3_constants.ExtVersioningKey]; exists { if versioning, exists := entry.Extended[s3_constants.ExtVersioningKey]; exists {
config.Versioning = string(versioning) config.Versioning = string(versioning)
+12 -26
View File
@@ -144,11 +144,11 @@ func decodeContinuationToken(token string) (string, error) {
// bucketVisibleToIdentity reports whether ListBuckets should include the bucket: // bucketVisibleToIdentity reports whether ListBuckets should include the bucket:
// the identity owns it or has explicit permission to list it. // the identity owns it or has explicit permission to list it.
func (s3a *S3ApiServer) bucketVisibleToIdentity(r *http.Request, entry *filer_pb.Entry, identity *Identity) bool { func (s3a *S3ApiServer) bucketVisibleToIdentity(r *http.Request, bucket, owner string, identity *Identity) bool {
if isBucketOwnedByIdentity(entry, identity) { if isBucketOwnedByIdentity(owner, identity) {
return true return true
} }
return s3a.iam.VerifyActionPermission(r, identity, s3_constants.ACTION_LIST, entry.Name, "") == s3err.ErrNone return s3a.iam.VerifyActionPermission(r, identity, s3_constants.ACTION_LIST, bucket, "") == s3err.ErrNone
} }
// scanVisibleBuckets pages through /buckets and collects up to maxBuckets // scanVisibleBuckets pages through /buckets and collects up to maxBuckets
@@ -169,7 +169,7 @@ func (s3a *S3ApiServer) scanVisibleBuckets(r *http.Request, identity *Identity,
if !entry.IsDirectory || strings.HasPrefix(entry.Name, ".") { if !entry.IsDirectory || strings.HasPrefix(entry.Name, ".") {
continue continue
} }
if !s3a.bucketVisibleToIdentity(r, entry, identity) { if !s3a.bucketVisibleToIdentity(r, entry.Name, bucketEntryOwner(entry), identity) {
continue continue
} }
buckets = append(buckets, ListAllMyBucketsEntry{ buckets = append(buckets, ListAllMyBucketsEntry{
@@ -191,18 +191,14 @@ func (s3a *S3ApiServer) scanVisibleBuckets(r *http.Request, identity *Identity,
return buckets, nextToken, nil return buckets, nextToken, nil
} }
// isBucketOwnedByIdentity checks if a bucket entry is owned by the given identity. // isBucketOwnedByIdentity checks if a bucket with the given owner id (its
// Returns true if the identity owns the bucket, false otherwise. // AmzIdentityId metadata) is owned by the given identity.
// //
// Ownership rules: // Ownership rules:
// - Admin users: considered owners of all buckets // - Admin users: considered owners of all buckets
// - Non-admin users: own buckets where AmzIdentityId matches identity.Name // - Non-admin users: own buckets whose owner id matches identity.Name
// - Buckets without owner metadata are not owned by anyone (except admins) // - Buckets without owner metadata are not owned by anyone (except admins)
func isBucketOwnedByIdentity(entry *filer_pb.Entry, identity *Identity) bool { func isBucketOwnedByIdentity(owner string, identity *Identity) bool {
if !entry.IsDirectory {
return false
}
if identity == nil { if identity == nil {
return false return false
} }
@@ -214,17 +210,7 @@ func isBucketOwnedByIdentity(entry *filer_pb.Entry, identity *Identity) bool {
// Non-admin users with no name cannot own buckets. // Non-admin users with no name cannot own buckets.
// This prevents misconfigured identities from matching buckets with empty owner IDs. // This prevents misconfigured identities from matching buckets with empty owner IDs.
if identity.Name == "" { return identity.Name != "" && identity.Name == owner
return false
}
// Check ownership via AmzIdentityId metadata
id, ok := entry.Extended[s3_constants.AmzIdentityId]
if !ok || string(id) != identity.Name {
return false
}
return true
} }
func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) { func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
@@ -416,10 +402,10 @@ func (s3a *S3ApiServer) healBucketOwnerIndex(bucket, identityId string) {
return return
} }
config, errCode := s3a.getBucketConfig(bucket) config, errCode := s3a.getBucketConfig(bucket)
if errCode != s3err.ErrNone || bucketEntryOwner(config.Entry) != identityId { if errCode != s3err.ErrNone || config.IdentityId != identityId {
return return
} }
if err := s3a.addBucketToOwnerIndex(identityId, bucket, config.Entry.Attributes.Crtime); err != nil { if err := s3a.addBucketToOwnerIndex(identityId, bucket, config.Crtime); err != nil {
glog.V(1).Infof("owner index heal %s/%s: %v", identityId, bucket, err) glog.V(1).Infof("owner index heal %s/%s: %v", identityId, bucket, err)
} }
} }
@@ -483,7 +469,7 @@ func (s3a *S3ApiServer) DeleteBucketHandler(w http.ResponseWriter, r *http.Reque
return return
} }
if owner := bucketEntryOwner(bucketConfig.Entry); owner != "" { if owner := bucketConfig.IdentityId; owner != "" {
if err := s3a.removeBucketFromOwnerIndex(owner, bucket); err != nil { if err := s3a.removeBucketFromOwnerIndex(owner, bucket); err != nil {
glog.Warningf("DeleteBucketHandler: owner index remove %s/%s: %v", owner, bucket, err) glog.Warningf("DeleteBucketHandler: owner index remove %s/%s: %v", owner, bucket, err)
} }
+2 -2
View File
@@ -166,12 +166,12 @@ func (s3a *S3ApiServer) resolveGrantedBuckets(r *http.Request, identity *Identit
} }
continue continue
} }
if !s3a.bucketVisibleToIdentity(r, config.Entry, identity) { if !s3a.bucketVisibleToIdentity(r, name, config.IdentityId, identity) {
continue continue
} }
granted = append(granted, ListAllMyBucketsEntry{ granted = append(granted, ListAllMyBucketsEntry{
Name: name, Name: name,
CreationDate: time.Unix(config.Entry.Attributes.Crtime, 0).UTC(), CreationDate: time.Unix(config.Crtime, 0).UTC(),
}) })
} }
return granted return granted