All checks were successful
/ DCO (pull_request) Successful in 33s
/ Vulncheck (pull_request) Successful in 59s
/ Builds (pull_request) Successful in 1m28s
/ OCI image (pull_request) Successful in 2m7s
/ Lint (pull_request) Successful in 2m21s
/ Tests (pull_request) Successful in 1m45s
/ Builds (push) Successful in 1m12s
/ Vulncheck (push) Successful in 1m44s
/ Lint (push) Successful in 2m26s
/ Tests (push) Successful in 1m35s
/ OCI image (push) Successful in 2m18s
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
315 lines
11 KiB
Go
315 lines
11 KiB
Go
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
|
|
}
|