package handler import ( "bytes" "context" "encoding/base64" "encoding/xml" "fmt" "net/http" "net/http/httptest" "testing" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "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 TestGetPublicAccessBlock(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-get-public-access-block" createBucket(hc, bktName) deletePublicAccessBlock(hc, bktName) getPublicAccessBlockErr(hc, bktName, apierr.GetAPIError(apierr.ErrNoSuchPublicAccessBlockConfiguration)) putPublicAccessBlock(hc, bktName, &data.PublicAccessBlockConfiguration{ BlockPublicAcls: true, BlockPublicPolicy: true, IgnorePublicAcls: true, RestrictPublicBuckets: true, }, nil) cfg := getPublicAccessBlock(hc, bktName) require.True(t, cfg.BlockPublicAcls) require.True(t, cfg.BlockPublicPolicy) require.True(t, cfg.IgnorePublicAcls) require.True(t, cfg.RestrictPublicBuckets) deletePublicAccessBlock(hc, bktName) getPublicAccessBlockErr(hc, bktName, apierr.GetAPIError(apierr.ErrNoSuchPublicAccessBlockConfiguration)) } func TestPutPublicAccessBlock(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-put-public-access-block" createBucket(hc, bktName) cfg := &data.PublicAccessBlockConfiguration{ BlockPublicAcls: true, BlockPublicPolicy: true, IgnorePublicAcls: true, RestrictPublicBuckets: true, } body, err := xml.Marshal(cfg) require.NoError(t, err) contentMD5, err := getContentMD5(bytes.NewReader(body)) require.NoError(t, err) putPublicAccessBlockErr(hc, bktName, cfg, map[string]string{api.AmzExpectedBucketOwner: "owner"}, apierr.GetAPIError(apierr.ErrAccessDenied)) putPublicAccessBlockErr(hc, bktName, cfg, map[string]string{api.ContentMD5: "invalid"}, apierr.GetAPIError(apierr.ErrInvalidDigest)) putPublicAccessBlockErr(hc, bktName, cfg, map[string]string{api.ContentMD5: base64.StdEncoding.EncodeToString([]byte{})}, apierr.GetAPIError(apierr.ErrInvalidDigest)) putPublicAccessBlock(hc, bktName, cfg, map[string]string{api.ContentMD5: base64.StdEncoding.EncodeToString(contentMD5)}) w, r := prepareTestRequest(hc, bktName, "", &data.LifecycleConfiguration{}) hc.Handler().PutPublicAccessBlockHandler(w, r) assertS3Error(t, w, apierr.GetAPIError(apierr.ErrMalformedXML)) } func TestBlockPublicAcls(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-block-public-acls" info := createBucket(hc, bktName) putPublicAccessBlock(hc, bktName, &data.PublicAccessBlockConfiguration{ BlockPublicAcls: true, }, nil) putBucketACLErr(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLPublic}, apierr.GetAPIError(apierr.ErrAccessDenied)) putBucketACLErr(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLReadOnly}, apierr.GetAPIError(apierr.ErrAccessDenied)) putBucketACL(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLPrivate}) } func TestBlockPublicPolicy(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-block-public-policy" createBucket(hc, bktName) putPublicAccessBlock(hc, bktName, &data.PublicAccessBlockConfiguration{ BlockPublicPolicy: true, }, nil) putBucketPolicy(hc, bktName, iam.Policy{ // public policy Version: "2012-10-17", Statement: []iam.Statement{ { Principal: map[iam.PrincipalType][]string{iam.Wildcard: {}}, Effect: "Allow", Action: []string{"s3:*"}, Resource: []string{fmt.Sprintf("arn:aws:s3:::%s/*", bktName)}, }, }, }, apierr.ErrAccessDenied) key, err := keys.NewPrivateKey() require.NoError(t, err) hc.Handler().frostfsid.(*frostfsidMock).data["devenv"] = key.PublicKey() putBucketPolicy(hc, bktName, iam.Policy{ // non-public policy Version: "2012-10-17", Statement: []iam.Statement{ { Principal: map[iam.PrincipalType][]string{iam.AWSPrincipalType: {"arn:aws:iam:::user/devenv"}}, Effect: "Allow", Action: []string{"s3:*"}, Resource: []string{fmt.Sprintf("arn:aws:s3:::%s/*", bktName)}, }, }, }) } func TestIgnorePublicAcls(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-ignore-public-acls" info := createBucket(hc, bktName) chains := hc.h.policyEngine.APE.(*apeMock).chainMap[engine.ContainerTarget(info.BktInfo.CID.EncodeToString())] require.Len(t, chains, 2) require.Len(t, chains[0].Rules, 0) require.Len(t, chains[1].Rules, 0) putPublicAccessBlock(hc, bktName, &data.PublicAccessBlockConfiguration{ IgnorePublicAcls: true, }, nil) chains = hc.h.policyEngine.APE.(*apeMock).chainMap[engine.ContainerTarget(info.BktInfo.CID.EncodeToString())] require.Len(t, chains, 2) require.Len(t, chains[0].Rules, 0) require.Len(t, chains[1].Rules, 0) putBucketACL(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLPrivate}) chains = hc.h.policyEngine.APE.(*apeMock).chainMap[engine.ContainerTarget(info.BktInfo.CID.EncodeToString())] require.Len(t, chains, 2) require.Len(t, chains[0].Rules, 0) require.Len(t, chains[1].Rules, 0) putBucketACL(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLPublic}) chains = hc.h.policyEngine.APE.(*apeMock).chainMap[engine.ContainerTarget(info.BktInfo.CID.EncodeToString())] require.Len(t, chains, 2) require.Len(t, chains[0].Rules, 0) require.Len(t, chains[1].Rules, 0) putBucketACL(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLReadOnly}) chains = hc.h.policyEngine.APE.(*apeMock).chainMap[engine.ContainerTarget(info.BktInfo.CID.EncodeToString())] require.Len(t, chains, 2) require.Len(t, chains[0].Rules, 0) require.Len(t, chains[1].Rules, 0) putPublicAccessBlock(hc, bktName, &data.PublicAccessBlockConfiguration{ IgnorePublicAcls: false, }, nil) chains = hc.h.policyEngine.APE.(*apeMock).chainMap[engine.ContainerTarget(info.BktInfo.CID.EncodeToString())] require.Len(t, chains, 2) require.Len(t, chains[0].Rules, 1) require.Len(t, chains[1].Rules, 1) putBucketACL(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLPrivate}) chains = hc.h.policyEngine.APE.(*apeMock).chainMap[engine.ContainerTarget(info.BktInfo.CID.EncodeToString())] require.Len(t, chains, 2) require.Len(t, chains[0].Rules, 0) require.Len(t, chains[1].Rules, 0) putBucketACL(hc, bktName, info.Box, map[string]string{api.AmzACL: basicACLPublic}) chains = hc.h.policyEngine.APE.(*apeMock).chainMap[engine.ContainerTarget(info.BktInfo.CID.EncodeToString())] require.Len(t, chains, 2) require.Len(t, chains[0].Rules, 1) require.Len(t, chains[1].Rules, 1) putPublicAccessBlock(hc, bktName, &data.PublicAccessBlockConfiguration{ IgnorePublicAcls: true, }, nil) require.Len(t, hc.h.policyEngine.APE.(*apeMock).chainMap[engine.ContainerTarget(info.BktInfo.CID.EncodeToString())], 0) deletePublicAccessBlock(hc, bktName) chains = hc.h.policyEngine.APE.(*apeMock).chainMap[engine.ContainerTarget(info.BktInfo.CID.EncodeToString())] require.Len(t, chains, 2) require.Len(t, chains[0].Rules, 1) require.Len(t, chains[1].Rules, 1) } func TestCheckRestrictPublicBuckets(t *testing.T) { hc := prepareHandlerContext(t) bktName := "bucket-restrict-public-buckets" ctx := context.Background() ctx = middleware.SetReqInfo(ctx, &middleware.ReqInfo{ BucketName: bktName, }) err := hc.Handler().CheckRestrictPublicBuckets(ctx) // operations on non-existing bucket, such as create-bucket must not lead to fail require.NoError(t, err) createBucket(hc, bktName) putPublicAccessBlock(hc, bktName, &data.PublicAccessBlockConfiguration{ RestrictPublicBuckets: true, }, nil) err = hc.Handler().CheckRestrictPublicBuckets(ctx) require.NoError(t, err) key, err := keys.NewPrivateKey() require.NoError(t, err) hc.Handler().frostfsid.(*frostfsidMock).data["devenv"] = key.PublicKey() putBucketPolicy(hc, bktName, iam.Policy{ // non-public policy Version: "2012-10-17", Statement: []iam.Statement{ { Principal: map[iam.PrincipalType][]string{iam.AWSPrincipalType: {"arn:aws:iam:::user/devenv"}}, Effect: "Allow", Action: []string{"s3:*"}, Resource: []string{fmt.Sprintf("arn:aws:s3:::%s/*", bktName)}, }, }, }) err = hc.Handler().CheckRestrictPublicBuckets(ctx) require.NoError(t, err) putBucketPolicy(hc, bktName, iam.Policy{ // public policy Version: "2012-10-17", Statement: []iam.Statement{ { Principal: map[iam.PrincipalType][]string{iam.Wildcard: {}}, Effect: "Allow", Action: []string{"s3:*"}, Resource: []string{fmt.Sprintf("arn:aws:s3:::%s/*", bktName)}, }, }, }) err = hc.Handler().CheckRestrictPublicBuckets(ctx) require.ErrorIs(t, err, apierr.GetAPIError(apierr.ErrAccessDenied)) ctx = middleware.SetReqInfo(ctx, &middleware.ReqInfo{ BucketName: bktName, UserNamespace: ptr("namespace"), }) err = hc.Handler().CheckRestrictPublicBuckets(ctx) require.ErrorIs(t, err, apierr.GetAPIError(apierr.ErrAccessDenied)) ctx = middleware.SetReqInfo(ctx, &middleware.ReqInfo{ BucketName: bktName, UserNamespace: ptr(""), }) err = hc.Handler().CheckRestrictPublicBuckets(ctx) require.NoError(t, err) deletePublicAccessBlock(hc, bktName) ctx = middleware.SetReqInfo(ctx, &middleware.ReqInfo{ BucketName: bktName, }) err = hc.Handler().CheckRestrictPublicBuckets(ctx) require.NoError(t, err) } func putPublicAccessBlock(hc *handlerContext, bktName string, cfg *data.PublicAccessBlockConfiguration, headers map[string]string) { w := putPublicAccessBlockBase(hc, bktName, cfg, headers) assertStatus(hc.t, w, http.StatusOK) } func putPublicAccessBlockErr(hc *handlerContext, bktName string, cfg *data.PublicAccessBlockConfiguration, headers map[string]string, err apierr.Error) { w := putPublicAccessBlockBase(hc, bktName, cfg, headers) assertS3Error(hc.t, w, err) } func putPublicAccessBlockBase(hc *handlerContext, bktName string, cfg *data.PublicAccessBlockConfiguration, headers map[string]string) *httptest.ResponseRecorder { w, r := prepareTestRequest(hc, bktName, "", cfg) for k, v := range headers { r.Header.Set(k, v) } hc.Handler().PutPublicAccessBlockHandler(w, r) return w } func getPublicAccessBlock(hc *handlerContext, bktName string) *data.PublicAccessBlockConfiguration { w := getPublicAccessBlockBase(hc, bktName) assertStatus(hc.t, w, http.StatusOK) res := &data.PublicAccessBlockConfiguration{} parseTestResponse(hc.t, w, res) return res } func getPublicAccessBlockErr(hc *handlerContext, bktName string, err apierr.Error) { w := getPublicAccessBlockBase(hc, bktName) assertS3Error(hc.t, w, err) } func getPublicAccessBlockBase(hc *handlerContext, bktName string) *httptest.ResponseRecorder { w, r := prepareTestRequest(hc, bktName, "", nil) hc.Handler().GetPublicAccessBlockHandler(w, r) return w } func deletePublicAccessBlock(hc *handlerContext, bktName string) { w := deletePublicAccessBlockBase(hc, bktName) assertStatus(hc.t, w, http.StatusNoContent) } func deletePublicAccessBlockBase(hc *handlerContext, bktName string) *httptest.ResponseRecorder { w, r := prepareTestRequest(hc, bktName, "", nil) hc.Handler().DeletePublicAccessBlockHandler(w, r) return w }