From 0aae8c595a1d68c10e41898064eb170a080a6f49 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Wed, 7 Jul 2021 17:56:29 +0300 Subject: [PATCH 1/5] [#125] Fixed bucket creation Bucket should has unique name. Signed-off-by: Denis Kirillov --- api/handler/put.go | 1 + api/layer/layer.go | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/api/handler/put.go b/api/handler/put.go index cae8a8df2..71aa0f44f 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -124,6 +124,7 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) { Description: err.Error(), HTTPStatusCode: http.StatusInternalServerError, }, r.URL) + return } h.log.Info("bucket is created", diff --git a/api/layer/layer.go b/api/layer/layer.go index 6dbd7375f..e40380f5c 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -127,6 +127,8 @@ var ( ErrObjectExists = errors.New("object exists") // ErrObjectNotExists is returned on attempts to work with non-existing object. ErrObjectNotExists = errors.New("object not exists") + // ErrBucketAlreadyExists is returned on attempts to create already existing bucket. + ErrBucketAlreadyExists = errors.New("bucket exists") ) const ( @@ -491,7 +493,17 @@ func (n *layer) DeleteObjects(ctx context.Context, bucket string, objects []stri } func (n *layer) CreateBucket(ctx context.Context, p *CreateBucketParams) (*cid.ID, error) { - return n.createContainer(ctx, p) + _, err := n.GetBucketInfo(ctx, p.Name) + if err != nil { + errMsg := err.Error() + if strings.Contains(errMsg, "bucket not found") || + strings.Contains(errMsg, "container not found") { + return n.createContainer(ctx, p) + } + return nil, err + } + + return nil, ErrBucketAlreadyExists } func (n *layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error { From 6ba5167f5d89aabc294f4e6a6b77a10c678f3bbe Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Wed, 7 Jul 2021 17:58:53 +0300 Subject: [PATCH 2/5] [#125] Fixed acl rule Container must be public (basic acl) to enable bearer token. Signed-off-by: Denis Kirillov --- api/handler/put.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/handler/put.go b/api/handler/put.go index 71aa0f44f..73b5d0b95 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -20,6 +20,8 @@ const ( basicACLReadOnly = "public-read" basicACLPublic = "public-read-write" defaultPolicy = "REP 3" + + publicBasicRule = 0x0FFFFFFF ) func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { @@ -85,7 +87,7 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) { if val, ok := r.Header["X-Amz-Acl"]; ok { p.ACL, err = parseBasicACL(val[0]) } else { - p.ACL = acl.PrivateBasicRule + p.ACL = publicBasicRule } if err != nil { From e11b1b76ba4753d03e5f3733ea9e7dd3c875738c Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Thu, 8 Jul 2021 13:10:46 +0300 Subject: [PATCH 3/5] [#125] Added eacl to newly created container Signed-off-by: Denis Kirillov --- api/layer/container.go | 91 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/api/layer/container.go b/api/layer/container.go index db3a130fb..0e10a2fdd 100644 --- a/api/layer/container.go +++ b/api/layer/container.go @@ -2,10 +2,13 @@ package layer import ( "context" + "crypto/ecdsa" "fmt" "strconv" "time" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" "github.com/nspcc-dev/neofs-api-go/pkg/container" cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" "github.com/nspcc-dev/neofs-api-go/pkg/owner" @@ -122,7 +125,14 @@ func (n *layer) createContainer(ctx context.Context, p *CreateBucketParams) (*ci container.WithAttribute(container.AttributeName, p.Name), container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10))) - cnr.SetSessionToken(ctx.Value(api.GateData).(*accessbox.GateData).SessionToken) + var gateData *accessbox.GateData + if data, ok := ctx.Value(api.GateData).(*accessbox.GateData); ok && data != nil { + gateData = data + } else { + return nil, fmt.Errorf("couldn't get gate data from context") + } + + cnr.SetSessionToken(gateData.SessionToken) cnr.SetOwnerID(n.Owner(ctx)) cid, err := n.pool.PutContainer(ctx, cnr) @@ -130,14 +140,89 @@ func (n *layer) createContainer(ctx context.Context, p *CreateBucketParams) (*ci return nil, fmt.Errorf("failed to create a bucket: %w", err) } - err = n.pool.WaitForContainerPresence(ctx, cid, pool.DefaultPollingParams()) - if err != nil { + if err = n.pool.WaitForContainerPresence(ctx, cid, pool.DefaultPollingParams()); err != nil { + return nil, err + } + + if err := n.setContainerEACL(ctx, cid, gateData.GateKey); err != nil { return nil, err } return cid, nil } +func (n *layer) setContainerEACL(ctx context.Context, cid *cid.ID, gateKey *keys.PublicKey) error { + if gateKey == nil { + return fmt.Errorf("gate key must not be nil") + } + + table := formDefaultTable(cid, *(*ecdsa.PublicKey)(gateKey)) + if err := n.pool.SetEACL(ctx, table, n.SessionOpt(ctx)); err != nil { + return err + } + + if err := n.waitEACLPresence(ctx, cid, defaultWaitParams()); err != nil { + return err + } + + return nil +} + +func formDefaultTable(cid *cid.ID, gateKey ecdsa.PublicKey) *eacl.Table { + table := eacl.NewTable() + table.SetCID(cid) + + for op := eacl.OperationGet; op <= eacl.OperationRangeHash; op++ { + record := eacl.NewRecord() + record.SetOperation(op) + record.SetAction(eacl.ActionAllow) + eacl.AddFormedTarget(record, eacl.RoleUser, gateKey) + table.AddRecord(record) + + record2 := eacl.NewRecord() + record2.SetOperation(op) + record2.SetAction(eacl.ActionDeny) + eacl.AddFormedTarget(record2, eacl.RoleOthers) + table.AddRecord(record2) + } + + return table +} + +type waitParams struct { + WaitTimeout time.Duration + PollInterval time.Duration +} + +func defaultWaitParams() *waitParams { + return &waitParams{ + WaitTimeout: 60 * time.Second, + PollInterval: 3 * time.Second, + } +} + +func (n *layer) waitEACLPresence(ctx context.Context, cid *cid.ID, params *waitParams) error { + wctx, cancel := context.WithTimeout(ctx, params.WaitTimeout) + defer cancel() + ticker := time.NewTimer(params.PollInterval) + defer ticker.Stop() + wdone := wctx.Done() + done := ctx.Done() + for { + select { + case <-done: + return ctx.Err() + case <-wdone: + return wctx.Err() + case <-ticker.C: + if _, err := n.pool.GetEACL(ctx, cid); err == nil { + return nil + } + ticker.Reset(params.PollInterval) + } + } +} + func (n *layer) deleteContainer(ctx context.Context, cid *cid.ID) error { return n.pool.DeleteContainer(ctx, cid, n.SessionOpt(ctx)) } From e78543adf3f34d69f77fbd13c967ce1afc6248d4 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Fri, 9 Jul 2021 11:57:44 +0300 Subject: [PATCH 4/5] [#125] Updated error handling Signed-off-by: Denis Kirillov --- api/handler/head.go | 12 +++--------- api/handler/put.go | 2 ++ api/layer/container.go | 4 ++++ api/layer/layer.go | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/api/handler/head.go b/api/handler/head.go index e3b758063..c916b7d6b 100644 --- a/api/handler/head.go +++ b/api/handler/head.go @@ -3,14 +3,13 @@ package handler import ( "bytes" "context" + "errors" "net/http" "github.com/gorilla/mux" "github.com/nspcc-dev/neofs-s3-gw/api" "github.com/nspcc-dev/neofs-s3-gw/api/layer" "go.uber.org/zap" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) const sizeToDetectType = 512 @@ -125,13 +124,8 @@ func (h *handler) HeadBucketHandler(w http.ResponseWriter, r *http.Request) { zap.Error(err)) code := http.StatusBadRequest - if st, ok := status.FromError(err); ok && st != nil { - switch st.Code() { //nolint:exhaustive // we have default value set above - case codes.NotFound: - code = http.StatusNotFound - case codes.PermissionDenied: - code = http.StatusForbidden - } + if errors.Is(err, layer.ErrBucketNotFound) { + code = http.StatusNotFound } api.WriteResponse(w, code, nil, api.MimeNone) diff --git a/api/handler/put.go b/api/handler/put.go index 73b5d0b95..40ff6432e 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -100,6 +100,7 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) { Description: err.Error(), HTTPStatusCode: http.StatusBadRequest, }, r.URL) + return } p.Policy, err = policy.Parse(defaultPolicy) @@ -113,6 +114,7 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) { Description: err.Error(), HTTPStatusCode: http.StatusBadRequest, }, r.URL) + return } cid, err := h.obj.CreateBucket(r.Context(), &p) diff --git a/api/layer/container.go b/api/layer/container.go index 0e10a2fdd..36ef2de48 100644 --- a/api/layer/container.go +++ b/api/layer/container.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "fmt" "strconv" + "strings" "time" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -58,6 +59,9 @@ func (n *layer) containerInfo(ctx context.Context, cid *cid.ID) (*BucketInfo, er zap.String("request_id", rid), zap.Error(err)) + if strings.Contains(err.Error(), "container not found") { + return nil, ErrBucketNotFound + } return nil, err } diff --git a/api/layer/layer.go b/api/layer/layer.go index e40380f5c..328dbea4d 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -129,6 +129,8 @@ var ( ErrObjectNotExists = errors.New("object not exists") // ErrBucketAlreadyExists is returned on attempts to create already existing bucket. ErrBucketAlreadyExists = errors.New("bucket exists") + // ErrBucketNotFound is returned on attempts to get not existing bucket. + ErrBucketNotFound = errors.New("bucket not found") ) const ( @@ -198,7 +200,7 @@ func (n *layer) GetBucketInfo(ctx context.Context, name string) (*BucketInfo, er } } - return nil, status.Error(codes.NotFound, "bucket not found") + return nil, ErrBucketNotFound } return n.containerInfo(ctx, containerID) @@ -495,9 +497,7 @@ func (n *layer) DeleteObjects(ctx context.Context, bucket string, objects []stri func (n *layer) CreateBucket(ctx context.Context, p *CreateBucketParams) (*cid.ID, error) { _, err := n.GetBucketInfo(ctx, p.Name) if err != nil { - errMsg := err.Error() - if strings.Contains(errMsg, "bucket not found") || - strings.Contains(errMsg, "container not found") { + if errors.Is(err, ErrBucketNotFound) { return n.createContainer(ctx, p) } return nil, err From acc2cdd96cd253f3741ba0e5c90753ebd691c03b Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Fri, 9 Jul 2021 17:06:35 +0300 Subject: [PATCH 5/5] [#125] Added exact eacl checking Signed-off-by: Denis Kirillov --- api/layer/container.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/api/layer/container.go b/api/layer/container.go index 36ef2de48..ce3023271 100644 --- a/api/layer/container.go +++ b/api/layer/container.go @@ -1,6 +1,7 @@ package layer import ( + "bytes" "context" "crypto/ecdsa" "fmt" @@ -165,7 +166,7 @@ func (n *layer) setContainerEACL(ctx context.Context, cid *cid.ID, gateKey *keys return err } - if err := n.waitEACLPresence(ctx, cid, defaultWaitParams()); err != nil { + if err := n.waitEACLPresence(ctx, cid, table, defaultWaitParams()); err != nil { return err } @@ -205,7 +206,12 @@ func defaultWaitParams() *waitParams { } } -func (n *layer) waitEACLPresence(ctx context.Context, cid *cid.ID, params *waitParams) error { +func (n *layer) waitEACLPresence(ctx context.Context, cid *cid.ID, table *eacl.Table, params *waitParams) error { + exp, err := table.Marshal() + if err != nil { + return fmt.Errorf("couldn't marshal eacl: %w", err) + } + wctx, cancel := context.WithTimeout(ctx, params.WaitTimeout) defer cancel() ticker := time.NewTimer(params.PollInterval) @@ -219,8 +225,12 @@ func (n *layer) waitEACLPresence(ctx context.Context, cid *cid.ID, params *waitP case <-wdone: return wctx.Err() case <-ticker.C: - if _, err := n.pool.GetEACL(ctx, cid); err == nil { - return nil + signedEacl, err := n.pool.GetEACL(ctx, cid) + if err == nil { + got, err := signedEacl.EACL().Marshal() + if err == nil && bytes.Equal(exp, got) { + return nil + } } ticker.Reset(params.PollInterval) }