Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to sdk rc10 #807

Merged
merged 12 commits into from
Aug 15, 2023
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ This document outlines major changes between releases.

## [Unreleased]

### Added
- NoOpResolver as backward compatibility for case when rpc_endpoint/S3_GW_RPC_ENDPOINT param is empty in config.

### Changed
- Options `rpc_endpoint` in yaml config or `S3_GW_RPC_ENDPOINT` in env config is mandatory.

### Removed
- Options `resolve_order` in yaml config and `S3_GW_RESOLVE_ORDER` in env.

## [0.27.1] - 2023-06-15

### Fixed
Expand Down
26 changes: 17 additions & 9 deletions api/handler/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ import (
"github.com/nspcc-dev/neofs-s3-gw/api"
"github.com/nspcc-dev/neofs-s3-gw/api/data"
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
"github.com/nspcc-dev/neofs-s3-gw/api/resolver"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
"github.com/nspcc-dev/neofs-sdk-go/object"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
Expand All @@ -28,6 +26,14 @@ import (
"go.uber.org/zap"
)

type contResolver struct {
layer *layer.TestNeoFS
}

func (r *contResolver) Resolve(_ context.Context, name string) (cid.ID, error) {
return r.layer.ContainerID(name)
}

type handlerContext struct {
owner user.ID
t *testing.T
Expand Down Expand Up @@ -68,20 +74,22 @@ func prepareHandlerContext(t *testing.T) *handlerContext {
key, err := keys.NewPrivateKey()
require.NoError(t, err)

anonKey, err := keys.NewPrivateKey()
require.NoError(t, err)
anonSigner := user.NewAutoIDSignerRFC6979(anonKey.PrivateKey)

l := zap.NewExample()
tp := layer.NewTestNeoFS()

testResolver := &resolver.Resolver{Name: "test_resolver"}
testResolver.SetResolveFunc(func(_ context.Context, name string) (cid.ID, error) {
return tp.ContainerID(name)
})
testResolver := &contResolver{layer: tp}

var owner user.ID
require.NoError(t, user.IDFromSigner(&owner, neofsecdsa.SignerRFC6979(key.PrivateKey)))
signer := user.NewAutoIDSignerRFC6979(key.PrivateKey)
owner := signer.UserID()

layerCfg := &layer.Config{
Caches: layer.DefaultCachesConfigs(zap.NewExample()),
AnonKey: layer.AnonymousKey{Key: key},
GateKey: key,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's supposed to be a feature, see #271. Although I don't quite understand it.

Copy link
Contributor Author

@smallhive smallhive Aug 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On current master it doesn't work:

$ aws s3api create-bucket --bucket $BUCKET --endpoint http://localhost:9080 --no-sign-request

An error occurred (InternalError) when calling the CreateBucket operation (reached max retries: 0): We encountered an internal error, please try again.

In the gate logs:

2023-08-04T07:42:20.324+0400    error   handler/util.go:29      call method     {"status": 500, "request_id": "dc3da4ba-b5b0-42aa-800f-2dbb71b80ddc", "method": "CreateBucket", "bucket": "heh3", "object": "", "description": "couldn't get bearer token signature key", "error": "couldn't get box data from context"}

In any case, we have to sign requests. Many client calls requires Signer as an explicit parameter. I tend to think it doesn't matter what key it should be either a gate key or an anonymous one.
But the gate key we have to have because all our sessions were created for the gate key. And we need to sign requests by it

I also agreed with comment in #271 because we can't create container with random key, because containerPut is not a free operation. Signer is required and must not be nil. The account corresponding to the specified Signer will be charged for the operation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be used for reads, I guess.

Anonymous: anonSigner.UserID(),
Resolver: testResolver,
TreeService: layer.NewTreeService(),
}
Expand Down
6 changes: 6 additions & 0 deletions api/handler/multipart_upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,12 @@
h.logAndSendError(w, "could not translate acl of completed multipart upload to ast", reqInfo, err, additional...)
return
}

if sessionTokenSetEACL, err = getSessionTokenSetEACL(r.Context()); err != nil {
roman-khimov marked this conversation as resolved.
Show resolved Hide resolved
h.logAndSendError(w, "couldn't get eacl token", reqInfo, err)
return
}

Check warning on line 450 in api/handler/multipart_upload.go

View check run for this annotation

Codecov / codecov/patch

api/handler/multipart_upload.go#L447-L450

Added lines #L447 - L450 were not covered by tests

if _, err = h.updateBucketACL(r, astObject, bktInfo, sessionTokenSetEACL); err != nil {
h.logAndSendError(w, "could not update bucket acl while completing multipart upload", reqInfo, err, additional...)
return
Expand Down
3 changes: 1 addition & 2 deletions api/handler/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"github.com/nspcc-dev/neofs-s3-gw/api/data"
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
"github.com/nspcc-dev/neofs-s3-gw/api/s3errors"
"github.com/nspcc-dev/neofs-sdk-go/bearer"
)

type (
Expand Down Expand Up @@ -163,7 +162,7 @@

box, err := layer.GetBoxData(ctx)
if err == nil && box.Gate.BearerToken != nil {
p.User = bearer.ResolveIssuer(*box.Gate.BearerToken).EncodeToString()
p.User = box.Gate.BearerToken.ResolveIssuer().EncodeToString()

Check warning on line 165 in api/handler/notifications.go

View check run for this annotation

Codecov / codecov/patch

api/handler/notifications.go#L165

Added line #L165 was not covered by tests
}

p.Time = layer.TimeNow(ctx)
Expand Down
47 changes: 14 additions & 33 deletions api/layer/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
"github.com/nspcc-dev/neofs-s3-gw/api"
"github.com/nspcc-dev/neofs-s3-gw/api/data"
"github.com/nspcc-dev/neofs-s3-gw/api/layer/encryption"
"github.com/nspcc-dev/neofs-s3-gw/api/resolver"
"github.com/nspcc-dev/neofs-s3-gw/api/s3errors"
"github.com/nspcc-dev/neofs-s3-gw/creds/accessbox"
"github.com/nspcc-dev/neofs-sdk-go/bearer"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
Expand All @@ -39,15 +39,13 @@

MsgHandlerFunc func(context.Context, *nats.Msg) error

BucketResolver interface {
Resolve(ctx context.Context, name string) (cid.ID, error)
}

layer struct {
neoFS NeoFS
log *zap.Logger
anonKey AnonymousKey
resolver BucketResolver
neoFS NeoFS
log *zap.Logger
// used in case of user wants to do something like anonymous.
// Typical using is a flag --no-sign-request in aws-cli.
anonymous user.ID
roman-khimov marked this conversation as resolved.
Show resolved Hide resolved
resolver resolver.Resolver
ncontroller EventListener
cache *Cache
treeService TreeService
Expand All @@ -56,16 +54,12 @@
Config struct {
ChainAddress string
Caches *CachesConfig
AnonKey AnonymousKey
Resolver BucketResolver
GateKey *keys.PrivateKey
Anonymous user.ID
Resolver resolver.Resolver
TreeService TreeService
}

// AnonymousKey contains data for anonymous requests.
AnonymousKey struct {
Key *keys.PrivateKey
}

// GetObjectParams stores object get request parameters.
GetObjectParams struct {
Range *RangeParams
Expand Down Expand Up @@ -185,7 +179,6 @@
// Client provides S3 API client interface.
Client interface {
Initialize(ctx context.Context, c EventListener) error
EphemeralKey() *keys.PublicKey

GetBucketSettings(ctx context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error)
PutBucketSettings(ctx context.Context, p *PutSettingsParams) error
Expand Down Expand Up @@ -271,17 +264,13 @@
return &layer{
neoFS: neoFS,
log: log,
anonKey: config.AnonKey,
anonymous: config.Anonymous,
resolver: config.Resolver,
cache: NewCache(config.Caches),
treeService: config.TreeService,
}
}

func (n *layer) EphemeralKey() *keys.PublicKey {
return n.anonKey.Key.PublicKey()
}

func (n *layer) Initialize(ctx context.Context, c EventListener) error {
if n.IsNotificationEnabled() {
return fmt.Errorf("already initialized")
Expand Down Expand Up @@ -317,26 +306,18 @@
// Owner returns owner id from BearerToken (context) or from client owner.
func (n *layer) Owner(ctx context.Context) user.ID {
if bd, ok := ctx.Value(api.BoxData).(*accessbox.Box); ok && bd != nil && bd.Gate != nil && bd.Gate.BearerToken != nil {
return bearer.ResolveIssuer(*bd.Gate.BearerToken)
}

var ownerID user.ID
if err := user.IDFromKey(&ownerID, n.EphemeralKey().Bytes()); err != nil {
panic(fmt.Errorf("id from key: %w", err))
return bd.Gate.BearerToken.ResolveIssuer()
}

return ownerID
return n.anonymous

Check warning on line 312 in api/layer/layer.go

View check run for this annotation

Codecov / codecov/patch

api/layer/layer.go#L312

Added line #L312 was not covered by tests
}

func (n *layer) prepareAuthParameters(ctx context.Context, prm *PrmAuth, bktOwner user.ID) {
if bd, ok := ctx.Value(api.BoxData).(*accessbox.Box); ok && bd != nil && bd.Gate != nil && bd.Gate.BearerToken != nil {
if bktOwner.Equals(bearer.ResolveIssuer(*bd.Gate.BearerToken)) {
if bktOwner.Equals(bd.Gate.BearerToken.ResolveIssuer()) {
prm.BearerToken = bd.Gate.BearerToken
return
}
}

prm.PrivateKey = &n.anonKey.Key.PrivateKey
}

// GetBucketInfo returns bucket info by name.
Expand Down
4 changes: 0 additions & 4 deletions api/layer/neofs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package layer

import (
"context"
"crypto/ecdsa"
"errors"
"io"
"time"
Expand Down Expand Up @@ -47,9 +46,6 @@ type PrmContainerCreate struct {
type PrmAuth struct {
// Bearer token to be used for the operation. Overlaps PrivateKey. Optional.
BearerToken *bearer.Token

// Private key used for the operation if BearerToken is missing (in this case non-nil).
PrivateKey *ecdsa.PrivateKey
}

// PrmObjectRead groups parameters of NeoFS.ReadObject operation.
Expand Down
3 changes: 1 addition & 2 deletions api/layer/neofs_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-s3-gw/api"
"github.com/nspcc-dev/neofs-s3-gw/creds/accessbox"
"github.com/nspcc-dev/neofs-sdk-go/bearer"
"github.com/nspcc-dev/neofs-sdk-go/checksum"
"github.com/nspcc-dev/neofs-sdk-go/container"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
Expand Down Expand Up @@ -284,7 +283,7 @@ func (t *TestNeoFS) ContainerEACL(_ context.Context, cnrID cid.ID) (*eacl.Table,

func getOwner(ctx context.Context) user.ID {
if bd, ok := ctx.Value(api.BoxData).(*accessbox.Box); ok && bd != nil && bd.Gate != nil && bd.Gate.BearerToken != nil {
return bearer.ResolveIssuer(*bd.Gate.BearerToken)
return bd.Gate.BearerToken.ResolveIssuer()
}

return user.ID{}
Expand Down
15 changes: 10 additions & 5 deletions api/layer/versioning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/nspcc-dev/neofs-s3-gw/api/data"
"github.com/nspcc-dev/neofs-s3-gw/creds/accessbox"
bearertest "github.com/nspcc-dev/neofs-sdk-go/bearer/test"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
"github.com/nspcc-dev/neofs-sdk-go/object"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
"github.com/nspcc-dev/neofs-sdk-go/user"
Expand Down Expand Up @@ -138,8 +137,14 @@ func prepareContext(t *testing.T, cachesConfig ...*CachesConfig) *testContext {
key, err := keys.NewPrivateKey()
require.NoError(t, err)

anonKey, err := keys.NewPrivateKey()
require.NoError(t, err)
anonSigner := user.NewAutoIDSignerRFC6979(anonKey.PrivateKey)

signer := user.NewAutoIDSignerRFC6979(key.PrivateKey)

bearerToken := bearertest.Token(t)
require.NoError(t, bearerToken.Sign(neofsecdsa.SignerRFC6979(key.PrivateKey)))
require.NoError(t, bearerToken.Sign(signer))

ctx := context.WithValue(context.Background(), api.BoxData, &accessbox.Box{
Gate: &accessbox.GateData{
Expand All @@ -160,12 +165,12 @@ func prepareContext(t *testing.T, cachesConfig ...*CachesConfig) *testContext {
config = cachesConfig[0]
}

var owner user.ID
require.NoError(t, user.IDFromSigner(&owner, neofsecdsa.SignerRFC6979(key.PrivateKey)))
owner := signer.UserID()

layerCfg := &Config{
Caches: config,
AnonKey: AnonymousKey{Key: key},
GateKey: key,
Anonymous: anonSigner.UserID(),
TreeService: NewTreeService(),
}

Expand Down
106 changes: 106 additions & 0 deletions api/resolver/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package resolver

import (
"context"
"fmt"
"sync"

"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/util"
rpcNNS "github.com/nspcc-dev/neofs-contract/rpc/nns"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
)

const (
nnsContract = int32(1)
)

// Container is a wrapper for the [Resolver]. It allows to update resolvers in runtime, without service restarting.
//
// The Container should be used like regular [Resolver].
type Container struct {
mu sync.RWMutex
resolver Resolver
}

// Resolve looks up the container id by its name via NNS contract.
// The method calls inline resolver.
func (r *Container) Resolve(ctx context.Context, name string) (cid.ID, error) {
r.mu.RLock()
defer r.mu.RUnlock()

return r.resolver.Resolve(ctx, name)
}

// UpdateResolvers allows to update resolver in runtime. Resolvers will be created from scratch.
func (r *Container) UpdateResolvers(ctx context.Context, endpoint string) error {
newResolver, err := NewResolver(ctx, endpoint)
if err != nil {
return fmt.Errorf("resolver reinit: %w", err)
}

r.mu.Lock()
r.resolver = newResolver
r.mu.Unlock()

return nil
}

// NewContainer is a constructor for the [Container].
func NewContainer(ctx context.Context, endpoint string) (*Container, error) {
newResolver, err := NewResolver(ctx, endpoint)
if err != nil {
return nil, fmt.Errorf("resolver reinit: %w", err)
}

return &Container{
resolver: newResolver,
}, nil
}

// NewResolver returns resolver depending on corresponding endpoint.
//
// If endpoint is empty, [NoOpResolver] will be returned.
func NewResolver(ctx context.Context, endpoint string) (Resolver, error) {
if endpoint == "" {
return NewNoOpResolver(), nil
}

cl, err := rpcClient(ctx, endpoint)
if err != nil {
return nil, fmt.Errorf("rpcclient: %w", err)
}

nnsHash, err := systemContractHash(cl, nnsContract)
if err != nil {
return nil, fmt.Errorf("nns contract: %w", err)
}

inv := invoker.New(cl, nil)
nnsReader := rpcNNS.NewReader(inv, nnsHash)
return NewNNSResolver(nnsReader), nil
}

func systemContractHash(cl *rpcclient.Client, id int32) (util.Uint160, error) {
c, err := cl.GetContractStateByID(id)
if err != nil {
return util.Uint160{}, fmt.Errorf("GetContractStateByID [%d]: %w", id, err)
}

return c.Hash, nil
}

func rpcClient(ctx context.Context, endpoint string) (*rpcclient.Client, error) {
cl, err := rpcclient.New(ctx, endpoint, rpcclient.Options{})
if err != nil {
return nil, fmt.Errorf("new: %w", err)
}

err = cl.Init()
if err != nil {
return nil, fmt.Errorf("init: %w", err)
}

return cl, nil
}
Loading
Loading