diff --git a/api/handler/acl.go b/api/handler/acl.go index 2be1a22..6e8356e 100644 --- a/api/handler/acl.go +++ b/api/handler/acl.go @@ -1,6 +1,7 @@ package handler import ( + "context" "crypto/ecdsa" "encoding/hex" "encoding/json" @@ -141,15 +142,30 @@ func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { } } +func (h *handler) gateKey(ctx context.Context) (*keys.PublicKey, error) { + gateKey := h.obj.EphemeralKey() + box, err := layer.GetBoxData(ctx) + if err == nil { + if box.Gate.GateKey == nil { + return nil, fmt.Errorf("gate key must not be nil") + } + gateKey = box.Gate.GateKey + } + + return gateKey, nil +} + func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) { - var ( - err error - reqInfo = api.GetReqInfo(r.Context()) - ) + reqInfo := api.GetReqInfo(r.Context()) + gateKey, err := h.gateKey(r.Context()) + if err != nil { + h.logAndSendError(w, "couldn't get gate key", reqInfo, err) + return + } list := &AccessControlPolicy{} if r.ContentLength == 0 { - list, err = parseACLHeaders(r) + list, err = parseACLHeaders(r.Header, gateKey) if err != nil { h.logAndSendError(w, "could not parse bucket acl", reqInfo, err) return @@ -231,15 +247,17 @@ func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) { } func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) { - var ( - err error - reqInfo = api.GetReqInfo(r.Context()) - versionID = reqInfo.URL.Query().Get(api.QueryVersionID) - ) + reqInfo := api.GetReqInfo(r.Context()) + versionID := reqInfo.URL.Query().Get(api.QueryVersionID) + gateKey, err := h.gateKey(r.Context()) + if err != nil { + h.logAndSendError(w, "couldn't get gate key", reqInfo, err) + return + } list := &AccessControlPolicy{} if r.ContentLength == 0 { - list, err = parseACLHeaders(r) + list, err = parseACLHeaders(r.Header, gateKey) if err != nil { h.logAndSendError(w, "could not parse bucket acl", reqInfo, err) return @@ -336,16 +354,8 @@ func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusOK) } -func parseACLHeaders(r *http.Request) (*AccessControlPolicy, error) { +func parseACLHeaders(header http.Header, gateKey *keys.PublicKey) (*AccessControlPolicy, error) { var err error - box, err := layer.GetBoxData(r.Context()) - if err != nil { - return nil, err - } else if box.Gate.GateKey == nil { - return nil, fmt.Errorf("gate key must not be nil") - } - gateKey := box.Gate.GateKey - acp := &AccessControlPolicy{Owner: Owner{ ID: hex.EncodeToString(gateKey.Bytes()), DisplayName: gateKey.Address(), @@ -359,18 +369,18 @@ func parseACLHeaders(r *http.Request) (*AccessControlPolicy, error) { Permission: aclFullControl, }} - cannedACL := r.Header.Get(api.AmzACL) + cannedACL := header.Get(api.AmzACL) if cannedACL != "" { return addPredefinedACP(acp, cannedACL) } - if acp.AccessControlList, err = addGrantees(acp.AccessControlList, r.Header, api.AmzGrantFullControl); err != nil { + if acp.AccessControlList, err = addGrantees(acp.AccessControlList, header, api.AmzGrantFullControl); err != nil { return nil, err } - if acp.AccessControlList, err = addGrantees(acp.AccessControlList, r.Header, api.AmzGrantRead); err != nil { + if acp.AccessControlList, err = addGrantees(acp.AccessControlList, header, api.AmzGrantRead); err != nil { return nil, err } - if acp.AccessControlList, err = addGrantees(acp.AccessControlList, r.Header, api.AmzGrantWrite); err != nil { + if acp.AccessControlList, err = addGrantees(acp.AccessControlList, header, api.AmzGrantWrite); err != nil { return nil, err } diff --git a/api/handler/acl_test.go b/api/handler/acl_test.go index c732ce1..d827eb2 100644 --- a/api/handler/acl_test.go +++ b/api/handler/acl_test.go @@ -1,7 +1,6 @@ package handler import ( - "context" "crypto/ecdsa" "crypto/rand" "crypto/sha256" @@ -14,7 +13,6 @@ import ( "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-s3-gw/api" - "github.com/nspcc-dev/neofs-s3-gw/creds/accessbox" "github.com/stretchr/testify/require" ) @@ -562,14 +560,6 @@ func TestParseCannedACLHeaders(t *testing.T) { }, } - box := &accessbox.Box{ - Gate: &accessbox.GateData{ - GateKey: key.PublicKey(), - }, - } - ctx := context.Background() - ctx = context.WithValue(ctx, api.BoxData, box) - expectedACL := &AccessControlPolicy{ Owner: Owner{ ID: id, @@ -591,7 +581,7 @@ func TestParseCannedACLHeaders(t *testing.T) { }}, } - actualACL, err := parseACLHeaders(req.WithContext(ctx)) + actualACL, err := parseACLHeaders(req.Header, key.PublicKey()) require.NoError(t, err) require.Equal(t, expectedACL, actualACL) } @@ -611,14 +601,6 @@ func TestParseACLHeaders(t *testing.T) { }, } - box := &accessbox.Box{ - Gate: &accessbox.GateData{ - GateKey: key.PublicKey(), - }, - } - ctx := context.Background() - ctx = context.WithValue(ctx, api.BoxData, box) - expectedACL := &AccessControlPolicy{ Owner: Owner{ ID: id, @@ -664,7 +646,7 @@ func TestParseACLHeaders(t *testing.T) { }}, } - actualACL, err := parseACLHeaders(req.WithContext(ctx)) + actualACL, err := parseACLHeaders(req.Header, key.PublicKey()) require.NoError(t, err) require.Equal(t, expectedACL, actualACL) } diff --git a/api/handler/put.go b/api/handler/put.go index 1794fcd..3e07ff6 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -20,6 +20,7 @@ import ( "github.com/nspcc-dev/neofs-s3-gw/api/data" "github.com/nspcc-dev/neofs-s3-gw/api/errors" "github.com/nspcc-dev/neofs-s3-gw/api/layer" + "github.com/nspcc-dev/neofs-s3-gw/creds/accessbox" "go.uber.org/zap" ) @@ -422,7 +423,11 @@ func containsACLHeaders(r *http.Request) bool { func (h *handler) getNewEAclTable(r *http.Request, objInfo *data.ObjectInfo) (*eacl.Table, error) { var newEaclTable *eacl.Table - objectACL, err := parseACLHeaders(r) + gateKey, err := h.gateKey(r.Context()) + if err != nil { + return nil, err + } + objectACL, err := parseACLHeaders(r.Header, gateKey) if err != nil { return nil, fmt.Errorf("could not parse object acl: %w", err) } @@ -508,7 +513,13 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) { return } - bktACL, err := parseACLHeaders(r) + gateKey, err := h.gateKey(r.Context()) + if err != nil { + h.logAndSendError(w, "couldn't get gate key", reqInfo, err) + return + } + + bktACL, err := parseACLHeaders(r.Header, gateKey) if err != nil { h.logAndSendError(w, "could not parse bucket acl", reqInfo, err) return @@ -527,14 +538,15 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) { return } - p.BoxData, err = layer.GetBoxData(r.Context()) - if err != nil { - h.logAndSendError(w, "could not get boxData", reqInfo, err) - return + var policies []*accessbox.ContainerPolicy + boxData, err := layer.GetBoxData(r.Context()) + if err == nil { + policies = boxData.Policies + p.SessionToken = boxData.Gate.SessionToken } if createParams.LocationConstraint != "" { - for _, placementPolicy := range p.BoxData.Policies { + for _, placementPolicy := range policies { if placementPolicy.LocationConstraint == createParams.LocationConstraint { p.Policy = placementPolicy.Policy break diff --git a/api/layer/container.go b/api/layer/container.go index 63aa08a..22bc641 100644 --- a/api/layer/container.go +++ b/api/layer/container.go @@ -28,17 +28,16 @@ type ( func (n *layer) containerInfo(ctx context.Context, cid *cid.ID) (*data.BucketInfo, error) { var ( - err error - res *container.Container - rid = api.GetRequestID(ctx) - bearerOpt = n.BearerOpt(ctx) + err error + res *container.Container + rid = api.GetRequestID(ctx) info = &data.BucketInfo{ CID: cid, Name: cid.String(), } ) - res, err = n.pool.GetContainer(ctx, cid, bearerOpt) + res, err = n.pool.GetContainer(ctx, cid, n.CallOptions(ctx)...) if err != nil { n.log.Error("could not fetch container", zap.Stringer("cid", cid), @@ -86,13 +85,12 @@ func (n *layer) containerInfo(ctx context.Context, cid *cid.ID) (*data.BucketInf func (n *layer) containerList(ctx context.Context) ([]*data.BucketInfo, error) { var ( - err error - own = n.Owner(ctx) - bearerOpt = n.BearerOpt(ctx) - res []*cid.ID - rid = api.GetRequestID(ctx) + err error + own = n.Owner(ctx) + res []*cid.ID + rid = api.GetRequestID(ctx) ) - res, err = n.pool.ListContainers(ctx, own, bearerOpt) + res, err = n.pool.ListContainers(ctx, own, n.CallOptions(ctx)...) if err != nil { n.log.Error("could not fetch container", zap.String("request_id", rid), @@ -130,7 +128,7 @@ func (n *layer) createContainer(ctx context.Context, p *CreateBucketParams) (*ci container.WithAttribute(container.AttributeName, p.Name), container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(bktInfo.Created.Unix(), 10))) - cnr.SetSessionToken(p.BoxData.Gate.SessionToken) + cnr.SetSessionToken(p.SessionToken) cnr.SetOwnerID(bktInfo.Owner) if bktInfo.CID, err = n.pool.PutContainer(ctx, cnr); err != nil { diff --git a/api/layer/layer.go b/api/layer/layer.go index 57a4ad1..e740c47 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -6,20 +6,24 @@ import ( "crypto/ecdsa" "fmt" "io" + "math" "net/url" "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/client" cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" "github.com/nspcc-dev/neofs-api-go/pkg/netmap" "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-api-go/pkg/owner" + "github.com/nspcc-dev/neofs-api-go/pkg/session" "github.com/nspcc-dev/neofs-s3-gw/api" "github.com/nspcc-dev/neofs-s3-gw/api/cache" "github.com/nspcc-dev/neofs-s3-gw/api/data" "github.com/nspcc-dev/neofs-s3-gw/api/errors" + "github.com/nspcc-dev/neofs-s3-gw/authmate" "github.com/nspcc-dev/neofs-s3-gw/creds/accessbox" "github.com/nspcc-dev/neofs-sdk-go/pool" "go.uber.org/zap" @@ -29,6 +33,7 @@ type ( layer struct { pool pool.Pool log *zap.Logger + anonKey AnonymousKey listsCache *cache.ObjectsListCache objCache *cache.ObjectsCache namesCache *cache.ObjectsNameCache @@ -36,6 +41,11 @@ type ( systemCache *cache.SystemCache } + // AnonymousKey contains data for anonymous requests. + AnonymousKey struct { + Key *keys.PrivateKey + } + // CachesConfig contains params for caches. CachesConfig struct { Objects *cache.Config @@ -112,11 +122,11 @@ type ( } // CreateBucketParams stores bucket create request parameters. CreateBucketParams struct { - Name string - ACL uint32 - Policy *netmap.PlacementPolicy - EACL *eacl.Table - BoxData *accessbox.Box + Name string + ACL uint32 + Policy *netmap.PlacementPolicy + EACL *eacl.Table + SessionToken *session.Token } // PutBucketACLParams stores put bucket acl request parameters. PutBucketACLParams struct { @@ -171,6 +181,8 @@ type ( Client interface { NeoFS + EphemeralKey() *keys.PublicKey + PutBucketVersioning(ctx context.Context, p *PutVersioningParams) (*data.ObjectInfo, error) GetBucketVersioning(ctx context.Context, name string) (*BucketSettings, error) @@ -228,10 +240,11 @@ func DefaultCachesConfigs() *CachesConfig { // NewLayer creates instance of layer. It checks credentials // and establishes gRPC connection with node. -func NewLayer(log *zap.Logger, conns pool.Pool, config *CachesConfig) Client { +func NewLayer(log *zap.Logger, conns pool.Pool, config *CachesConfig, anonKey AnonymousKey) Client { return &layer{ pool: conns, log: log, + anonKey: anonKey, listsCache: cache.NewObjectsListCache(config.ObjectsList), objCache: cache.New(config.Objects), namesCache: cache.NewObjectsNameCache(config.Names), @@ -240,22 +253,40 @@ func NewLayer(log *zap.Logger, conns pool.Pool, config *CachesConfig) Client { } } +func (n *layer) EphemeralKey() *keys.PublicKey { + return n.anonKey.Key.PublicKey() +} + +// randomSession returns client.WithSession for ephemeral key or nil if access box matches gate key. +func (n *layer) randomSession(ctx context.Context) client.CallOption { + c, _, err := n.pool.Connection() + if err != nil { + return client.WithSession(nil) + } + st, err := c.CreateSession(ctx, math.MaxUint64, client.WithKey(&n.anonKey.Key.PrivateKey)) + if err != nil { + return client.WithSession(nil) + } + return client.WithSession(st) +} + // Owner returns owner id from BearerToken (context) or from client owner. func (n *layer) Owner(ctx context.Context) *owner.ID { if data, ok := ctx.Value(api.BoxData).(*accessbox.Box); ok && data != nil && data.Gate != nil { return data.Gate.BearerToken.Issuer() } - return n.pool.OwnerID() + id, _ := authmate.OwnerIDFromNeoFSKey(n.EphemeralKey()) + return id } -// BearerOpt returns client.WithBearer call option with token from context or with nil token. -func (n *layer) BearerOpt(ctx context.Context) client.CallOption { +// CallOptions returns []client.CallOption options: client.WithBearer and client.WithKey. +func (n *layer) CallOptions(ctx context.Context) []client.CallOption { if data, ok := ctx.Value(api.BoxData).(*accessbox.Box); ok && data != nil && data.Gate != nil { - return client.WithBearer(data.Gate.BearerToken) + return []client.CallOption{client.WithBearer(data.Gate.BearerToken)} } - return client.WithBearer(nil) + return []client.CallOption{client.WithKey(&n.anonKey.Key.PrivateKey), n.randomSession(ctx)} } // SessionOpt returns client.WithSession call option with token from context or with nil token. @@ -270,7 +301,7 @@ func (n *layer) SessionOpt(ctx context.Context) client.CallOption { // Get NeoFS Object by refs.Address (should be used by auth.Center). func (n *layer) Get(ctx context.Context, address *object.Address) (*object.Object, error) { ops := new(client.GetObjectParams).WithAddress(address) - return n.pool.GetObject(ctx, ops, n.BearerOpt(ctx)) + return n.pool.GetObject(ctx, ops, n.CallOptions(ctx)...) } // GetBucketInfo returns bucket info by name. diff --git a/api/layer/object.go b/api/layer/object.go index 66ee9bf..92304f6 100644 --- a/api/layer/object.go +++ b/api/layer/object.go @@ -88,7 +88,8 @@ func (n *layer) objectSearch(ctx context.Context, p *findParams) ([]*object.ID, } else if prefix != "" { opts.AddFilter(object.AttributeFileName, prefix, object.MatchCommonPrefix) } - return n.pool.SearchObject(ctx, new(client.SearchObjectParams).WithContainerID(p.cid).WithSearchFilters(opts), n.BearerOpt(ctx)) + searchParams := new(client.SearchObjectParams).WithContainerID(p.cid).WithSearchFilters(opts) + return n.pool.SearchObject(ctx, searchParams, n.CallOptions(ctx)...) } func newAddress(cid *cid.ID, oid *object.ID) *object.Address { @@ -101,7 +102,7 @@ func newAddress(cid *cid.ID, oid *object.ID) *object.Address { // objectHead returns all object's headers. func (n *layer) objectHead(ctx context.Context, cid *cid.ID, oid *object.ID) (*object.Object, error) { ops := new(client.ObjectHeaderParams).WithAddress(newAddress(cid, oid)).WithAllFields() - return n.pool.GetObjectHeader(ctx, ops, n.BearerOpt(ctx)) + return n.pool.GetObjectHeader(ctx, ops, n.CallOptions(ctx)...) } // objectGetWithPayloadWriter and write it into provided io.Reader. @@ -109,20 +110,20 @@ func (n *layer) objectGetWithPayloadWriter(ctx context.Context, p *getParams) (* // prepare length/offset writer w := newWriter(p.Writer, p.offset, p.length) ops := new(client.GetObjectParams).WithAddress(newAddress(p.cid, p.oid)).WithPayloadWriter(w) - return n.pool.GetObject(ctx, ops, n.BearerOpt(ctx)) + return n.pool.GetObject(ctx, ops, n.CallOptions(ctx)...) } // objectGet returns an object with payload in the object. func (n *layer) objectGet(ctx context.Context, cid *cid.ID, oid *object.ID) (*object.Object, error) { ops := new(client.GetObjectParams).WithAddress(newAddress(cid, oid)) - return n.pool.GetObject(ctx, ops, n.BearerOpt(ctx)) + return n.pool.GetObject(ctx, ops, n.CallOptions(ctx)...) } // objectRange gets object range and writes it into provided io.Writer. func (n *layer) objectRange(ctx context.Context, p *getParams) ([]byte, error) { w := newWriter(p.Writer, p.offset, p.length) ops := new(client.RangeDataParams).WithAddress(newAddress(p.cid, p.oid)).WithDataWriter(w).WithRange(p.Range) - return n.pool.ObjectPayloadRangeData(ctx, ops, n.BearerOpt(ctx)) + return n.pool.ObjectPayloadRangeData(ctx, ops, n.CallOptions(ctx)...) } // objectPut into NeoFS, took payload from io.Reader. @@ -151,7 +152,7 @@ func (n *layer) objectPut(ctx context.Context, bkt *data.BucketInfo, p *PutObjec rawObject := formRawObject(p, bkt.CID, own, obj) ops := new(client.PutObjectParams).WithObject(rawObject.Object()).WithPayloadReader(r) - oid, err := n.pool.PutObject(ctx, ops, n.BearerOpt(ctx)) + oid, err := n.pool.PutObject(ctx, ops, n.CallOptions(ctx)...) if err != nil { return nil, err } @@ -380,7 +381,7 @@ func (n *layer) objectDelete(ctx context.Context, cid *cid.ID, oid *object.ID) e dop := new(client.DeleteObjectParams) dop.WithAddress(address) n.objCache.Delete(address) - return n.pool.DeleteObject(ctx, dop, n.BearerOpt(ctx)) + return n.pool.DeleteObject(ctx, dop, n.CallOptions(ctx)...) } // ListObjectsV1 returns objects in a bucket for requests of Version 1. diff --git a/api/layer/system_object.go b/api/layer/system_object.go index 104d13a..5c94115 100644 --- a/api/layer/system_object.go +++ b/api/layer/system_object.go @@ -98,7 +98,7 @@ func (n *layer) putSystemObjectIntoNeoFS(ctx context.Context, p *PutSystemObject raw.SetAttributes(attributes...) ops := new(client.PutObjectParams).WithObject(raw.Object()).WithPayloadReader(p.Reader) - oid, err := n.pool.PutObject(ctx, ops, n.BearerOpt(ctx)) + oid, err := n.pool.PutObject(ctx, ops, n.CallOptions(ctx)...) if err != nil { return nil, err } diff --git a/api/layer/versioning_test.go b/api/layer/versioning_test.go index dba658d..d2f6dfb 100644 --- a/api/layer/versioning_test.go +++ b/api/layer/versioning_test.go @@ -346,7 +346,7 @@ func prepareContext(t *testing.T, cachesConfig ...*CachesConfig) *testContext { return &testContext{ ctx: ctx, - layer: NewLayer(l, tp, config), + layer: NewLayer(l, tp, config, AnonymousKey{Key: key}), bkt: bktName, bktID: bktID, obj: "obj1", diff --git a/api/user_auth.go b/api/user_auth.go index a3967ab..595d189 100644 --- a/api/user_auth.go +++ b/api/user_auth.go @@ -24,7 +24,7 @@ func AttachUserAuth(router *mux.Router, center auth.Center, log *zap.Logger) { box, err := center.Authenticate(r) if err != nil { if err == auth.ErrNoAuthorizationHeader { - log.Debug("couldn't receive bearer token, using neofs-key") + log.Debug("couldn't receive access box for gate key, random key will be used") ctx = r.Context() } else { log.Error("failed to pass authentication", zap.Error(err)) diff --git a/authmate/authmate.go b/authmate/authmate.go index 9e01dc2..d31ff55 100644 --- a/authmate/authmate.go +++ b/authmate/authmate.go @@ -240,7 +240,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr box.ContainerPolicy = policies - oid, err := ownerIDFromNeoFSKey(options.NeoFSKey.PublicKey()) + oid, err := OwnerIDFromNeoFSKey(options.NeoFSKey.PublicKey()) if err != nil { return err } @@ -391,7 +391,7 @@ func buildContext(rules []byte) (*session.ContainerContext, error) { } func buildBearerToken(key *keys.PrivateKey, table *eacl.Table, lifetime lifetimeOptions, gateKey *keys.PublicKey) (*token.BearerToken, error) { - oid, err := ownerIDFromNeoFSKey(gateKey) + oid, err := OwnerIDFromNeoFSKey(gateKey) if err != nil { return nil, err } @@ -466,7 +466,7 @@ func createTokens(options *IssueSecretOptions, lifetime lifetimeOptions, cid *ci if err != nil { return nil, fmt.Errorf("failed to build context for session token: %w", err) } - oid, err := ownerIDFromNeoFSKey(options.NeoFSKey.PublicKey()) + oid, err := OwnerIDFromNeoFSKey(options.NeoFSKey.PublicKey()) if err != nil { return nil, err } @@ -483,7 +483,7 @@ func createTokens(options *IssueSecretOptions, lifetime lifetimeOptions, cid *ci return gates, nil } -func ownerIDFromNeoFSKey(key *keys.PublicKey) (*owner.ID, error) { +func OwnerIDFromNeoFSKey(key *keys.PublicKey) (*owner.ID, error) { wallet, err := owner.NEO3WalletFromPublicKey((*ecdsa.PublicKey)(key)) if err != nil { return nil, err diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index 7438569..745bffd 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -112,10 +112,19 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { l.Fatal("failed to create connection pool", zap.Error(err)) } + // prepare random key for anonymous requests + randomKey, err := keys.NewPrivateKey() + if err != nil { + l.Fatal("couldn't generate random key", zap.Error(err)) + } + anonKey := layer.AnonymousKey{ + Key: randomKey, + } + cacheCfg := getCacheOptions(v, l) // prepare object layer - obj = layer.NewLayer(l, conns, cacheCfg) + obj = layer.NewLayer(l, conns, cacheCfg, anonKey) // prepare auth center ctr = auth.New(conns, key, getAccessBoxCacheConfig(v, l))