aboutsummaryrefslogtreecommitdiff
path: root/weed/admin/dash
diff options
context:
space:
mode:
Diffstat (limited to 'weed/admin/dash')
-rw-r--r--weed/admin/dash/admin_server.go19
-rw-r--r--weed/admin/dash/bucket_management.go112
-rw-r--r--weed/admin/dash/types.go1
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 {