forked from TrueCloudLab/frostfs-s3-gw
[#306] acl: Handle put/get acl for APE buckets
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
1f2cf0ed67
commit
3d0d2032c6
13 changed files with 280 additions and 42 deletions
|
@ -8,6 +8,7 @@ import (
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -61,9 +62,10 @@ type (
|
||||||
|
|
||||||
// BucketSettings stores settings such as versioning.
|
// BucketSettings stores settings such as versioning.
|
||||||
BucketSettings struct {
|
BucketSettings struct {
|
||||||
Versioning string `json:"versioning"`
|
Versioning string
|
||||||
LockConfiguration *ObjectLockConfiguration `json:"lock_configuration"`
|
LockConfiguration *ObjectLockConfiguration
|
||||||
APE bool `json:"ape"`
|
CannedACL string
|
||||||
|
OwnerKey *keys.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// CORSConfiguration stores CORS configuration of a request.
|
// CORSConfiguration stores CORS configuration of a request.
|
||||||
|
|
|
@ -26,6 +26,7 @@ type (
|
||||||
const (
|
const (
|
||||||
_ ErrorCode = iota
|
_ ErrorCode = iota
|
||||||
ErrAccessDenied
|
ErrAccessDenied
|
||||||
|
ErrAccessControlListNotSupported
|
||||||
ErrBadDigest
|
ErrBadDigest
|
||||||
ErrEntityTooSmall
|
ErrEntityTooSmall
|
||||||
ErrEntityTooLarge
|
ErrEntityTooLarge
|
||||||
|
@ -376,6 +377,12 @@ var errorCodes = errorCodeMap{
|
||||||
Description: "Access Denied.",
|
Description: "Access Denied.",
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
|
ErrAccessControlListNotSupported: {
|
||||||
|
ErrCode: ErrAccessControlListNotSupported,
|
||||||
|
Code: "AccessControlListNotSupported",
|
||||||
|
Description: "The bucket does not allow ACLs.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrBadDigest: {
|
ErrBadDigest: {
|
||||||
ErrCode: ErrBadDigest,
|
ErrCode: ErrBadDigest,
|
||||||
Code: "BadDigest",
|
Code: "BadDigest",
|
||||||
|
|
|
@ -257,6 +257,20 @@ func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if bktInfo.APEEnabled || len(settings.CannedACL) != 0 {
|
||||||
|
if err = middleware.EncodeToResponse(w, h.encodeBucketCannedACL(ctx, bktInfo, settings)); err != nil {
|
||||||
|
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
bucketACL, err := h.obj.GetBucketACL(ctx, bktInfo)
|
bucketACL, err := h.obj.GetBucketACL(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not fetch bucket acl", reqInfo, err)
|
h.logAndSendError(w, "could not fetch bucket acl", reqInfo, err)
|
||||||
|
@ -269,6 +283,54 @@ func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *handler) encodeBucketCannedACL(ctx context.Context, bktInfo *data.BucketInfo, settings *data.BucketSettings) *AccessControlPolicy {
|
||||||
|
ownerDisplayName := bktInfo.Owner.EncodeToString()
|
||||||
|
ownerEncodedID := ownerDisplayName
|
||||||
|
|
||||||
|
if settings.OwnerKey == nil {
|
||||||
|
h.reqLogger(ctx).Warn(logs.BucketOwnerKeyIsMissing, zap.String("owner", bktInfo.Owner.String()))
|
||||||
|
} else {
|
||||||
|
ownerDisplayName = settings.OwnerKey.Address()
|
||||||
|
ownerEncodedID = hex.EncodeToString(settings.OwnerKey.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &AccessControlPolicy{Owner: Owner{
|
||||||
|
ID: ownerEncodedID,
|
||||||
|
DisplayName: ownerDisplayName,
|
||||||
|
}}
|
||||||
|
|
||||||
|
res.AccessControlList = []*Grant{{
|
||||||
|
Grantee: &Grantee{
|
||||||
|
ID: ownerEncodedID,
|
||||||
|
DisplayName: ownerDisplayName,
|
||||||
|
Type: acpCanonicalUser,
|
||||||
|
},
|
||||||
|
Permission: aclFullControl,
|
||||||
|
}}
|
||||||
|
|
||||||
|
switch settings.CannedACL {
|
||||||
|
case basicACLPublic:
|
||||||
|
res.AccessControlList = append(res.AccessControlList, &Grant{
|
||||||
|
Grantee: &Grantee{
|
||||||
|
URI: allUsersGroup,
|
||||||
|
Type: acpGroup,
|
||||||
|
},
|
||||||
|
Permission: aclWrite,
|
||||||
|
})
|
||||||
|
fallthrough
|
||||||
|
case basicACLReadOnly:
|
||||||
|
res.AccessControlList = append(res.AccessControlList, &Grant{
|
||||||
|
Grantee: &Grantee{
|
||||||
|
URI: allUsersGroup,
|
||||||
|
Type: acpGroup,
|
||||||
|
},
|
||||||
|
Permission: aclRead,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func (h *handler) bearerTokenIssuerKey(ctx context.Context) (*keys.PublicKey, error) {
|
func (h *handler) bearerTokenIssuerKey(ctx context.Context) (*keys.PublicKey, error) {
|
||||||
box, err := middleware.GetBoxData(ctx)
|
box, err := middleware.GetBoxData(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -288,6 +350,24 @@ func (h *handler) bearerTokenIssuerKey(ctx context.Context) (*keys.PublicKey, er
|
||||||
|
|
||||||
func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := middleware.GetReqInfo(r.Context())
|
reqInfo := middleware.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if bktInfo.APEEnabled || len(settings.CannedACL) != 0 {
|
||||||
|
h.putBucketACLAPEHandler(w, r, reqInfo, bktInfo, settings)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
key, err := h.bearerTokenIssuerKey(r.Context())
|
key, err := h.bearerTokenIssuerKey(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "couldn't get bearer token issuer key", reqInfo, err)
|
h.logAndSendError(w, "couldn't get bearer token issuer key", reqInfo, err)
|
||||||
|
@ -319,12 +399,6 @@ func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = h.updateBucketACL(r, astBucket, bktInfo, token); err != nil {
|
if _, err = h.updateBucketACL(r, astBucket, bktInfo, token); err != nil {
|
||||||
h.logAndSendError(w, "could not update bucket acl", reqInfo, err)
|
h.logAndSendError(w, "could not update bucket acl", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
@ -332,6 +406,64 @@ func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *handler) putBucketACLAPEHandler(w http.ResponseWriter, r *http.Request, reqInfo *middleware.ReqInfo, bktInfo *data.BucketInfo, settings *data.BucketSettings) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if errBody := r.Body.Close(); errBody != nil {
|
||||||
|
h.reqLogger(r.Context()).Warn(logs.CouldNotCloseRequestBody, zap.Error(errBody))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
written, err := io.Copy(io.Discard, r.Body)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't read request body", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if written != 0 || len(r.Header.Get(api.AmzACL)) == 0 {
|
||||||
|
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cannedACL, err := parseCannedACL(r.Header)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not parse canned ACL", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := h.bearerTokenIssuerKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't get bearer token issuer key", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chainRules := bucketCannedACLToAPERules(cannedACL, reqInfo, key, bktInfo.CID)
|
||||||
|
|
||||||
|
target := engine.NamespaceTarget(reqInfo.Namespace)
|
||||||
|
for _, chainPolicy := range chainRules {
|
||||||
|
if err = h.ape.AddChain(target, chainPolicy); err != nil {
|
||||||
|
h.logAndSendError(w, "failed to add morph rule chain", reqInfo, err, zap.String("chain_id", string(chainPolicy.ID)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.CannedACL = cannedACL
|
||||||
|
|
||||||
|
sp := &layer.PutSettingsParams{
|
||||||
|
BktInfo: bktInfo,
|
||||||
|
Settings: settings,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = h.obj.PutBucketSettings(ctx, sp); err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't save bucket settings", reqInfo, err,
|
||||||
|
zap.String("container_id", bktInfo.CID.EncodeToString()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *handler) updateBucketACL(r *http.Request, astChild *ast, bktInfo *data.BucketInfo, sessionToken *session.Container) (bool, error) {
|
func (h *handler) updateBucketACL(r *http.Request, astChild *ast, bktInfo *data.BucketInfo, sessionToken *session.Container) (bool, error) {
|
||||||
bucketACL, err := h.obj.GetBucketACL(r.Context(), bktInfo)
|
bucketACL, err := h.obj.GetBucketACL(r.Context(), bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -380,6 +512,22 @@ func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apeEnabled := bktInfo.APEEnabled
|
||||||
|
|
||||||
|
if !apeEnabled {
|
||||||
|
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apeEnabled = len(settings.CannedACL) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if apeEnabled {
|
||||||
|
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
bucketACL, err := h.obj.GetBucketACL(ctx, bktInfo)
|
bucketACL, err := h.obj.GetBucketACL(ctx, bktInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not fetch bucket acl", reqInfo, err)
|
h.logAndSendError(w, "could not fetch bucket acl", reqInfo, err)
|
||||||
|
@ -406,6 +554,29 @@ func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
reqInfo := middleware.GetReqInfo(ctx)
|
reqInfo := middleware.GetReqInfo(ctx)
|
||||||
|
|
||||||
|
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apeEnabled := bktInfo.APEEnabled
|
||||||
|
|
||||||
|
if !apeEnabled {
|
||||||
|
settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apeEnabled = len(settings.CannedACL) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if apeEnabled {
|
||||||
|
h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
versionID := reqInfo.URL.Query().Get(api.QueryVersionID)
|
versionID := reqInfo.URL.Query().Get(api.QueryVersionID)
|
||||||
key, err := h.bearerTokenIssuerKey(ctx)
|
key, err := h.bearerTokenIssuerKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -419,12 +590,6 @@ func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &layer.HeadObjectParams{
|
p := &layer.HeadObjectParams{
|
||||||
BktInfo: bktInfo,
|
BktInfo: bktInfo,
|
||||||
Object: reqInfo.ObjectName,
|
Object: reqInfo.ObjectName,
|
||||||
|
|
|
@ -16,9 +16,11 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||||
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
s3errors "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"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
@ -1303,7 +1305,7 @@ func TestPutBucketACL(t *testing.T) {
|
||||||
bktName := "bucket-for-acl"
|
bktName := "bucket-for-acl"
|
||||||
|
|
||||||
box, _ := createAccessBox(t)
|
box, _ := createAccessBox(t)
|
||||||
bktInfo := createBucket(t, tc, bktName, box)
|
bktInfo := createBucketOldACL(tc, bktName, box)
|
||||||
|
|
||||||
header := map[string]string{api.AmzACL: "public-read"}
|
header := map[string]string{api.AmzACL: "public-read"}
|
||||||
putBucketACL(t, tc, bktName, box, header)
|
putBucketACL(t, tc, bktName, box, header)
|
||||||
|
@ -1488,12 +1490,56 @@ func createAccessBox(t *testing.T) (*accessbox.Box, *keys.PrivateKey) {
|
||||||
return box, key
|
return box, key
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBucket(t *testing.T, hc *handlerContext, bktName string, box *accessbox.Box) *data.BucketInfo {
|
func createBucket(hc *handlerContext, bktName string) (*data.BucketInfo, *accessbox.Box) {
|
||||||
|
box, _ := createAccessBox(hc.t)
|
||||||
|
|
||||||
w := createBucketBase(hc, bktName, box)
|
w := createBucketBase(hc, bktName, box)
|
||||||
assertStatus(t, w, http.StatusOK)
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
|
|
||||||
bktInfo, err := hc.Layer().GetBucketInfo(hc.Context(), bktName)
|
bktInfo, err := hc.Layer().GetBucketInfo(hc.Context(), bktName)
|
||||||
require.NoError(t, err)
|
require.NoError(hc.t, err)
|
||||||
|
return bktInfo, box
|
||||||
|
}
|
||||||
|
|
||||||
|
func createBucketOldACL(hc *handlerContext, bktName string, box *accessbox.Box) *data.BucketInfo {
|
||||||
|
w := createBucketBase(hc, bktName, box)
|
||||||
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
|
|
||||||
|
cnrID, err := hc.tp.ContainerID(bktName)
|
||||||
|
require.NoError(hc.t, err)
|
||||||
|
|
||||||
|
cnr, err := hc.tp.Container(hc.Context(), cnrID)
|
||||||
|
require.NoError(hc.t, err)
|
||||||
|
cnr.SetBasicACL(acl.PublicRWExtended)
|
||||||
|
cnr.SetAttribute(layer.AttributeAPEEnabled, "false")
|
||||||
|
hc.tp.SetContainer(cnrID, cnr)
|
||||||
|
table := eacl.NewTable()
|
||||||
|
table.SetCID(cnrID)
|
||||||
|
|
||||||
|
key, err := hc.h.bearerTokenIssuerKey(hc.Context())
|
||||||
|
require.NoError(hc.t, err)
|
||||||
|
for _, op := range fullOps {
|
||||||
|
table.AddRecord(getAllowRecord(op, key))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, op := range fullOps {
|
||||||
|
table.AddRecord(getOthersRecord(op, eacl.ActionDeny))
|
||||||
|
}
|
||||||
|
err = hc.tp.SetContainerEACL(hc.Context(), *table, nil)
|
||||||
|
require.NoError(hc.t, err)
|
||||||
|
|
||||||
|
bktInfo, err := hc.Layer().GetBucketInfo(hc.Context(), bktName)
|
||||||
|
require.NoError(hc.t, err)
|
||||||
|
|
||||||
|
settings, err := hc.tree.GetSettingsNode(hc.Context(), bktInfo)
|
||||||
|
require.NoError(hc.t, err)
|
||||||
|
settings.CannedACL = ""
|
||||||
|
err = hc.Layer().PutBucketSettings(hc.Context(), &layer.PutSettingsParams{BktInfo: bktInfo, Settings: settings})
|
||||||
|
require.NoError(hc.t, err)
|
||||||
|
|
||||||
|
bktInfo, err = hc.Layer().GetBucketInfo(hc.Context(), bktName)
|
||||||
|
require.NoError(hc.t, err)
|
||||||
|
|
||||||
return bktInfo
|
return bktInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -168,7 +168,7 @@ func TestDeleteDeletedObject(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("versioned bucket not found obj", func(t *testing.T) {
|
t.Run("versioned bucket not found obj", func(t *testing.T) {
|
||||||
bktName, objName := "bucket-versioned-for-removal", "object-to-delete"
|
bktName, objName := "bucket-versioned-for-removal-not-found", "object-to-delete"
|
||||||
_, objInfo := createVersionedBucketAndObject(t, tc, bktName, objName)
|
_, objInfo := createVersionedBucketAndObject(t, tc, bktName, objName)
|
||||||
|
|
||||||
versionID, isDeleteMarker := deleteObject(t, tc, bktName, objName, objInfo.VersionID())
|
versionID, isDeleteMarker := deleteObject(t, tc, bktName, objName, objInfo.VersionID())
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
@ -267,15 +266,7 @@ func (a *apeMock) DeletePolicy(namespace string, cnrID cid.ID) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestBucket(hc *handlerContext, bktName string) *data.BucketInfo {
|
func createTestBucket(hc *handlerContext, bktName string) *data.BucketInfo {
|
||||||
_, err := hc.MockedPool().CreateContainer(hc.Context(), layer.PrmContainerCreate{
|
bktInfo, _ := createBucket(hc, bktName)
|
||||||
Creator: hc.owner,
|
|
||||||
Name: bktName,
|
|
||||||
BasicACL: acl.PublicRWExtended,
|
|
||||||
})
|
|
||||||
require.NoError(hc.t, err)
|
|
||||||
|
|
||||||
bktInfo, err := hc.Layer().GetBucketInfo(hc.Context(), bktName)
|
|
||||||
require.NoError(hc.t, err)
|
|
||||||
return bktInfo
|
return bktInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,11 +288,15 @@ func createTestBucketWithLock(hc *handlerContext, bktName string, conf *data.Obj
|
||||||
HomomorphicHashDisabled: res.HomomorphicHashDisabled,
|
HomomorphicHashDisabled: res.HomomorphicHashDisabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
key, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(hc.t, err)
|
||||||
|
|
||||||
sp := &layer.PutSettingsParams{
|
sp := &layer.PutSettingsParams{
|
||||||
BktInfo: bktInfo,
|
BktInfo: bktInfo,
|
||||||
Settings: &data.BucketSettings{
|
Settings: &data.BucketSettings{
|
||||||
Versioning: data.VersioningEnabled,
|
Versioning: data.VersioningEnabled,
|
||||||
LockConfiguration: conf,
|
LockConfiguration: conf,
|
||||||
|
OwnerKey: key.PublicKey(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -834,7 +834,11 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
sp := &layer.PutSettingsParams{
|
sp := &layer.PutSettingsParams{
|
||||||
BktInfo: bktInfo,
|
BktInfo: bktInfo,
|
||||||
Settings: &data.BucketSettings{APE: true},
|
Settings: &data.BucketSettings{
|
||||||
|
CannedACL: cannedACL,
|
||||||
|
OwnerKey: key,
|
||||||
|
Versioning: data.VersioningUnversioned,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.ObjectLockEnabled {
|
if p.ObjectLockEnabled {
|
||||||
|
|
|
@ -372,8 +372,7 @@ func TestCreateBucket(t *testing.T) {
|
||||||
hc := prepareHandlerContext(t)
|
hc := prepareHandlerContext(t)
|
||||||
bktName := "bkt-name"
|
bktName := "bkt-name"
|
||||||
|
|
||||||
box, _ := createAccessBox(t)
|
_, box := createBucket(hc, bktName)
|
||||||
createBucket(t, hc, bktName, box)
|
|
||||||
createBucketAssertS3Error(hc, bktName, box, s3errors.ErrBucketAlreadyOwnedByYou)
|
createBucketAssertS3Error(hc, bktName, box, s3errors.ErrBucketAlreadyOwnedByYou)
|
||||||
|
|
||||||
box2, _ := createAccessBox(t)
|
box2, _ := createAccessBox(t)
|
||||||
|
|
|
@ -28,7 +28,7 @@ type (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
attributeLocationConstraint = ".s3-location-constraint"
|
attributeLocationConstraint = ".s3-location-constraint"
|
||||||
attributeAPEEnabled = ".s3-APE-enabled"
|
AttributeAPEEnabled = ".s3-APE-enabled"
|
||||||
AttributeLockEnabled = "LockEnabled"
|
AttributeLockEnabled = "LockEnabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ func (n *layer) containerInfo(ctx context.Context, idCnr cid.ID) (*data.BucketIn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
APEEnabled := cnr.Attribute(attributeAPEEnabled)
|
APEEnabled := cnr.Attribute(AttributeAPEEnabled)
|
||||||
if len(APEEnabled) > 0 {
|
if len(APEEnabled) > 0 {
|
||||||
info.APEEnabled, err = strconv.ParseBool(APEEnabled)
|
info.APEEnabled, err = strconv.ParseBool(APEEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -136,7 +136,7 @@ func (n *layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da
|
||||||
|
|
||||||
attributes := [][2]string{
|
attributes := [][2]string{
|
||||||
{attributeLocationConstraint, p.LocationConstraint},
|
{attributeLocationConstraint, p.LocationConstraint},
|
||||||
{attributeAPEEnabled, "true"},
|
{AttributeAPEEnabled, "true"},
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.ObjectLockEnabled {
|
if p.ObjectLockEnabled {
|
||||||
|
|
|
@ -136,6 +136,10 @@ func (t *TestFrostFS) ContainerID(name string) (cid.ID, error) {
|
||||||
return cid.ID{}, fmt.Errorf("not found")
|
return cid.ID{}, fmt.Errorf("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TestFrostFS) SetContainer(cnrID cid.ID, cnr *container.Container) {
|
||||||
|
t.containers[cnrID.EncodeToString()] = cnr
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) CreateContainer(_ context.Context, prm PrmContainerCreate) (*ContainerCreateResult, error) {
|
func (t *TestFrostFS) CreateContainer(_ context.Context, prm PrmContainerCreate) (*ContainerCreateResult, error) {
|
||||||
var cnr container.Container
|
var cnr container.Container
|
||||||
cnr.Init()
|
cnr.Init()
|
||||||
|
|
|
@ -142,4 +142,7 @@ const (
|
||||||
CouldntPutAccessBoxIntoCache = "couldn't put accessbox into cache"
|
CouldntPutAccessBoxIntoCache = "couldn't put accessbox into cache"
|
||||||
InvalidAccessBoxCacheRemovingCheckInterval = "invalid accessbox check removing interval, using default value"
|
InvalidAccessBoxCacheRemovingCheckInterval = "invalid accessbox check removing interval, using default value"
|
||||||
CouldNotParseContainerAPEEnabledAttribute = "could not parse container APE enabled attribute"
|
CouldNotParseContainerAPEEnabledAttribute = "could not parse container APE enabled attribute"
|
||||||
|
CouldNotCloseRequestBody = "could not close request body"
|
||||||
|
BucketOwnerKeyIsMissing = "bucket owner key is missing"
|
||||||
|
SettingsNodeInvalidOwnerKey = "settings node: invalid owner key"
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package tree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -16,6 +17,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
)
|
)
|
||||||
|
@ -78,7 +80,8 @@ var (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
versioningKV = "Versioning"
|
versioningKV = "Versioning"
|
||||||
apeKV = "APE"
|
cannedACLKV = "cannedACL"
|
||||||
|
ownerKeyKV = "ownerKey"
|
||||||
lockConfigurationKV = "LockConfiguration"
|
lockConfigurationKV = "LockConfiguration"
|
||||||
oidKV = "OID"
|
oidKV = "OID"
|
||||||
|
|
||||||
|
@ -333,7 +336,7 @@ func newPartInfo(node NodeResponse) (*data.PartInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Tree) GetSettingsNode(ctx context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error) {
|
func (c *Tree) GetSettingsNode(ctx context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error) {
|
||||||
keysToReturn := []string{versioningKV, lockConfigurationKV, apeKV}
|
keysToReturn := []string{versioningKV, lockConfigurationKV, cannedACLKV, ownerKeyKV}
|
||||||
node, err := c.getSystemNode(ctx, bktInfo, []string{settingsFileName}, keysToReturn)
|
node, err := c.getSystemNode(ctx, bktInfo, []string{settingsFileName}, keysToReturn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("couldn't get node: %w", err)
|
return nil, fmt.Errorf("couldn't get node: %w", err)
|
||||||
|
@ -350,8 +353,12 @@ func (c *Tree) GetSettingsNode(ctx context.Context, bktInfo *data.BucketInfo) (*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ape, ok := node.Get(apeKV); ok {
|
settings.CannedACL, _ = node.Get(cannedACLKV)
|
||||||
settings.APE, _ = strconv.ParseBool(ape)
|
|
||||||
|
if ownerKeyHex, ok := node.Get(ownerKeyKV); ok {
|
||||||
|
if settings.OwnerKey, err = keys.NewPublicKeyFromString(ownerKeyHex); err != nil {
|
||||||
|
c.reqLogger(ctx).Error(logs.SettingsNodeInvalidOwnerKey, zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings, nil
|
return settings, nil
|
||||||
|
@ -1389,7 +1396,8 @@ func metaFromSettings(settings *data.BucketSettings) map[string]string {
|
||||||
results[FileNameKey] = settingsFileName
|
results[FileNameKey] = settingsFileName
|
||||||
results[versioningKV] = settings.Versioning
|
results[versioningKV] = settings.Versioning
|
||||||
results[lockConfigurationKV] = encodeLockConfiguration(settings.LockConfiguration)
|
results[lockConfigurationKV] = encodeLockConfiguration(settings.LockConfiguration)
|
||||||
results[apeKV] = strconv.FormatBool(settings.APE)
|
results[cannedACLKV] = settings.CannedACL
|
||||||
|
results[ownerKeyKV] = hex.EncodeToString(settings.OwnerKey.Bytes())
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||||
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
|
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
@ -111,6 +112,9 @@ func TestTreeServiceSettings(t *testing.T) {
|
||||||
CID: cidtest.ID(),
|
CID: cidtest.ID(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
key, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
settings := &data.BucketSettings{
|
settings := &data.BucketSettings{
|
||||||
Versioning: "Versioning",
|
Versioning: "Versioning",
|
||||||
LockConfiguration: &data.ObjectLockConfiguration{
|
LockConfiguration: &data.ObjectLockConfiguration{
|
||||||
|
@ -122,6 +126,7 @@ func TestTreeServiceSettings(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
OwnerKey: key.PublicKey(),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = treeService.PutSettingsNode(ctx, bktInfo, settings)
|
err = treeService.PutSettingsNode(ctx, bktInfo, settings)
|
||||||
|
|
Loading…
Reference in a new issue