diff options
Diffstat (limited to 'weed/admin/dash')
| -rw-r--r-- | weed/admin/dash/admin_server.go | 19 | ||||
| -rw-r--r-- | weed/admin/dash/bucket_management.go | 112 | ||||
| -rw-r--r-- | weed/admin/dash/types.go | 1 |
3 files changed, 125 insertions, 7 deletions
diff --git a/weed/admin/dash/admin_server.go b/weed/admin/dash/admin_server.go index c499ca8fe..eeeccf981 100644 --- a/weed/admin/dash/admin_server.go +++ b/weed/admin/dash/admin_server.go @@ -26,6 +26,7 @@ import ( "google.golang.org/grpc" "github.com/seaweedfs/seaweedfs/weed/s3api" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "github.com/seaweedfs/seaweedfs/weed/worker/tasks" ) @@ -317,11 +318,12 @@ func (s *AdminServer) GetS3Buckets() ([]S3Bucket, error) { quotaEnabled = false } - // Get versioning and object lock information from extended attributes + // Get versioning, object lock, and owner information from extended attributes versioningEnabled := false objectLockEnabled := false objectLockMode := "" var objectLockDuration int32 = 0 + var owner string if resp.Entry.Extended != nil { // Use shared utility to extract versioning information @@ -329,6 +331,11 @@ func (s *AdminServer) GetS3Buckets() ([]S3Bucket, error) { // Use shared utility to extract Object Lock information objectLockEnabled, objectLockMode, objectLockDuration = extractObjectLockInfoFromEntry(resp.Entry) + + // Extract owner information + if ownerBytes, ok := resp.Entry.Extended[s3_constants.AmzIdentityId]; ok { + owner = string(ownerBytes) + } } bucket := S3Bucket{ @@ -343,6 +350,7 @@ func (s *AdminServer) GetS3Buckets() ([]S3Bucket, error) { ObjectLockEnabled: objectLockEnabled, ObjectLockMode: objectLockMode, ObjectLockDuration: objectLockDuration, + Owner: owner, } buckets = append(buckets, bucket) } @@ -394,11 +402,12 @@ func (s *AdminServer) GetBucketDetails(bucketName string) (*BucketDetails, error details.Bucket.Quota = quota details.Bucket.QuotaEnabled = quotaEnabled - // Get versioning and object lock information from extended attributes + // Get versioning, object lock, and owner information from extended attributes versioningEnabled := false objectLockEnabled := false objectLockMode := "" var objectLockDuration int32 = 0 + var owner string if bucketResp.Entry.Extended != nil { // Use shared utility to extract versioning information @@ -406,12 +415,18 @@ func (s *AdminServer) GetBucketDetails(bucketName string) (*BucketDetails, error // Use shared utility to extract Object Lock information objectLockEnabled, objectLockMode, objectLockDuration = extractObjectLockInfoFromEntry(bucketResp.Entry) + + // Extract owner information + if ownerBytes, ok := bucketResp.Entry.Extended[s3_constants.AmzIdentityId]; ok { + owner = string(ownerBytes) + } } details.Bucket.VersioningEnabled = versioningEnabled details.Bucket.ObjectLockEnabled = objectLockEnabled details.Bucket.ObjectLockMode = objectLockMode details.Bucket.ObjectLockDuration = objectLockDuration + details.Bucket.Owner = owner // List objects in bucket (recursively) return s.listBucketObjects(client, bucketPath, "", details) diff --git a/weed/admin/dash/bucket_management.go b/weed/admin/dash/bucket_management.go index 5942d5695..eb99e9fa4 100644 --- a/weed/admin/dash/bucket_management.go +++ b/weed/admin/dash/bucket_management.go @@ -11,8 +11,14 @@ import ( "github.com/gin-gonic/gin" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/s3api" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" ) +// MaxOwnerNameLength is the maximum allowed length for bucket owner identity names. +// This is a reasonable limit to prevent abuse; AWS IAM user names are limited to 64 chars, +// but we use 256 to allow for more complex identity formats (e.g., email addresses). +const MaxOwnerNameLength = 256 + // S3 Bucket management data structures for templates type S3BucketsData struct { Username string `json:"username"` @@ -33,6 +39,7 @@ type CreateBucketRequest struct { ObjectLockMode string `json:"object_lock_mode"` // Object lock mode: "GOVERNANCE" or "COMPLIANCE" SetDefaultRetention bool `json:"set_default_retention"` // Whether to set default retention ObjectLockDuration int32 `json:"object_lock_duration"` // Default retention duration in days + Owner string `json:"owner"` // Bucket owner identity (for S3 IAM authentication) } // S3 Bucket Management Handlers @@ -118,7 +125,14 @@ func (s *AdminServer) CreateBucket(c *gin.Context) { // Convert quota to bytes quotaBytes := convertQuotaToBytes(req.QuotaSize, req.QuotaUnit) - err := s.CreateS3BucketWithObjectLock(req.Name, quotaBytes, req.QuotaEnabled, req.VersioningEnabled, req.ObjectLockEnabled, req.ObjectLockMode, req.SetDefaultRetention, req.ObjectLockDuration) + // Sanitize owner: trim whitespace and enforce max length + owner := strings.TrimSpace(req.Owner) + if len(owner) > MaxOwnerNameLength { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Owner name must be %d characters or less", MaxOwnerNameLength)}) + return + } + + err := s.CreateS3BucketWithObjectLock(req.Name, quotaBytes, req.QuotaEnabled, req.VersioningEnabled, req.ObjectLockEnabled, req.ObjectLockMode, req.SetDefaultRetention, req.ObjectLockDuration, owner) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create bucket: " + err.Error()}) return @@ -134,6 +148,7 @@ func (s *AdminServer) CreateBucket(c *gin.Context) { "object_lock_enabled": req.ObjectLockEnabled, "object_lock_mode": req.ObjectLockMode, "object_lock_duration": req.ObjectLockDuration, + "owner": owner, }) } @@ -193,6 +208,88 @@ func (s *AdminServer) DeleteBucket(c *gin.Context) { }) } +// UpdateBucketOwner updates the owner of an S3 bucket +func (s *AdminServer) UpdateBucketOwner(c *gin.Context) { + bucketName := c.Param("bucket") + if bucketName == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Bucket name is required"}) + return + } + + // Use pointer to detect if owner field was explicitly provided + var req struct { + Owner *string `json:"owner"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()}) + return + } + + // Require owner field to be explicitly provided + if req.Owner == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Owner field is required (use empty string to clear owner)"}) + return + } + + // Trim and validate owner + owner := strings.TrimSpace(*req.Owner) + if len(owner) > MaxOwnerNameLength { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Owner name must be %d characters or less", MaxOwnerNameLength)}) + return + } + + err := s.SetBucketOwner(bucketName, owner) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update bucket owner: " + err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Bucket owner updated successfully", + "bucket": bucketName, + "owner": owner, + }) +} + +// SetBucketOwner sets the owner of a bucket +func (s *AdminServer) SetBucketOwner(bucketName string, owner string) error { + return s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { + // Get the current bucket entry + lookupResp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{ + Directory: "/buckets", + Name: bucketName, + }) + if err != nil { + return fmt.Errorf("lookup bucket %s: %w", bucketName, err) + } + + bucketEntry := lookupResp.Entry + + // Initialize Extended map if nil + if bucketEntry.Extended == nil { + bucketEntry.Extended = make(map[string][]byte) + } + + // Set or remove the owner + if owner == "" { + delete(bucketEntry.Extended, s3_constants.AmzIdentityId) + } else { + bucketEntry.Extended[s3_constants.AmzIdentityId] = []byte(owner) + } + + // Update the entry + _, err = client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{ + Directory: "/buckets", + Entry: bucketEntry, + }) + if err != nil { + return fmt.Errorf("failed to update bucket owner: %w", err) + } + + return nil + }) +} + // ListBucketsAPI returns the list of buckets as JSON func (s *AdminServer) ListBucketsAPI(c *gin.Context) { buckets, err := s.GetS3Buckets() @@ -288,11 +385,11 @@ func (s *AdminServer) SetBucketQuota(bucketName string, quotaBytes int64, quotaE // CreateS3BucketWithQuota creates a new S3 bucket with quota settings func (s *AdminServer) CreateS3BucketWithQuota(bucketName string, quotaBytes int64, quotaEnabled bool) error { - return s.CreateS3BucketWithObjectLock(bucketName, quotaBytes, quotaEnabled, false, false, "", false, 0) + return s.CreateS3BucketWithObjectLock(bucketName, quotaBytes, quotaEnabled, false, false, "", false, 0, "") } -// CreateS3BucketWithObjectLock creates a new S3 bucket with quota, versioning, and object lock settings -func (s *AdminServer) CreateS3BucketWithObjectLock(bucketName string, quotaBytes int64, quotaEnabled, versioningEnabled, objectLockEnabled bool, objectLockMode string, setDefaultRetention bool, objectLockDuration int32) error { +// CreateS3BucketWithObjectLock creates a new S3 bucket with quota, versioning, object lock settings, and owner +func (s *AdminServer) CreateS3BucketWithObjectLock(bucketName string, quotaBytes int64, quotaEnabled, versioningEnabled, objectLockEnabled bool, objectLockMode string, setDefaultRetention bool, objectLockDuration int32, owner string) error { return s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { // First ensure /buckets directory exists _, err := client.CreateEntry(context.Background(), &filer_pb.CreateEntryRequest{ @@ -344,9 +441,14 @@ func (s *AdminServer) CreateS3BucketWithObjectLock(bucketName string, quotaBytes TtlSec: 0, } - // Create extended attributes map for versioning + // Create extended attributes map for versioning and owner extended := make(map[string][]byte) + // Set bucket owner if specified + if owner != "" { + extended[s3_constants.AmzIdentityId] = []byte(owner) + } + // Create bucket entry bucketEntry := &filer_pb.Entry{ Name: bucketName, diff --git a/weed/admin/dash/types.go b/weed/admin/dash/types.go index ec2692321..5c2ac60e8 100644 --- a/weed/admin/dash/types.go +++ b/weed/admin/dash/types.go @@ -82,6 +82,7 @@ type S3Bucket struct { ObjectLockEnabled bool `json:"object_lock_enabled"` // Whether object lock is enabled ObjectLockMode string `json:"object_lock_mode"` // Object lock mode: "GOVERNANCE" or "COMPLIANCE" ObjectLockDuration int32 `json:"object_lock_duration"` // Default retention duration in days + Owner string `json:"owner,omitempty"` // Bucket owner identity; empty means admin-only access } type S3Object struct { |
