frostfs-s3-gw/api/handler/access_block_test.go
Alex Vanin 03bd3abcd6
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
[#535] Skip access block on non-existing bucket operations
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2025-04-17 18:22:56 +03:00

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
}