Browse Source
add basic object ACL (#7004)
add basic object ACL (#7004)
* add back tests
* get put object acl
* check permission to put object acl
* rename file
* object list versions now contains owners
* set object owner
* refactoring
* Revert "add back tests"
This reverts commit 9adc507c45
.
pull/7009/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 365 additions and 42 deletions
-
30weed/s3api/filer_multipart.go
-
21weed/s3api/s3api_bucket_config.go
-
6weed/s3api/s3api_bucket_handlers.go
-
236weed/s3api/s3api_object_handlers_acl.go
-
5weed/s3api/s3api_object_handlers_multipart.go
-
30weed/s3api/s3api_object_handlers_put.go
-
21weed/s3api/s3api_object_handlers_skip.go
-
58weed/s3api/s3api_object_versioning.go
@ -0,0 +1,236 @@ |
|||||
|
package s3api |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
"net/http" |
||||
|
|
||||
|
"github.com/seaweedfs/seaweedfs/weed/glog" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err" |
||||
|
) |
||||
|
|
||||
|
// GetObjectAclHandler Get object ACL
|
||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAcl.html
|
||||
|
func (s3a *S3ApiServer) GetObjectAclHandler(w http.ResponseWriter, r *http.Request) { |
||||
|
// collect parameters
|
||||
|
bucket, object := s3_constants.GetBucketAndObject(r) |
||||
|
glog.V(3).Infof("GetObjectAclHandler %s %s", bucket, object) |
||||
|
|
||||
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone { |
||||
|
s3err.WriteErrorResponse(w, r, err) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Check if object exists and get its metadata
|
||||
|
bucketDir := s3a.option.BucketsPath + "/" + bucket |
||||
|
entry, err := s3a.getEntry(bucketDir, object) |
||||
|
if err != nil { |
||||
|
if errors.Is(err, filer_pb.ErrNotFound) { |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey) |
||||
|
return |
||||
|
} |
||||
|
glog.Errorf("GetObjectAclHandler: error checking object %s/%s: %v", bucket, object, err) |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
||||
|
return |
||||
|
} |
||||
|
if entry == nil { |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Get object owner from metadata, fallback to request account
|
||||
|
var objectOwner string |
||||
|
var objectOwnerDisplayName string |
||||
|
amzAccountId := r.Header.Get(s3_constants.AmzAccountId) |
||||
|
|
||||
|
if entry.Extended != nil { |
||||
|
if ownerBytes, exists := entry.Extended[s3_constants.ExtAmzOwnerKey]; exists { |
||||
|
objectOwner = string(ownerBytes) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Fallback to current account if no owner stored
|
||||
|
if objectOwner == "" { |
||||
|
objectOwner = amzAccountId |
||||
|
} |
||||
|
|
||||
|
objectOwnerDisplayName = s3a.iam.GetAccountNameById(objectOwner) |
||||
|
|
||||
|
// Build ACL response
|
||||
|
response := AccessControlPolicy{ |
||||
|
Owner: CanonicalUser{ |
||||
|
ID: objectOwner, |
||||
|
DisplayName: objectOwnerDisplayName, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
// Get grants from stored ACL metadata
|
||||
|
grants := GetAcpGrants(entry.Extended) |
||||
|
if len(grants) > 0 { |
||||
|
// Convert AWS SDK grants to local Grant format
|
||||
|
for _, grant := range grants { |
||||
|
localGrant := Grant{ |
||||
|
Permission: Permission(*grant.Permission), |
||||
|
} |
||||
|
|
||||
|
if grant.Grantee != nil { |
||||
|
localGrant.Grantee = Grantee{ |
||||
|
Type: *grant.Grantee.Type, |
||||
|
XMLXSI: "CanonicalUser", |
||||
|
XMLNS: "http://www.w3.org/2001/XMLSchema-instance", |
||||
|
} |
||||
|
|
||||
|
if grant.Grantee.ID != nil { |
||||
|
localGrant.Grantee.ID = *grant.Grantee.ID |
||||
|
localGrant.Grantee.DisplayName = s3a.iam.GetAccountNameById(*grant.Grantee.ID) |
||||
|
} |
||||
|
|
||||
|
if grant.Grantee.URI != nil { |
||||
|
localGrant.Grantee.URI = *grant.Grantee.URI |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
response.AccessControlList.Grant = append(response.AccessControlList.Grant, localGrant) |
||||
|
} |
||||
|
} else { |
||||
|
// Fallback to default full control for object owner
|
||||
|
response.AccessControlList.Grant = append(response.AccessControlList.Grant, Grant{ |
||||
|
Grantee: Grantee{ |
||||
|
ID: objectOwner, |
||||
|
DisplayName: objectOwnerDisplayName, |
||||
|
Type: "CanonicalUser", |
||||
|
XMLXSI: "CanonicalUser", |
||||
|
XMLNS: "http://www.w3.org/2001/XMLSchema-instance"}, |
||||
|
Permission: Permission(s3_constants.PermissionFullControl), |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
writeSuccessResponseXML(w, r, response) |
||||
|
} |
||||
|
|
||||
|
// PutObjectAclHandler Put object ACL
|
||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html
|
||||
|
func (s3a *S3ApiServer) PutObjectAclHandler(w http.ResponseWriter, r *http.Request) { |
||||
|
// collect parameters
|
||||
|
bucket, object := s3_constants.GetBucketAndObject(r) |
||||
|
glog.V(3).Infof("PutObjectAclHandler %s %s", bucket, object) |
||||
|
|
||||
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone { |
||||
|
s3err.WriteErrorResponse(w, r, err) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Check if object exists and get its metadata
|
||||
|
bucketDir := s3a.option.BucketsPath + "/" + bucket |
||||
|
entry, err := s3a.getEntry(bucketDir, object) |
||||
|
if err != nil { |
||||
|
if errors.Is(err, filer_pb.ErrNotFound) { |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey) |
||||
|
return |
||||
|
} |
||||
|
glog.Errorf("PutObjectAclHandler: error checking object %s/%s: %v", bucket, object, err) |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
||||
|
return |
||||
|
} |
||||
|
if entry == nil { |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Get current object owner from metadata
|
||||
|
var objectOwner string |
||||
|
amzAccountId := r.Header.Get(s3_constants.AmzAccountId) |
||||
|
|
||||
|
if entry.Extended != nil { |
||||
|
if ownerBytes, exists := entry.Extended[s3_constants.ExtAmzOwnerKey]; exists { |
||||
|
objectOwner = string(ownerBytes) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Fallback to current account if no owner stored
|
||||
|
if objectOwner == "" { |
||||
|
objectOwner = amzAccountId |
||||
|
} |
||||
|
|
||||
|
// **PERMISSION CHECKS**
|
||||
|
|
||||
|
// 1. Check if user is admin (admins can modify any ACL)
|
||||
|
if !s3a.isUserAdmin(r) { |
||||
|
// 2. Check object ownership - only object owner can modify ACL (unless admin)
|
||||
|
if objectOwner != amzAccountId { |
||||
|
glog.V(3).Infof("PutObjectAclHandler: Access denied - user %s is not owner of object %s/%s (owner: %s)", |
||||
|
amzAccountId, bucket, object, objectOwner) |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 3. Check object-level WRITE_ACP permission
|
||||
|
// Create the specific action for this object
|
||||
|
writeAcpAction := Action(fmt.Sprintf("WriteAcp:%s/%s", bucket, object)) |
||||
|
identity, errCode := s3a.iam.authRequest(r, writeAcpAction) |
||||
|
if errCode != s3err.ErrNone { |
||||
|
glog.V(3).Infof("PutObjectAclHandler: Auth failed for WriteAcp action on %s/%s: %v", bucket, object, errCode) |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 4. Verify the authenticated identity can perform WriteAcp on this specific object
|
||||
|
if identity == nil || !identity.canDo(writeAcpAction, bucket, object) { |
||||
|
glog.V(3).Infof("PutObjectAclHandler: Identity %v cannot perform WriteAcp on %s/%s", identity, bucket, object) |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied) |
||||
|
return |
||||
|
} |
||||
|
} else { |
||||
|
glog.V(3).Infof("PutObjectAclHandler: Admin user %s granted ACL modification permission for %s/%s", amzAccountId, bucket, object) |
||||
|
} |
||||
|
|
||||
|
// Get bucket config for ownership settings
|
||||
|
bucketConfig, errCode := s3a.getBucketConfig(bucket) |
||||
|
if errCode != s3err.ErrNone { |
||||
|
s3err.WriteErrorResponse(w, r, errCode) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
bucketOwnership := bucketConfig.Ownership |
||||
|
bucketOwnerId := bucketConfig.Owner |
||||
|
|
||||
|
// Extract ACL from request (either canned ACL or XML body)
|
||||
|
// This function also validates that the owner in the request matches the object owner
|
||||
|
grants, errCode := ExtractAcl(r, s3a.iam, bucketOwnership, bucketOwnerId, objectOwner, amzAccountId) |
||||
|
if errCode != s3err.ErrNone { |
||||
|
s3err.WriteErrorResponse(w, r, errCode) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Store ACL in object metadata
|
||||
|
if errCode := AssembleEntryWithAcp(entry, objectOwner, grants); errCode != s3err.ErrNone { |
||||
|
glog.Errorf("PutObjectAclHandler: failed to assemble entry with ACP: %v", errCode) |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Update the object with new ACL metadata
|
||||
|
err = s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { |
||||
|
request := &filer_pb.UpdateEntryRequest{ |
||||
|
Directory: bucketDir, |
||||
|
Entry: entry, |
||||
|
} |
||||
|
|
||||
|
if _, err := client.UpdateEntry(context.Background(), request); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
|
||||
|
if err != nil { |
||||
|
glog.Errorf("PutObjectAclHandler: failed to update entry: %v", err) |
||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
glog.V(3).Infof("PutObjectAclHandler: Successfully updated ACL for %s/%s by user %s", bucket, object, amzAccountId) |
||||
|
writeSuccessResponseEmpty(w, r) |
||||
|
} |
@ -1,21 +0,0 @@ |
|||||
package s3api |
|
||||
|
|
||||
import ( |
|
||||
"net/http" |
|
||||
) |
|
||||
|
|
||||
// GetObjectAclHandler Get object ACL
|
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAcl.html
|
|
||||
func (s3a *S3ApiServer) GetObjectAclHandler(w http.ResponseWriter, r *http.Request) { |
|
||||
|
|
||||
w.WriteHeader(http.StatusNoContent) |
|
||||
|
|
||||
} |
|
||||
|
|
||||
// PutObjectAclHandler Put object ACL
|
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html
|
|
||||
func (s3a *S3ApiServer) PutObjectAclHandler(w http.ResponseWriter, r *http.Request) { |
|
||||
|
|
||||
w.WriteHeader(http.StatusNoContent) |
|
||||
|
|
||||
} |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue