forked from TrueCloudLab/frostfs-s3-gw
[#535] Support public access block operations
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
parent
4f0f2ca7bd
commit
a7ce40d745
23 changed files with 940 additions and 87 deletions
217
api/handler/access_block.go
Normal file
217
api/handler/access_block.go
Normal file
|
@ -0,0 +1,217 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
||||
"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/layer"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||
s3common "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/policy-engine/common"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (h *handler) PutPublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutPublicAccessBlock")
|
||||
defer span.End()
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
tee := io.TeeReader(r.Body, &buf)
|
||||
reqInfo := middleware.GetReqInfo(ctx)
|
||||
|
||||
cfg := new(data.PublicAccessBlockConfiguration)
|
||||
if err := h.cfg.NewXMLDecoder(tee, r.UserAgent()).Decode(cfg); err != nil {
|
||||
h.logAndSendError(ctx, w, "could not decode body", reqInfo, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := r.Header[api.ContentMD5]; ok {
|
||||
headerMD5, err := base64.StdEncoding.DecodeString(r.Header.Get(api.ContentMD5))
|
||||
if err != nil {
|
||||
h.logAndSendError(ctx, w, "invalid Content-MD5", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
|
||||
return
|
||||
}
|
||||
|
||||
bodyMD5, err := getContentMD5(&buf)
|
||||
if err != nil {
|
||||
h.logAndSendError(ctx, w, "could not get content md5", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(headerMD5, bodyMD5) {
|
||||
h.logAndSendError(ctx, w, "Content-MD5 does not match", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||
if err != nil {
|
||||
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||
if err != nil {
|
||||
h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete ACL chains if IgnorePublicAcls is set to true
|
||||
if (settings.PublicAccessBlock == nil || !settings.PublicAccessBlock.IgnorePublicAcls) && cfg.IgnorePublicAcls && settings.CannedACL != basicACLPrivate {
|
||||
if err = h.policyEngine.APE.DeleteACLChains(bktInfo.CID.EncodeToString(), []chain.ID{
|
||||
getBucketCannedChainID(chain.S3, bktInfo.CID),
|
||||
getBucketCannedChainID(chain.Ingress, bktInfo.CID),
|
||||
}); err != nil {
|
||||
h.logAndSendError(ctx, w, "failed to delete morph rule chains", reqInfo, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Set ACL chains if IgnorePublicAcls is set to false
|
||||
if settings.PublicAccessBlock != nil && settings.PublicAccessBlock.IgnorePublicAcls && !cfg.IgnorePublicAcls {
|
||||
chainRules := bucketCannedACLToAPERules(settings.CannedACL, reqInfo, bktInfo.CID)
|
||||
if err = h.policyEngine.APE.SaveACLChains(bktInfo.CID.EncodeToString(), chainRules); err != nil {
|
||||
h.logAndSendError(ctx, w, "failed to add morph rule chains", reqInfo, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newSettings := *settings
|
||||
newSettings.PublicAccessBlock = cfg
|
||||
sp := &layer.PutSettingsParams{
|
||||
BktInfo: bktInfo,
|
||||
Settings: &newSettings,
|
||||
}
|
||||
|
||||
if err = h.obj.PutBucketSettings(ctx, sp); err != nil {
|
||||
h.logAndSendError(ctx, w, "couldn't save bucket settings", reqInfo, err,
|
||||
zap.String("container_id", bktInfo.CID.EncodeToString()))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *handler) GetPublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetPublicAccessBlock")
|
||||
defer span.End()
|
||||
|
||||
reqInfo := middleware.GetReqInfo(ctx)
|
||||
|
||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||
if err != nil {
|
||||
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||
if err != nil {
|
||||
h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
if settings.PublicAccessBlock == nil {
|
||||
h.logAndSendError(ctx, w, "no public access block", reqInfo, apierr.GetAPIError(apierr.ErrNoSuchPublicAccessBlockConfiguration))
|
||||
return
|
||||
}
|
||||
|
||||
if err = middleware.EncodeToResponse(w, settings.PublicAccessBlock); err != nil {
|
||||
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) DeletePublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.DeletePublicAccessBlock")
|
||||
defer span.End()
|
||||
|
||||
reqInfo := middleware.GetReqInfo(ctx)
|
||||
|
||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||
if err != nil {
|
||||
h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||
if err != nil {
|
||||
h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
if settings.PublicAccessBlock == nil {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
// Set ACL chains if IgnorePublicAcls was set to true
|
||||
if settings.PublicAccessBlock.IgnorePublicAcls {
|
||||
chainRules := bucketCannedACLToAPERules(settings.CannedACL, reqInfo, bktInfo.CID)
|
||||
if err = h.policyEngine.APE.SaveACLChains(bktInfo.CID.EncodeToString(), chainRules); err != nil {
|
||||
h.logAndSendError(ctx, w, "failed to add morph rule chains", reqInfo, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newSettings := *settings
|
||||
newSettings.PublicAccessBlock = nil
|
||||
sp := &layer.PutSettingsParams{
|
||||
BktInfo: bktInfo,
|
||||
Settings: &newSettings,
|
||||
}
|
||||
if err = h.obj.PutBucketSettings(ctx, sp); err != nil {
|
||||
h.logAndSendError(ctx, w, "couldn't save bucket settings", reqInfo, err,
|
||||
zap.String("container_id", bktInfo.CID.EncodeToString()))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *handler) CheckRestrictPublicBuckets(ctx context.Context) error {
|
||||
reqInfo := middleware.GetReqInfo(ctx)
|
||||
|
||||
bktInfo, err := h.obj.GetBucketInfo(ctx, reqInfo.BucketName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get bucket info: %w", err)
|
||||
}
|
||||
|
||||
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get bucket settings: %w", err)
|
||||
}
|
||||
|
||||
if settings.PublicAccessBlock != nil && settings.PublicAccessBlock.RestrictPublicBuckets {
|
||||
jsonPolicy, err := h.policyEngine.APE.GetBucketPolicy(reqInfo.Namespace, bktInfo.CID)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("get bucket policy: %w", err)
|
||||
}
|
||||
|
||||
var bktPolicy s3common.Policy
|
||||
if err = json.Unmarshal(jsonPolicy, &bktPolicy); err != nil {
|
||||
return fmt.Errorf("unmarshal bucket policy: %w", err)
|
||||
}
|
||||
|
||||
// Check whether bucket policy is public and namespaces of bucket and user are equal
|
||||
if getPolicyStatus(bktPolicy) == PolicyStatusIsPublicTrue &&
|
||||
(reqInfo.UserNamespace == nil || *reqInfo.UserNamespace != reqInfo.Namespace) {
|
||||
return fmt.Errorf("public buckets are restricted: %w", apierr.GetAPIError(apierr.ErrAccessDenied))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
311
api/handler/access_block_test.go
Normal file
311
api/handler/access_block_test.go
Normal file
|
@ -0,0 +1,311 @@
|
|||
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"
|
||||
|
||||
createBucket(hc, bktName)
|
||||
putPublicAccessBlock(hc, bktName, &data.PublicAccessBlockConfiguration{
|
||||
RestrictPublicBuckets: true,
|
||||
}, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = middleware.SetReqInfo(ctx, &middleware.ReqInfo{
|
||||
BucketName: bktName,
|
||||
})
|
||||
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
|
||||
}
|
|
@ -177,17 +177,26 @@ func (h *handler) putBucketACLAPEHandler(w http.ResponseWriter, r *http.Request,
|
|||
return
|
||||
}
|
||||
|
||||
chainRules := bucketCannedACLToAPERules(cannedACL, reqInfo, bktInfo.CID)
|
||||
if err = h.policyEngine.APE.SaveACLChains(bktInfo.CID.EncodeToString(), chainRules); err != nil {
|
||||
h.logAndSendError(ctx, w, "failed to add morph rule chains", reqInfo, err)
|
||||
if settings.PublicAccessBlock != nil && settings.PublicAccessBlock.BlockPublicAcls && cannedACL != basicACLPrivate {
|
||||
h.logAndSendError(ctx, w, "public acls are blocked", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
||||
return
|
||||
}
|
||||
|
||||
settings.CannedACL = cannedACL
|
||||
// Don't set ACL chains if IgnorePublicAcls is set to true and new ACL isn't private
|
||||
if settings.PublicAccessBlock == nil || !settings.PublicAccessBlock.IgnorePublicAcls || cannedACL == basicACLPrivate {
|
||||
chainRules := bucketCannedACLToAPERules(cannedACL, reqInfo, bktInfo.CID)
|
||||
if err = h.policyEngine.APE.SaveACLChains(bktInfo.CID.EncodeToString(), chainRules); err != nil {
|
||||
h.logAndSendError(ctx, w, "failed to add morph rule chains", reqInfo, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newSettings := *settings
|
||||
newSettings.CannedACL = cannedACL
|
||||
|
||||
sp := &layer.PutSettingsParams{
|
||||
BktInfo: bktInfo,
|
||||
Settings: settings,
|
||||
Settings: &newSettings,
|
||||
}
|
||||
|
||||
if err = h.obj.PutBucketSettings(ctx, sp); err != nil {
|
||||
|
@ -258,22 +267,14 @@ func (h *handler) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Re
|
|||
return
|
||||
}
|
||||
|
||||
var bktPolicy engineiam.Policy
|
||||
var bktPolicy s3common.Policy
|
||||
if err = json.Unmarshal(jsonPolicy, &bktPolicy); err != nil {
|
||||
h.logAndSendError(ctx, w, "could not parse bucket policy", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
policyStatus := &PolicyStatus{
|
||||
IsPublic: PolicyStatusIsPublicFalse,
|
||||
}
|
||||
|
||||
for _, st := range bktPolicy.Statement {
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html#access-control-block-public-access-policy-status
|
||||
if _, ok := st.Principal[engineiam.Wildcard]; ok {
|
||||
policyStatus.IsPublic = PolicyStatusIsPublicTrue
|
||||
break
|
||||
}
|
||||
IsPublic: getPolicyStatus(bktPolicy),
|
||||
}
|
||||
|
||||
if err = middleware.EncodeToResponse(w, policyStatus); err != nil {
|
||||
|
@ -282,6 +283,16 @@ func (h *handler) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Re
|
|||
}
|
||||
}
|
||||
|
||||
func getPolicyStatus(policy s3common.Policy) PolicyStatusIsPublic {
|
||||
for _, st := range policy.Statement {
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html#access-control-block-public-access-policy-status
|
||||
if _, ok := st.Principal[s3common.Wildcard]; ok && st.Effect == s3common.AllowEffect {
|
||||
return PolicyStatusIsPublicTrue
|
||||
}
|
||||
}
|
||||
return PolicyStatusIsPublicFalse
|
||||
}
|
||||
|
||||
func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketPolicy")
|
||||
defer span.End()
|
||||
|
@ -355,6 +366,12 @@ func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
settings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||
if err != nil {
|
||||
h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonPolicy, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
h.logAndSendError(ctx, w, "read body", reqInfo, err)
|
||||
|
@ -367,6 +384,11 @@ func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
if settings.PublicAccessBlock != nil && settings.PublicAccessBlock.BlockPublicPolicy && getPolicyStatus(bktPolicy) == PolicyStatusIsPublicTrue {
|
||||
h.logAndSendError(ctx, w, "public policy is blocked", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
||||
return
|
||||
}
|
||||
|
||||
for _, stat := range bktPolicy.Statement {
|
||||
if len(stat.NotResource) != 0 {
|
||||
h.logAndSendError(ctx, w, "policy resource mismatched bucket", reqInfo, errors.GetAPIError(errors.ErrMalformedPolicy))
|
||||
|
|
|
@ -358,6 +358,11 @@ func putBucketACL(hc *handlerContext, bktName string, box *accessbox.Box, header
|
|||
assertStatus(hc.t, w, http.StatusOK)
|
||||
}
|
||||
|
||||
func putBucketACLErr(hc *handlerContext, bktName string, box *accessbox.Box, header map[string]string, err apierr.Error) {
|
||||
w := putBucketACLBase(hc, bktName, box, header, nil)
|
||||
assertS3Error(hc.t, w, err)
|
||||
}
|
||||
|
||||
func putBucketACLAssertS3Error(hc *handlerContext, bktName string, box *accessbox.Box, header map[string]string, body *AccessControlPolicy, code apierr.ErrorCode) {
|
||||
w := putBucketACLBase(hc, bktName, box, header, body)
|
||||
assertS3Error(hc.t, w, apierr.GetAPIError(code))
|
||||
|
|
|
@ -66,6 +66,7 @@ type (
|
|||
DeleteBucketPolicy(ns string, cnrID cid.ID, chainIDs []chain.ID) error
|
||||
GetBucketPolicy(ns string, cnrID cid.ID) ([]byte, error)
|
||||
SaveACLChains(cid string, chains []*chain.Chain) error
|
||||
DeleteACLChains(cid string, chainIDs []chain.ID) error
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -499,6 +499,20 @@ func (a *apeMock) SaveACLChains(cid string, chains []*chain.Chain) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *apeMock) DeleteACLChains(cid string, chainIDs []chain.ID) error {
|
||||
if a.err != nil {
|
||||
return a.err
|
||||
}
|
||||
|
||||
for i := range chainIDs {
|
||||
if err := a.RemoveChain(engine.ContainerTarget(cid), chainIDs[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type frostfsidMock struct {
|
||||
data map[string]*keys.PublicKey
|
||||
}
|
||||
|
|
|
@ -100,14 +100,15 @@ func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *htt
|
|||
return
|
||||
}
|
||||
|
||||
if settings.LockConfiguration == nil {
|
||||
settings.LockConfiguration = &data.ObjectLockConfiguration{}
|
||||
newSettings := *settings
|
||||
if newSettings.LockConfiguration == nil {
|
||||
newSettings.LockConfiguration = &data.ObjectLockConfiguration{}
|
||||
}
|
||||
if settings.LockConfiguration.ObjectLockEnabled == "" {
|
||||
settings.LockConfiguration.ObjectLockEnabled = enabledValue
|
||||
if newSettings.LockConfiguration.ObjectLockEnabled == "" {
|
||||
newSettings.LockConfiguration.ObjectLockEnabled = enabledValue
|
||||
}
|
||||
|
||||
if err = middleware.EncodeToResponse(w, settings.LockConfiguration); err != nil {
|
||||
if err = middleware.EncodeToResponse(w, newSettings.LockConfiguration); err != nil {
|
||||
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue