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 2c054e5fe..d4041c8cb 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) { @@ -99,7 +101,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 { @@ -112,6 +114,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) @@ -125,6 +128,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) @@ -138,6 +142,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/container.go b/api/layer/container.go index db3a130fb..ce3023271 100644 --- a/api/layer/container.go +++ b/api/layer/container.go @@ -1,11 +1,16 @@ package layer import ( + "bytes" "context" + "crypto/ecdsa" "fmt" "strconv" + "strings" "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" @@ -55,6 +60,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 } @@ -122,7 +130,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 +145,98 @@ 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, table, 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, 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) + defer ticker.Stop() + wdone := wctx.Done() + done := ctx.Done() + for { + select { + case <-done: + return ctx.Err() + case <-wdone: + return wctx.Err() + case <-ticker.C: + 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) + } + } +} + func (n *layer) deleteContainer(ctx context.Context, cid *cid.ID) error { return n.pool.DeleteContainer(ctx, cid, n.SessionOpt(ctx)) } diff --git a/api/layer/layer.go b/api/layer/layer.go index 6dbd7375f..328dbea4d 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -127,6 +127,10 @@ 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") + // ErrBucketNotFound is returned on attempts to get not existing bucket. + ErrBucketNotFound = errors.New("bucket not found") ) const ( @@ -196,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) @@ -491,7 +495,15 @@ 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 { + if errors.Is(err, ErrBucketNotFound) { + return n.createContainer(ctx, p) + } + return nil, err + } + + return nil, ErrBucketAlreadyExists } func (n *layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {