From e8f8e58e90aa4e3e5462e1f953a1ee6288f6afba Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Sun, 28 Nov 2021 18:48:32 +0300 Subject: [PATCH] [#979] adm: Add subnet-related commands Add `subnet` command which contains all subnet-related commands. Add sub-commands: * `create` for creation; * `remove` for removal; * `get` for reading; * `admin` for admin management; * `client` for client management. Signed-off-by: Leonard Lyubich --- .../internal/modules/morph/internal/types.go | 65 ++ .../modules/morph/internal/types.pb.go | 156 ++++ .../modules/morph/internal/types.proto | 15 + cmd/neofs-adm/internal/modules/morph/root.go | 2 + .../internal/modules/morph/subnet.go | 711 ++++++++++++++++++ pkg/morph/client/subnet/admin.go | 87 +++ pkg/morph/client/subnet/clients.go | 56 ++ pkg/services/control/ir/service.pb.go | 2 +- pkg/services/control/ir/types.pb.go | 2 +- pkg/services/control/service.pb.go | 2 +- pkg/services/control/types.pb.go | 2 +- 11 files changed, 1096 insertions(+), 4 deletions(-) create mode 100644 cmd/neofs-adm/internal/modules/morph/internal/types.go create mode 100644 cmd/neofs-adm/internal/modules/morph/internal/types.pb.go create mode 100644 cmd/neofs-adm/internal/modules/morph/internal/types.proto create mode 100644 cmd/neofs-adm/internal/modules/morph/subnet.go create mode 100644 pkg/morph/client/subnet/admin.go diff --git a/cmd/neofs-adm/internal/modules/morph/internal/types.go b/cmd/neofs-adm/internal/modules/morph/internal/types.go new file mode 100644 index 000000000..501625be9 --- /dev/null +++ b/cmd/neofs-adm/internal/modules/morph/internal/types.go @@ -0,0 +1,65 @@ +package internal + +import ( + "fmt" + "strconv" + + "google.golang.org/protobuf/proto" +) + +// StringifySubnetClientGroupID returns string representation of SubnetClientGroupID using MarshalText. +// Returns string with message on error. +func StringifySubnetClientGroupID(id *SubnetClientGroupID) string { + text, err := id.MarshalText() + if err != nil { + return fmt.Sprintf(" %v", err) + } + + return string(text) +} + +// MarshalText encodes SubnetClientGroupID into text format according to NeoFS API V2 protocol: +// value in base-10 integer string format. +// +// Implements encoding.TextMarshaler. +func (x *SubnetClientGroupID) MarshalText() ([]byte, error) { + num := x.GetValue() // NPE safe, returns zero on nil + + return []byte(strconv.FormatUint(uint64(num), 10)), nil +} + +// UnmarshalText decodes SubnetID from the text according to NeoFS API V2 protocol: +// should be base-10 integer string format with bitsize = 32. +// +// Returns strconv.ErrRange if integer overflows uint32. +// +// Must not be called on nil. +// +// Implements encoding.TextUnmarshaler. +func (x *SubnetClientGroupID) UnmarshalText(txt []byte) error { + num, err := strconv.ParseUint(string(txt), 10, 32) + if err != nil { + return fmt.Errorf("invalid numeric value: %w", err) + } + + x.SetNumber(uint32(num)) + + return nil +} + +// Marshal encodes SubnetClientGroupID into a binary format of NeoFS API V2 protocol +// (Protocol Buffers with direct field order). +func (x *SubnetClientGroupID) Marshal() ([]byte, error) { + return proto.Marshal(x) +} + +// Unmarshal decodes SubnetClientGroupID from NeoFS API V2 binary format (see Marshal). Must not be called on nil. +func (x *SubnetClientGroupID) Unmarshal(data []byte) error { + return proto.Unmarshal(data, x) +} + +// SetNumber sets SubnetClientGroupID value in uint32 format. Must not be called on nil. +// By default, number is 0. +func (x *SubnetClientGroupID) SetNumber(num uint32) { + x.Value = num +} diff --git a/cmd/neofs-adm/internal/modules/morph/internal/types.pb.go b/cmd/neofs-adm/internal/modules/morph/internal/types.pb.go new file mode 100644 index 000000000..03c183877 --- /dev/null +++ b/cmd/neofs-adm/internal/modules/morph/internal/types.pb.go @@ -0,0 +1,156 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.1 +// source: cmd/neofs-adm/internal/modules/morph/internal/types.proto + +package internal + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Client group identifier in the NeoFS subnet. +// +// String representation of a value is base-10 integer. +// +// JSON representation is an object containing single `value` number field. +type SubnetClientGroupID struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 4-byte integer identifier of the subnet client group. + Value uint32 `protobuf:"fixed32,1,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *SubnetClientGroupID) Reset() { + *x = SubnetClientGroupID{} + if protoimpl.UnsafeEnabled { + mi := &file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubnetClientGroupID) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubnetClientGroupID) ProtoMessage() {} + +func (x *SubnetClientGroupID) ProtoReflect() protoreflect.Message { + mi := &file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubnetClientGroupID.ProtoReflect.Descriptor instead. +func (*SubnetClientGroupID) Descriptor() ([]byte, []int) { + return file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescGZIP(), []int{0} +} + +func (x *SubnetClientGroupID) GetValue() uint32 { + if x != nil { + return x.Value + } + return 0 +} + +var File_cmd_neofs_adm_internal_modules_morph_internal_types_proto protoreflect.FileDescriptor + +var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc = []byte{ + 0x0a, 0x39, 0x63, 0x6d, 0x64, 0x2f, 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x2d, 0x61, 0x64, 0x6d, 0x2f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, + 0x2f, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x6e, 0x65, 0x6f, + 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x72, 0x65, 0x66, 0x73, 0x22, 0x2b, 0x0a, 0x13, 0x53, + 0x75, 0x62, 0x6e, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x07, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x4f, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x73, 0x70, 0x63, 0x63, 0x2d, 0x64, 0x65, 0x76, + 0x2f, 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x63, 0x6d, 0x64, 0x2f, + 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x2d, 0x61, 0x64, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2f, 0x6d, 0x6f, 0x72, 0x70, 0x68, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescOnce sync.Once + file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData = file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc +) + +func file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescGZIP() []byte { + file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescOnce.Do(func() { + file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData) + }) + return file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData +} + +var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_goTypes = []interface{}{ + (*SubnetClientGroupID)(nil), // 0: neo.fs.v2.refs.SubnetClientGroupID +} +var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_init() } +func file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_init() { + if File_cmd_neofs_adm_internal_modules_morph_internal_types_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubnetClientGroupID); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_goTypes, + DependencyIndexes: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_depIdxs, + MessageInfos: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes, + }.Build() + File_cmd_neofs_adm_internal_modules_morph_internal_types_proto = out.File + file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc = nil + file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_goTypes = nil + file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_depIdxs = nil +} diff --git a/cmd/neofs-adm/internal/modules/morph/internal/types.proto b/cmd/neofs-adm/internal/modules/morph/internal/types.proto new file mode 100644 index 000000000..c610eb7aa --- /dev/null +++ b/cmd/neofs-adm/internal/modules/morph/internal/types.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package neo.fs.v2.refs; + +option go_package = "github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/morph/internal"; + +// Client group identifier in the NeoFS subnet. +// +// String representation of a value is base-10 integer. +// +// JSON representation is an object containing single `value` number field. +message SubnetClientGroupID { + // 4-byte integer identifier of the subnet client group. + fixed32 value = 1 [json_name = "value"]; +} diff --git a/cmd/neofs-adm/internal/modules/morph/root.go b/cmd/neofs-adm/internal/modules/morph/root.go index f05f9c458..418c7f68c 100644 --- a/cmd/neofs-adm/internal/modules/morph/root.go +++ b/cmd/neofs-adm/internal/modules/morph/root.go @@ -205,4 +205,6 @@ func init() { refillGasCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") refillGasCmd.Flags().String(storageWalletFlag, "", "path to storage node wallet") refillGasCmd.Flags().String(refillGasAmountFlag, "", "additional amount of GAS to transfer") + + RootCmd.AddCommand(cmdSubnet) } diff --git a/cmd/neofs-adm/internal/modules/morph/subnet.go b/cmd/neofs-adm/internal/modules/morph/subnet.go new file mode 100644 index 000000000..38fdc66ea --- /dev/null +++ b/cmd/neofs-adm/internal/modules/morph/subnet.go @@ -0,0 +1,711 @@ +package morph + +import ( + "encoding/hex" + "errors" + "fmt" + "math" + "os" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/morph/internal" + "github.com/nspcc-dev/neofs-node/pkg/morph/client" + morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet" + "github.com/nspcc-dev/neofs-node/pkg/util/rand" + "github.com/nspcc-dev/neofs-sdk-go/owner" + "github.com/nspcc-dev/neofs-sdk-go/subnet" + subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// cmdSubnet flags. +const ( + // Neo RPC endpoint + flagSubnetEndpoint = endpointFlag + // filepath to private key + flagSubnetKey = "key" + // contract address + flagSubnetContract = "contract" + // disable notary + flagSubnetNonNotary = "non-notary" +) + +func viperBindFlags(cmd *cobra.Command, flags ...string) { + for i := range flags { + _ = viper.BindPFlag(flags[i], cmd.Flags().Lookup(flags[i])) + } +} + +// subnet command section. +var cmdSubnet = &cobra.Command{ + Use: "subnet", + Short: "NeoFS subnet management.", + PreRun: func(cmd *cobra.Command, _ []string) { + viperBindFlags(cmd, + flagSubnetEndpoint, + flagSubnetKey, + flagSubnetContract, + flagSubnetNonNotary, + ) + }, +} + +// shared flags of cmdSubnet sub-commands. +const ( + // subnet identifier + flagSubnet = "subnet" + // subnet client group ID + flagSubnetGroup = "group" +) + +// reads private key from the filepath configured in flagSubnetKey flag. +func readSubnetKey(key *keys.PrivateKey) error { + // read key from file + keyPath := viper.GetString(flagSubnetKey) + if keyPath == "" { + return errors.New("missing path to private key") + } + + data, err := os.ReadFile(keyPath) + if err != nil { + return fmt.Errorf("read private key file: %w", err) + } + + // decode key + k, err := keys.NewPrivateKeyFromBytes(data) + if err != nil { + return fmt.Errorf("decode private key: %w", err) + } + + *key = *k + + return nil +} + +// initializes morph subnet client with the specified private key. +// +// Parameters are read from: +// * contract address: flagSubnetContract; +// * endpoint: flagSubnetEndpoint; +// * non-notary mode: flagSubnetNonNotary. +func initSubnetClient(c *morphsubnet.Client, key *keys.PrivateKey) error { + // read endpoint + endpoint := viper.GetString(flagSubnetEndpoint) + if endpoint == "" { + return errors.New("missing endpoint") + } + + // read contract address + contractAddr, err := util.Uint160DecodeStringLE(viper.GetString(flagSubnetContract)) + if err != nil { + return fmt.Errorf("subnet contract address: %w", err) + } + + // create base morph client + cMorph, err := client.New(key, endpoint) + if err != nil { + return err + } + + // calc client mode + cMode := morphsubnet.NotaryNonAlphabet + + if viper.GetBool(flagSubnetNonNotary) { + cMode = morphsubnet.NonNotary + } + + // init subnet morph client + var prmInit morphsubnet.InitPrm + + prmInit.SetBaseClient(cMorph) + prmInit.SetContractAddress(contractAddr) + prmInit.SetMode(cMode) + + if err := c.Init(prmInit); err != nil { + return fmt.Errorf("init call: %w", err) + } + + return nil +} + +// create subnet command. +var cmdSubnetCreate = &cobra.Command{ + Use: "create", + Short: "Create NeoFS subnet.", + RunE: func(cmd *cobra.Command, _ []string) error { + // read private key + var key keys.PrivateKey + + err := readSubnetKey(&key) + if err != nil { + return fmt.Errorf("read private key: %w", err) + } + + // calculate wallet address from key + n3Wallet, err := owner.NEO3WalletFromPublicKey(&key.PrivateKey.PublicKey) + if err != nil { + return fmt.Errorf("wallet from key: %w", err) + } + + // generate subnet ID and marshal it + var ( + id subnetid.ID + num uint32 + ) + + for { + num = uint32(rand.Uint64(rand.New(), math.MaxUint32)) + + id.SetNumber(num) + + if !subnetid.IsZero(id) { + break + } + } + + binID, err := id.Marshal() + if err != nil { + return fmt.Errorf("marshal subnet ID: %w", err) + } + + // declare creator ID and encode it + creator := *owner.NewIDFromNeo3Wallet(n3Wallet) + + binCreator, err := creator.Marshal() + if err != nil { + return fmt.Errorf("marshal creator ID: %w", err) + } + + // fill subnet info and encode it + var info subnet.Info + + info.SetID(id) + info.SetOwner(creator) + + binInfo, err := info.Marshal() + if err != nil { + return fmt.Errorf("marshal subnet info: %w", err) + } + + // initialize morph subnet client + var cSubnet morphsubnet.Client + + err = initSubnetClient(&cSubnet, &key) + if err != nil { + return fmt.Errorf("init subnet client: %w", err) + } + + // prepare call parameters and create subnet + var prm morphsubnet.PutPrm + + prm.SetID(binID) + prm.SetOwner(binCreator) + prm.SetInfo(binInfo) + + _, err = cSubnet.Put(prm) + if err != nil { + return fmt.Errorf("morph call: %w", err) + } + + cmd.Printf("Create subnet request sent successfully. ID: %s.\n", &id) + + return nil + }, +} + +// cmdSubnetRemove flags. +const ( + // subnet ID to be removed + flagSubnetRemoveID = flagSubnet +) + +// errZeroSubnet is returned on attempts to work with zero subnet which is virtual. +var errZeroSubnet = errors.New("zero subnet") + +// remove subnet command. +var cmdSubnetRemove = &cobra.Command{ + Use: "remove", + Short: "Remove NeoFS subnet.", + PreRun: func(cmd *cobra.Command, _ []string) { + viperBindFlags(cmd, + flagSubnetRemoveID, + ) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + // read private key + var key keys.PrivateKey + + err := readSubnetKey(&key) + if err != nil { + return fmt.Errorf("read private key: %w", err) + } + + // read ID and encode it + var id subnetid.ID + + err = id.UnmarshalText([]byte(viper.GetString(flagSubnetRemoveID))) + if err != nil { + return fmt.Errorf("decode ID text: %w", err) + } + + if subnetid.IsZero(id) { + return errZeroSubnet + } + + binID, err := id.Marshal() + if err != nil { + return fmt.Errorf("marshal subnet ID: %w", err) + } + + // initialize morph subnet client + var cSubnet morphsubnet.Client + + err = initSubnetClient(&cSubnet, &key) + if err != nil { + return fmt.Errorf("init subnet client: %w", err) + } + + // prepare call parameters and remove subnet + var prm morphsubnet.DeletePrm + + prm.SetID(binID) + + _, err = cSubnet.Delete(prm) + if err != nil { + return fmt.Errorf("morph call: %w", err) + } + + cmd.Println("Remove subnet request sent successfully.") + + return nil + }, +} + +// cmdSubnetGet flags. +const ( + // subnet ID to be read + flagSubnetGetID = flagSubnet +) + +// get subnet command. +var cmdSubnetGet = &cobra.Command{ + Use: "get", + Short: "Read information about the NeoFS subnet.", + PreRun: func(cmd *cobra.Command, _ []string) { + viperBindFlags(cmd, + flagSubnetGetID, + ) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + // read private key + var key keys.PrivateKey + + err := readSubnetKey(&key) + if err != nil { + return fmt.Errorf("read private key: %w", err) + } + + // read ID and encode it + var id subnetid.ID + + err = id.UnmarshalText([]byte(viper.GetString(flagSubnetGetID))) + if err != nil { + return fmt.Errorf("decode ID text: %w", err) + } + + if subnetid.IsZero(id) { + return errZeroSubnet + } + + binID, err := id.Marshal() + if err != nil { + return fmt.Errorf("marshal subnet ID: %w", err) + } + + // initialize morph subnet client + var cSubnet morphsubnet.Client + + err = initSubnetClient(&cSubnet, &key) + if err != nil { + return fmt.Errorf("init subnet client: %w", err) + } + + // prepare call parameters and read subnet + var prm morphsubnet.GetPrm + + prm.SetID(binID) + + res, err := cSubnet.Get(prm) + if err != nil { + return fmt.Errorf("morph call: %w", err) + } + + // decode info + var info subnet.Info + + if err = info.Unmarshal(res.Info()); err != nil { + return fmt.Errorf("decode subnet info: %w", err) + } + + // print information + var ownerID owner.ID + + info.ReadOwner(&ownerID) + + cmd.Printf("Owner: %s\n", ownerID) + + return nil + }, +} + +// cmdSubnetAdmin subnet flags. +const ( + // subnet ID to be managed + flagSubnetAdminSubnet = flagSubnet + // admin public key + flagSubnetAdminID = "id" + // manage client admins instead of node ones + flagSubnetAdminClient = "client" +) + +// command to manage subnet admins. +var cmdSubnetAdmin = &cobra.Command{ + Use: "admin", + Short: "Manage administrators of the NeoFS subnet.", + PreRun: func(cmd *cobra.Command, _ []string) { + viperBindFlags(cmd, + flagSubnetAdminSubnet, + flagSubnetAdminID, + ) + }, +} + +// cmdSubnetAdminAdd flags. +const ( + // client group ID + flagSubnetAdminAddGroup = flagSubnetGroup +) + +// common executor cmdSubnetAdminAdd and cmdSubnetAdminRemove commands. +func manageSubnetAdmins(cmd *cobra.Command, rm bool) error { + // read private key + var key keys.PrivateKey + + err := readSubnetKey(&key) + if err != nil { + return fmt.Errorf("read private key: %w", err) + } + + // read ID and encode it + var id subnetid.ID + + err = id.UnmarshalText([]byte(viper.GetString(flagSubnetAdminSubnet))) + if err != nil { + return fmt.Errorf("decode ID text: %w", err) + } + + if subnetid.IsZero(id) { + return errZeroSubnet + } + + binID, err := id.Marshal() + if err != nil { + return fmt.Errorf("marshal subnet ID: %w", err) + } + + // read admin key and decode it + binAdminKey, err := hex.DecodeString(viper.GetString(flagSubnetAdminID)) + if err != nil { + return fmt.Errorf("decode admin key text: %w", err) + } + + var pubkey keys.PublicKey + if err = pubkey.DecodeBytes(binAdminKey); err != nil { + return fmt.Errorf("admin key format: %w", err) + } + + // prepare call parameters + var prm morphsubnet.ManageAdminsPrm + + if viper.GetBool(flagSubnetAdminClient) { + // read group ID and encode it + var groupID internal.SubnetClientGroupID + + err = groupID.UnmarshalText([]byte(viper.GetString(flagSubnetAdminAddGroup))) + if err != nil { + return fmt.Errorf("decode group ID text: %w", err) + } + + binGroupID, err := groupID.Marshal() + if err != nil { + return fmt.Errorf("marshal group ID: %w", err) + } + + prm.SetClient() + prm.SetGroup(binGroupID) + } + + prm.SetSubnet(binID) + prm.SetAdmin(binAdminKey) + + if rm { + prm.SetRemove() + } + + // initialize morph subnet client + var cSubnet morphsubnet.Client + + err = initSubnetClient(&cSubnet, &key) + if err != nil { + return fmt.Errorf("init subnet client: %w", err) + } + + _, err = cSubnet.ManageAdmins(prm) + if err != nil { + return fmt.Errorf("morph call: %w", err) + } + + var op string + + if rm { + op = "Remove" + } else { + op = "Add" + } + + cmd.Printf("%s admin request sent successfully.\n", op) + + return nil +} + +// command to add subnet admin. +var cmdSubnetAdminAdd = &cobra.Command{ + Use: "add", + Short: "Add admin to the NeoFS subnet.", + PreRun: func(cmd *cobra.Command, _ []string) { + viperBindFlags(cmd, + flagSubnetAdminAddGroup, + flagSubnetAdminClient, + ) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + return manageSubnetAdmins(cmd, false) + }, +} + +// command to remove subnet admin. +var cmdSubnetAdminRemove = &cobra.Command{ + Use: "remove", + Short: "Remove admin of the NeoFS subnet.", + PreRun: func(cmd *cobra.Command, _ []string) { + viperBindFlags(cmd, + flagSubnetAdminClient, + ) + }, + RunE: func(cmd *cobra.Command, _ []string) error { + return manageSubnetAdmins(cmd, true) + }, +} + +// cmdSubnetClient flags. +const ( + // ID of the subnet to be managed + flagSubnetClientSubnet = flagSubnet + // client's NeoFS ID + flagSubnetClientID = flagSubnetAdminClient + // ID of the subnet client group + flagSubnetClientGroup = flagSubnetGroup +) + +// command to manage subnet clients. +var cmdSubnetClient = &cobra.Command{ + Use: "client", + Short: "Manage clients of the NeoFS subnet.", + PreRun: func(cmd *cobra.Command, _ []string) { + viperBindFlags(cmd, + flagSubnetClientSubnet, + flagSubnetClientID, + flagSubnetClientGroup, + ) + }, +} + +// common executor cmdSubnetClientAdd and cmdSubnetClientRemove commands. +func manageSubnetClients(cmd *cobra.Command, rm bool) error { + // read private key + var key keys.PrivateKey + + err := readSubnetKey(&key) + if err != nil { + return fmt.Errorf("read private key: %w", err) + } + + // read ID and encode it + var id subnetid.ID + + err = id.UnmarshalText([]byte(viper.GetString(flagSubnetClientSubnet))) + if err != nil { + return fmt.Errorf("decode ID text: %w", err) + } + + if subnetid.IsZero(id) { + return errZeroSubnet + } + + binID, err := id.Marshal() + if err != nil { + return fmt.Errorf("marshal subnet ID: %w", err) + } + + // read client ID and encode it + var clientID owner.ID + + err = clientID.Parse(viper.GetString(flagSubnetClientID)) + if err != nil { + return fmt.Errorf("decode client ID text: %w", err) + } + + binClientID, err := clientID.Marshal() + if err != nil { + return fmt.Errorf("marshal client ID: %w", err) + } + + var prm morphsubnet.ManageClientsPrm + + if viper.GetBool(flagSubnetAdminClient) { + // read group ID and encode it + var groupID internal.SubnetClientGroupID + + err = groupID.UnmarshalText([]byte(viper.GetString(flagSubnetAdminAddGroup))) + if err != nil { + return fmt.Errorf("decode group ID text: %w", err) + } + + binGroupID, err := groupID.Marshal() + if err != nil { + return fmt.Errorf("marshal group ID: %w", err) + } + + prm.SetGroup(binGroupID) + } + + prm.SetSubnet(binID) + prm.SetClient(binClientID) + + if rm { + prm.SetRemove() + } + + // initialize morph subnet client + var cSubnet morphsubnet.Client + + err = initSubnetClient(&cSubnet, &key) + if err != nil { + return fmt.Errorf("init subnet client: %w", err) + } + + _, err = cSubnet.ManageClients(prm) + if err != nil { + return fmt.Errorf("morph call: %w", err) + } + + var op string + + if rm { + op = "Remove" + } else { + op = "Add" + } + + cmd.Printf("%s client request sent successfully.\n", op) + + return nil +} + +// command to add subnet client. +var cmdSubnetClientAdd = &cobra.Command{ + Use: "add", + Short: "Add client to the NeoFS subnet.", + RunE: func(cmd *cobra.Command, _ []string) error { + return manageSubnetClients(cmd, false) + }, +} + +// command to remove subnet client. +var cmdSubnetClientRemove = &cobra.Command{ + Use: "remove", + Short: "Remove client of the NeoFS subnet.", + RunE: func(cmd *cobra.Command, _ []string) error { + return manageSubnetClients(cmd, true) + }, +} + +// registers flags and binds sub-commands for subnet commands. +func init() { + // get subnet flags + cmdSubnetGet.Flags().String(flagSubnetGetID, "", "ID of the subnet to read") + _ = cmdSubnetAdminAdd.MarkFlagRequired(flagSubnetGetID) + + // remove subnet flags + cmdSubnetRemove.Flags().String(flagSubnetRemoveID, "", "ID of the subnet to remove") + _ = cmdSubnetRemove.MarkFlagRequired(flagSubnetRemoveID) + + // subnet administer flags + adminFlags := cmdSubnetAdmin.Flags() + adminFlags.String(flagSubnetAdminSubnet, "", "ID of the subnet to manage administrators") + _ = cmdSubnetAdmin.MarkFlagRequired(flagSubnetAdminSubnet) + adminFlags.String(flagSubnetAdminID, "", "Hex-encoded public key of the admin") + _ = cmdSubnetAdmin.MarkFlagRequired(flagSubnetAdminID) + + // add admin flags + cmdSubnetAdminAddFlags := cmdSubnetAdminAdd.Flags() + cmdSubnetAdminAddFlags.String(flagSubnetAdminAddGroup, "", fmt.Sprintf( + "Client group ID in text format (needed with --%s only)", flagSubnetAdminClient)) + cmdSubnetAdminAddFlags.Bool(flagSubnetAdminClient, false, "Add client admin instead of node one") + + // remove admin flags + cmdSubnetAdminRemoveFlags := cmdSubnetAdminRemove.Flags() + cmdSubnetAdminRemoveFlags.Bool(flagSubnetAdminClient, false, "Remove client admin instead of node one") + + // client managements flags + clientFlags := cmdSubnetClient.Flags() + clientFlags.String(flagSubnetClientSubnet, "", "ID of the subnet to be managed") + _ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientSubnet) + clientFlags.String(flagSubnetClientGroup, "", "ID of the client group to work with") + _ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientGroup) + clientFlags.String(flagSubnetClientID, "", "Client's user ID in NeoFS system in text format") + _ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientID) + + // add all admin managing commands to corresponding command section + cmdSubnetAdmin.AddCommand( + cmdSubnetAdminAdd, + cmdSubnetAdminRemove, + ) + + // add all client managing commands to corresponding command section + cmdSubnetClient.AddCommand( + cmdSubnetClientAdd, + cmdSubnetClientRemove, + ) + + // subnet global flags + cmdSubnetFlags := cmdSubnet.Flags() + cmdSubnetFlags.StringP(flagSubnetEndpoint, "r", "", "N3 RPC node endpoint") + _ = cmdSubnet.MarkFlagRequired(flagSubnetEndpoint) + cmdSubnetFlags.StringP(flagSubnetKey, "k", "", "Path to file with private key") + _ = cmdSubnet.MarkFlagRequired(flagSubnetKey) + cmdSubnetFlags.StringP(flagSubnetContract, "a", "", "Subnet contract address (string LE)") + _ = cmdSubnet.MarkFlagRequired(flagSubnetContract) + cmdSubnetFlags.Bool(flagSubnetNonNotary, false, "Flag to work in non-notary environment") + + // add all subnet commands to corresponding command section + cmdSubnet.AddCommand( + cmdSubnetCreate, + cmdSubnetRemove, + cmdSubnetGet, + cmdSubnetAdmin, + cmdSubnetClient, + ) +} diff --git a/pkg/morph/client/subnet/admin.go b/pkg/morph/client/subnet/admin.go new file mode 100644 index 000000000..f2f9b9c63 --- /dev/null +++ b/pkg/morph/client/subnet/admin.go @@ -0,0 +1,87 @@ +package morphsubnet + +import "github.com/nspcc-dev/neofs-node/pkg/morph/client" + +// ManageAdminsPrm groups parameters of administer methods of Subnet contract. +// +// Zero value adds node admin. Subnet, key and group must be specified via setters. +type ManageAdminsPrm struct { + // remove or add admin + rm bool + + // client or node admin + client bool + + subnet []byte + + admin []byte + + group []byte +} + +// SetRemove marks admin to be removed. By default, admin is added. +func (x *ManageAdminsPrm) SetRemove() { + x.rm = true +} + +// SetClient switches to client admin. By default, node admin is modified. +func (x *ManageAdminsPrm) SetClient() { + x.client = true +} + +// SetSubnet sets identifier of the subnet in a binary NeoFS API protocol format. +func (x *ManageAdminsPrm) SetSubnet(id []byte) { + x.subnet = id +} + +// SetAdmin sets admin's public key in a binary format. +func (x *ManageAdminsPrm) SetAdmin(key []byte) { + x.admin = key +} + +// SetGroup sets identifier of the client group in a binary NeoFS API protocol format. +// Makes sense only for client admins (see ManageAdminsPrm.SetClient). +func (x *ManageAdminsPrm) SetGroup(id []byte) { + x.group = id +} + +// ManageAdminsRes groups resulting values of node administer methods of Subnet contract. +type ManageAdminsRes struct{} + +// ManageAdmins manages admin list of the NeoFS subnet through Subnet contract calls. +func (x Client) ManageAdmins(prm ManageAdminsPrm) (*ManageAdminsPrm, error) { + var method string + + args := make([]interface{}, 1, 3) + args[0] = prm.subnet + + if prm.client { + args = append(args, prm.group, prm.admin) + + if prm.rm { + method = "removeClientAdmin" + } else { + method = "addClientAdmin" + } + } else { + args = append(args, prm.admin) + + if prm.rm { + method = "removeNodeAdmin" + } else { + method = "addNodeAdmin" + } + } + + var prmInvoke client.InvokePrm + + prmInvoke.SetMethod(method) + prmInvoke.SetArgs(args...) + + err := x.client.Invoke(prmInvoke) + if err != nil { + return nil, err + } + + return new(ManageAdminsPrm), nil +} diff --git a/pkg/morph/client/subnet/clients.go b/pkg/morph/client/subnet/clients.go index e7ff8494a..335bb5210 100644 --- a/pkg/morph/client/subnet/clients.go +++ b/pkg/morph/client/subnet/clients.go @@ -56,3 +56,59 @@ func (x *Client) UserAllowed(prm UserAllowedPrm) (*UserAllowedRes, error) { result: result, }, nil } + +// ManageClientsPrm groups parameters of client management in Subnet contract. +// +// Zero value adds subnet client. Subnet, group and client ID must be specified via setters. +type ManageClientsPrm struct { + // remove or add client + rm bool + + args [3]interface{} +} + +// SetRemove marks client to be removed. By default, client is added. +func (x *ManageClientsPrm) SetRemove() { + x.rm = true +} + +// SetSubnet sets identifier of the subnet in a binary NeoFS API protocol format. +func (x *ManageClientsPrm) SetSubnet(id []byte) { + x.args[0] = id +} + +// SetGroup sets identifier of the client group in a binary NeoFS API protocol format. +func (x *ManageClientsPrm) SetGroup(id []byte) { + x.args[1] = id +} + +// SetClient sets client's user ID in a binary NeoFS API protocol format. +func (x *ManageClientsPrm) SetClient(id []byte) { + x.args[2] = id +} + +// ManageClientsRes groups resulting values of client management methods of Subnet contract. +type ManageClientsRes struct{} + +// ManageClients manages client list of the NeoFS subnet through Subnet contract calls. +func (x Client) ManageClients(prm ManageClientsPrm) (*ManageClientsRes, error) { + var method string + + if prm.rm { + method = "removeUser" + } else { + method = "addUser" + } + + var prmInvoke client.InvokePrm + + prmInvoke.SetMethod(method) + prmInvoke.SetArgs(prm.args[:]...) + + err := x.client.Invoke(prmInvoke) + if err != nil { + return nil, err + } + + return new(ManageClientsRes), nil +} diff --git a/pkg/services/control/ir/service.pb.go b/pkg/services/control/ir/service.pb.go index cbf3cf2da..26a6120d0 100644 --- a/pkg/services/control/ir/service.pb.go +++ b/pkg/services/control/ir/service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.18.0 +// protoc v3.19.1 // source: pkg/services/control/ir/service.proto package control diff --git a/pkg/services/control/ir/types.pb.go b/pkg/services/control/ir/types.pb.go index e7be9d496..4795b092c 100644 --- a/pkg/services/control/ir/types.pb.go +++ b/pkg/services/control/ir/types.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.18.0 +// protoc v3.19.1 // source: pkg/services/control/ir/types.proto package control diff --git a/pkg/services/control/service.pb.go b/pkg/services/control/service.pb.go index 44ee54a8c..e5da57108 100644 --- a/pkg/services/control/service.pb.go +++ b/pkg/services/control/service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.18.0 +// protoc v3.19.1 // source: pkg/services/control/service.proto package control diff --git a/pkg/services/control/types.pb.go b/pkg/services/control/types.pb.go index 56331f355..b87a7f8f3 100644 --- a/pkg/services/control/types.pb.go +++ b/pkg/services/control/types.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.18.0 +// protoc v3.19.1 // source: pkg/services/control/types.proto package control