diff options
Diffstat (limited to 'weed/s3api/s3api_bucket_handlers.go')
| -rw-r--r-- | weed/s3api/s3api_bucket_handlers.go | 99 |
1 files changed, 88 insertions, 11 deletions
diff --git a/weed/s3api/s3api_bucket_handlers.go b/weed/s3api/s3api_bucket_handlers.go index eaff6d442..d73fabd2f 100644 --- a/weed/s3api/s3api_bucket_handlers.go +++ b/weed/s3api/s3api_bucket_handlers.go @@ -244,18 +244,8 @@ func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) return } - fn := func(entry *filer_pb.Entry) { - // Reuse currentIdentityId from above (already retrieved from context) - if currentIdentityId != "" { - if entry.Extended == nil { - entry.Extended = make(map[string][]byte) - } - entry.Extended[s3_constants.AmzIdentityId] = []byte(currentIdentityId) - } - } - // create the folder for bucket, but lazily create actual collection - if err := s3a.mkdir(s3a.option.BucketsPath, bucket, fn); err != nil { + if err := s3a.mkdir(s3a.option.BucketsPath, bucket, setBucketOwner(r)); err != nil { glog.Errorf("PutBucketHandler mkdir: %v", err) s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) return @@ -565,6 +555,93 @@ func (s3a *S3ApiServer) checkBucket(r *http.Request, bucket string) s3err.ErrorC return s3err.ErrNone } +// ErrAutoCreatePermissionDenied is returned when a user lacks permission to auto-create buckets +var ErrAutoCreatePermissionDenied = errors.New("permission denied - requires Admin permission") + +// ErrInvalidBucketName is returned when a bucket name doesn't meet S3 naming requirements +var ErrInvalidBucketName = errors.New("invalid bucket name") + +// setBucketOwner creates a function that sets the bucket owner from the request context +func setBucketOwner(r *http.Request) func(entry *filer_pb.Entry) { + currentIdentityId := s3_constants.GetIdentityNameFromContext(r) + return func(entry *filer_pb.Entry) { + if currentIdentityId != "" { + if entry.Extended == nil { + entry.Extended = make(map[string][]byte) + } + entry.Extended[s3_constants.AmzIdentityId] = []byte(currentIdentityId) + } + } +} + +// autoCreateBucket creates a bucket if it doesn't exist, setting the owner from the request context +// Only users with admin permissions are allowed to auto-create buckets +func (s3a *S3ApiServer) autoCreateBucket(r *http.Request, bucket string) error { + // Validate the bucket name before auto-creating + if err := s3bucket.VerifyS3BucketName(bucket); err != nil { + return fmt.Errorf("auto-create bucket %s: %w", bucket, errors.Join(ErrInvalidBucketName, err)) + } + + // Check if user has admin permissions + if !s3a.isUserAdmin(r) { + return fmt.Errorf("auto-create bucket %s: %w", bucket, ErrAutoCreatePermissionDenied) + } + + if err := s3a.mkdir(s3a.option.BucketsPath, bucket, setBucketOwner(r)); err != nil { + // In case of a race condition where another request created the bucket + // in the meantime, check for existence before returning an error. + if exist, err2 := s3a.exists(s3a.option.BucketsPath, bucket, true); err2 != nil { + glog.Warningf("autoCreateBucket: failed to check existence for bucket %s: %v", bucket, err2) + return fmt.Errorf("failed to auto-create bucket %s: %w", bucket, errors.Join(err, err2)) + } else if exist { + // The bucket exists, which is fine. However, we should ensure it has an owner. + // If it was created by a concurrent request that didn't set an owner, + // we'll set it here to ensure consistency. + if entry, getErr := s3a.getEntry(s3a.option.BucketsPath, bucket); getErr == nil { + if entry.Extended == nil || len(entry.Extended[s3_constants.AmzIdentityId]) == 0 { + // No owner set, assign current admin as owner + setBucketOwner(r)(entry) + if updateErr := s3a.updateEntry(s3a.option.BucketsPath, entry); updateErr != nil { + glog.Warningf("autoCreateBucket: failed to set owner for existing bucket %s: %v", bucket, updateErr) + } else { + glog.V(1).Infof("Set owner for existing bucket %s (created by concurrent request)", bucket) + } + } + } else { + glog.Warningf("autoCreateBucket: failed to get entry for existing bucket %s: %v", bucket, getErr) + } + return nil + } + return fmt.Errorf("failed to auto-create bucket %s: %w", bucket, err) + } + + // Remove bucket from negative cache after successful creation + if s3a.bucketConfigCache != nil { + s3a.bucketConfigCache.RemoveNegativeCache(bucket) + } + + glog.V(1).Infof("Auto-created bucket %s", bucket) + return nil +} + +// handleAutoCreateBucket attempts to auto-create a bucket and writes appropriate error responses +// Returns true if the bucket was created successfully or already exists, false if an error was written +func (s3a *S3ApiServer) handleAutoCreateBucket(w http.ResponseWriter, r *http.Request, bucket, handlerName string) bool { + if err := s3a.autoCreateBucket(r, bucket); err != nil { + glog.Warningf("%s: %v", handlerName, err) + // Check for specific errors to return appropriate S3 error codes + if errors.Is(err, ErrInvalidBucketName) { + s3err.WriteErrorResponse(w, r, s3err.ErrInvalidBucketName) + } else if errors.Is(err, ErrAutoCreatePermissionDenied) { + s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied) + } else { + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) + } + return false + } + return true +} + func (s3a *S3ApiServer) hasAccess(r *http.Request, entry *filer_pb.Entry) bool { // Check if user is properly authenticated as admin through IAM system if s3a.isUserAdmin(r) { |
