From 39faefb1753a8a05a6470bfca8d8fc99478385e9 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Sun, 19 Jan 2025 21:25:19 +0300 Subject: [PATCH] [#1607] adm/ape: Adopt policy reader Embed https://git.frostfs.info/dkirillov/policy-reader tool to 'frostfs-adm morph ape' command Signed-off-by: Denis Kirillov --- .../morph/ape/chains/list_container.go | 96 +++++++++ .../modules/morph/ape/chains/list_user.go | 197 ++++++++++++++++++ .../internal/modules/morph/ape/chains/root.go | 32 +++ .../modules/morph/ape/raw/list_chain_names.go | 74 +++++++ .../morph/ape/raw/list_chains_by_prefix.go | 71 +++++++ .../modules/morph/ape/raw/list_targets.go | 88 ++++++++ .../modules/morph/ape/raw/output/chains.go | 56 +++++ .../internal/modules/morph/ape/raw/root.go | 32 +++ .../internal/modules/morph/ape/root.go | 9 +- .../modules/morph/helper/initialize.go | 35 ++++ 10 files changed, 689 insertions(+), 1 deletion(-) create mode 100644 cmd/frostfs-adm/internal/modules/morph/ape/chains/list_container.go create mode 100644 cmd/frostfs-adm/internal/modules/morph/ape/chains/list_user.go create mode 100644 cmd/frostfs-adm/internal/modules/morph/ape/chains/root.go create mode 100644 cmd/frostfs-adm/internal/modules/morph/ape/raw/list_chain_names.go create mode 100644 cmd/frostfs-adm/internal/modules/morph/ape/raw/list_chains_by_prefix.go create mode 100644 cmd/frostfs-adm/internal/modules/morph/ape/raw/list_targets.go create mode 100644 cmd/frostfs-adm/internal/modules/morph/ape/raw/output/chains.go create mode 100644 cmd/frostfs-adm/internal/modules/morph/ape/raw/root.go diff --git a/cmd/frostfs-adm/internal/modules/morph/ape/chains/list_container.go b/cmd/frostfs-adm/internal/modules/morph/ape/chains/list_container.go new file mode 100644 index 00000000..11a529cf --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/chains/list_container.go @@ -0,0 +1,96 @@ +package chains + +import ( + "math/big" + + "git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient" + policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape/raw/output" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" + "github.com/spf13/cobra" +) + +var listContainerCmd = &cobra.Command{ + Use: "list-container", + Short: "List container related (namespace) policies", + Long: "List container related (namespace) policies along with filtering by service (s3/storage)", + Example: `chains list-container -r http://localhost:40332 list --container 7h7NcXcF6k6b1yidqEHc1jkyXUm1MfUDrrTuHAefhiDe +chains list-container -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list --container 7h7NcXcF6k6b1yidqEHc1jkyXUm1MfUDrrTuHAefhiDe --namespace test`, + RunE: runListContainerCmd, +} + +const ( + containerFlag = "container" +) + +func initListContainerCmd() { + listContainerCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc) + listContainerCmd.Flags().String(apeCmd.ChainNameFlag, "", apeCmd.ChainNameFlagDesc) + listContainerCmd.Flags().String(containerFlag, "", "Container id or bucket name in nns (if name is provided than 'namespace' should be set too)") + listContainerCmd.Flags().String(namespaceFlag, "", "Namespace where container name will be looked up") + listContainerCmd.Flags().Bool(decodeChainFlag, false, "Use this flag to decode chain") + listContainerCmd.Flags().Bool(decodeIDFlag, false, "Use this flag to additionally decode chain id (without --decode-chain no take effect)") + + _ = listContainerCmd.MarkFlagRequired(containerFlag) +} + +func runListContainerCmd(cmd *cobra.Command, _ []string) error { + chainName := parseChainName(cmd) + namespace := parseNamespace(cmd) + + inv, policyHash, _ := initReaders(cmd) + cnrID := parseContainer(cmd, inv, namespace) + printContainer(cmd, namespace, cnrID) + + res, err := commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, big.NewInt(int64(policycontract.Namespace)), namespace, string(chainName)) + commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err) + + decodeChain, _ := cmd.Flags().GetBool(decodeChainFlag) + decodeID, _ := cmd.Flags().GetBool(decodeIDFlag) + + cmd.Printf("\nnamespace policies: %d\n", len(res)) + err = output.PrintChains(cmd, res, decodeChain, decodeID) + commonCmd.ExitOnErr(cmd, "can't print chains: %w", err) + + res, err = commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, big.NewInt(int64(policycontract.Container)), cnrID.EncodeToString(), string(chainName)) + commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err) + + cmd.Printf("\ncontainer policies: %d\n", len(res)) + return output.PrintChains(cmd, res, decodeChain, decodeID) +} + +func printContainer(cmd *cobra.Command, namespace string, cnrID cid.ID) { + cmd.Println("container:") + cmd.Printf("\tnamespace: '%s'\n", namespace) + cmd.Printf("\tCID: '%s'\n", cnrID.EncodeToString()) +} + +func parseContainer(cmd *cobra.Command, inv *invoker.Invoker, namespace string) cid.ID { + containerName, _ := cmd.Flags().GetString(containerFlag) + + var cnrID cid.ID + if err := cnrID.DecodeString(containerName); err == nil { + return cnrID + } + + var domain container.Domain + domain.SetName(containerName) + if namespace != "" { + domain.SetZone(namespace + ".ns") + } + + nnsCs, err := helper.GetContractByID(management.NewReader(inv), 1) + commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err) + + cnrID, err = helper.NNSResolveContainerDomain(inv, nnsCs.Hash, domain.Name()+"."+domain.Zone()) + commonCmd.ExitOnErr(cmd, "can't resolve container id: %w", err) + + return cnrID +} diff --git a/cmd/frostfs-adm/internal/modules/morph/ape/chains/list_user.go b/cmd/frostfs-adm/internal/modules/morph/ape/chains/list_user.go new file mode 100644 index 00000000..fbbdb68e --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/chains/list_user.go @@ -0,0 +1,197 @@ +package chains + +import ( + "encoding/hex" + "errors" + "fmt" + "math/big" + + "git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient" + ffsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" + policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape/raw/output" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper" + + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape" + apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" + neoflags "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var listUserCmd = &cobra.Command{ + Use: "list-user", + Short: "List user related (groups/namespace) policies", + Long: "List user related (groups/namespace) policies along with filtering by service (s3/storage)", + Example: `policy-reader list-user -r http://localhost:40332 list --user NiGqBpUdMvAC68SxUeyYwVPyBCsqzNuof +policy-reader list-user -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list --user NiGqBpUdMvAC68SxUeyYwVPyBCsqzNuofL --service s3`, + RunE: runListCmd, +} + +var errUnknownChainNameType = errors.New("unknown chain-name") + +const ( + userFlag = "user" + namespaceFlag = "namespace" + decodeChainFlag = "decode-chain" + decodeIDFlag = "decode-id" +) + +const methodIteratorChainsByPrefix = "iteratorChainsByPrefix" + +func initListUserCmd() { + listUserCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc) + listUserCmd.Flags().String(apeCmd.ChainNameFlag, "", apeCmd.ChainNameFlagDesc) + listUserCmd.Flags().String(userFlag, "", "User address or name in frostfsid contract (if name is provided than 'namespace' should be set too)") + listUserCmd.Flags().String(namespaceFlag, "", "Namespace where user name will be looked up") + listUserCmd.Flags().Bool(decodeChainFlag, false, "Use this flag to decode chain") + listUserCmd.Flags().Bool(decodeIDFlag, false, "Use this flag to additionally decode chain id (without --decode-chain no take effect)") + + _ = listUserCmd.MarkFlagRequired(userFlag) +} + +func runListCmd(cmd *cobra.Command, _ []string) error { + chainName := parseChainName(cmd) + namespace := parseNamespace(cmd) + + inv, policyHash, ffsidCli := initReaders(cmd) + user, _ := cmd.Flags().GetString(userFlag) + subj, err := resolveSubject(ffsidCli, namespace, user) + commonCmd.ExitOnErr(cmd, "can't resolve frostfsid subject: %w", err) + + printSubject(cmd, subj) + + res, err := commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, big.NewInt(int64(policycontract.Namespace)), subj.Namespace, string(chainName)) + commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err) + + decodeChain, _ := cmd.Flags().GetBool(decodeChainFlag) + decodeID, _ := cmd.Flags().GetBool(decodeIDFlag) + + cmd.Printf("\nnamespace policies: %d\n", len(res)) + err = output.PrintChains(cmd, res, decodeChain, decodeID) + commonCmd.ExitOnErr(cmd, "can't print chains: %w", err) + + userEntity := big.NewInt(int64(policycontract.User)) + userEntityName := fmt.Sprintf("%s:%s", subj.Namespace, subj.PrimaryKey.Address()) + + res, err = commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, userEntity, userEntityName, string(chainName)) + commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err) + + cmd.Printf("\nuser policies: %d\n", len(res)) + err = output.PrintChains(cmd, res, decodeChain, decodeID) + commonCmd.ExitOnErr(cmd, "can't print chains: %w", err) + + cmd.Printf("\ngroup policies: %d\n", len(subj.Groups)) + + groupEntity := big.NewInt(int64(policycontract.Group)) + for _, group := range subj.Groups { + groupEntityName := fmt.Sprintf("%s:%d", group.Namespace, group.ID) + + res, err = commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, groupEntity, groupEntityName, string(chainName)) + commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err) + + cmd.Printf("user group '%s' (id: %d) policies: %d\n", group.Name, group.ID, len(res)) + err = output.PrintChains(cmd, res, decodeChain, decodeID) + commonCmd.ExitOnErr(cmd, "can't print chains: %w", err) + } + + return nil +} + +func resolveSubject(ffsid *ffsidclient.Client, namespace, userName string) (*ffsidclient.SubjectExtended, error) { + if userHash, err := neoflags.ParseAddress(userName); err == nil { + subj, err := ffsid.GetSubject(userHash) + if err != nil { + return nil, err + } + + return ffsid.GetSubjectExtended(subj.PrimaryKey.GetScriptHash()) + } + + subj, err := ffsid.GetSubjectByName(namespace, userName) + if err != nil { + return nil, err + } + + return ffsid.GetSubjectExtended(subj.PrimaryKey.GetScriptHash()) +} + +func parseChainName(cmd *cobra.Command) apechain.Name { + chainName, _ := cmd.Flags().GetString(apeCmd.ChainNameFlag) + + switch chainName { + case "": + return "" + case "s3": + return apechain.S3 + case "ingress": + return apechain.Ingress + } + + commonCmd.ExitOnErr(cmd, "can't parse chain-name: %w", errUnknownChainNameType) + panic("unreachable") +} + +func parseNamespace(cmd *cobra.Command) string { + namespace, _ := cmd.Flags().GetString(namespaceFlag) + if namespace == "root" { + namespace = "" + } + + return namespace +} + +func printSubject(cmd *cobra.Command, subj *ffsidclient.SubjectExtended) { + cmd.Println("subject:") + cmd.Printf("\tnamespace: '%s'\n", subj.Namespace) + cmd.Printf("\tname: '%s'\n", subj.Name) + cmd.Printf("\tkey: '%s'\n", hex.EncodeToString(subj.PrimaryKey.Bytes())) + + cmd.Printf("\tadditional keys:\n") + for _, key := range subj.AdditionalKeys { + cmd.Printf("\t\t%s\n", hex.EncodeToString(key.Bytes())) + } + + cmd.Printf("\tclaims:\n") + for k, v := range subj.KV { + cmd.Printf("\t\t%s: '%s'\n", k, v) + } + cmd.Printf("\tgroups:\n") + for _, gr := range subj.Groups { + cmd.Printf("\t\t%d: '%s'\n", gr.ID, gr.Name) + } +} + +func initReaders(cmd *cobra.Command) (*invoker.Invoker, util.Uint160, *ffsidclient.Client) { + endpoint := viper.GetString(commonflags.EndpointFlag) + + rpcCli, err := rpcclient.New(cmd.Context(), endpoint, rpcclient.Options{}) + commonCmd.ExitOnErr(cmd, "can't init rpc client: %w", err) + + inv := invoker.New(rpcCli, nil) + nnsCs, err := helper.GetContractByID(management.NewReader(inv), 1) + commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err) + + policyHashStr, _ := cmd.Flags().GetString(policyHashFlag) + policyHash, err := helper.NNSResolveHash(inv, nnsCs.Hash, policyHashStr) + commonCmd.ExitOnErr(cmd, "can't resolve NNS policy contract: %w", err) + + frostfsidHashStr, _ := cmd.Flags().GetString(frostfsidHashFlag) + frostfsidHash, err := helper.NNSResolveHash(inv, nnsCs.Hash, frostfsidHashStr) + commonCmd.ExitOnErr(cmd, "can't resolve NNS frostfsid contract: %w", err) + + acc, err := wallet.NewAccount() + commonCmd.ExitOnErr(cmd, "can't create new account: %w", err) + + ffsidCli, err := ffsidclient.New(rpcCli, acc, frostfsidHash, ffsidclient.Options{}) + commonCmd.ExitOnErr(cmd, "can't init frostfsid client: %w", err) + + return inv, policyHash, ffsidCli +} diff --git a/cmd/frostfs-adm/internal/modules/morph/ape/chains/root.go b/cmd/frostfs-adm/internal/modules/morph/ape/chains/root.go new file mode 100644 index 00000000..31e1217d --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/chains/root.go @@ -0,0 +1,32 @@ +package chains + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var Cmd = &cobra.Command{ + Use: "chains", + Short: "Chain related policy operations", + Long: "Chain related policy operations. Complex scenarios like: list all user chains (including groups, namespaces).", + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag)) + }, +} + +const ( + policyHashFlag = "policy-hash" + frostfsidHashFlag = "frostfsid-hash" +) + +func init() { + Cmd.PersistentFlags().String(policyHashFlag, "policy.frostfs", "NNS name or script hash of policy contract") + Cmd.PersistentFlags().String(frostfsidHashFlag, "frostfsid.frostfs", "NNS name or script hash of frostfsid contract") + + Cmd.AddCommand(listUserCmd) + initListUserCmd() + + Cmd.AddCommand(listContainerCmd) + initListContainerCmd() +} diff --git a/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_chain_names.go b/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_chain_names.go new file mode 100644 index 00000000..659a0636 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_chain_names.go @@ -0,0 +1,74 @@ +package raw + +import ( + "encoding/base64" + + "git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var listChainNamesCmd = &cobra.Command{ + Use: "list-chain-names", + Short: "Invoke 'listChainNames' method", + Long: "Invoke 'listChainNames' method in policy contract and print results to stdout", + Example: `raw -r http://localhost:40332 list-chain-names --kind n --name '' +raw -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list-chain-names --kind c --name 7h7NcXcF6k6b1yidqEHc1jkyXUm1MfUDrrTuHAefhiDe`, + RunE: runListChainNamesCmd, +} + +const ( + nameFlag = "name" + nameBase64Flag = "name-base64" +) + +func initListChainNamesCmd() { + listChainNamesCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc) + listChainNamesCmd.Flags().String(kindFlag, "n", "Target kind (1-byte) to list (n(namespace)/c(container)/g(group)/u(user)/i(iam))") + listChainNamesCmd.Flags().String(nameFlag, "", "Target name to list") + listChainNamesCmd.Flags().Bool(nameBase64Flag, false, "Use this flag if you provide name in base64 format") + + _ = listChainNamesCmd.MarkFlagRequired(kindFlag) + _ = listChainNamesCmd.MarkFlagRequired(nameFlag) +} + +func runListChainNamesCmd(cmd *cobra.Command, _ []string) error { + kind, _ := cmd.Flags().GetString(kindFlag) + entity, err := parseTargetKind(kind) + commonCmd.ExitOnErr(cmd, "can't parse target kind: %w", err) + + entityName, err := parseEntityName(cmd) + commonCmd.ExitOnErr(cmd, "can't parse name: %w", err) + + inv, policyHash := initPolicyReader(cmd) + res, err := commonclient.ReadIteratorItems(inv, 100, policyHash, methodListChainNames, entity, entityName) + commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err) + + cmd.Printf("%s target chain names: %d\n", kind, len(res)) + + for _, re := range res { + bytes, err := re.TryBytes() + commonCmd.ExitOnErr(cmd, "can't parse result: %w", err) + + cmd.Printf("%s\t(base64: '%s')\n", string(bytes), base64.StdEncoding.EncodeToString(bytes)) + } + + return nil +} + +func parseEntityName(cmd *cobra.Command) ([]byte, error) { + entityNameStr, _ := cmd.Flags().GetString(nameFlag) + var entityName []byte + if viper.GetBool(nameBase64Flag) { + return base64.StdEncoding.DecodeString(entityNameStr) + } + + if entityNameStr == "root" { + entityNameStr = "" + } + entityName = []byte(entityNameStr) + + return entityName, nil +} diff --git a/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_chains_by_prefix.go b/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_chains_by_prefix.go new file mode 100644 index 00000000..6534c143 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_chains_by_prefix.go @@ -0,0 +1,71 @@ +package raw + +import ( + "encoding/base64" + + "git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape/raw/output" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + "github.com/spf13/cobra" +) + +var listChainsByPrefixCmd = &cobra.Command{ + Use: "list-chains-by-prefix", + Short: "Invoke 'iteratorChainsByPrefix' method", + Long: "Invoke 'iteratorChainsByPrefix' method in policy contract and print results to stdout", + Example: `raw -r http://localhost:40332 list-chains-by-prefix --kind n --name '' +raw -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list-chains-by-prefix --kind c --name 7h7NcXcF6k6b1yidqEHc1jkyXUm1MfUDrrTuHAefhiDe`, + RunE: runListChainsByPrefixCmd, +} + +const ( + prefixFlag = "prefix" + prefixBase64Flag = "prefix-base64" + decodeChainFlag = "decode-chain" + decodeIDFlag = "decode-id" +) + +func initListChainsByPrefixCmd() { + listChainsByPrefixCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc) + listChainsByPrefixCmd.Flags().String(kindFlag, "n", "Target kind (1-byte) to list (n(namespace)/c(container)/g(group)/u(user)/i(iam))") + listChainsByPrefixCmd.Flags().String(nameFlag, "", "Target name to list") + listChainsByPrefixCmd.Flags().String(prefixFlag, "", "Prefix to list") + listChainsByPrefixCmd.Flags().Bool(prefixBase64Flag, false, "Use this flag if you provide prefix in base64 format") + listChainsByPrefixCmd.Flags().Bool(nameBase64Flag, false, "Use this flag if you provide name in base64 format") + listChainsByPrefixCmd.Flags().Bool(decodeChainFlag, false, "Use this flag to decode chain") + listChainsByPrefixCmd.Flags().Bool(decodeIDFlag, false, "Use this flag to additionally decode chain id (without --decode-chain no take effect)") + + _ = listChainsByPrefixCmd.MarkFlagRequired(kindFlag) + _ = listChainsByPrefixCmd.MarkFlagRequired(nameFlag) +} + +func runListChainsByPrefixCmd(cmd *cobra.Command, _ []string) error { + kind, _ := cmd.Flags().GetString(kindFlag) + entity, err := parseTargetKind(kind) + commonCmd.ExitOnErr(cmd, "can't parse target kind: %w", err) + + entityName, err := parseEntityName(cmd) + commonCmd.ExitOnErr(cmd, "can't parse name: %w", err) + + prefixStr, _ := cmd.Flags().GetString(prefixFlag) + prefixBase64, _ := cmd.Flags().GetBool(prefixBase64Flag) + var prefix []byte + if prefixBase64 { + if prefix, err = base64.StdEncoding.DecodeString(prefixStr); err != nil { + return err + } + } else { + prefix = []byte(prefixStr) + } + + inv, policyHash := initPolicyReader(cmd) + res, err := commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, entity, entityName, prefix) + commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err) + + cmd.Printf("%s target chains names: %d\n", kind, len(res)) + + decodeChain, _ := cmd.Flags().GetBool(decodeChainFlag) + decodeID, _ := cmd.Flags().GetBool(decodeIDFlag) + return output.PrintChains(cmd, res, decodeChain, decodeID) +} diff --git a/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_targets.go b/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_targets.go new file mode 100644 index 00000000..c5174605 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_targets.go @@ -0,0 +1,88 @@ +package raw + +import ( + "encoding/base64" + "fmt" + "math/big" + + "git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var listTargetsCmd = &cobra.Command{ + Use: "list-targets", + Short: "Invoke 'listTargets' method", + Long: "Invoke 'listTargets' method in policy contract and print results to stdout", + Example: `raw -r http://localhost:40332 list-targets +raw -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list-targets --kind c`, + RunE: runListTargetsCmd, +} + +const ( + kindFlag = "kind" +) + +const ( + methodIteratorChainsByPrefix = "iteratorChainsByPrefix" + methodListTargets = "listTargets" + methodListChainNames = "listChainNames" +) + +func initListTargetsCmd() { + listTargetsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc) + listTargetsCmd.Flags().String(kindFlag, "n", "Target kind (1-byte) to list (n(namespace)/c(container)/g(group)/u(user)/i(iam))") +} + +func runListTargetsCmd(cmd *cobra.Command, _ []string) error { + kind, _ := cmd.Flags().GetString(kindFlag) + entity, err := parseTargetKind(kind) + commonCmd.ExitOnErr(cmd, "can't parse target kind: %w", err) + + inv, policyHash := initPolicyReader(cmd) + res, err := commonclient.ReadIteratorItems(inv, 100, policyHash, methodListTargets, entity) + commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err) + + cmd.Printf("%s targets: %d\n", kind, len(res)) + + for _, re := range res { + bytes, err := re.TryBytes() + commonCmd.ExitOnErr(cmd, "can't parse result: %w", err) + + cmd.Printf("%s\t(base64: '%s')\n", string(bytes), base64.StdEncoding.EncodeToString(bytes)) + } + + return nil +} + +func parseTargetKind(typ string) (*big.Int, error) { + if len(typ) != 1 { + return nil, fmt.Errorf("invalid type: %s", typ) + } + + return big.NewInt(int64(typ[0])), nil +} + +func initPolicyReader(cmd *cobra.Command) (*invoker.Invoker, util.Uint160) { + endpoint := viper.GetString(commonflags.EndpointFlag) + + rpcCli, err := rpcclient.New(cmd.Context(), endpoint, rpcclient.Options{}) + commonCmd.ExitOnErr(cmd, "can't init rpc client: %w", err) + + inv := invoker.New(rpcCli, nil) + nnsCs, err := helper.GetContractByID(management.NewReader(inv), 1) + commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err) + + policyHashStr, _ := cmd.Flags().GetString(policyHashFlag) + policyHash, err := helper.NNSResolveHash(inv, nnsCs.Hash, policyHashStr) + commonCmd.ExitOnErr(cmd, "can't resolve NNS policy contract: %w", err) + + return inv, policyHash +} diff --git a/cmd/frostfs-adm/internal/modules/morph/ape/raw/output/chains.go b/cmd/frostfs-adm/internal/modules/morph/ape/raw/output/chains.go new file mode 100644 index 00000000..3fa87521 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/raw/output/chains.go @@ -0,0 +1,56 @@ +package output + +import ( + "encoding/base64" + + apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/spf13/cobra" +) + +const ( + minPrintable = 32 + maxPrintable = 127 +) + +func PrintChains(cmd *cobra.Command, list []stackitem.Item, decodeChain, decodeID bool) error { + for _, item := range list { + bytes, err := item.TryBytes() + if err != nil { + return err + } + + if !decodeChain { + cmd.Printf("\t%s\n", string(bytes)) + continue + } + + var chain apechain.Chain + if err = chain.DecodeBytes(bytes); err != nil { + cmd.PrintErrf("invalid chain format: %s\n", base64.StdEncoding.EncodeToString(bytes)) + continue + } + + raw, err := chain.MarshalJSON() + if err != nil { + return err + } + + if decodeID { + var printableID string + + for _, r := range string(chain.ID) { + if minPrintable <= r && r <= maxPrintable { + printableID += string(r) + } else { + printableID += "." + } + } + cmd.Printf("\t%s - %s\n", printableID, string(raw)) + } else { + cmd.Printf("\t%s\n", string(raw)) + } + } + + return nil +} diff --git a/cmd/frostfs-adm/internal/modules/morph/ape/raw/root.go b/cmd/frostfs-adm/internal/modules/morph/ape/raw/root.go new file mode 100644 index 00000000..0e673461 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/raw/root.go @@ -0,0 +1,32 @@ +package raw + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var Cmd = &cobra.Command{ + Use: "raw", + Short: "FrostFS policy contract raw reader", + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag)) + }, +} + +const ( + policyHashFlag = "policy-hash" +) + +func init() { + Cmd.PersistentFlags().String(policyHashFlag, "policy.frostfs", "NNS name or script hash of policy contract") + + Cmd.AddCommand(listTargetsCmd) + initListTargetsCmd() + + Cmd.AddCommand(listChainNamesCmd) + initListChainNamesCmd() + + Cmd.AddCommand(listChainsByPrefixCmd) + initListChainsByPrefixCmd() +} diff --git a/cmd/frostfs-adm/internal/modules/morph/ape/root.go b/cmd/frostfs-adm/internal/modules/morph/ape/root.go index a4746cd2..cdf413f3 100644 --- a/cmd/frostfs-adm/internal/modules/morph/ape/root.go +++ b/cmd/frostfs-adm/internal/modules/morph/ape/root.go @@ -1,6 +1,10 @@ package ape -import "github.com/spf13/cobra" +import ( + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape/chains" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape/raw" + "github.com/spf13/cobra" +) var Cmd = &cobra.Command{ Use: "ape", @@ -8,6 +12,9 @@ var Cmd = &cobra.Command{ } func init() { + Cmd.AddCommand(raw.Cmd) + Cmd.AddCommand(chains.Cmd) + initAddRuleChainCmd() initRemoveRuleChainCmd() initListRuleChainsCmd() diff --git a/cmd/frostfs-adm/internal/modules/morph/helper/initialize.go b/cmd/frostfs-adm/internal/modules/morph/helper/initialize.go index 961ceba5..d509a502 100644 --- a/cmd/frostfs-adm/internal/modules/morph/helper/initialize.go +++ b/cmd/frostfs-adm/internal/modules/morph/helper/initialize.go @@ -10,6 +10,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" @@ -158,6 +159,40 @@ func NNSResolveHash(inv *invoker.Invoker, nnsHash util.Uint160, domain string) ( return ParseNNSResolveResult(item) } +// NNSResolveContainerDomain returns errMissingNNSRecord if invocation fault exception contains "token not found". +func NNSResolveContainerDomain(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (cid.ID, error) { + item, err := NNSResolve(inv, nnsHash, domain) + if err != nil { + return cid.ID{}, err + } + + return parseNNSResolveResultCID(item) +} + +func parseNNSResolveResultCID(res stackitem.Item) (cid.ID, error) { + arr, ok := res.Value().([]stackitem.Item) + if !ok { + arr = []stackitem.Item{res} + } + if _, ok := res.Value().(stackitem.Null); ok || len(arr) == 0 { + return cid.ID{}, errors.New("NNS record is missing") + } + + var cnrID cid.ID + + for i := range arr { + bs, err := arr[i].TryBytes() + if err != nil { + continue + } + + if err = cnrID.DecodeString(string(bs)); err == nil { + return cnrID, nil + } + } + return cid.ID{}, errors.New("no valid CIDs are found") +} + func DomainOf(contract string) string { return contract + ".frostfs" }