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
2026-07-02 21:27:58 -07:00
parent 17af32f3ff
commit 98e49d2d42
3 changed files with 19 additions and 28 deletions
+5
View File
@@ -38,6 +38,7 @@ type BucketConfig struct {
ACL []byte
Owner string
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
CORS *cors.CORSConfiguration
ObjectLockConfig *ObjectLockConfiguration // Cached parsed Object Lock configuration
@@ -404,6 +405,10 @@ func (s3a *S3ApiServer) newBucketConfigFromEntry(bucket string, entry *filer_pb.
return config
}
if entry.Attributes != nil {
config.Crtime = entry.Attributes.Crtime
}
if entry.Extended != nil {
if versioning, exists := entry.Extended[s3_constants.ExtVersioningKey]; exists {
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:
// 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 {
if isBucketOwnedByIdentity(entry, identity) {
func (s3a *S3ApiServer) bucketVisibleToIdentity(r *http.Request, bucket, owner string, identity *Identity) bool {
if isBucketOwnedByIdentity(owner, identity) {
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
@@ -169,7 +169,7 @@ func (s3a *S3ApiServer) scanVisibleBuckets(r *http.Request, identity *Identity,
if !entry.IsDirectory || strings.HasPrefix(entry.Name, ".") {
continue
}
if !s3a.bucketVisibleToIdentity(r, entry, identity) {
if !s3a.bucketVisibleToIdentity(r, entry.Name, bucketEntryOwner(entry), identity) {
continue
}
buckets = append(buckets, ListAllMyBucketsEntry{
@@ -191,18 +191,14 @@ func (s3a *S3ApiServer) scanVisibleBuckets(r *http.Request, identity *Identity,
return buckets, nextToken, nil
}
// isBucketOwnedByIdentity checks if a bucket entry is owned by the given identity.
// Returns true if the identity owns the bucket, false otherwise.
// isBucketOwnedByIdentity checks if a bucket with the given owner id (its
// AmzIdentityId metadata) is owned by the given identity.
//
// Ownership rules:
// - 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)
func isBucketOwnedByIdentity(entry *filer_pb.Entry, identity *Identity) bool {
if !entry.IsDirectory {
return false
}
func isBucketOwnedByIdentity(owner string, identity *Identity) bool {
if identity == nil {
return false
}
@@ -214,17 +210,7 @@ func isBucketOwnedByIdentity(entry *filer_pb.Entry, identity *Identity) bool {
// Non-admin users with no name cannot own buckets.
// This prevents misconfigured identities from matching buckets with empty owner IDs.
if identity.Name == "" {
return false
}
// Check ownership via AmzIdentityId metadata
id, ok := entry.Extended[s3_constants.AmzIdentityId]
if !ok || string(id) != identity.Name {
return false
}
return true
return identity.Name != "" && identity.Name == owner
}
func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
@@ -416,10 +402,10 @@ func (s3a *S3ApiServer) healBucketOwnerIndex(bucket, identityId string) {
return
}
config, errCode := s3a.getBucketConfig(bucket)
if errCode != s3err.ErrNone || bucketEntryOwner(config.Entry) != identityId {
if errCode != s3err.ErrNone || config.IdentityId != identityId {
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)
}
}
@@ -483,7 +469,7 @@ func (s3a *S3ApiServer) DeleteBucketHandler(w http.ResponseWriter, r *http.Reque
return
}
if owner := bucketEntryOwner(bucketConfig.Entry); owner != "" {
if owner := bucketConfig.IdentityId; owner != "" {
if err := s3a.removeBucketFromOwnerIndex(owner, bucket); err != nil {
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
}
if !s3a.bucketVisibleToIdentity(r, config.Entry, identity) {
if !s3a.bucketVisibleToIdentity(r, name, config.IdentityId, identity) {
continue
}
granted = append(granted, ListAllMyBucketsEntry{
Name: name,
CreationDate: time.Unix(config.Entry.Attributes.Crtime, 0).UTC(),
CreationDate: time.Unix(config.Crtime, 0).UTC(),
})
}
return granted