package handler import ( "bytes" "encoding/hex" "encoding/json" "encoding/xml" "net/http" "net/http/httptest" "testing" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/stretchr/testify/require" ) func TestPutObjectACLErrorAPE(t *testing.T) { hc := prepareHandlerContext(t) bktName, objName := "bucket-for-acl-ape", "object" info := createBucket(hc, bktName) putObjectWithHeadersAssertS3Error(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPublic}, s3errors.ErrAccessControlListNotSupported) putObjectWithHeaders(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPrivate}) // only `private` canned acl is allowed, that is actually ignored putObjectWithHeaders(hc, bktName, objName, nil) aclBody := &AccessControlPolicy{} putObjectACLAssertS3Error(hc, bktName, objName, info.Box, nil, aclBody, s3errors.ErrAccessControlListNotSupported) aclRes := getObjectACL(hc, bktName, objName) checkPrivateACL(t, aclRes, info.Key.PublicKey()) } func TestCreateObjectACLErrorAPE(t *testing.T) { hc := prepareHandlerContext(t) bktName, objName, objNameCopy := "bucket-for-acl-ape", "object", "copy" createBucket(hc, bktName) putObject(hc, bktName, objName) copyObject(hc, bktName, objName, objNameCopy, CopyMeta{Headers: map[string]string{api.AmzACL: basicACLPublic}}, http.StatusBadRequest) copyObject(hc, bktName, objName, objNameCopy, CopyMeta{Headers: map[string]string{api.AmzACL: basicACLPrivate}}, http.StatusOK) createMultipartUploadAssertS3Error(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPublic}, s3errors.ErrAccessControlListNotSupported) createMultipartUpload(hc, bktName, objName, map[string]string{api.AmzACL: basicACLPrivate}) } func TestBucketACLAPE(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-for-acl-ape" info := createBucket(hc, bktName) aclBody := &AccessControlPolicy{} putBucketACLAssertS3Error(hc, bktName, info.Box, nil, aclBody, s3errors.ErrAccessControlListNotSupported) aclRes := getBucketACL(hc, bktName) checkPrivateACL(t, aclRes, info.Key.PublicKey()) putBucketACL(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLPrivate}) aclRes = getBucketACL(hc, bktName) checkPrivateACL(t, aclRes, info.Key.PublicKey()) putBucketACL(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLReadOnly}) aclRes = getBucketACL(hc, bktName) checkPublicReadACL(t, aclRes, info.Key.PublicKey()) putBucketACL(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLPublic}) aclRes = getBucketACL(hc, bktName) checkPublicReadWriteACL(t, aclRes, info.Key.PublicKey()) } func checkPrivateACL(t *testing.T, aclRes *AccessControlPolicy, ownerKey *keys.PublicKey) { checkACLOwner(t, aclRes, ownerKey) } func checkPublicReadACL(t *testing.T, aclRes *AccessControlPolicy, ownerKey *keys.PublicKey) { checkACLOwner(t, aclRes, ownerKey) require.Equal(t, allUsersGroup, aclRes.AccessControlList[0].Grantee.URI) require.Equal(t, aclRead, aclRes.AccessControlList[0].Permission) } func checkPublicReadWriteACL(t *testing.T, aclRes *AccessControlPolicy, ownerKey *keys.PublicKey) { checkACLOwner(t, aclRes, ownerKey) require.Equal(t, allUsersGroup, aclRes.AccessControlList[0].Grantee.URI) require.Equal(t, aclWrite, aclRes.AccessControlList[0].Permission) require.Equal(t, allUsersGroup, aclRes.AccessControlList[1].Grantee.URI) require.Equal(t, aclRead, aclRes.AccessControlList[1].Permission) } func checkACLOwner(t *testing.T, aclRes *AccessControlPolicy, ownerKey *keys.PublicKey) { ownerIDStr := hex.EncodeToString(ownerKey.Bytes()) ownerNameStr := ownerKey.Address() require.Equal(t, ownerIDStr, aclRes.Owner.ID) require.Equal(t, ownerNameStr, aclRes.Owner.DisplayName) } func TestBucketPolicy(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-for-policy" createTestBucket(hc, bktName) getBucketPolicy(hc, bktName, s3errors.ErrNoSuchBucketPolicy) newPolicy := engineiam.Policy{ Version: "2012-10-17", Statement: []engineiam.Statement{{ Principal: map[engineiam.PrincipalType][]string{engineiam.Wildcard: {}}, Effect: engineiam.DenyEffect, Action: engineiam.Action{"s3:PutObject"}, Resource: engineiam.Resource{"arn:aws:s3:::test/*"}, }}, } putBucketPolicy(hc, bktName, newPolicy, s3errors.ErrMalformedPolicy) newPolicy.Statement[0].Resource[0] = arnAwsPrefix + bktName + "/*" putBucketPolicy(hc, bktName, newPolicy) bktPolicy := getBucketPolicy(hc, bktName) require.Equal(t, newPolicy, bktPolicy) } func TestBucketPolicyStatus(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-for-policy" createTestBucket(hc, bktName) getBucketPolicy(hc, bktName, s3errors.ErrNoSuchBucketPolicy) newPolicy := engineiam.Policy{ Version: "2012-10-17", Statement: []engineiam.Statement{{ NotPrincipal: engineiam.Principal{engineiam.Wildcard: {}}, Effect: engineiam.AllowEffect, Action: engineiam.Action{"s3:PutObject"}, Resource: engineiam.Resource{arnAwsPrefix + bktName + "/*"}, }}, } putBucketPolicy(hc, bktName, newPolicy, s3errors.ErrMalformedPolicyNotPrincipal) newPolicy.Statement[0].NotPrincipal = nil newPolicy.Statement[0].Principal = map[engineiam.PrincipalType][]string{engineiam.Wildcard: {}} putBucketPolicy(hc, bktName, newPolicy) bktPolicyStatus := getBucketPolicyStatus(hc, bktName) require.True(t, PolicyStatusIsPublicTrue == bktPolicyStatus.IsPublic) key, err := keys.NewPrivateKey() require.NoError(t, err) hc.Handler().frostfsid.(*frostfsidMock).data["devenv"] = key.PublicKey() newPolicy.Statement[0].Principal = map[engineiam.PrincipalType][]string{engineiam.AWSPrincipalType: {"arn:aws:iam:::user/devenv"}} putBucketPolicy(hc, bktName, newPolicy) bktPolicyStatus = getBucketPolicyStatus(hc, bktName) require.True(t, PolicyStatusIsPublicFalse == bktPolicyStatus.IsPublic) } func TestDeleteBucketWithPolicy(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-for-policy" bi := createTestBucket(hc, bktName) newPolicy := engineiam.Policy{ Version: "2012-10-17", Statement: []engineiam.Statement{{ Principal: map[engineiam.PrincipalType][]string{engineiam.Wildcard: {}}, Effect: engineiam.AllowEffect, Action: engineiam.Action{"s3:PutObject"}, Resource: engineiam.Resource{"arn:aws:s3:::bucket-for-policy/*"}, }}, } putBucketPolicy(hc, bktName, newPolicy) require.Len(t, hc.h.ape.(*apeMock).policyMap, 1) require.Len(t, hc.h.ape.(*apeMock).chainMap[engine.ContainerTarget(bi.CID.EncodeToString())], 4) deleteBucket(t, hc, bktName, http.StatusNoContent) require.Empty(t, hc.h.ape.(*apeMock).policyMap) chains, err := hc.h.ape.(*apeMock).ListChains(engine.ContainerTarget(bi.CID.EncodeToString())) require.NoError(t, err) require.Empty(t, chains) } func TestPutBucketPolicy(t *testing.T) { bktPolicy := ` { "Version": "2012-10-17", "Statement": [{ "Principal": "*", "Effect": "Deny", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucket-for-policy/*" }] } ` hc := prepareHandlerContext(t) bktName := "bucket-for-policy" createTestBucket(hc, bktName) w, r := prepareTestPayloadRequest(hc, bktName, "", bytes.NewReader([]byte(bktPolicy))) hc.Handler().PutBucketPolicyHandler(w, r) assertStatus(hc.t, w, http.StatusOK) } func getBucketPolicy(hc *handlerContext, bktName string, errCode ...s3errors.ErrorCode) engineiam.Policy { w, r := prepareTestRequest(hc, bktName, "", nil) hc.Handler().GetBucketPolicyHandler(w, r) var policy engineiam.Policy if len(errCode) == 0 { assertStatus(hc.t, w, http.StatusOK) err := json.NewDecoder(w.Result().Body).Decode(&policy) require.NoError(hc.t, err) } else { assertS3Error(hc.t, w, s3errors.GetAPIError(errCode[0])) } return policy } func getBucketPolicyStatus(hc *handlerContext, bktName string, errCode ...s3errors.ErrorCode) PolicyStatus { w, r := prepareTestRequest(hc, bktName, "", nil) hc.Handler().GetBucketPolicyStatusHandler(w, r) var policyStatus PolicyStatus if len(errCode) == 0 { assertStatus(hc.t, w, http.StatusOK) err := xml.NewDecoder(w.Result().Body).Decode(&policyStatus) require.NoError(hc.t, err) } else { assertS3Error(hc.t, w, s3errors.GetAPIError(errCode[0])) } return policyStatus } func putBucketPolicy(hc *handlerContext, bktName string, bktPolicy engineiam.Policy, errCode ...s3errors.ErrorCode) { body, err := json.Marshal(bktPolicy) require.NoError(hc.t, err) w, r := prepareTestPayloadRequest(hc, bktName, "", bytes.NewReader(body)) hc.Handler().PutBucketPolicyHandler(w, r) if len(errCode) == 0 { assertStatus(hc.t, w, http.StatusOK) } else { assertS3Error(hc.t, w, s3errors.GetAPIError(errCode[0])) } } func createAccessBox(t *testing.T) (*accessbox.Box, *keys.PrivateKey) { key, err := keys.NewPrivateKey() require.NoError(t, err) var bearerToken bearer.Token err = bearerToken.Sign(key.PrivateKey) require.NoError(t, err) tok := new(session.Container) tok.ForVerb(session.VerbContainerPut) err = tok.Sign(key.PrivateKey) require.NoError(t, err) box := &accessbox.Box{ Gate: &accessbox.GateData{ SessionTokens: []*session.Container{tok}, BearerToken: &bearerToken, }, } return box, key } type createBucketInfo struct { BktInfo *data.BucketInfo Box *accessbox.Box Key *keys.PrivateKey } func createBucket(hc *handlerContext, bktName string) *createBucketInfo { box, key := createAccessBox(hc.t) w := createBucketBase(hc, bktName, box) assertStatus(hc.t, w, http.StatusOK) bktInfo, err := hc.Layer().GetBucketInfo(hc.Context(), bktName) require.NoError(hc.t, err) return &createBucketInfo{ BktInfo: bktInfo, Box: box, Key: key, } } func createBucketAssertS3Error(hc *handlerContext, bktName string, box *accessbox.Box, code s3errors.ErrorCode) { w := createBucketBase(hc, bktName, box) assertS3Error(hc.t, w, s3errors.GetAPIError(code)) } func createBucketBase(hc *handlerContext, bktName string, box *accessbox.Box) *httptest.ResponseRecorder { w, r := prepareTestRequest(hc, bktName, "", nil) ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box}) r = r.WithContext(ctx) hc.Handler().CreateBucketHandler(w, r) return w } func putBucketACL(hc *handlerContext, bktName string, box *accessbox.Box, header map[string]string) { w := putBucketACLBase(hc, bktName, box, header, nil) assertStatus(hc.t, w, http.StatusOK) } func putBucketACLAssertS3Error(hc *handlerContext, bktName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy, code s3errors.ErrorCode) { w := putBucketACLBase(hc, bktName, box, header, body) assertS3Error(hc.t, w, s3errors.GetAPIError(code)) } func putBucketACLBase(hc *handlerContext, bktName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy) *httptest.ResponseRecorder { w, r := prepareTestRequest(hc, bktName, "", body) for key, val := range header { r.Header.Set(key, val) } ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box}) r = r.WithContext(ctx) hc.Handler().PutBucketACLHandler(w, r) return w } func getBucketACL(hc *handlerContext, bktName string) *AccessControlPolicy { w := getBucketACLBase(hc, bktName) assertStatus(hc.t, w, http.StatusOK) res := &AccessControlPolicy{} parseTestResponse(hc.t, w, res) return res } func getBucketACLBase(hc *handlerContext, bktName string) *httptest.ResponseRecorder { w, r := prepareTestRequest(hc, bktName, "", nil) hc.Handler().GetBucketACLHandler(w, r) return w } func putObjectACLAssertS3Error(hc *handlerContext, bktName, objName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy, code s3errors.ErrorCode) { w := putObjectACLBase(hc, bktName, objName, box, header, body) assertS3Error(hc.t, w, s3errors.GetAPIError(code)) } func putObjectACLBase(hc *handlerContext, bktName, objName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy) *httptest.ResponseRecorder { w, r := prepareTestRequest(hc, bktName, objName, body) for key, val := range header { r.Header.Set(key, val) } ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box}) r = r.WithContext(ctx) hc.Handler().PutObjectACLHandler(w, r) return w } func getObjectACL(hc *handlerContext, bktName, objName string) *AccessControlPolicy { w := getObjectACLBase(hc, bktName, objName) assertStatus(hc.t, w, http.StatusOK) res := &AccessControlPolicy{} parseTestResponse(hc.t, w, res) return res } func getObjectACLBase(hc *handlerContext, bktName, objName string) *httptest.ResponseRecorder { w, r := prepareTestRequest(hc, bktName, objName, nil) hc.Handler().GetObjectACLHandler(w, r) return w } func putObjectWithHeaders(hc *handlerContext, bktName, objName string, headers map[string]string) http.Header { w := putObjectWithHeadersBase(hc, bktName, objName, headers, nil, nil) assertStatus(hc.t, w, http.StatusOK) return w.Header() } func putObjectWithHeadersAssertS3Error(hc *handlerContext, bktName, objName string, headers map[string]string, code s3errors.ErrorCode) { w := putObjectWithHeadersBase(hc, bktName, objName, headers, nil, nil) assertS3Error(hc.t, w, s3errors.GetAPIError(code)) } func putObjectWithHeadersBase(hc *handlerContext, bktName, objName string, headers map[string]string, box *accessbox.Box, data []byte) *httptest.ResponseRecorder { body := bytes.NewReader(data) w, r := prepareTestPayloadRequest(hc, bktName, objName, body) for k, v := range headers { r.Header.Set(k, v) } ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box}) r = r.WithContext(ctx) hc.Handler().PutObjectHandler(w, r) return w }