From 4c8ec20e326fdec30fbd9cee5a9e1843d95d14b9 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 18 May 2022 18:20:08 +0300 Subject: [PATCH] [#1423] session: Upgrade SDK package Signed-off-by: Leonard Lyubich --- cmd/neofs-cli/internal/client/client.go | 14 +- cmd/neofs-cli/internal/client/prm.go | 30 +- cmd/neofs-cli/internal/common/token.go | 22 +- cmd/neofs-cli/modules/container.go | 38 ++- cmd/neofs-cli/modules/object.go | 45 ++- cmd/neofs-cli/modules/root.go | 11 +- cmd/neofs-cli/modules/session/create.go | 59 ++-- cmd/neofs-cli/modules/util/sign_session.go | 15 +- go.mod | 2 +- go.sum | 4 +- pkg/core/container/delete.go | 6 +- pkg/core/object/fmt.go | 25 +- pkg/core/object/fmt_test.go | 8 +- pkg/innerring/processors/container/common.go | 261 ++++++++---------- .../processors/container/process_container.go | 137 +++------ .../processors/container/process_eacl.go | 56 ++-- pkg/morph/client/container/delete.go | 21 +- pkg/morph/client/container/eacl.go | 4 +- pkg/morph/client/container/eacl_set.go | 10 +- pkg/morph/client/container/get.go | 4 +- pkg/morph/client/container/put.go | 10 +- pkg/services/container/morph/executor.go | 65 +++-- pkg/services/container/morph/executor_test.go | 8 +- pkg/services/object/acl/eacl/v2/headers.go | 10 + pkg/services/object/acl/eacl/v2/xheader.go | 19 +- pkg/services/object/acl/v2/request.go | 2 +- pkg/services/object/acl/v2/service.go | 63 +++-- pkg/services/object/acl/v2/util.go | 131 +++++---- pkg/services/object/acl/v2/util_test.go | 67 +++-- pkg/services/object/delete/exec.go | 8 +- pkg/services/object/get/exec.go | 14 +- pkg/services/object/get/service.go | 7 +- pkg/services/object/internal/client/client.go | 31 +-- pkg/services/object/put/remote.go | 13 +- pkg/services/object/put/streamer.go | 13 +- pkg/services/object/search/service.go | 7 +- pkg/services/object/search/util.go | 13 +- pkg/services/object/util/key.go | 24 +- pkg/services/object/util/key_test.go | 62 ++--- pkg/services/object/util/prm.go | 62 ++++- .../object_manager/transformer/fmt.go | 2 +- 41 files changed, 740 insertions(+), 663 deletions(-) diff --git a/cmd/neofs-cli/internal/client/client.go b/cmd/neofs-cli/internal/client/client.go index d6f680704..74cbce033 100644 --- a/cmd/neofs-cli/internal/client/client.go +++ b/cmd/neofs-cli/internal/client/client.go @@ -337,7 +337,7 @@ func PutObject(prm PutObjectPrm) (*PutObjectRes, error) { wrt.MarkLocal() } - wrt.WithXHeaders(prm.xHeadersPrm()...) + wrt.WithXHeaders(prm.xHeaders...) if wrt.WriteHeader(*prm.hdr) { sz := prm.hdr.PayloadSize() @@ -437,7 +437,7 @@ func DeleteObject(prm DeleteObjectPrm) (*DeleteObjectRes, error) { delPrm.WithBearerToken(*prm.bearerToken) } - delPrm.WithXHeaders(prm.xHeadersPrm()...) + delPrm.WithXHeaders(prm.xHeaders...) cliRes, err := prm.cli.ObjectDelete(context.Background(), delPrm) if err != nil { @@ -517,7 +517,7 @@ func GetObject(prm GetObjectPrm) (*GetObjectRes, error) { getPrm.MarkLocal() } - getPrm.WithXHeaders(prm.xHeadersPrm()...) + getPrm.WithXHeaders(prm.xHeaders...) rdr, err := prm.cli.ObjectGetInit(context.Background(), getPrm) if err != nil { @@ -599,7 +599,7 @@ func HeadObject(prm HeadObjectPrm) (*HeadObjectRes, error) { cliPrm.MarkLocal() } - cliPrm.WithXHeaders(prm.xHeadersPrm()...) + cliPrm.WithXHeaders(prm.xHeaders...) res, err := prm.cli.ObjectHead(context.Background(), cliPrm) if err != nil { @@ -664,7 +664,7 @@ func SearchObjects(prm SearchObjectsPrm) (*SearchObjectsRes, error) { cliPrm.MarkLocal() } - cliPrm.WithXHeaders(prm.xHeadersPrm()...) + cliPrm.WithXHeaders(prm.xHeaders...) rdr, err := prm.cli.ObjectSearchInit(context.Background(), cliPrm) if err != nil { @@ -775,7 +775,7 @@ func HashPayloadRanges(prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) cliPrm.WithBearerToken(*prm.bearerToken) } - cliPrm.WithXHeaders(prm.xHeadersPrm()...) + cliPrm.WithXHeaders(prm.xHeaders...) res, err := prm.cli.ObjectHash(context.Background(), cliPrm) if err != nil { @@ -841,7 +841,7 @@ func PayloadRange(prm PayloadRangePrm) (*PayloadRangeRes, error) { cliPrm.SetOffset(prm.rng.GetOffset()) cliPrm.SetLength(prm.rng.GetLength()) - cliPrm.WithXHeaders(prm.xHeadersPrm()...) + cliPrm.WithXHeaders(prm.xHeaders...) rdr, err := prm.cli.ObjectRangeInit(context.Background(), cliPrm) if err != nil { diff --git a/cmd/neofs-cli/internal/client/prm.go b/cmd/neofs-cli/internal/client/prm.go index 5b81e06db..d46221164 100644 --- a/cmd/neofs-cli/internal/client/prm.go +++ b/cmd/neofs-cli/internal/client/prm.go @@ -30,15 +30,6 @@ func (x *containerIDPrm) SetContainerID(id *cid.ID) { x.cnrID = id } -type sessionTokenPrm struct { - sessionToken *session.Token -} - -// SetSessionToken sets the token of the session within which the request should be sent. -func (x *sessionTokenPrm) SetSessionToken(tok *session.Token) { - x.sessionToken = tok -} - type bearerTokenPrm struct { bearerToken *bearer.Token } @@ -76,12 +67,13 @@ func (x *payloadWriterPrm) SetPayloadWriter(wrt io.Writer) { type commonObjectPrm struct { commonPrm - sessionTokenPrm bearerTokenPrm + sessionToken *session.Object + local bool - xHeaders []*session.XHeader + xHeaders []string } // SetTTL sets request TTL value. @@ -90,19 +82,11 @@ func (x *commonObjectPrm) SetTTL(ttl uint32) { } // SetXHeaders sets request X-Headers. -func (x *commonObjectPrm) SetXHeaders(hs []*session.XHeader) { +func (x *commonObjectPrm) SetXHeaders(hs []string) { x.xHeaders = hs } -func (x commonObjectPrm) xHeadersPrm() (res []string) { - if x.xHeaders != nil { - res = make([]string, len(x.xHeaders)*2) - - for i := range x.xHeaders { - res[2*i] = x.xHeaders[i].Key() - res[2*i+1] = x.xHeaders[i].Value() - } - } - - return +// SetSessionToken sets the token of the session within which the request should be sent. +func (x *commonObjectPrm) SetSessionToken(tok *session.Object) { + x.sessionToken = tok } diff --git a/cmd/neofs-cli/internal/common/token.go b/cmd/neofs-cli/internal/common/token.go index 654c34c22..5300b5f5f 100644 --- a/cmd/neofs-cli/internal/common/token.go +++ b/cmd/neofs-cli/internal/common/token.go @@ -1,10 +1,10 @@ package common import ( + "encoding/json" "os" "github.com/nspcc-dev/neofs-sdk-go/bearer" - "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/spf13/cobra" ) @@ -35,23 +35,11 @@ func ReadBearerToken(cmd *cobra.Command, flagname string) *bearer.Token { // ReadSessionToken reads session token as JSON file with session token // from path provided in a specified flag. -func ReadSessionToken(cmd *cobra.Command, flag string) *session.Token { +func ReadSessionToken(cmd *cobra.Command, dst json.Unmarshaler, fPath string) { // try to read session token from file - var tok *session.Token - - path, err := cmd.Flags().GetString(flag) - ExitOnErr(cmd, "", err) - - if path == "" { - return tok - } - - data, err := os.ReadFile(path) + data, err := os.ReadFile(fPath) ExitOnErr(cmd, "could not open file with session token: %w", err) - tok = session.NewToken() - err = tok.UnmarshalJSON(data) - ExitOnErr(cmd, "could not ummarshal session token from file: %w", err) - - return tok + err = dst.UnmarshalJSON(data) + ExitOnErr(cmd, "could not unmarshal session token from file: %w", err) } diff --git a/cmd/neofs-cli/modules/container.go b/cmd/neofs-cli/modules/container.go index 5eba8f9ec..34a39c3d1 100644 --- a/cmd/neofs-cli/modules/container.go +++ b/cmd/neofs-cli/modules/container.go @@ -24,6 +24,7 @@ import ( "github.com/nspcc-dev/neofs-sdk-go/object" addressSDK "github.com/nspcc-dev/neofs-sdk-go/object/address" "github.com/nspcc-dev/neofs-sdk-go/policy" + "github.com/nspcc-dev/neofs-sdk-go/session" subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" "github.com/nspcc-dev/neofs-sdk-go/user" versionSDK "github.com/nspcc-dev/neofs-sdk-go/version" @@ -158,26 +159,33 @@ It will be stored in sidechain when inner ring will accepts it.`, nonce, err := parseNonce(containerNonce) common.ExitOnErr(cmd, "", err) - tok := common.ReadSessionToken(cmd, sessionTokenFlag) key := key.GetOrGenerate(cmd) - var idOwner *user.ID + cnr := container.New() + var tok *session.Container - if idOwner = tok.OwnerID(); idOwner == nil { - idOwner = new(user.ID) - user.IDFromKey(idOwner, key.PublicKey) + if sessionTokenPath != "" { + tok = new(session.Container) + common.ReadSessionToken(cmd, tok, sessionTokenPath) + + issuer := tok.Issuer() + cnr.SetOwnerID(&issuer) + cnr.SetSessionToken(tok) + } else { + var idOwner user.ID + user.IDFromKey(&idOwner, key.PublicKey) + + cnr.SetOwnerID(&idOwner) } ver := versionSDK.Current() - cnr := container.New() cnr.SetVersion(&ver) cnr.SetPlacementPolicy(placementPolicy) cnr.SetBasicACL(basicACL) cnr.SetAttributes(attributes) cnr.SetNonceUUID(nonce) cnr.SetSessionToken(tok) - cnr.SetOwnerID(idOwner) var ( putPrm internalclient.PutContainerPrm @@ -223,7 +231,12 @@ Only owner of the container has a permission to remove container.`, id, err := parseContainerID(containerID) common.ExitOnErr(cmd, "", err) - tok := common.ReadSessionToken(cmd, sessionTokenFlag) + var tok *session.Container + + if sessionTokenPath != "" { + tok = new(session.Container) + common.ReadSessionToken(cmd, tok, sessionTokenPath) + } var ( delPrm internalclient.DeleteContainerPrm @@ -234,7 +247,7 @@ Only owner of the container has a permission to remove container.`, delPrm.SetContainer(*id) if tok != nil { - delPrm.SetSessionToken(*tok) + delPrm.WithinSession(*tok) } _, err = internalclient.DeleteContainer(delPrm) @@ -412,7 +425,12 @@ Container ID in EACL table will be substituted with ID from the CLI.`, eaclTable := common.ReadEACL(cmd, eaclPathFrom) - tok := common.ReadSessionToken(cmd, sessionTokenFlag) + var tok *session.Container + + if sessionTokenPath != "" { + tok = new(session.Container) + common.ReadSessionToken(cmd, tok, sessionTokenPath) + } eaclTable.SetCID(*id) eaclTable.SetSessionToken(tok) diff --git a/cmd/neofs-cli/modules/object.go b/cmd/neofs-cli/modules/object.go index 87b5ad4a8..8e2275fe0 100644 --- a/cmd/neofs-cli/modules/object.go +++ b/cmd/neofs-cli/modules/object.go @@ -314,7 +314,7 @@ func init() { type clientKeySession interface { clientWithKey - SetSessionToken(*session.Token) + SetSessionToken(*session.Object) } func prepareSessionPrm(cmd *cobra.Command, addr *addressSDK.Address, prms ...clientKeySession) { @@ -339,65 +339,54 @@ func prepareSessionPrmWithOwner( ) { cli := internalclient.GetSDKClientByFlag(cmd, key, commonflags.RPC) - var sessionToken *session.Token + var tok session.Object if tokenPath, _ := cmd.Flags().GetString(sessionTokenFlag); len(tokenPath) != 0 { data, err := ioutil.ReadFile(tokenPath) common.ExitOnErr(cmd, "can't read session token: %w", err) - sessionToken = session.NewToken() - if err := sessionToken.Unmarshal(data); err != nil { - err = sessionToken.UnmarshalJSON(data) + if err := tok.Unmarshal(data); err != nil { + err = tok.UnmarshalJSON(data) common.ExitOnErr(cmd, "can't unmarshal session token: %w", err) } } else { - var err error - sessionToken, err = sessionCli.CreateSession(cli, ownerID, sessionTokenLifetime) - common.ExitOnErr(cmd, "", err) + err := sessionCli.CreateSession(&tok, cli, sessionTokenLifetime) + common.ExitOnErr(cmd, "create session: %w", err) } for i := range prms { - objectContext := session.NewObjectContext() switch prms[i].(type) { case *internalclient.GetObjectPrm: - objectContext.ForGet() + tok.ForVerb(session.VerbObjectGet) case *internalclient.HeadObjectPrm: - objectContext.ForHead() + tok.ForVerb(session.VerbObjectHead) case *internalclient.PutObjectPrm: - objectContext.ForPut() + tok.ForVerb(session.VerbObjectPut) case *internalclient.DeleteObjectPrm: - objectContext.ForDelete() + tok.ForVerb(session.VerbObjectDelete) case *internalclient.SearchObjectsPrm: - objectContext.ForSearch() + tok.ForVerb(session.VerbObjectSearch) case *internalclient.PayloadRangePrm: - objectContext.ForRange() + tok.ForVerb(session.VerbObjectRange) case *internalclient.HashPayloadRangesPrm: - objectContext.ForRangeHash() + tok.ForVerb(session.VerbObjectRangeHash) default: panic("invalid client parameter type") } - objectContext.ApplyTo(addr) - tok := session.NewToken() - tok.SetID(sessionToken.ID()) - tok.SetSessionKey(sessionToken.SessionKey()) - tok.SetOwnerID(sessionToken.OwnerID()) - tok.SetContext(objectContext) - tok.SetExp(sessionToken.Exp()) - tok.SetIat(sessionToken.Iat()) - tok.SetNbf(sessionToken.Nbf()) + tok.ApplyTo(*addr) - err := tok.Sign(key) + err := tok.Sign(*key) common.ExitOnErr(cmd, "session token signing: %w", err) prms[i].SetClient(cli) - prms[i].SetSessionToken(tok) + prms[i].SetSessionToken(&tok) } } type objectPrm interface { bearerPrm SetTTL(uint32) - SetXHeaders([]*session.XHeader) + SetXHeaders([]string) } func prepareObjectPrm(cmd *cobra.Command, prms ...objectPrm) { diff --git a/cmd/neofs-cli/modules/root.go b/cmd/neofs-cli/modules/root.go index 5ca5486a5..c0279c9b8 100644 --- a/cmd/neofs-cli/modules/root.go +++ b/cmd/neofs-cli/modules/root.go @@ -22,7 +22,6 @@ import ( "github.com/nspcc-dev/neofs-node/pkg/util/gendoc" "github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/client" - "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -188,8 +187,8 @@ func userFromString(id *user.ID, s string) error { return nil } -func parseXHeaders() []*session.XHeader { - xs := make([]*session.XHeader, 0, len(xHeaders)) +func parseXHeaders() []string { + xs := make([]string, 0, 2*len(xHeaders)) for i := range xHeaders { kv := strings.SplitN(xHeaders[i], "=", 2) @@ -197,11 +196,7 @@ func parseXHeaders() []*session.XHeader { panic(fmt.Errorf("invalid X-Header format: %s", xHeaders[i])) } - x := session.NewXHeader() - x.SetKey(kv[0]) - x.SetValue(kv[1]) - - xs = append(xs, x) + xs = append(xs, kv[0], kv[1]) } return xs diff --git a/cmd/neofs-cli/modules/session/create.go b/cmd/neofs-cli/modules/session/create.go index bfbd0cce4..252044351 100644 --- a/cmd/neofs-cli/modules/session/create.go +++ b/cmd/neofs-cli/modules/session/create.go @@ -4,13 +4,14 @@ import ( "fmt" "io/ioutil" + "github.com/google/uuid" internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" "github.com/nspcc-dev/neofs-node/pkg/network" "github.com/nspcc-dev/neofs-sdk-go/client" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -66,10 +67,9 @@ func createSession(cmd *cobra.Command, _ []string) error { lifetime = lfArg } - var ownerID user.ID - user.IDFromKey(&ownerID, privKey.PublicKey) + var tok session.Object - tok, err := CreateSession(c, &ownerID, lifetime) + err = CreateSession(&tok, c, lifetime) if err != nil { return err } @@ -78,11 +78,11 @@ func createSession(cmd *cobra.Command, _ []string) error { if toJSON, _ := cmd.Flags().GetBool(jsonFlag); toJSON { data, err = tok.MarshalJSON() + if err != nil { + return fmt.Errorf("decode session token JSON: %w", err) + } } else { - data, err = tok.Marshal() - } - if err != nil { - return fmt.Errorf("can't marshal token: %w", err) + data = tok.Marshal() } filename, _ := cmd.Flags().GetString(outFlag) @@ -92,15 +92,18 @@ func createSession(cmd *cobra.Command, _ []string) error { return nil } -// CreateSession returns newly created session token with the specified owner and lifetime. -// `Issued-At` and `Not-Valid-Before` fields are set to current epoch. -func CreateSession(c *client.Client, owner *user.ID, lifetime uint64) (*session.Token, error) { +// CreateSession opens a new communication with NeoFS storage node using client connection. +// The session is expected to be maintained by the storage node during the given +// number of epochs. +// +// Fills ID, lifetime and session key. +func CreateSession(dst *session.Object, c *client.Client, lifetime uint64) error { var netInfoPrm internalclient.NetworkInfoPrm netInfoPrm.SetClient(c) ni, err := internalclient.NetworkInfo(netInfoPrm) if err != nil { - return nil, fmt.Errorf("can't fetch network info: %w", err) + return fmt.Errorf("can't fetch network info: %w", err) } cur := ni.NetworkInfo().CurrentEpoch() @@ -112,16 +115,30 @@ func CreateSession(c *client.Client, owner *user.ID, lifetime uint64) (*session. sessionRes, err := internalclient.CreateSession(sessionPrm) if err != nil { - return nil, fmt.Errorf("can't open session: %w", err) + return fmt.Errorf("can't open session: %w", err) } - tok := session.NewToken() - tok.SetID(sessionRes.ID()) - tok.SetSessionKey(sessionRes.SessionKey()) - tok.SetOwnerID(owner) - tok.SetExp(exp) - tok.SetIat(cur) - tok.SetNbf(cur) + binIDSession := sessionRes.ID() - return tok, nil + var keySession neofsecdsa.PublicKey + + err = keySession.Decode(sessionRes.SessionKey()) + if err != nil { + return fmt.Errorf("decode public session key: %w", err) + } + + var idSession uuid.UUID + + err = idSession.UnmarshalBinary(binIDSession) + if err != nil { + return fmt.Errorf("decode session ID: %w", err) + } + + dst.SetID(idSession) + dst.SetNbf(cur) + dst.SetIat(cur) + dst.SetExp(exp) + dst.SetAuthKey(&keySession) + + return nil } diff --git a/cmd/neofs-cli/modules/util/sign_session.go b/cmd/neofs-cli/modules/util/sign_session.go index b441d6670..f9713ff73 100644 --- a/cmd/neofs-cli/modules/util/sign_session.go +++ b/cmd/neofs-cli/modules/util/sign_session.go @@ -1,12 +1,14 @@ package util import ( + "errors" "fmt" "os" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" + "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/spf13/cobra" ) @@ -29,10 +31,19 @@ func initSignSessionCmd() { } func signSessionToken(cmd *cobra.Command, _ []string) { - stok := common.ReadSessionToken(cmd, signFromFlag) + fPath, err := cmd.Flags().GetString(signFromFlag) + common.ExitOnErr(cmd, "", err) + + if fPath == "" { + common.ExitOnErr(cmd, "", errors.New("missing session token flag")) + } + + var stok session.Object + common.ReadSessionToken(cmd, &stok, signFromFlag) + pk := key.GetOrGenerate(cmd) - err := stok.Sign(pk) + err = stok.Sign(*pk) common.ExitOnErr(cmd, "can't sign token: %w", err) data, err := stok.MarshalJSON() diff --git a/go.mod b/go.mod index 8ee871d82..b8794579c 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220321144137-d5a9af5860af // indirect github.com/nspcc-dev/neofs-api-go/v2 v2.12.1 github.com/nspcc-dev/neofs-contract v0.15.1 - github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220424111116-497053c785f5 + github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220525080251-1f7fe6864d34 github.com/nspcc-dev/tzhash v1.5.2 github.com/panjf2000/ants/v2 v2.4.0 github.com/paulmach/orb v0.2.2 diff --git a/go.sum b/go.sum index 7558f4b92..2f85e764b 100644 --- a/go.sum +++ b/go.sum @@ -407,8 +407,8 @@ github.com/nspcc-dev/neofs-crypto v0.3.0 h1:zlr3pgoxuzrmGCxc5W8dGVfA9Rro8diFvVnB github.com/nspcc-dev/neofs-crypto v0.3.0/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw= github.com/nspcc-dev/neofs-sdk-go v0.0.0-20211201182451-a5b61c4f6477/go.mod h1:dfMtQWmBHYpl9Dez23TGtIUKiFvCIxUZq/CkSIhEpz4= github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659/go.mod h1:/jay1lr3w7NQd/VDBkEhkJmDmyPNsu4W+QV2obsUV40= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220424111116-497053c785f5 h1:upiT6iVOy81tiY2x593E8+mxpb9BuW3fsvKFdqdXenk= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220424111116-497053c785f5/go.mod h1:u567oWTnAyGXbPWMrbcN0NB5zCPF+PqkaKg+vcijcho= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220525080251-1f7fe6864d34 h1:s9+ckFRFDVJfYh/QezTUE4g1JP3pyU5NaNxBfUIyGh8= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220525080251-1f7fe6864d34/go.mod h1:u567oWTnAyGXbPWMrbcN0NB5zCPF+PqkaKg+vcijcho= github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE= github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= diff --git a/pkg/core/container/delete.go b/pkg/core/container/delete.go index 700d349f2..84976cd8f 100644 --- a/pkg/core/container/delete.go +++ b/pkg/core/container/delete.go @@ -12,7 +12,7 @@ type RemovalWitness struct { sig []byte - token *session.Token + token *session.Container } // ContainerID returns the identifier of the container @@ -39,12 +39,12 @@ func (x *RemovalWitness) SetSignature(sig []byte) { // SessionToken returns the token of the session within // which the container was removed. -func (x RemovalWitness) SessionToken() *session.Token { +func (x RemovalWitness) SessionToken() *session.Container { return x.token } // SetSessionToken sets the token of the session within // which the container was removed. -func (x *RemovalWitness) SetSessionToken(tok *session.Token) { +func (x *RemovalWitness) SetSessionToken(tok *session.Container) { x.token = tok } diff --git a/pkg/core/object/fmt.go b/pkg/core/object/fmt.go index b38cdd7d0..4e6fe3ed1 100644 --- a/pkg/core/object/fmt.go +++ b/pkg/core/object/fmt.go @@ -1,18 +1,16 @@ package object import ( - "bytes" "crypto/ecdsa" - "crypto/elliptic" "errors" "fmt" "strconv" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-node/pkg/core/netmap" 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/object" addressSDK "github.com/nspcc-dev/neofs-sdk-go/object/address" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" @@ -139,10 +137,18 @@ func (v *FormatValidator) validateSignatureKey(obj *object.Object) error { var sigV2 refs.Signature sig.WriteToV2(&sigV2) - key := sigV2.GetKey() + binKey := sigV2.GetKey() + + var key neofsecdsa.PublicKey + + err := key.Decode(binKey) + if err != nil { + return fmt.Errorf("decode public key: %w", err) + } + token := obj.SessionToken() - if token == nil || !bytes.Equal(token.SessionKey(), key) { + if token == nil || !token.AssertAuthKey(&key) { return v.checkOwnerKey(obj.OwnerID(), key) } @@ -151,14 +157,9 @@ func (v *FormatValidator) validateSignatureKey(obj *object.Object) error { return nil } -func (v *FormatValidator) checkOwnerKey(id *user.ID, key []byte) error { - pub, err := keys.NewPublicKeyFromBytes(key, elliptic.P256()) - if err != nil { - return err - } - +func (v *FormatValidator) checkOwnerKey(id *user.ID, key neofsecdsa.PublicKey) error { var id2 user.ID - user.IDFromKey(&id2, (ecdsa.PublicKey)(*pub)) + user.IDFromKey(&id2, (ecdsa.PublicKey)(key)) if !id.Equals(id2) { return fmt.Errorf("(%T) different owner identifiers %s/%s", v, id, id2) diff --git a/pkg/core/object/fmt_test.go b/pkg/core/object/fmt_test.go index ed688042d..9b9d1694c 100644 --- a/pkg/core/object/fmt_test.go +++ b/pkg/core/object/fmt_test.go @@ -88,13 +88,13 @@ func TestFormatValidator_Validate(t *testing.T) { var idOwner user.ID user.IDFromKey(&idOwner, ownerKey.PrivateKey.PublicKey) - tok := sessiontest.Token() - tok.SetOwnerID(&idOwner) + tok := sessiontest.Object() + tok.Sign(ownerKey.PrivateKey) obj := object.New() obj.SetContainerID(cidtest.ID()) - obj.SetSessionToken(sessiontest.Token()) - obj.SetOwnerID(tok.OwnerID()) + obj.SetSessionToken(tok) + obj.SetOwnerID(&idOwner) require.NoError(t, object.SetIDWithSignature(ownerKey.PrivateKey, obj)) diff --git a/pkg/innerring/processors/container/common.go b/pkg/innerring/processors/container/common.go index 46940aad4..0463e0a31 100644 --- a/pkg/innerring/processors/container/common.go +++ b/pkg/innerring/processors/container/common.go @@ -3,184 +3,167 @@ package container import ( "bytes" "crypto/ecdsa" - "crypto/elliptic" "errors" "fmt" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-node/pkg/morph/client/neofsid" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/user" ) var ( - errWrongSessionContext = errors.New("wrong session context") - errWrongSessionVerb = errors.New("wrong token verb") - errWrongCID = errors.New("wrong container ID") + errWrongSessionVerb = errors.New("wrong token verb") + errWrongCID = errors.New("wrong container ID") ) -type ownerIDSource interface { - OwnerID() *user.ID +type signatureVerificationData struct { + ownerContainer user.ID + + verb session.ContainerVerb + + idContainerSet bool + idContainer cid.ID + + binTokenSession []byte + + binPublicKey []byte + + signature []byte + + signedData []byte } -func tokenFromEvent(src interface { - SessionToken() []byte -}) (*session.Token, error) { - binToken := src.SessionToken() +// verifySignature is a common method of Container service authentication. Asserts that: +// - for trusted parties: session is valid (*) and issued by container owner +// - operation data is signed by container owner or trusted party +// - operation data signature is correct +// +// (*) includes: +// - session token decodes correctly +// - signature is valid +// - session issued by the container owner +// - v.binPublicKey is a public session key +// - session context corresponds to the container and verb in v +// - session is "alive" +func (cp *Processor) verifySignature(v signatureVerificationData) error { + var err error + var key neofsecdsa.PublicKeyRFC6979 + keyProvided := v.binPublicKey != nil + withSession := len(v.binTokenSession) > 0 - if len(binToken) == 0 { - return nil, nil - } - - tok := session.NewToken() - - err := tok.Unmarshal(binToken) - if err != nil { - return nil, fmt.Errorf("could not unmarshal session token: %w", err) - } - - return tok, nil -} - -func (cp *Processor) checkKeyOwnership(ownerIDSrc ownerIDSource, key *keys.PublicKey) error { - if tokenSrc, ok := ownerIDSrc.(interface { - SessionToken() *session.Token - }); ok { - if token := tokenSrc.SessionToken(); token != nil { - return cp.checkKeyOwnershipWithToken(ownerIDSrc, key, tokenSrc.SessionToken()) + if keyProvided { + err = key.Decode(v.binPublicKey) + if err != nil { + return fmt.Errorf("decode public key: %w", err) } } - ownerSrc := ownerIDSrc.OwnerID() - if ownerSrc == nil { - return errors.New("missing owner") + if withSession { + var tok session.Container + + err = tok.Unmarshal(v.binTokenSession) + if err != nil { + return fmt.Errorf("decode session token: %w", err) + } + + if !tok.VerifySignature() { + return errors.New("invalid session token signature") + } + + // FIXME(@cthulhu-rider): #1387 check token is signed by container owner, see neofs-sdk-go#233 + // We'll get container owner's keys which is needed below, so it's worth to cache them + + if keyProvided && !tok.AssertAuthKey(&key) { + return errors.New("signed with a non-session key") + } + + if !tok.AssertVerb(v.verb) { + return errWrongSessionVerb + } + + if v.idContainerSet && !tok.AppliedTo(v.idContainer) { + return errWrongCID + } + + if !session.IssuedBy(tok, v.ownerContainer) { + return errors.New("owner differs with token owner") + } + + err = cp.checkTokenLifetime(tok) + if err != nil { + return fmt.Errorf("check session lifetime: %w", err) + } } - var ownerKey user.ID - user.IDFromKey(&ownerKey, (ecdsa.PublicKey)(*key)) + var verificationKeys []neofscrypto.PublicKey - if ownerSrc.Equals(ownerKey) { - return nil + if keyProvided { + if withSession { + verificationKeys = []neofscrypto.PublicKey{&key} + } else { + var idFromKey user.ID + user.IDFromKey(&idFromKey, (ecdsa.PublicKey)(key)) + + if v.ownerContainer.Equals(idFromKey) { + verificationKeys = []neofscrypto.PublicKey{&key} + } + } } - prm := neofsid.AccountKeysPrm{} - prm.SetID(ownerIDSrc.OwnerID()) + if verificationKeys == nil { + var prm neofsid.AccountKeysPrm + prm.SetID(&v.ownerContainer) - ownerKeys, err := cp.idClient.AccountKeys(prm) - if err != nil { - return fmt.Errorf("could not received owner keys %s: %w", ownerIDSrc.OwnerID(), err) + ownerKeys, err := cp.idClient.AccountKeys(prm) + if err != nil { + return fmt.Errorf("receive owner keys %s: %w", v.ownerContainer, err) + } + + if !keyProvided { + verificationKeys = make([]neofscrypto.PublicKey, 0, len(ownerKeys)) + } + + for i := range ownerKeys { + if keyProvided { + // TODO(@cthulhu-rider): keys have been decoded in order to encode only, should be optimized by #1387 + if bytes.Equal(ownerKeys[i].Bytes(), v.binPublicKey) { + verificationKeys = []neofscrypto.PublicKey{(*neofsecdsa.PublicKeyRFC6979)(ownerKeys[i])} + break + } + } else { + verificationKeys = append(verificationKeys, (*neofsecdsa.PublicKeyRFC6979)(ownerKeys[i])) + } + } } - for _, ownerKey := range ownerKeys { - if ownerKey.Equal(key) { + if len(verificationKeys) == 0 { + return errors.New("key is not a container owner's key") + } + + for i := range verificationKeys { + if verificationKeys[i].Verify(v.signedData, v.signature) { return nil } } - return fmt.Errorf("key %s is not tied to the owner of the container", key) + return errors.New("invalid signature") } -func (cp *Processor) checkKeyOwnershipWithToken(ownerIDSrc ownerIDSource, key *keys.PublicKey, token *session.Token) error { - // check session key - if !bytes.Equal( - key.Bytes(), - token.SessionKey(), - ) { - return errors.New("signed with a non-session key") - } - - ownerToken, ownerSrc := token.OwnerID(), ownerIDSrc.OwnerID() - - // check owner - if ownerToken == nil || ownerSrc == nil || !ownerToken.Equals(*ownerSrc) { - return errors.New("owner differs with token owner") - } - - err := cp.checkSessionToken(token) - if err != nil { - return fmt.Errorf("invalid session token: %w", err) - } - - return nil -} - -func (cp *Processor) checkSessionToken(token *session.Token) error { - // verify signature - if !token.VerifySignature() { - return errors.New("invalid signature") - } - - // check lifetime - err := cp.checkTokenLifetime(token) - if err != nil { - return err - } - - // check token owner's key ownership - - // FIXME(@cthulhu-rider): #1387 see neofs-sdk-go#233 - key, err := keys.NewPublicKeyFromBytes(token.ToV2().GetSignature().GetKey(), elliptic.P256()) - if err != nil { - return fmt.Errorf("invalid key: %w", err) - } - - return cp.checkKeyOwnership(token, key) -} - -type verbAssert func(*session.ContainerContext) bool - -func contextWithVerifiedVerb(tok *session.Token, verbAssert verbAssert) (*session.ContainerContext, error) { - c := session.GetContainerContext(tok) - if c == nil { - return nil, errWrongSessionContext - } - - if !verbAssert(c) { - return nil, errWrongSessionVerb - } - - return c, nil -} - -func checkTokenContext(tok *session.Token, verbAssert verbAssert) error { - _, err := contextWithVerifiedVerb(tok, verbAssert) - return err -} - -func checkTokenContextWithCID(tok *session.Token, id cid.ID, verbAssert verbAssert) error { - c, err := contextWithVerifiedVerb(tok, verbAssert) - if err != nil { - return err - } - - tokCID := c.Container() - if tokCID != nil && !tokCID.Equals(id) { - return errWrongCID - } - - return nil -} - -func (cp *Processor) checkTokenLifetime(token *session.Token) error { +func (cp *Processor) checkTokenLifetime(token session.Container) error { curEpoch, err := cp.netState.Epoch() if err != nil { return fmt.Errorf("could not read current epoch: %w", err) } - nbf := token.Nbf() - if curEpoch < nbf { - return fmt.Errorf("token is not valid yet: nbf %d, cur %d", nbf, curEpoch) + if token.ExpiredAt(curEpoch) { + return fmt.Errorf("token is expired at %d", curEpoch) } - iat := token.Iat() - if curEpoch < iat { - return fmt.Errorf("token is issued in future: iat %d, cur %d", iat, curEpoch) - } - - exp := token.Exp() - if curEpoch >= exp { - return fmt.Errorf("token is expired: exp %d, cur %d", exp, curEpoch) + if token.InvalidAt(curEpoch) { + return fmt.Errorf("token is not valid at %d", curEpoch) } return nil diff --git a/pkg/innerring/processors/container/process_container.go b/pkg/innerring/processors/container/process_container.go index b6d8804c7..0e18bc800 100644 --- a/pkg/innerring/processors/container/process_container.go +++ b/pkg/innerring/processors/container/process_container.go @@ -1,16 +1,12 @@ package container import ( - "crypto/elliptic" - "crypto/sha256" "errors" "fmt" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/network/payload" "github.com/nspcc-dev/neofs-node/pkg/core/container" cntClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/container" - "github.com/nspcc-dev/neofs-node/pkg/morph/client/neofsid" morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet" "github.com/nspcc-dev/neofs-node/pkg/morph/event" containerEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/container" @@ -62,29 +58,32 @@ func (cp *Processor) processContainerPut(put putEvent) { } func (cp *Processor) checkPutContainer(ctx *putContainerContext) error { - e := ctx.e + binCnr := ctx.e.Container() - // verify signature - key, err := keys.NewPublicKeyFromBytes(e.PublicKey(), elliptic.P256()) - if err != nil { - return fmt.Errorf("invalid key: %w", err) - } - - binCnr := e.Container() - tableHash := sha256.Sum256(binCnr) - - if !key.Verify(e.Signature(), tableHash[:]) { - return errors.New("invalid signature") - } - - // unmarshal container structure cnr := containerSDK.New() - err = cnr.Unmarshal(binCnr) + err := cnr.Unmarshal(binCnr) if err != nil { return fmt.Errorf("invalid binary container: %w", err) } + ownerContainer := cnr.OwnerID() + if ownerContainer == nil { + return errors.New("missing container owner") + } + + err = cp.verifySignature(signatureVerificationData{ + ownerContainer: *ownerContainer, + verb: session.VerbContainerPut, + binTokenSession: ctx.e.SessionToken(), + binPublicKey: ctx.e.PublicKey(), + signature: ctx.e.Signature(), + signedData: binCnr, + }) + if err != nil { + return fmt.Errorf("auth container creation: %w", err) + } + // check owner allowance in the subnetwork err = checkSubnet(cp.subnetClient, cnr) if err != nil { @@ -103,25 +102,7 @@ func (cp *Processor) checkPutContainer(ctx *putContainerContext) error { return fmt.Errorf("incorrect container format: %w", err) } - // unmarshal session token if presented - tok, err := tokenFromEvent(e) - if err != nil { - return err - } - - if tok != nil { - // check token context - err = checkTokenContext(tok, func(c *session.ContainerContext) bool { - return c.IsForPut() - }) - if err != nil { - return err - } - } - - cnr.SetSessionToken(tok) - - return cp.checkKeyOwnership(cnr, key) + return nil } func (cp *Processor) approvePutContainer(ctx *putContainerContext) { @@ -175,70 +156,38 @@ func (cp *Processor) processContainerDelete(delete *containerEvent.Delete) { func (cp *Processor) checkDeleteContainer(e *containerEvent.Delete) error { binCID := e.ContainerID() + var idCnr cid.ID + + err := idCnr.Decode(binCID) + if err != nil { + return fmt.Errorf("invalid container ID: %w", err) + } + // receive owner of the related container cnr, err := cp.cnrClient.Get(binCID) if err != nil { return fmt.Errorf("could not receive the container: %w", err) } - token, err := tokenFromEvent(e) + ownerContainer := cnr.OwnerID() + if ownerContainer == nil { + return errors.New("missing container owner") + } + + err = cp.verifySignature(signatureVerificationData{ + ownerContainer: *ownerContainer, + verb: session.VerbContainerDelete, + idContainerSet: true, + idContainer: idCnr, + binTokenSession: e.SessionToken(), + signature: e.Signature(), + signedData: binCID, + }) if err != nil { - return err + return fmt.Errorf("auth container creation: %w", err) } - var checkKeys keys.PublicKeys - - if token != nil { - // check token context - // TODO: #1147 think how to avoid version casts - var id cid.ID - - err = id.Decode(binCID) - if err != nil { - return fmt.Errorf("decode container ID: %w", err) - } - - err = checkTokenContextWithCID(token, id, func(c *session.ContainerContext) bool { - return c.IsForDelete() - }) - if err != nil { - return err - } - - key, err := keys.NewPublicKeyFromBytes(token.SessionKey(), elliptic.P256()) - if err != nil { - return fmt.Errorf("invalid session key: %w", err) - } - - // check token ownership - err = cp.checkKeyOwnershipWithToken(cnr, key, token) - if err != nil { - return err - } - - checkKeys = keys.PublicKeys{key} - } else { - prm := neofsid.AccountKeysPrm{} - prm.SetID(cnr.OwnerID()) - - // receive all owner keys from NeoFS ID contract - checkKeys, err = cp.idClient.AccountKeys(prm) - if err != nil { - return fmt.Errorf("could not received owner keys %s: %w", cnr.OwnerID(), err) - } - } - - // verify signature - cidHash := sha256.Sum256(binCID) - sig := e.Signature() - - for _, key := range checkKeys { - if key.Verify(sig, cidHash[:]) { - return nil - } - } - - return errors.New("signature verification failed on all owner keys ") + return nil } func (cp *Processor) approveDeleteContainer(e *containerEvent.Delete) { diff --git a/pkg/innerring/processors/container/process_eacl.go b/pkg/innerring/processors/container/process_eacl.go index d11f0008c..52d619331 100644 --- a/pkg/innerring/processors/container/process_eacl.go +++ b/pkg/innerring/processors/container/process_eacl.go @@ -1,12 +1,9 @@ package container import ( - "crypto/elliptic" - "crypto/sha256" "errors" "fmt" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" cntClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/container" "github.com/nspcc-dev/neofs-node/pkg/morph/event/container" "github.com/nspcc-dev/neofs-sdk-go/eacl" @@ -33,25 +30,12 @@ func (cp *Processor) processSetEACL(e container.SetEACL) { } func (cp *Processor) checkSetEACL(e container.SetEACL) error { - // verify signature - key, err := keys.NewPublicKeyFromBytes(e.PublicKey(), elliptic.P256()) - if err != nil { - return fmt.Errorf("invalid key: %w", err) - } - binTable := e.Table() - tableHash := sha256.Sum256(binTable) - - if !key.Verify(e.Signature(), tableHash[:]) { - return errors.New("invalid signature") - } - - // verify the identity of the container owner // unmarshal table table := eacl.NewTable() - err = table.Unmarshal(binTable) + err := table.Unmarshal(binTable) if err != nil { return fmt.Errorf("invalid binary table: %w", err) } @@ -67,30 +51,26 @@ func (cp *Processor) checkSetEACL(e container.SetEACL) error { return fmt.Errorf("could not receive the container: %w", err) } - // unmarshal session token if presented - tok, err := tokenFromEvent(e) + ownerContainer := cnr.OwnerID() + if ownerContainer == nil { + return errors.New("missing container owner") + } + + err = cp.verifySignature(signatureVerificationData{ + ownerContainer: *ownerContainer, + verb: session.VerbContainerSetEACL, + idContainerSet: true, + idContainer: idCnr, + binTokenSession: e.SessionToken(), + binPublicKey: e.PublicKey(), + signature: e.Signature(), + signedData: binTable, + }) if err != nil { - return err + return fmt.Errorf("auth eACL table setting: %w", err) } - if tok != nil { - // check token context - err = checkTokenContextWithCID(tok, idCnr, func(c *session.ContainerContext) bool { - return c.IsForSetEACL() - }) - if err != nil { - return err - } - } - - // statement below is a little hack, but if we write a token from an event to the container, - // checkKeyOwnership method will work just as it should: - // * tok == nil => we will check if key is a container owner's key - // * tok != nil => we will check if token was signed correctly (context is checked at the statement above) - cnr.SetSessionToken(tok) - - // check key ownership - return cp.checkKeyOwnership(cnr, key) + return nil } func (cp *Processor) approveSetEACL(e container.SetEACL) { diff --git a/pkg/morph/client/container/delete.go b/pkg/morph/client/container/delete.go index fbb990782..157b5e7f9 100644 --- a/pkg/morph/client/container/delete.go +++ b/pkg/morph/client/container/delete.go @@ -18,20 +18,19 @@ func Delete(c *Client, witness core.RemovalWitness) error { return errNilArgument } - binToken, err := witness.SessionToken().Marshal() - if err != nil { - return fmt.Errorf("could not marshal session token: %w", err) - } - binCnr := make([]byte, sha256.Size) id.Encode(binCnr) - return c.Delete( - DeletePrm{ - cid: binCnr, - signature: witness.Signature(), - token: binToken, - }) + var prm DeletePrm + + prm.SetCID(binCnr) + prm.SetSignature(witness.Signature()) + + if tok := witness.SessionToken(); tok != nil { + prm.SetToken(tok.Marshal()) + } + + return c.Delete(prm) } // DeletePrm groups parameters of Delete client operation. diff --git a/pkg/morph/client/container/eacl.go b/pkg/morph/client/container/eacl.go index 7d732d22a..13247e8d7 100644 --- a/pkg/morph/client/container/eacl.go +++ b/pkg/morph/client/container/eacl.go @@ -77,14 +77,14 @@ func (c *Client) GetEACL(cnr *cid.ID) (*eacl.Table, error) { } if len(binToken) > 0 { - tok := session.NewToken() + var tok session.Container err = tok.Unmarshal(binToken) if err != nil { return nil, fmt.Errorf("could not unmarshal session token: %w", err) } - table.SetSessionToken(tok) + table.SetSessionToken(&tok) } // FIXME(@cthulhu-rider): #1387 temp solution, later table structure won't have a signature diff --git a/pkg/morph/client/container/eacl_set.go b/pkg/morph/client/container/eacl_set.go index c2352006b..ffaf85c8a 100644 --- a/pkg/morph/client/container/eacl_set.go +++ b/pkg/morph/client/container/eacl_set.go @@ -24,14 +24,12 @@ func PutEACL(c *Client, table *eacl.Table) error { return fmt.Errorf("can't marshal eacl table: %w", err) } - binToken, err := table.SessionToken().Marshal() - if err != nil { - return fmt.Errorf("could not marshal session token: %w", err) - } - var prm PutEACLPrm prm.SetTable(data) - prm.SetToken(binToken) + + if tok := table.SessionToken(); tok != nil { + prm.SetToken(tok.Marshal()) + } if sig := table.Signature(); sig != nil { // TODO(@cthulhu-rider): #1387 implement and use another approach to avoid conversion diff --git a/pkg/morph/client/container/get.go b/pkg/morph/client/container/get.go index 06f920951..b6fbb82b6 100644 --- a/pkg/morph/client/container/get.go +++ b/pkg/morph/client/container/get.go @@ -96,14 +96,14 @@ func (c *Client) Get(cid []byte) (*container.Container, error) { } if len(tokBytes) > 0 { - tok := session.NewToken() + var tok session.Container err = tok.Unmarshal(tokBytes) if err != nil { return nil, fmt.Errorf("could not unmarshal session token: %w", err) } - cnr.SetSessionToken(tok) + cnr.SetSessionToken(&tok) } // FIXME(@cthulhu-rider): #1387 temp solution, later table structure won't have a signature diff --git a/pkg/morph/client/container/put.go b/pkg/morph/client/container/put.go index d463f4826..fad512ce9 100644 --- a/pkg/morph/client/container/put.go +++ b/pkg/morph/client/container/put.go @@ -24,19 +24,17 @@ func Put(c *Client, cnr *container.Container) (*cid.ID, error) { return nil, fmt.Errorf("can't marshal container: %w", err) } - binToken, err := cnr.SessionToken().Marshal() - if err != nil { - return nil, fmt.Errorf("could not marshal session token: %w", err) - } - name, zone := container.GetNativeNameWithZone(cnr) var prm PutPrm prm.SetContainer(data) - prm.SetToken(binToken) prm.SetName(name) prm.SetZone(zone) + if tok := cnr.SessionToken(); tok != nil { + prm.SetToken(tok.Marshal()) + } + if sig := cnr.Signature(); sig != nil { // TODO(@cthulhu-rider): #1387 implement and use another approach to avoid conversion var sigV2 refs.Signature diff --git a/pkg/services/container/morph/executor.go b/pkg/services/container/morph/executor.go index cefc3cfb9..884e08875 100644 --- a/pkg/services/container/morph/executor.go +++ b/pkg/services/container/morph/executor.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neofs-api-go/v2/container" "github.com/nspcc-dev/neofs-api-go/v2/refs" + sessionV2 "github.com/nspcc-dev/neofs-api-go/v2/session" containercore "github.com/nspcc-dev/neofs-node/pkg/core/container" containerSvc "github.com/nspcc-dev/neofs-node/pkg/services/container" "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl" @@ -44,10 +45,6 @@ type Writer interface { PutEACL(*eaclSDK.Table) error } -// ErrInvalidContext is thrown by morph ServiceExecutor when provided session -// token does not contain expected container context. -var ErrInvalidContext = errors.New("session token does not contain container context") - func NewExecutor(rdr Reader, wrt Writer) containerSvc.ServiceExecutor { return &morphExecutor{ rdr: rdr, @@ -69,12 +66,16 @@ func (s *morphExecutor) Put(ctx containerSvc.ContextWithToken, body *container.P cnr.SetSignature(&sig) - tok := session.NewTokenFromV2(ctx.SessionToken) - if ctx.SessionToken != nil && session.GetContainerContext(tok) == nil { - return nil, ErrInvalidContext - } + if ctx.SessionToken != nil { + var tok session.Container - cnr.SetSessionToken(tok) + err := tok.ReadFromV2(*ctx.SessionToken) + if err != nil { + return nil, fmt.Errorf("invalid session token: %w", err) + } + + cnr.SetSessionToken(&tok) + } idCnr, err := s.wrt.Put(cnr) if err != nil { @@ -105,9 +106,15 @@ func (s *morphExecutor) Delete(ctx containerSvc.ContextWithToken, body *containe sig := body.GetSignature().GetSign() - tok := session.NewTokenFromV2(ctx.SessionToken) - if ctx.SessionToken != nil && session.GetContainerContext(tok) == nil { - return nil, ErrInvalidContext + var tok *session.Container + + if ctx.SessionToken != nil { + tok = new(session.Container) + + err := tok.ReadFromV2(*ctx.SessionToken) + if err != nil { + return nil, fmt.Errorf("invalid session token: %w", err) + } } var rmWitness containercore.RemovalWitness @@ -149,10 +156,18 @@ func (s *morphExecutor) Get(ctx context.Context, body *container.GetRequestBody) sig.WriteToV2(sigV2) } + var tokV2 *sessionV2.Token + + if tok := cnr.SessionToken(); tok != nil { + tokV2 = new(sessionV2.Token) + + tok.WriteToV2(tokV2) + } + res := new(container.GetResponseBody) res.SetContainer(cnr.ToV2()) res.SetSignature(sigV2) - res.SetSessionToken(cnr.SessionToken().ToV2()) + res.SetSessionToken(tokV2) return res, nil } @@ -200,12 +215,16 @@ func (s *morphExecutor) SetExtendedACL(ctx containerSvc.ContextWithToken, body * table.SetSignature(&sig) - tok := session.NewTokenFromV2(ctx.SessionToken) - if ctx.SessionToken != nil && session.GetContainerContext(tok) == nil { - return nil, ErrInvalidContext - } + if ctx.SessionToken != nil { + var tok session.Container - table.SetSessionToken(tok) + err := tok.ReadFromV2(*ctx.SessionToken) + if err != nil { + return nil, fmt.Errorf("invalid session token: %w", err) + } + + table.SetSessionToken(&tok) + } err := s.wrt.PutEACL(table) if err != nil { @@ -240,10 +259,18 @@ func (s *morphExecutor) GetExtendedACL(ctx context.Context, body *container.GetE sig.WriteToV2(sigV2) } + var tokV2 *sessionV2.Token + + if tok := table.SessionToken(); tok != nil { + tokV2 = new(sessionV2.Token) + + tok.WriteToV2(tokV2) + } + res := new(container.GetExtendedACLResponseBody) res.SetEACL(table.ToV2()) res.SetSignature(sigV2) - res.SetSessionToken(table.SessionToken().ToV2()) + res.SetSessionToken(tokV2) return res, nil } diff --git a/pkg/services/container/morph/executor_test.go b/pkg/services/container/morph/executor_test.go index dabebe535..08e936d2c 100644 --- a/pkg/services/container/morph/executor_test.go +++ b/pkg/services/container/morph/executor_test.go @@ -14,6 +14,7 @@ import ( cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" "github.com/nspcc-dev/neofs-sdk-go/eacl" + sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" "github.com/stretchr/testify/require" ) @@ -42,6 +43,9 @@ func TestInvalidToken(t *testing.T) { var cnrV2 refs.ContainerID cnr.WriteToV2(&cnrV2) + var tokV2 session.Token + sessiontest.ContainerSigned().WriteToV2(&tokV2) + tests := []struct { name string op func(e containerSvc.ServiceExecutor, ctx containerSvc.ContextWithToken) error @@ -84,9 +88,9 @@ func TestInvalidToken(t *testing.T) { Context: context.Background(), SessionToken: generateToken(new(session.ObjectSessionContext)), } - require.Error(t, test.op(e, ctx), containerSvcMorph.ErrInvalidContext) + require.Error(t, test.op(e, ctx)) - ctx.SessionToken = generateToken(new(session.ContainerSessionContext)) + ctx.SessionToken = &tokV2 require.NoError(t, test.op(e, ctx)) ctx.SessionToken = nil diff --git a/pkg/services/object/acl/eacl/v2/headers.go b/pkg/services/object/acl/eacl/v2/headers.go index 8d0d71e1b..51eb109f3 100644 --- a/pkg/services/object/acl/eacl/v2/headers.go +++ b/pkg/services/object/acl/eacl/v2/headers.go @@ -84,6 +84,16 @@ func (h headerSource) HeadersOfType(typ eaclSDK.FilterHeaderType) ([]eaclSDK.Hea } } +type xHeader session.XHeader + +func (x xHeader) Key() string { + return (*session.XHeader)(&x).GetKey() +} + +func (x xHeader) Value() string { + return (*session.XHeader)(&x).GetValue() +} + func requestHeaders(msg xHeaderSource) []eaclSDK.Header { return msg.GetXHeaders() } diff --git a/pkg/services/object/acl/eacl/v2/xheader.go b/pkg/services/object/acl/eacl/v2/xheader.go index cda629b20..aa6f5f9da 100644 --- a/pkg/services/object/acl/eacl/v2/xheader.go +++ b/pkg/services/object/acl/eacl/v2/xheader.go @@ -1,8 +1,8 @@ package v2 import ( + "github.com/nspcc-dev/neofs-api-go/v2/session" eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl" - sessionSDK "github.com/nspcc-dev/neofs-sdk-go/session" ) type xHeaderSource interface { @@ -30,7 +30,7 @@ func (s requestXHeaderSource) GetXHeaders() []eaclSDK.Header { for meta := s.req.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() { x := meta.GetXHeaders() for i := range x { - res = append(res, sessionSDK.NewXHeaderFromV2(&x[i])) + res = append(res, (xHeader)(x[i])) } } @@ -39,16 +39,21 @@ func (s requestXHeaderSource) GetXHeaders() []eaclSDK.Header { func (s responseXHeaderSource) GetXHeaders() []eaclSDK.Header { ln := 0 + xHdrs := make([][]session.XHeader, 0) for meta := s.req.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() { - ln += len(meta.GetXHeaders()) + x := meta.GetXHeaders() + + ln += len(x) + + xHdrs = append(xHdrs, x) } res := make([]eaclSDK.Header, 0, ln) - for meta := s.req.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() { - x := meta.GetXHeaders() - for i := range x { - res = append(res, sessionSDK.NewXHeaderFromV2(&x[i])) + + for i := range xHdrs { + for j := range xHdrs[i] { + res = append(res, xHeader(xHdrs[i][j])) } } diff --git a/pkg/services/object/acl/v2/request.go b/pkg/services/object/acl/v2/request.go index 47ba38c94..3f8cce755 100644 --- a/pkg/services/object/acl/v2/request.go +++ b/pkg/services/object/acl/v2/request.go @@ -107,7 +107,7 @@ func (r RequestInfo) RequestRole() eaclSDK.Role { // verification header and raw API request. type MetaWithToken struct { vheader *sessionV2.RequestVerificationHeader - token *sessionSDK.Token + token *sessionSDK.Object bearer *bearer.Token src interface{} } diff --git a/pkg/services/object/acl/v2/service.go b/pkg/services/object/acl/v2/service.go index de97d5210..36c6073ef 100644 --- a/pkg/services/object/acl/v2/service.go +++ b/pkg/services/object/acl/v2/service.go @@ -113,7 +113,10 @@ func (b Service) Get(request *objectV2.GetRequest, stream object.GetObjectStream return err } - sTok := originalSessionToken(request.GetMetaHeader()) + sTok, err := originalSessionToken(request.GetMetaHeader()) + if err != nil { + return err + } req := MetaWithToken{ vheader: request.GetVerificationHeader(), @@ -164,7 +167,10 @@ func (b Service) Head( return nil, err } - sTok := originalSessionToken(request.GetMetaHeader()) + sTok, err := originalSessionToken(request.GetMetaHeader()) + if err != nil { + return nil, err + } req := MetaWithToken{ vheader: request.GetVerificationHeader(), @@ -207,9 +213,14 @@ func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStr return err } + sTok, err := originalSessionToken(request.GetMetaHeader()) + if err != nil { + return err + } + req := MetaWithToken{ vheader: request.GetVerificationHeader(), - token: originalSessionToken(request.GetMetaHeader()), + token: sTok, bearer: originalBearerToken(request.GetMetaHeader()), src: request, } @@ -245,7 +256,10 @@ func (b Service) Delete( return nil, err } - sTok := originalSessionToken(request.GetMetaHeader()) + sTok, err := originalSessionToken(request.GetMetaHeader()) + if err != nil { + return nil, err + } req := MetaWithToken{ vheader: request.GetVerificationHeader(), @@ -281,7 +295,10 @@ func (b Service) GetRange(request *objectV2.GetRangeRequest, stream object.GetOb return err } - sTok := originalSessionToken(request.GetMetaHeader()) + sTok, err := originalSessionToken(request.GetMetaHeader()) + if err != nil { + return err + } req := MetaWithToken{ vheader: request.GetVerificationHeader(), @@ -322,7 +339,10 @@ func (b Service) GetRangeHash( return nil, err } - sTok := originalSessionToken(request.GetMetaHeader()) + sTok, err := originalSessionToken(request.GetMetaHeader()) + if err != nil { + return nil, err + } req := MetaWithToken{ vheader: request.GetVerificationHeader(), @@ -377,7 +397,16 @@ func (p putStreamBasicChecker) Send(request *objectV2.PutRequest) error { return fmt.Errorf("invalid object owner: %w", err) } - sTok := sessionSDK.NewTokenFromV2(request.GetMetaHeader().GetSessionToken()) + var sTok *sessionSDK.Object + + if tokV2 := request.GetMetaHeader().GetSessionToken(); tokV2 != nil { + sTok = new(sessionSDK.Object) + + err = sTok.ReadFromV2(*tokV2) + if err != nil { + return fmt.Errorf("invalid session token: %w", err) + } + } req := MetaWithToken{ vheader: request.GetVerificationHeader(), @@ -449,14 +478,18 @@ func (b Service) findRequestInfo( return info, errors.New("missing owner in container descriptor") } - if req.token != nil && req.token.Exp() != 0 { + if req.token != nil { currentEpoch, err := b.nm.Epoch() if err != nil { return info, errors.New("can't fetch current epoch") } - if req.token.Exp() < currentEpoch { - return info, fmt.Errorf("%w: token has expired (current epoch: %d, expired at %d)", - ErrMalformedRequest, currentEpoch, req.token.Exp()) + if req.token.ExpiredAt(currentEpoch) { + return info, fmt.Errorf("%w: token has expired (current epoch: %d)", + ErrMalformedRequest, currentEpoch) + } + + if !assertVerb(*req.token, op) { + return info, ErrInvalidVerb } } @@ -470,16 +503,10 @@ func (b Service) findRequestInfo( return info, ErrUnknownRole } - // find verb from token if it is present - verb, isUnknown := sourceVerbOfRequest(req.token, op) - if !isUnknown && verb != op && !isVerbCompatible(verb, op) { - return info, ErrInvalidVerb - } - info.basicACL = cnr.BasicACL() info.requestRole = res.role info.isInnerRing = res.isIR - info.operation = verb + info.operation = op info.cnrOwner = cnr.OwnerID() info.idCnr = cid diff --git a/pkg/services/object/acl/v2/util.go b/pkg/services/object/acl/v2/util.go index 122e94be6..a8b1cae52 100644 --- a/pkg/services/object/acl/v2/util.go +++ b/pkg/services/object/acl/v2/util.go @@ -75,12 +75,24 @@ func originalBearerToken(header *sessionV2.RequestMetaHeader) *bearer.Token { // originalSessionToken goes down to original request meta header and fetches // session token from there. -func originalSessionToken(header *sessionV2.RequestMetaHeader) *sessionSDK.Token { +func originalSessionToken(header *sessionV2.RequestMetaHeader) (*sessionSDK.Object, error) { for header.GetOrigin() != nil { header = header.GetOrigin() } - return sessionSDK.NewTokenFromV2(header.GetSessionToken()) + tokV2 := header.GetSessionToken() + if tokV2 == nil { + return nil, nil + } + + var tok sessionSDK.Object + + err := tok.ReadFromV2(*tokV2) + if err != nil { + return nil, fmt.Errorf("invalid session token: %w", err) + } + + return &tok, nil } func getObjectIDFromRequestBody(body interface{}) (*oidSDK.ID, error) { @@ -113,58 +125,35 @@ func getObjectIDFromRequestBody(body interface{}) (*oidSDK.ID, error) { return &id, nil } -// sourceVerbOfRequest looks for verb in session token and if it is not found, -// returns reqVerb. Second return value is true if operation is unknown. -func sourceVerbOfRequest(tok *sessionSDK.Token, reqVerb eaclSDK.Operation) (eaclSDK.Operation, bool) { - ctx, ok := tok.Context().(*sessionSDK.ObjectContext) - if ok { - op := tokenVerbToOperation(ctx) - if op != eaclSDK.OperationUnknown { - return op, false - } - } - - return reqVerb, true -} - -func useObjectIDFromSession(req *RequestInfo, token *sessionSDK.Token) { +func useObjectIDFromSession(req *RequestInfo, token *sessionSDK.Object) { if token == nil { return } - objCtx, ok := token.Context().(*sessionSDK.ObjectContext) + // TODO(@cthulhu-rider): It'd be nice to not pull object identifiers from + // the token, but assert them. Track #1420 + var tokV2 sessionV2.Token + token.WriteToV2(&tokV2) + + ctx, ok := tokV2.GetBody().GetContext().(*sessionV2.ObjectSessionContext) if !ok { + panic(fmt.Sprintf("wrong object session context %T, is it verified?", tokV2.GetBody().GetContext())) + } + + idV2 := ctx.GetAddress().GetObjectID() + if idV2 == nil { return } - id, ok := objCtx.Address().ObjectID() - if ok { - req.oid = &id + req.oid = new(oidSDK.ID) + + err := req.oid.ReadFromV2(*idV2) + if err != nil { + panic(fmt.Sprintf("unexpected protocol violation error after correct session token decoding: %v", err)) } } -func tokenVerbToOperation(ctx *sessionSDK.ObjectContext) eaclSDK.Operation { - switch { - case ctx.IsForGet(): - return eaclSDK.OperationGet - case ctx.IsForPut(): - return eaclSDK.OperationPut - case ctx.IsForHead(): - return eaclSDK.OperationHead - case ctx.IsForSearch(): - return eaclSDK.OperationSearch - case ctx.IsForDelete(): - return eaclSDK.OperationDelete - case ctx.IsForRange(): - return eaclSDK.OperationRange - case ctx.IsForRangeHash(): - return eaclSDK.OperationRangeHash - default: - return eaclSDK.OperationUnknown - } -} - -func ownerFromToken(token *sessionSDK.Token) (*user.ID, *keys.PublicKey, error) { +func ownerFromToken(token *sessionSDK.Object) (*user.ID, *keys.PublicKey, error) { // 1. First check signature of session token. if !token.VerifySignature() { return nil, nil, fmt.Errorf("%w: invalid session token signature", ErrMalformedRequest) @@ -172,21 +161,32 @@ func ownerFromToken(token *sessionSDK.Token) (*user.ID, *keys.PublicKey, error) // 2. Then check if session token owner issued the session token // TODO(@cthulhu-rider): #1387 implement and use another approach to avoid conversion - tokV2 := token.ToV2() + var tokV2 sessionV2.Token + token.WriteToV2(&tokV2) + + ownerSessionV2 := tokV2.GetBody().GetOwnerID() + if ownerSessionV2 == nil { + return nil, nil, errors.New("missing session owner") + } + + var ownerSession user.ID + + err := ownerSession.ReadFromV2(*ownerSessionV2) + if err != nil { + return nil, nil, fmt.Errorf("invalid session token: %w", err) + } tokenIssuerKey, err := unmarshalPublicKey(tokV2.GetSignature().GetKey()) if err != nil { return nil, nil, fmt.Errorf("invalid key in session token signature: %w", err) } - tokenOwner := token.OwnerID() - - if !isOwnerFromKey(tokenOwner, tokenIssuerKey) { + if !isOwnerFromKey(&ownerSession, tokenIssuerKey) { // TODO: #767 in this case we can issue all owner keys from neofs.id and check once again return nil, nil, fmt.Errorf("%w: invalid session token owner", ErrMalformedRequest) } - return tokenOwner, tokenIssuerKey, nil + return &ownerSession, tokenIssuerKey, nil } func originalBodySignature(v *sessionV2.RequestVerificationHeader) *refsV2.Signature { @@ -216,17 +216,30 @@ func isOwnerFromKey(id *user.ID, key *keys.PublicKey) bool { return id2.Equals(*id) } -// isVerbCompatible checks that tokenVerb operation can create auxiliary op operation. -func isVerbCompatible(tokenVerb, op eaclSDK.Operation) bool { - switch tokenVerb { - case eaclSDK.OperationGet: - return op == eaclSDK.OperationGet || op == eaclSDK.OperationHead +// assertVerb checks that token verb corresponds to op. +func assertVerb(tok sessionSDK.Object, op eaclSDK.Operation) bool { + //nolint:exhaustive + switch op { + case eaclSDK.OperationPut: + return tok.AssertVerb(sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete) case eaclSDK.OperationDelete: - return op == eaclSDK.OperationPut || op == eaclSDK.OperationHead || - op == eaclSDK.OperationSearch - case eaclSDK.OperationRange, eaclSDK.OperationRangeHash: - return op == eaclSDK.OperationRange || op == eaclSDK.OperationHead - default: - return tokenVerb == op + return tok.AssertVerb(sessionSDK.VerbObjectDelete) + case eaclSDK.OperationGet: + return tok.AssertVerb(sessionSDK.VerbObjectGet) + case eaclSDK.OperationHead: + return tok.AssertVerb( + sessionSDK.VerbObjectHead, + sessionSDK.VerbObjectGet, + sessionSDK.VerbObjectDelete, + sessionSDK.VerbObjectRange, + sessionSDK.VerbObjectRangeHash) + case eaclSDK.OperationSearch: + return tok.AssertVerb(sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete) + case eaclSDK.OperationRange: + return tok.AssertVerb(sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash) + case eaclSDK.OperationRangeHash: + return tok.AssertVerb(sessionSDK.VerbObjectRangeHash) } + + return false } diff --git a/pkg/services/object/acl/v2/util_test.go b/pkg/services/object/acl/v2/util_test.go index 2095f6993..2ad72e2be 100644 --- a/pkg/services/object/acl/v2/util_test.go +++ b/pkg/services/object/acl/v2/util_test.go @@ -6,23 +6,28 @@ import ( "github.com/nspcc-dev/neofs-api-go/v2/acl" acltest "github.com/nspcc-dev/neofs-api-go/v2/acl/test" "github.com/nspcc-dev/neofs-api-go/v2/session" - sessiontest "github.com/nspcc-dev/neofs-api-go/v2/session/test" "github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/eacl" sessionSDK "github.com/nspcc-dev/neofs-sdk-go/session" + sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" "github.com/stretchr/testify/require" ) func TestOriginalTokens(t *testing.T) { - sToken := sessiontest.GenerateSessionToken(false) + sToken := sessiontest.ObjectSigned() bTokenV2 := acltest.GenerateBearerToken(false) var bToken bearer.Token bToken.ReadFromV2(*bTokenV2) + var sTokenV2 session.Token + sToken.WriteToV2(&sTokenV2) + for i := 0; i < 10; i++ { - metaHeaders := testGenerateMetaHeader(uint32(i), bTokenV2, sToken) - require.Equal(t, sessionSDK.NewTokenFromV2(sToken), originalSessionToken(metaHeaders), i) + metaHeaders := testGenerateMetaHeader(uint32(i), bTokenV2, &sTokenV2) + res, err := originalSessionToken(metaHeaders) + require.NoError(t, err) + require.Equal(t, sToken, res, i) require.Equal(t, &bToken, originalBearerToken(metaHeaders), i) } } @@ -43,38 +48,48 @@ func testGenerateMetaHeader(depth uint32, b *acl.BearerToken, s *session.Token) func TestIsVerbCompatible(t *testing.T) { // Source: https://nspcc.ru/upload/neofs-spec-latest.pdf#page=28 - table := map[eacl.Operation][]eacl.Operation{ - eacl.OperationPut: {eacl.OperationPut}, - eacl.OperationDelete: {eacl.OperationPut, eacl.OperationHead, eacl.OperationSearch}, - eacl.OperationHead: {eacl.OperationHead}, - eacl.OperationRange: {eacl.OperationRange, eacl.OperationHead}, - eacl.OperationRangeHash: {eacl.OperationRange, eacl.OperationHead}, - eacl.OperationGet: {eacl.OperationGet, eacl.OperationHead}, - eacl.OperationSearch: {eacl.OperationSearch}, + table := map[eacl.Operation][]sessionSDK.ObjectVerb{ + eacl.OperationPut: {sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete}, + eacl.OperationDelete: {sessionSDK.VerbObjectDelete}, + eacl.OperationGet: {sessionSDK.VerbObjectGet}, + eacl.OperationHead: { + sessionSDK.VerbObjectHead, + sessionSDK.VerbObjectGet, + sessionSDK.VerbObjectDelete, + sessionSDK.VerbObjectRange, + sessionSDK.VerbObjectRangeHash, + }, + eacl.OperationRange: {sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash}, + eacl.OperationRangeHash: {sessionSDK.VerbObjectRangeHash}, + eacl.OperationSearch: {sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete}, } - ops := []eacl.Operation{ - eacl.OperationPut, - eacl.OperationDelete, - eacl.OperationHead, - eacl.OperationRange, - eacl.OperationRangeHash, - eacl.OperationGet, - eacl.OperationSearch, + verbs := []sessionSDK.ObjectVerb{ + sessionSDK.VerbObjectPut, + sessionSDK.VerbObjectDelete, + sessionSDK.VerbObjectHead, + sessionSDK.VerbObjectRange, + sessionSDK.VerbObjectRangeHash, + sessionSDK.VerbObjectGet, + sessionSDK.VerbObjectSearch, } - for _, opToken := range ops { - for _, op := range ops { + var tok sessionSDK.Object + + for op, list := range table { + for _, verb := range verbs { var contains bool - for _, o := range table[opToken] { - if o == op { + for _, v := range list { + if v == verb { contains = true break } } - require.Equal(t, contains, isVerbCompatible(opToken, op), - "%s in token, %s executing", opToken, op) + tok.ForVerb(verb) + + require.Equal(t, contains, assertVerb(tok, op), + "%v in token, %s executing", verb, op) } } } diff --git a/pkg/services/object/delete/exec.go b/pkg/services/object/delete/exec.go index bebc2f18b..1acb3d854 100644 --- a/pkg/services/object/delete/exec.go +++ b/pkg/services/object/delete/exec.go @@ -243,15 +243,15 @@ func (exec *execCtx) initTombstoneObject() bool { return false } - tombOwnerID := exec.commonParameters().SessionToken().OwnerID() - if tombOwnerID == nil { + tombOwnerID, ok := exec.commonParameters().SessionOwner() + if !ok { // make local node a tombstone object owner - tombOwnerID = exec.svc.netInfo.LocalNodeID() + tombOwnerID = *exec.svc.netInfo.LocalNodeID() } exec.tombstoneObj = object.New() exec.tombstoneObj.SetContainerID(*exec.containerID()) - exec.tombstoneObj.SetOwnerID(tombOwnerID) + exec.tombstoneObj.SetOwnerID(&tombOwnerID) exec.tombstoneObj.SetType(object.TypeTombstone) exec.tombstoneObj.SetPayload(payload) diff --git a/pkg/services/object/get/exec.go b/pkg/services/object/get/exec.go index 1cc753343..6a7c7afa6 100644 --- a/pkg/services/object/get/exec.go +++ b/pkg/services/object/get/exec.go @@ -7,6 +7,7 @@ import ( clientcore "github.com/nspcc-dev/neofs-node/pkg/core/client" "github.com/nspcc-dev/neofs-node/pkg/core/object" + "github.com/nspcc-dev/neofs-node/pkg/services/object/util" "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/placement" "github.com/nspcc-dev/neofs-node/pkg/util/logger" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" @@ -105,7 +106,18 @@ func (exec execCtx) isChild(obj *objectSDK.Object) bool { } func (exec execCtx) key() (*ecdsa.PrivateKey, error) { - return exec.svc.keyStore.GetKey(exec.prm.common.SessionToken()) + var sessionInfo *util.SessionInfo + + if tok := exec.prm.common.SessionToken(); tok != nil { + ownerSession, _ := exec.prm.common.SessionOwner() + + sessionInfo = &util.SessionInfo{ + ID: tok.ID(), + Owner: ownerSession, + } + } + + return exec.svc.keyStore.GetKey(sessionInfo) } func (exec *execCtx) canAssemble() bool { diff --git a/pkg/services/object/get/service.go b/pkg/services/object/get/service.go index 6f6b37864..61924627e 100644 --- a/pkg/services/object/get/service.go +++ b/pkg/services/object/get/service.go @@ -1,8 +1,6 @@ package getsvc import ( - "crypto/ecdsa" - "github.com/nspcc-dev/neofs-node/pkg/core/client" "github.com/nspcc-dev/neofs-node/pkg/core/netmap" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine" @@ -11,7 +9,6 @@ import ( "github.com/nspcc-dev/neofs-node/pkg/util/logger" "github.com/nspcc-dev/neofs-sdk-go/object" addressSDK "github.com/nspcc-dev/neofs-sdk-go/object/address" - "github.com/nspcc-dev/neofs-sdk-go/session" "go.uber.org/zap" ) @@ -48,9 +45,7 @@ type cfg struct { currentEpoch() (uint64, error) } - keyStore interface { - GetKey(token *session.Token) (*ecdsa.PrivateKey, error) - } + keyStore *util.KeyStorage } func defaultCfg() *cfg { diff --git a/pkg/services/object/internal/client/client.go b/pkg/services/object/internal/client/client.go index e43c8c15c..7326a0550 100644 --- a/pkg/services/object/internal/client/client.go +++ b/pkg/services/object/internal/client/client.go @@ -25,13 +25,13 @@ type commonPrm struct { key *ecdsa.PrivateKey - tokenSession *session.Token + tokenSession *session.Object tokenBearer *bearer.Token local bool - xHeaders []*session.XHeader + xHeaders []string } // SetClient sets base client for NeoFS API communication. @@ -58,7 +58,7 @@ func (x *commonPrm) SetPrivateKey(key *ecdsa.PrivateKey) { // SetSessionToken sets token of the session within which request should be sent. // // By default the request will be sent outside the session. -func (x *commonPrm) SetSessionToken(tok *session.Token) { +func (x *commonPrm) SetSessionToken(tok *session.Object) { x.tokenSession = tok } @@ -77,23 +77,10 @@ func (x *commonPrm) SetTTL(ttl uint32) { // SetXHeaders sets request X-Headers. // // By default X-Headers will not be attached to the request. -func (x *commonPrm) SetXHeaders(hs []*session.XHeader) { +func (x *commonPrm) SetXHeaders(hs []string) { x.xHeaders = hs } -func (x commonPrm) xHeadersPrm() (res []string) { - if len(x.xHeaders) > 0 { - res = make([]string, len(x.xHeaders)*2) - - for i := range x.xHeaders { - res[2*i] = x.xHeaders[i].Key() - res[2*i+1] = x.xHeaders[i].Value() - } - } - - return -} - type readPrmCommon struct { commonPrm } @@ -163,7 +150,7 @@ func GetObject(prm GetObjectPrm) (*GetObjectRes, error) { prm.cliPrm.MarkLocal() } - prm.cliPrm.WithXHeaders(prm.xHeadersPrm()...) + prm.cliPrm.WithXHeaders(prm.xHeaders...) rdr, err := prm.cli.ObjectGetInit(prm.ctx, prm.cliPrm) if err != nil { @@ -258,7 +245,7 @@ func HeadObject(prm HeadObjectPrm) (*HeadObjectRes, error) { prm.cliPrm.WithBearerToken(*prm.tokenBearer) } - prm.cliPrm.WithXHeaders(prm.xHeadersPrm()...) + prm.cliPrm.WithXHeaders(prm.xHeaders...) cliRes, err := prm.cli.ObjectHead(prm.ctx, prm.cliPrm) if err == nil { @@ -350,7 +337,7 @@ func PayloadRange(prm PayloadRangePrm) (*PayloadRangeRes, error) { } prm.cliPrm.SetLength(prm.ln) - prm.cliPrm.WithXHeaders(prm.xHeadersPrm()...) + prm.cliPrm.WithXHeaders(prm.xHeaders...) rdr, err := prm.cli.ObjectRangeInit(prm.ctx, prm.cliPrm) if err != nil { @@ -420,7 +407,7 @@ func PutObject(prm PutObjectPrm) (*PutObjectRes, error) { w.WithBearerToken(*prm.tokenBearer) } - w.WithXHeaders(prm.xHeadersPrm()...) + w.WithXHeaders(prm.xHeaders...) if w.WriteHeader(*prm.obj) { w.WritePayloadChunk(prm.obj.Payload()) @@ -492,7 +479,7 @@ func SearchObjects(prm SearchObjectsPrm) (*SearchObjectsRes, error) { prm.cliPrm.WithBearerToken(*prm.tokenBearer) } - prm.cliPrm.WithXHeaders(prm.xHeadersPrm()...) + prm.cliPrm.WithXHeaders(prm.xHeaders...) rdr, err := prm.cli.ObjectSearchInit(prm.ctx, prm.cliPrm) if err != nil { diff --git a/pkg/services/object/put/remote.go b/pkg/services/object/put/remote.go index 1a9d964da..ce67034f2 100644 --- a/pkg/services/object/put/remote.go +++ b/pkg/services/object/put/remote.go @@ -48,7 +48,18 @@ func (t *remoteTarget) WriteHeader(obj *object.Object) error { } func (t *remoteTarget) Close() (*transformer.AccessIdentifiers, error) { - key, err := t.keyStorage.GetKey(t.commonPrm.SessionToken()) + var sessionInfo *util.SessionInfo + + if tok := t.commonPrm.SessionToken(); tok != nil { + ownerSession, _ := t.commonPrm.SessionOwner() + + sessionInfo = &util.SessionInfo{ + ID: tok.ID(), + Owner: ownerSession, + } + } + + key, err := t.keyStorage.GetKey(sessionInfo) if err != nil { return nil, fmt.Errorf("(%T) could not receive private key: %w", t, err) } diff --git a/pkg/services/object/put/streamer.go b/pkg/services/object/put/streamer.go index 42a09ee7c..c44843bb5 100644 --- a/pkg/services/object/put/streamer.go +++ b/pkg/services/object/put/streamer.go @@ -84,7 +84,18 @@ func (p *Streamer) initTarget(prm *PutInitPrm) error { // prepare trusted-Put object target // get private token from local storage - sessionKey, err := p.keyStorage.GetKey(sToken) + var sessionInfo *util.SessionInfo + + if sToken != nil { + ownerSession, _ := prm.common.SessionOwner() + + sessionInfo = &util.SessionInfo{ + ID: sToken.ID(), + Owner: ownerSession, + } + } + + sessionKey, err := p.keyStorage.GetKey(sessionInfo) if err != nil { return fmt.Errorf("(%T) could not receive session key: %w", p, err) } diff --git a/pkg/services/object/search/service.go b/pkg/services/object/search/service.go index 9aa8993d1..cf4d143ee 100644 --- a/pkg/services/object/search/service.go +++ b/pkg/services/object/search/service.go @@ -1,8 +1,6 @@ package searchsvc import ( - "crypto/ecdsa" - "github.com/nspcc-dev/neofs-node/pkg/core/client" "github.com/nspcc-dev/neofs-node/pkg/core/netmap" "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine" @@ -11,7 +9,6 @@ import ( "github.com/nspcc-dev/neofs-node/pkg/util/logger" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oidSDK "github.com/nspcc-dev/neofs-sdk-go/object/id" - "github.com/nspcc-dev/neofs-sdk-go/session" "go.uber.org/zap" ) @@ -51,9 +48,7 @@ type cfg struct { currentEpoch() (uint64, error) } - keyStore interface { - GetKey(token *session.Token) (*ecdsa.PrivateKey, error) - } + keyStore *util.KeyStorage } func defaultCfg() *cfg { diff --git a/pkg/services/object/search/util.go b/pkg/services/object/search/util.go index 6d95f2e37..eafa8f75e 100644 --- a/pkg/services/object/search/util.go +++ b/pkg/services/object/search/util.go @@ -85,7 +85,18 @@ func (c *clientWrapper) searchObjects(exec *execCtx, info client.NodeInfo) ([]oi return exec.prm.forwarder(info, c.client) } - key, err := exec.svc.keyStore.GetKey(exec.prm.common.SessionToken()) + var sessionInfo *util.SessionInfo + + if tok := exec.prm.common.SessionToken(); tok != nil { + ownerSession, _ := exec.prm.common.SessionOwner() + + sessionInfo = &util.SessionInfo{ + ID: tok.ID(), + Owner: ownerSession, + } + } + + key, err := exec.svc.keyStore.GetKey(sessionInfo) if err != nil { return nil, err } diff --git a/pkg/services/object/util/key.go b/pkg/services/object/util/key.go index 93c324bbd..32849ec8e 100644 --- a/pkg/services/object/util/key.go +++ b/pkg/services/object/util/key.go @@ -2,11 +2,12 @@ package util import ( "crypto/ecdsa" + "fmt" + "github.com/google/uuid" "github.com/nspcc-dev/neofs-node/pkg/core/netmap" "github.com/nspcc-dev/neofs-node/pkg/services/session/storage" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" - "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/user" ) @@ -40,13 +41,28 @@ func NewKeyStorage(localKey *ecdsa.PrivateKey, tokenStore SessionSource, net net } } +// SessionInfo groups information about NeoFS Object session +// which is reflected in KeyStorage. +type SessionInfo struct { + // Session unique identifier. + ID uuid.UUID + + // Session issuer. + Owner user.ID +} + // GetKey returns private key of the node. // // If token is not nil, session private key is returned. // Otherwise, node private key is returned. -func (s *KeyStorage) GetKey(token *session.Token) (*ecdsa.PrivateKey, error) { - if token != nil { - pToken := s.tokenStore.Get(token.OwnerID(), token.ID()) +func (s *KeyStorage) GetKey(info *SessionInfo) (*ecdsa.PrivateKey, error) { + if info != nil { + binID, err := info.ID.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("marshal ID: %w", err) + } + + pToken := s.tokenStore.Get(&info.Owner, binID) if pToken != nil { if pToken.ExpiredAt() <= s.networkState.CurrentEpoch() { var errExpired apistatus.SessionTokenExpired diff --git a/pkg/services/object/util/key_test.go b/pkg/services/object/util/key_test.go index 528a3031c..6584b8a0d 100644 --- a/pkg/services/object/util/key_test.go +++ b/pkg/services/object/util/key_test.go @@ -11,7 +11,9 @@ import ( sessionV2 "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-node/pkg/services/object/util" tokenStorage "github.com/nspcc-dev/neofs-node/pkg/services/session/storage/temporary" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/session" + "github.com/nspcc-dev/neofs-sdk-go/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" "github.com/stretchr/testify/require" ) @@ -23,6 +25,8 @@ func TestNewKeyStorage(t *testing.T) { tokenStor := tokenStorage.NewTokenStore() stor := util.NewKeyStorage(&nodeKey.PrivateKey, tokenStor, mockedNetworkState{42}) + owner := *usertest.ID() + t.Run("node key", func(t *testing.T) { key, err := stor.GetKey(nil) require.NoError(t, err) @@ -30,48 +34,35 @@ func TestNewKeyStorage(t *testing.T) { }) t.Run("unknown token", func(t *testing.T) { - tok := generateToken(t) - _, err = stor.GetKey(tok) + _, err = stor.GetKey(&util.SessionInfo{ + ID: uuid.New(), + Owner: *usertest.ID(), + }) require.Error(t, err) }) t.Run("known token", func(t *testing.T) { - tok := createToken(t, tokenStor, 100) - pubKey, err := keys.NewPublicKeyFromBytes(tok.SessionKey(), elliptic.P256()) - require.NoError(t, err) + tok := createToken(t, tokenStor, owner, 100) - key, err := stor.GetKey(tok) + key, err := stor.GetKey(&util.SessionInfo{ + ID: tok.ID(), + Owner: owner, + }) require.NoError(t, err) - require.Equal(t, pubKey.X, key.PublicKey.X) - require.Equal(t, pubKey.Y, key.PublicKey.Y) + require.True(t, tok.AssertAuthKey((*neofsecdsa.PublicKey)(&key.PublicKey))) }) t.Run("expired token", func(t *testing.T) { - tok := createToken(t, tokenStor, 30) - _, err := stor.GetKey(tok) + tok := createToken(t, tokenStor, owner, 30) + _, err := stor.GetKey(&util.SessionInfo{ + ID: tok.ID(), + Owner: owner, + }) require.Error(t, err) }) } -func generateToken(t *testing.T) *session.Token { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - pubKey := key.PublicKey().Bytes() - id, err := uuid.New().MarshalBinary() - require.NoError(t, err) - - tok := session.NewToken() - tok.SetSessionKey(pubKey) - tok.SetID(id) - tok.SetOwnerID(usertest.ID()) - - return tok -} - -func createToken(t *testing.T, store *tokenStorage.TokenStore, exp uint64) *session.Token { - owner := usertest.ID() - +func createToken(t *testing.T, store *tokenStorage.TokenStore, owner user.ID, exp uint64) session.Object { var ownerV2 refs.OwnerID owner.WriteToV2(&ownerV2) @@ -82,10 +73,15 @@ func createToken(t *testing.T, store *tokenStorage.TokenStore, exp uint64) *sess resp, err := store.Create(context.Background(), req) require.NoError(t, err) - tok := session.NewToken() - tok.SetSessionKey(resp.GetSessionKey()) - tok.SetID(resp.GetID()) - tok.SetOwnerID(owner) + pub, err := keys.NewPublicKeyFromBytes(resp.GetSessionKey(), elliptic.P256()) + require.NoError(t, err) + + var id uuid.UUID + require.NoError(t, id.UnmarshalBinary(resp.GetID())) + + var tok session.Object + tok.SetAuthKey((*neofsecdsa.PublicKey)(pub)) + tok.SetID(id) return tok } diff --git a/pkg/services/object/util/prm.go b/pkg/services/object/util/prm.go index 8baa85496..46582578a 100644 --- a/pkg/services/object/util/prm.go +++ b/pkg/services/object/util/prm.go @@ -1,11 +1,14 @@ package util import ( + "errors" + "fmt" "strconv" "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-sdk-go/bearer" sessionsdk "github.com/nspcc-dev/neofs-sdk-go/session" + "github.com/nspcc-dev/neofs-sdk-go/user" ) // maxLocalTTL is maximum TTL for an operation to be considered local. @@ -16,13 +19,15 @@ type CommonPrm struct { netmapEpoch, netmapLookupDepth uint64 - token *sessionsdk.Token + token *sessionsdk.Object bearer *bearer.Token ttl uint32 - xhdrs []*sessionsdk.XHeader + xhdrs []string + + ownerSession user.ID } // TTL returns TTL for new requests. @@ -35,7 +40,7 @@ func (p *CommonPrm) TTL() uint32 { } // XHeaders returns X-Headers for new requests. -func (p *CommonPrm) XHeaders() []*sessionsdk.XHeader { +func (p *CommonPrm) XHeaders() []string { if p != nil { return p.xhdrs } @@ -59,7 +64,7 @@ func (p *CommonPrm) LocalOnly() bool { return false } -func (p *CommonPrm) SessionToken() *sessionsdk.Token { +func (p *CommonPrm) SessionToken() *sessionsdk.Object { if p != nil { return p.token } @@ -67,6 +72,14 @@ func (p *CommonPrm) SessionToken() *sessionsdk.Token { return nil } +func (p *CommonPrm) SessionOwner() (user.ID, bool) { + if p != nil && p.token != nil { + return p.ownerSession, true + } + + return user.ID{}, false +} + func (p *CommonPrm) BearerToken() *bearer.Token { if p != nil { return p.bearer @@ -102,17 +115,38 @@ func CommonPrmFromV2(req interface { }) (*CommonPrm, error) { meta := req.GetMetaHeader() + var tokenSession *sessionsdk.Object + var err error + var ownerSession user.ID + + if tokenSessionV2 := meta.GetSessionToken(); tokenSessionV2 != nil { + ownerSessionV2 := tokenSessionV2.GetBody().GetOwnerID() + if ownerSessionV2 == nil { + return nil, errors.New("missing session owner") + } + + err = ownerSession.ReadFromV2(*ownerSessionV2) + if err != nil { + return nil, fmt.Errorf("invalid session token: %w", err) + } + + tokenSession = new(sessionsdk.Object) + + err = tokenSession.ReadFromV2(*tokenSessionV2) + if err != nil { + return nil, fmt.Errorf("invalid session token: %w", err) + } + } + xHdrs := meta.GetXHeaders() ttl := meta.GetTTL() prm := &CommonPrm{ - local: ttl <= maxLocalTTL, - xhdrs: make([]*sessionsdk.XHeader, 0, len(xHdrs)), - ttl: ttl - 1, // decrease TTL for new requests - } - - if tok := meta.GetSessionToken(); tok != nil { - prm.token = sessionsdk.NewTokenFromV2(tok) + local: ttl <= maxLocalTTL, + token: tokenSession, + ttl: ttl - 1, // decrease TTL for new requests + xhdrs: make([]string, 0, 2*len(xHdrs)), + ownerSession: ownerSession, } if tok := meta.GetBearerToken(); tok != nil { @@ -121,7 +155,7 @@ func CommonPrmFromV2(req interface { } for i := range xHdrs { - switch xHdrs[i].GetKey() { + switch key := xHdrs[i].GetKey(); key { case session.XHeaderNetmapEpoch: var err error @@ -137,9 +171,7 @@ func CommonPrmFromV2(req interface { return nil, err } default: - xhdr := sessionsdk.NewXHeaderFromV2(&xHdrs[i]) - - prm.xhdrs = append(prm.xhdrs, xhdr) + prm.xhdrs = append(prm.xhdrs, key, xHdrs[i].GetValue()) } } diff --git a/pkg/services/object_manager/transformer/fmt.go b/pkg/services/object_manager/transformer/fmt.go index 2d39d483e..88677f4c7 100644 --- a/pkg/services/object_manager/transformer/fmt.go +++ b/pkg/services/object_manager/transformer/fmt.go @@ -25,7 +25,7 @@ type FormatterParams struct { NextTarget ObjectTarget - SessionToken *session.Token + SessionToken *session.Object NetworkState netmap.State }