diff options
Diffstat (limited to 'weed/s3api/s3api_bucket_handlers.go')
| -rw-r--r-- | weed/s3api/s3api_bucket_handlers.go | 81 |
1 files changed, 75 insertions, 6 deletions
diff --git a/weed/s3api/s3api_bucket_handlers.go b/weed/s3api/s3api_bucket_handlers.go index f68aaa3a0..060d453b1 100644 --- a/weed/s3api/s3api_bucket_handlers.go +++ b/weed/s3api/s3api_bucket_handlers.go @@ -108,8 +108,11 @@ func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) return } - // avoid duplicated buckets - errCode := s3err.ErrNone + // Check if bucket already exists and handle ownership/settings + currentIdentityId := r.Header.Get(s3_constants.AmzIdentityId) + + // Check collection existence first + collectionExists := false if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { if resp, err := client.CollectionList(context.Background(), &filer_pb.CollectionListRequest{ IncludeEcVolumes: true, @@ -120,7 +123,7 @@ func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) } else { for _, c := range resp.Collections { if s3a.getCollectionName(bucket) == c.Name { - errCode = s3err.ErrBucketAlreadyExists + collectionExists = true break } } @@ -130,11 +133,61 @@ func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) return } + + // Check bucket directory existence and get metadata if exist, err := s3a.exists(s3a.option.BucketsPath, bucket, true); err == nil && exist { - errCode = s3err.ErrBucketAlreadyExists + // Bucket exists, check ownership and settings + if entry, err := s3a.getEntry(s3a.option.BucketsPath, bucket); err == nil { + // Get existing bucket owner + var existingOwnerId string + if entry.Extended != nil { + if id, ok := entry.Extended[s3_constants.AmzIdentityId]; ok { + existingOwnerId = string(id) + } + } + + // Check ownership + if existingOwnerId != "" && existingOwnerId != currentIdentityId { + // Different owner - always fail with BucketAlreadyExists + glog.V(3).Infof("PutBucketHandler: bucket %s owned by %s, requested by %s", bucket, existingOwnerId, currentIdentityId) + s3err.WriteErrorResponse(w, r, s3err.ErrBucketAlreadyExists) + return + } + + // Same owner or no owner set - check for conflicting settings + objectLockRequested := strings.EqualFold(r.Header.Get(s3_constants.AmzBucketObjectLockEnabled), "true") + + // Get current bucket configuration + bucketConfig, errCode := s3a.getBucketConfig(bucket) + if errCode != s3err.ErrNone { + glog.Errorf("PutBucketHandler: failed to get bucket config for %s: %v", bucket, errCode) + // If we can't get config, assume no conflict and allow recreation + } else { + // Check for Object Lock conflict + currentObjectLockEnabled := bucketConfig.ObjectLockConfig != nil && + bucketConfig.ObjectLockConfig.ObjectLockEnabled == s3_constants.ObjectLockEnabled + + if objectLockRequested != currentObjectLockEnabled { + // Conflicting Object Lock settings - fail with BucketAlreadyExists + glog.V(3).Infof("PutBucketHandler: bucket %s has conflicting Object Lock settings (requested: %v, current: %v)", + bucket, objectLockRequested, currentObjectLockEnabled) + s3err.WriteErrorResponse(w, r, s3err.ErrBucketAlreadyExists) + return + } + } + + // Bucket already exists - always return BucketAlreadyExists per S3 specification + // The S3 tests expect BucketAlreadyExists in all cases, not BucketAlreadyOwnedByYou + glog.V(3).Infof("PutBucketHandler: bucket %s already exists", bucket) + s3err.WriteErrorResponse(w, r, s3err.ErrBucketAlreadyExists) + return + } } - if errCode != s3err.ErrNone { - s3err.WriteErrorResponse(w, r, errCode) + + // If collection exists but bucket directory doesn't, this is an inconsistent state + if collectionExists { + glog.Errorf("PutBucketHandler: collection exists but bucket directory missing for %s", bucket) + s3err.WriteErrorResponse(w, r, s3err.ErrBucketAlreadyExists) return } @@ -313,9 +366,11 @@ func (s3a *S3ApiServer) isBucketPublicRead(bucket string) bool { // Get bucket configuration which contains cached public-read status config, errCode := s3a.getBucketConfig(bucket) if errCode != s3err.ErrNone { + glog.V(4).Infof("isBucketPublicRead: failed to get bucket config for %s: %v", bucket, errCode) return false } + glog.V(4).Infof("isBucketPublicRead: bucket=%s, IsPublicRead=%v", bucket, config.IsPublicRead) // Return the cached public-read status (no JSON parsing needed) return config.IsPublicRead } @@ -341,13 +396,18 @@ func (s3a *S3ApiServer) AuthWithPublicRead(handler http.HandlerFunc, action Acti authType := getRequestAuthType(r) isAnonymous := authType == authTypeAnonymous + glog.V(4).Infof("AuthWithPublicRead: bucket=%s, authType=%v, isAnonymous=%v", bucket, authType, isAnonymous) + // For anonymous requests, check if bucket allows public read if isAnonymous { isPublic := s3a.isBucketPublicRead(bucket) + glog.V(4).Infof("AuthWithPublicRead: bucket=%s, isPublic=%v", bucket, isPublic) if isPublic { + glog.V(3).Infof("AuthWithPublicRead: allowing anonymous access to public-read bucket %s", bucket) handler(w, r) return } + glog.V(3).Infof("AuthWithPublicRead: bucket %s is not public-read, falling back to IAM auth", bucket) } // For all authenticated requests and anonymous requests to non-public buckets, @@ -414,6 +474,10 @@ func (s3a *S3ApiServer) PutBucketAclHandler(w http.ResponseWriter, r *http.Reque return } + glog.V(3).Infof("PutBucketAclHandler: bucket=%s, extracted %d grants", bucket, len(grants)) + isPublic := isPublicReadGrants(grants) + glog.V(3).Infof("PutBucketAclHandler: bucket=%s, isPublicReadGrants=%v", bucket, isPublic) + // Store the bucket ACL in bucket metadata errCode = s3a.updateBucketConfig(bucket, func(config *BucketConfig) error { if len(grants) > 0 { @@ -425,6 +489,7 @@ func (s3a *S3ApiServer) PutBucketAclHandler(w http.ResponseWriter, r *http.Reque config.ACL = grantsBytes // Cache the public-read status to avoid JSON parsing on every request config.IsPublicRead = isPublicReadGrants(grants) + glog.V(4).Infof("PutBucketAclHandler: bucket=%s, setting IsPublicRead=%v", bucket, config.IsPublicRead) } else { config.ACL = nil config.IsPublicRead = false @@ -440,6 +505,10 @@ func (s3a *S3ApiServer) PutBucketAclHandler(w http.ResponseWriter, r *http.Reque glog.V(3).Infof("PutBucketAclHandler: Successfully stored ACL for bucket %s with %d grants", bucket, len(grants)) + // Small delay to ensure ACL propagation across distributed caches + // This prevents race conditions in tests where anonymous access is attempted immediately after ACL change + time.Sleep(50 * time.Millisecond) + writeSuccessResponseEmpty(w, r) } |
