From a3ef7b58b46e7cf8bb1e3e94ed8c127c888fc387 Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Mon, 20 Nov 2023 17:03:19 +0300 Subject: [PATCH] [#755] innerring: Check container owner namespace Signed-off-by: Dmitrii Stepanov --- pkg/innerring/initialization.go | 16 ++- .../processors/container/handlers_test.go | 11 ++ .../processors/container/process_container.go | 25 +++- .../processors/container/processor.go | 38 +++--- pkg/morph/client/frostfsid/client.go | 31 +++++ pkg/morph/client/frostfsid/subject.go | 117 ++++++++++++++++++ 6 files changed, 218 insertions(+), 20 deletions(-) create mode 100644 pkg/morph/client/frostfsid/client.go create mode 100644 pkg/morph/client/frostfsid/subject.go diff --git a/pkg/innerring/initialization.go b/pkg/innerring/initialization.go index 71571c07..f4d9b416 100644 --- a/pkg/innerring/initialization.go +++ b/pkg/innerring/initialization.go @@ -20,6 +20,7 @@ import ( balanceClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" frostfsClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfs" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid" nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" control "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir" @@ -249,7 +250,7 @@ func (s *Server) initAlphabetProcessor(cfg *viper.Viper) error { return err } -func (s *Server) initContainerProcessor(cfg *viper.Viper, cnrClient *container.Client) error { +func (s *Server) initContainerProcessor(cfg *viper.Viper, cnrClient *container.Client, frostfsIDClient *frostfsid.Client) error { // container processor containerProcessor, err := cont.New(&cont.Params{ Log: s.log, @@ -258,6 +259,7 @@ func (s *Server) initContainerProcessor(cfg *viper.Viper, cnrClient *container.C AlphabetState: s, ContainerClient: cnrClient, MorphClient: cnrClient.Morph(), + FrostFSIDClient: frostfsIDClient, NetworkState: s.netmapClient, }) if err != nil { @@ -364,8 +366,9 @@ func (s *Server) initGRPCServer(cfg *viper.Viper) error { } type serverMorphClients struct { - CnrClient *container.Client - FrostFSClient *frostfsClient.Client + CnrClient *container.Client + FrostFSIDClient *frostfsid.Client + FrostFSClient *frostfsClient.Client } func (s *Server) initClientsFromMorph() (*serverMorphClients, error) { @@ -397,6 +400,11 @@ func (s *Server) initClientsFromMorph() (*serverMorphClients, error) { return nil, err } + result.FrostFSIDClient, err = frostfsid.NewFromMorph(s.morphClient, s.contracts.frostfsID, fee) + if err != nil { + return nil, err + } + result.FrostFSClient, err = frostfsClient.NewFromMorph(s.mainnetClient, s.contracts.frostfs, s.feeConfig.MainChainFee(), frostfsClient.TryNotary(), frostfsClient.AsAlphabet()) if err != nil { @@ -426,7 +434,7 @@ func (s *Server) initProcessors(cfg *viper.Viper, morphClients *serverMorphClien return err } - err = s.initContainerProcessor(cfg, morphClients.CnrClient) + err = s.initContainerProcessor(cfg, morphClients.CnrClient, morphClients.FrostFSIDClient) if err != nil { return err } diff --git a/pkg/innerring/processors/container/handlers_test.go b/pkg/innerring/processors/container/handlers_test.go index 3acfa225..9fbfdf81 100644 --- a/pkg/innerring/processors/container/handlers_test.go +++ b/pkg/innerring/processors/container/handlers_test.go @@ -3,9 +3,11 @@ package container import ( "crypto/ecdsa" "encoding/hex" + "fmt" "testing" "time" + frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container" @@ -42,6 +44,7 @@ func TestPutEvent(t *testing.T) { NetworkState: nst, ContainerClient: &testContainerClient{}, MorphClient: mc, + FrostFSIDClient: &testFrostFSIDClient{}, }) require.NoError(t, err, "failed to create processor") @@ -102,6 +105,7 @@ func TestDeleteEvent(t *testing.T) { NetworkState: nst, ContainerClient: cc, MorphClient: mc, + FrostFSIDClient: &testFrostFSIDClient{}, }) require.NoError(t, err, "failed to create processor") @@ -173,6 +177,7 @@ func TestSetEACLEvent(t *testing.T) { NetworkState: nst, ContainerClient: cc, MorphClient: mc, + FrostFSIDClient: &testFrostFSIDClient{}, }) require.NoError(t, err, "failed to create processor") @@ -316,3 +321,9 @@ func (c *testMorphClient) NotarySignAndInvokeTX(mainTx *transaction.Transaction) c.transactions = append(c.transactions, mainTx) return nil } + +type testFrostFSIDClient struct{} + +func (c *testFrostFSIDClient) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error) { + return nil, fmt.Errorf("subject not found") +} diff --git a/pkg/innerring/processors/container/process_container.go b/pkg/innerring/processors/container/process_container.go index 2629b9d2..2ad863b2 100644 --- a/pkg/innerring/processors/container/process_container.go +++ b/pkg/innerring/processors/container/process_container.go @@ -2,6 +2,7 @@ package container import ( "fmt" + "strings" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" @@ -10,6 +11,7 @@ import ( cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "github.com/nspcc-dev/neo-go/pkg/network/payload" + "github.com/nspcc-dev/neo-go/pkg/util" "go.uber.org/zap" ) @@ -88,7 +90,7 @@ func (cp *Processor) checkPutContainer(ctx *putContainerContext) error { } // check native name and zone - err = checkNNS(ctx, cnr) + err = cp.checkNNS(ctx, cnr) if err != nil { return fmt.Errorf("NNS: %w", err) } @@ -157,7 +159,7 @@ func (cp *Processor) checkDeleteContainer(e containerEvent.Delete) error { return nil } -func checkNNS(ctx *putContainerContext, cnr containerSDK.Container) error { +func (cp *Processor) checkNNS(ctx *putContainerContext, cnr containerSDK.Container) error { // fetch domain info ctx.d = containerSDK.ReadDomain(cnr) @@ -175,6 +177,25 @@ func checkNNS(ctx *putContainerContext, cnr containerSDK.Container) error { } } + namespace, hasNamespace := strings.CutSuffix(ctx.d.Zone(), ".ns") + if !hasNamespace { + return nil + } + + addr, err := util.Uint160DecodeBytesBE(cnr.Owner().WalletBytes()[1 : 1+util.Uint160Size]) + if err != nil { + return fmt.Errorf("could not get container owner address: %w", err) + } + + subject, err := cp.frostFSIDClient.GetSubject(addr) + if err != nil { + return fmt.Errorf("could not get subject from FrostfsID contract: %w", err) + } + + if subject.Namespace != namespace { + return fmt.Errorf("container and owner namespaces do not match") + } + return nil } diff --git a/pkg/innerring/processors/container/processor.go b/pkg/innerring/processors/container/processor.go index 9f0ae77b..8fd9edfb 100644 --- a/pkg/innerring/processors/container/processor.go +++ b/pkg/innerring/processors/container/processor.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/metrics" @@ -32,15 +33,20 @@ type ( NotarySignAndInvokeTX(mainTx *transaction.Transaction) error } + FrostFSIDClient interface { + GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error) + } + // Processor of events produced by container contract in the sidechain. Processor struct { - log *logger.Logger - metrics metrics.Register - pool *ants.Pool - alphabetState AlphabetState - cnrClient ContClient // notary must be enabled - morphClient MorphClient - netState NetworkState + log *logger.Logger + metrics metrics.Register + pool *ants.Pool + alphabetState AlphabetState + cnrClient ContClient // notary must be enabled + morphClient MorphClient + netState NetworkState + frostFSIDClient FrostFSIDClient } // Params of the processor constructor. @@ -52,6 +58,7 @@ type ( ContainerClient ContClient MorphClient MorphClient NetworkState NetworkState + FrostFSIDClient FrostFSIDClient } ) @@ -86,6 +93,8 @@ func New(p *Params) (*Processor, error) { return nil, errors.New("ir/container: Morph client is not set") case p.NetworkState == nil: return nil, errors.New("ir/container: network state is not set") + case p.FrostFSIDClient == nil: + return nil, errors.New("ir/container: FrostFSID client is not set") } p.Log.Debug(logs.ContainerContainerWorkerPool, zap.Int("size", p.PoolSize)) @@ -101,13 +110,14 @@ func New(p *Params) (*Processor, error) { } return &Processor{ - log: p.Log, - metrics: metricsRegister, - pool: pool, - alphabetState: p.AlphabetState, - cnrClient: p.ContainerClient, - netState: p.NetworkState, - morphClient: p.MorphClient, + log: p.Log, + metrics: metricsRegister, + pool: pool, + alphabetState: p.AlphabetState, + cnrClient: p.ContainerClient, + netState: p.NetworkState, + morphClient: p.MorphClient, + frostFSIDClient: p.FrostFSIDClient, }, nil } diff --git a/pkg/morph/client/frostfsid/client.go b/pkg/morph/client/frostfsid/client.go new file mode 100644 index 00000000..3efa522b --- /dev/null +++ b/pkg/morph/client/frostfsid/client.go @@ -0,0 +1,31 @@ +package frostfsid + +import ( + "fmt" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// Client is a wrapper over StaticClient +// which makes calls with the names and arguments +// of the FrostFS ID contract. +// +// Working client must be created via constructor New. +// Using the Client that has been created with new(Client) +// expression (or just declaring a Client variable) is unsafe +// and can lead to panic. +type Client struct { + client *client.StaticClient // static FrostFS ID contract client +} + +// NewFromMorph wraps client to work with FrostFS ID contract. +func NewFromMorph(cli *client.Client, contract util.Uint160, fee fixedn.Fixed8) (*Client, error) { + sc, err := client.NewStatic(cli, contract, fee, client.TryNotary(), client.AsAlphabet()) + if err != nil { + return nil, fmt.Errorf("could not create client of FrostFS ID contract: %w", err) + } + + return &Client{client: sc}, nil +} diff --git a/pkg/morph/client/frostfsid/subject.go b/pkg/morph/client/frostfsid/subject.go new file mode 100644 index 00000000..b14675d5 --- /dev/null +++ b/pkg/morph/client/frostfsid/subject.go @@ -0,0 +1,117 @@ +package frostfsid + +import ( + "errors" + "fmt" + + frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" +) + +const methodGetSubject = "getSubject" + +func (c *Client) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error) { + prm := client.TestInvokePrm{} + prm.SetMethod(methodGetSubject) + prm.SetArgs(addr) + + res, err := c.client.TestInvoke(prm) + if err != nil { + return nil, fmt.Errorf("could not perform test invocation (%s): %w", methodGetSubject, err) + } + + subj, err := parseSubject(res) + if err != nil { + return nil, fmt.Errorf("could not parse test invocation result (%s): %w", methodGetSubject, err) + } + + return subj, nil +} + +// parseSubject from https://git.frostfs.info/TrueCloudLab/frostfs-contract/src/commit/dd5919348da9731f24504e7bc485516c2ba5f11c/frostfsid/client/client.go#L592 +func parseSubject(structArr []stackitem.Item) (*frostfsidclient.Subject, error) { + if len(structArr) < 5 { + return nil, errors.New("invalid response subject struct") + } + + var ( + err error + subj frostfsidclient.Subject + ) + + subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0])) + if err != nil { + return nil, err + } + + if !structArr[1].Equals(stackitem.Null{}) { + subj.AdditionalKeys, err = unwrap.ArrayOfPublicKeys(makeValidRes(structArr[1])) + if err != nil { + return nil, err + } + } + + if !structArr[2].Equals(stackitem.Null{}) { + subj.Namespace, err = unwrap.UTF8String(makeValidRes(structArr[2])) + if err != nil { + return nil, err + } + } + + if !structArr[3].Equals(stackitem.Null{}) { + subj.Name, err = unwrap.UTF8String(makeValidRes(structArr[3])) + if err != nil { + return nil, err + } + } + + subj.KV, err = parseMap(structArr[4]) + if err != nil { + return nil, err + } + + return &subj, nil +} + +func makeValidRes(item stackitem.Item) (*result.Invoke, error) { + return &result.Invoke{ + Stack: []stackitem.Item{item}, + State: vmstate.Halt.String(), + }, nil +} + +func parseMap(item stackitem.Item) (map[string]string, error) { + if item.Equals(stackitem.Null{}) { + return nil, nil + } + + metaMap, err := unwrap.Map(makeValidRes(item)) + if err != nil { + return nil, err + } + + meta, ok := metaMap.Value().([]stackitem.MapElement) + if !ok { + return nil, errors.New("invalid map type") + } + + res := make(map[string]string, len(meta)) + for _, element := range meta { + key, err := element.Key.TryBytes() + if err != nil { + return nil, err + } + val, err := element.Value.TryBytes() + if err != nil { + return nil, err + } + res[string(key)] = string(val) + } + + return res, nil +}