Browse Source

Merge branch '4_object_write' into 10_acl_merged

# Conflicts:
#	weed/s3api/s3api_acp.go
#	weed/s3api/s3api_object_handlers.go
#	weed/s3api/s3err/s3api_errors.go
pull/4090/head
changlin.shi 2 years ago
parent
commit
3a01ccd0fa
  1. 28
      weed/s3api/chunked_reader_v4.go
  2. 104
      weed/s3api/s3api_acp.go
  3. 19
      weed/s3api/s3api_object_handlers.go
  4. 14
      weed/s3api/s3api_object_multipart_handlers.go

28
weed/s3api/chunked_reader_v4.go

@ -59,7 +59,7 @@ func getChunkSignature(secretKey string, seedSignature string, region string, da
//
// returns signature, error otherwise if the signature mismatches or any other
// error while parsing and validating.
func (iam *IdentityAccessManagement) calculateSeedSignature(r *http.Request) (cred *Credential, signature string, region string, date time.Time, errCode s3err.ErrorCode) {
func (iam *IdentityAccessManagement) calculateSeedSignature(r *http.Request) (idnt *Identity, cred *Credential, signature string, region string, date time.Time, errCode s3err.ErrorCode) {
// Copy request.
req := *r
@ -70,7 +70,7 @@ func (iam *IdentityAccessManagement) calculateSeedSignature(r *http.Request) (cr
// Parse signature version '4' header.
signV4Values, errCode := parseSignV4(v4Auth)
if errCode != s3err.ErrNone {
return nil, "", "", time.Time{}, errCode
return nil, nil, "", "", time.Time{}, errCode
}
// Payload streaming.
@ -78,18 +78,18 @@ func (iam *IdentityAccessManagement) calculateSeedSignature(r *http.Request) (cr
// Payload for STREAMING signature should be 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD'
if payload != req.Header.Get("X-Amz-Content-Sha256") {
return nil, "", "", time.Time{}, s3err.ErrContentSHA256Mismatch
return nil, nil, "", "", time.Time{}, s3err.ErrContentSHA256Mismatch
}
// Extract all the signed headers along with its values.
extractedSignedHeaders, errCode := extractSignedHeaders(signV4Values.SignedHeaders, r)
if errCode != s3err.ErrNone {
return nil, "", "", time.Time{}, errCode
return nil, nil, "", "", time.Time{}, errCode
}
// Verify if the access key id matches.
identity, cred, found := iam.lookupByAccessKey(signV4Values.Credential.accessKey)
if !found {
return nil, "", "", time.Time{}, s3err.ErrInvalidAccessKeyID
return nil, nil, "", "", time.Time{}, s3err.ErrInvalidAccessKeyID
}
bucket, object := s3_constants.GetBucketAndObject(r)
@ -105,14 +105,14 @@ func (iam *IdentityAccessManagement) calculateSeedSignature(r *http.Request) (cr
var dateStr string
if dateStr = req.Header.Get(http.CanonicalHeaderKey("x-amz-date")); dateStr == "" {
if dateStr = r.Header.Get("Date"); dateStr == "" {
return nil, "", "", time.Time{}, s3err.ErrMissingDateHeader
return nil, nil, "", "", time.Time{}, s3err.ErrMissingDateHeader
}
}
// Parse date header.
var err error
date, err = time.Parse(iso8601Format, dateStr)
if err != nil {
return nil, "", "", time.Time{}, s3err.ErrMalformedDate
return nil, nil, "", "", time.Time{}, s3err.ErrMalformedDate
}
// Query string.
@ -132,11 +132,11 @@ func (iam *IdentityAccessManagement) calculateSeedSignature(r *http.Request) (cr
// Verify if signature match.
if !compareSignatureV4(newSignature, signV4Values.Signature) {
return nil, "", "", time.Time{}, s3err.ErrSignatureDoesNotMatch
return nil, nil, "", "", time.Time{}, s3err.ErrSignatureDoesNotMatch
}
// Return calculated signature.
return cred, newSignature, region, date, s3err.ErrNone
return identity, cred, newSignature, region, date, s3err.ErrNone
}
const maxLineLength = 4 * humanize.KiByte // assumed <= bufio.defaultBufSize 4KiB
@ -150,20 +150,20 @@ var errMalformedEncoding = errors.New("malformed chunked encoding")
// newSignV4ChunkedReader returns a new s3ChunkedReader that translates the data read from r
// out of HTTP "chunked" format before returning it.
// The s3ChunkedReader returns io.EOF when the final 0-length chunk is read.
func (iam *IdentityAccessManagement) newSignV4ChunkedReader(req *http.Request) (io.ReadCloser, s3err.ErrorCode) {
ident, seedSignature, region, seedDate, errCode := iam.calculateSeedSignature(req)
func (iam *IdentityAccessManagement) newSignV4ChunkedReader(req *http.Request) (io.ReadCloser, *Identity, s3err.ErrorCode) {
ident, cred, seedSignature, region, seedDate, errCode := iam.calculateSeedSignature(req)
if errCode != s3err.ErrNone {
return nil, errCode
return nil, nil, errCode
}
return &s3ChunkedReader{
cred: ident,
cred: cred,
reader: bufio.NewReader(req.Body),
seedSignature: seedSignature,
seedDate: seedDate,
region: region,
chunkSHA256Writer: sha256.New(),
state: readChunkHeader,
}, s3err.ErrNone
}, ident, s3err.ErrNone
}
// Represents the overall state that is required for decoding a

104
weed/s3api/s3api_acp.go

@ -255,10 +255,106 @@ func (s3a *S3ApiServer) checkAccessForWriteObjectAcl(accountId, bucket, object s
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)
}
// Check Object-Write related access
// includes:
// - PutObjectHandler
// - PutObjectPartHandler
func (s3a *S3ApiServer) checkAccessForWriteObject(r *http.Request, bucket, object string) s3err.ErrorCode {
bucketMetadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket)
if errCode != s3err.ErrNone {
return errCode
}
accountId := s3acl.GetAccountId(r)
if bucketMetadata.ObjectOwnership == s3_constants.OwnershipBucketOwnerEnforced {
// validate grants (only bucketOwnerFullControl acl is allowed)
_, grants, errCode := s3acl.ParseAndValidateAclHeaders(r, s3a.accountManager, bucketMetadata.ObjectOwnership, *bucketMetadata.Owner.ID, accountId, false)
if errCode != s3err.ErrNone {
return errCode
}
if len(grants) > 1 {
return s3err.AccessControlListNotSupported
}
bucketOwnerFullControlGrant := &s3.Grant{
Permission: &s3_constants.PermissionFullControl,
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeCanonicalUser,
ID: bucketMetadata.Owner.ID,
},
}
if len(grants) == 0 {
// set default grants
s3acl.SetAcpOwnerHeader(r, accountId)
s3acl.SetAcpGrantsHeader(r, []*s3.Grant{bucketOwnerFullControlGrant})
return s3err.ErrNone
}
if !s3acl.GrantEquals(bucketOwnerFullControlGrant, grants[0]) {
return s3err.AccessControlListNotSupported
}
s3acl.SetAcpOwnerHeader(r, accountId)
s3acl.SetAcpGrantsHeader(r, []*s3.Grant{bucketOwnerFullControlGrant})
return s3err.ErrNone
}
//bucket access allowed
bucketAclAllowed := false
if accountId == *bucketMetadata.Owner.ID {
bucketAclAllowed = true
} else {
if len(bucketMetadata.Acl) > 0 {
reqGrants := s3acl.DetermineReqGrants(accountId, s3_constants.PermissionWrite)
bucketLoop:
for _, bucketGrant := range bucketMetadata.Acl {
for _, requiredGrant := range reqGrants {
if s3acl.GrantEquals(bucketGrant, requiredGrant) {
bucketAclAllowed = true
break bucketLoop
}
}
}
}
}
if !bucketAclAllowed {
glog.V(3).Infof("acl denied! request account id: %s", accountId)
return s3err.ErrAccessDenied
}
//object access allowed
entry, err := getObjectEntry(s3a, bucket, object)
if err != nil {
if err != filer_pb.ErrNotFound {
return s3err.ErrInternalError
}
} else {
if entry.IsDirectory {
return s3err.ErrExistingObjectIsDirectory
}
//Only the owner of the bucket and the owner of the object can overwrite the object
objectOwner := s3acl.GetAcpOwner(entry.Extended, *bucketMetadata.Owner.ID)
if accountId != objectOwner && accountId != *bucketMetadata.Owner.ID {
glog.V(3).Infof("acl denied! request account id: %s, expect account id: %s", accountId, *bucketMetadata.Owner.ID)
return s3err.ErrAccessDenied
}
}
ownerId, grants, errCode := s3acl.ParseAndValidateAclHeadersOrElseDefault(r, s3a.accountManager, bucketMetadata.ObjectOwnership, *bucketMetadata.Owner.ID, accountId, false)
if errCode != s3err.ErrNone {
return errCode
}
s3acl.SetAcpOwnerHeader(r, ownerId)
s3acl.SetAcpGrantsHeader(r, grants)
return s3err.ErrNone
}
func getObjectEntry(s3a *S3ApiServer, bucket, object string) (*filer_pb.Entry, error) {
return s3a.getEntry(util.Join(s3a.option.BucketsPath, bucket), object)
}

19
weed/s3api/s3api_object_handlers.go

@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/service/s3"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3acl"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
"github.com/seaweedfs/seaweedfs/weed/security"
"github.com/seaweedfs/seaweedfs/weed/util/mem"
"golang.org/x/exp/slices"
@ -74,18 +75,24 @@ func (s3a *S3ApiServer) PutObjectHandler(w http.ResponseWriter, r *http.Request)
rAuthType := getRequestAuthType(r)
if s3a.iam.isEnabled() {
var s3ErrCode s3err.ErrorCode
var identity *Identity
switch rAuthType {
case authTypeStreamingSigned:
dataReader, s3ErrCode = s3a.iam.newSignV4ChunkedReader(r)
dataReader, identity, s3ErrCode = s3a.iam.newSignV4ChunkedReader(r)
case authTypeSignedV2, authTypePresignedV2:
_, s3ErrCode = s3a.iam.isReqAuthenticatedV2(r)
identity, s3ErrCode = s3a.iam.isReqAuthenticatedV2(r)
case authTypePresigned, authTypeSigned:
_, s3ErrCode = s3a.iam.reqSignatureV4Verify(r)
identity, s3ErrCode = s3a.iam.reqSignatureV4Verify(r)
case authTypeAnonymous:
identity = IdentityAnonymous
}
if s3ErrCode != s3err.ErrNone {
s3err.WriteErrorResponse(w, r, s3ErrCode)
return
}
if identity.AccountId != s3account.AccountAnonymous.Id {
r.Header.Set(s3_constants.AmzAccountId, identity.AccountId)
}
} else {
if authTypeStreamingSigned == rAuthType {
s3err.WriteErrorResponse(w, r, s3err.ErrAuthNotSetup)
@ -94,6 +101,12 @@ func (s3a *S3ApiServer) PutObjectHandler(w http.ResponseWriter, r *http.Request)
}
defer dataReader.Close()
errCode := s3a.checkAccessForWriteObject(r, bucket, object)
if errCode != s3err.ErrNone {
s3err.WriteErrorResponse(w, r, errCode)
return
}
objectContentType := r.Header.Get("Content-Type")
if strings.HasSuffix(object, "/") && r.ContentLength == 0 {
if err := s3a.mkdir(

14
weed/s3api/s3api_object_multipart_handlers.go

@ -230,23 +230,31 @@ func (s3a *S3ApiServer) PutObjectPartHandler(w http.ResponseWriter, r *http.Requ
if s3a.iam.isEnabled() {
rAuthType := getRequestAuthType(r)
var s3ErrCode s3err.ErrorCode
var identity *Identity
switch rAuthType {
case authTypeStreamingSigned:
dataReader, s3ErrCode = s3a.iam.newSignV4ChunkedReader(r)
dataReader, identity, s3ErrCode = s3a.iam.newSignV4ChunkedReader(r)
case authTypeSignedV2, authTypePresignedV2:
_, s3ErrCode = s3a.iam.isReqAuthenticatedV2(r)
identity, s3ErrCode = s3a.iam.isReqAuthenticatedV2(r)
case authTypePresigned, authTypeSigned:
_, s3ErrCode = s3a.iam.reqSignatureV4Verify(r)
identity, s3ErrCode = s3a.iam.reqSignatureV4Verify(r)
}
if s3ErrCode != s3err.ErrNone {
s3err.WriteErrorResponse(w, r, s3ErrCode)
return
}
r.Header.Set(s3_constants.AmzAccountId, identity.AccountId)
}
defer dataReader.Close()
glog.V(2).Infof("PutObjectPartHandler %s %s %04d", bucket, uploadID, partID)
s3ErrCode := s3a.checkAccessForWriteObject(r, bucket, object)
if s3ErrCode != s3err.ErrNone {
s3err.WriteErrorResponse(w, r, s3ErrCode)
return
}
uploadUrl := fmt.Sprintf("http://%s%s/%s/%04d.part",
s3a.option.Filer.ToHttpAddress(), s3a.genUploadsFolder(bucket), uploadID, partID)

Loading…
Cancel
Save