From f79a3a73ee2f4f59eef9d19815edd9c30fcd5b30 Mon Sep 17 00:00:00 2001 From: "changlin.shi" Date: Thu, 13 Oct 2022 17:33:10 +0800 Subject: [PATCH] add acl support for `PutObjectAcl` --- weed/s3api/s3api_acp.go | 78 ++++++++++++++++++++++++ weed/s3api/s3api_object_handlers.go | 32 ++++++++++ weed/s3api/s3api_object_skip_handlers.go | 8 --- weed/s3api/s3err/s3api_errors.go | 7 ++- 4 files changed, 116 insertions(+), 9 deletions(-) diff --git a/weed/s3api/s3api_acp.go b/weed/s3api/s3api_acp.go index 7a76c2a67..bafe19e88 100644 --- a/weed/s3api/s3api_acp.go +++ b/weed/s3api/s3api_acp.go @@ -1,9 +1,13 @@ package s3api import ( + "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/s3account" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3acl" "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" + "github.com/seaweedfs/seaweedfs/weed/util" "net/http" ) @@ -27,3 +31,77 @@ func (s3a *S3ApiServer) checkAccessByOwnership(r *http.Request, bucket string) s } return s3err.ErrAccessDenied } + +// Check ObjectAcl-Write related access +// includes: +// - PutObjectAclHandler +func (s3a *S3ApiServer) checkAccessForWriteObjectAcl(accountId, bucket, object string) (bucketMetadata *BucketMetaData, objectEntry *filer_pb.Entry, objectOwner string, errCode s3err.ErrorCode) { + bucketMetadata, errCode = s3a.bucketRegistry.GetBucketMetadata(bucket) + if errCode != s3err.ErrNone { + return nil, nil, "", errCode + } + + if bucketMetadata.ObjectOwnership == s3_constants.OwnershipBucketOwnerEnforced { + return nil, nil, "", s3err.AccessControlListNotSupported + } + + //bucket acl + bucketAclAllowed := false + reqGrants := s3acl.DetermineReqGrants(accountId, s3_constants.PermissionWrite) + if accountId == *bucketMetadata.Owner.ID { + bucketAclAllowed = true + } else if bucketMetadata.Acl != nil { + bucketLoop: + for _, bucketGrant := range bucketMetadata.Acl { + for _, requiredGrant := range reqGrants { + if s3acl.GrantEquals(bucketGrant, requiredGrant) { + bucketAclAllowed = true + break bucketLoop + } + } + } + } + if !bucketAclAllowed { + return nil, nil, "", s3err.ErrAccessDenied + } + + //object acl + objectEntry, err := getObjectEntry(s3a, bucket, object) + if err != nil { + if err == filer_pb.ErrNotFound { + return nil, nil, "", s3err.ErrNoSuchKey + } + return nil, nil, "", s3err.ErrInternalError + } + + if objectEntry.IsDirectory { + return nil, nil, "", s3err.ErrExistingObjectIsDirectory + } + + objectOwner = s3acl.GetAcpOwner(objectEntry.Extended, *bucketMetadata.Owner.ID) + if accountId == objectOwner { + return bucketMetadata, objectEntry, objectOwner, s3err.ErrNone + } + + objectGrants := s3acl.GetAcpGrants(objectEntry.Extended) + if objectGrants != nil { + for _, objectGrant := range objectGrants { + for _, requiredGrant := range reqGrants { + if s3acl.GrantEquals(objectGrant, requiredGrant) { + return bucketMetadata, objectEntry, objectOwner, s3err.ErrNone + } + } + } + } + + glog.V(3).Infof("acl denied! request account id: %s", accountId) + return nil, nil, "", s3err.ErrAccessDenied +} + +func getObjectEntry(s3a *S3ApiServer, bucket, object string) (*filer_pb.Entry, error) { + return s3a.getEntry(util.Join(s3a.option.BucketsPath, bucket), object) +} + +func updateObjectEntry(s3a *S3ApiServer, bucket string, entry *filer_pb.Entry) error { + return s3a.updateEntry(util.Join(s3a.option.BucketsPath, bucket), entry) +} diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go index 2fc0111a4..8ee11c195 100644 --- a/weed/s3api/s3api_object_handlers.go +++ b/weed/s3api/s3api_object_handlers.go @@ -7,6 +7,7 @@ import ( "encoding/xml" "fmt" "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3acl" "github.com/seaweedfs/seaweedfs/weed/security" "github.com/seaweedfs/seaweedfs/weed/util/mem" "golang.org/x/exp/slices" @@ -525,3 +526,34 @@ func (s3a *S3ApiServer) maybeGetFilerJwtAuthorizationToken(isWrite bool) string } return string(encodedJwt) } + +// PutObjectAclHandler Put object ACL +// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjecthtml +func (s3a *S3ApiServer) PutObjectAclHandler(w http.ResponseWriter, r *http.Request) { + bucket, object := s3_constants.GetBucketAndObject(r) + + accountId := s3acl.GetAccountId(r) + bucketMetadata, objectEntry, objectOwner, errCode := s3a.checkAccessForWriteObjectAcl(accountId, bucket, object) + if errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + + grants, errCode := s3acl.ExtractAcl(r, s3a.accountManager, bucketMetadata.ObjectOwnership, *bucketMetadata.Owner.ID, objectOwner, accountId) + if errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + + errCode = s3acl.AssembleEntryWithAcp(objectEntry, objectOwner, grants) + if errCode != s3err.ErrNone { + return + } + + err := updateObjectEntry(s3a, bucket, objectEntry) + if err != nil { + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) + return + } + w.WriteHeader(http.StatusOK) +} diff --git a/weed/s3api/s3api_object_skip_handlers.go b/weed/s3api/s3api_object_skip_handlers.go index 160d02475..197a2d9b7 100644 --- a/weed/s3api/s3api_object_skip_handlers.go +++ b/weed/s3api/s3api_object_skip_handlers.go @@ -12,14 +12,6 @@ func (s3a *S3ApiServer) GetObjectAclHandler(w http.ResponseWriter, r *http.Reque } -// 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) - -} - // PutObjectRetentionHandler Put object Retention // https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectRetention.html func (s3a *S3ApiServer) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) { diff --git a/weed/s3api/s3err/s3api_errors.go b/weed/s3api/s3err/s3api_errors.go index 0348d4ddc..cdda16e87 100644 --- a/weed/s3api/s3err/s3api_errors.go +++ b/weed/s3api/s3err/s3api_errors.go @@ -109,6 +109,7 @@ const ( ErrRequestBytesExceed OwnershipControlsNotFoundError + AccessControlListNotSupported ) // error code to APIError structure, these fields carry respective @@ -416,12 +417,16 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "Simultaneous request bytes exceed limitations", HTTPStatusCode: http.StatusTooManyRequests, }, - OwnershipControlsNotFoundError: { Code: "OwnershipControlsNotFoundError", Description: "The bucket ownership controls were not found", HTTPStatusCode: http.StatusNotFound, }, + AccessControlListNotSupported: { + Code: "AccessControlListNotSupported", + Description: "The bucket does not allow ACLs", + HTTPStatusCode: http.StatusBadRequest, + }, } // GetAPIError provides API Error for input API error code.