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 000000000..307012a52 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/chains/list_container.go @@ -0,0 +1,99 @@ +package chains + +import ( + "fmt" + "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" + 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" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ns" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +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`, + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(containerFlag, cmd.Flags().Lookup(containerFlag)) + _ = viper.BindPFlag(namespaceFlag, cmd.Flags().Lookup(namespaceFlag)) + _ = viper.BindPFlag(decodeChainFlag, cmd.Flags().Lookup(decodeChainFlag)) + _ = viper.BindPFlag(decodeIDFlag, cmd.Flags().Lookup(decodeIDFlag)) + }, + 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, err := parseChainName(viper.GetString(apeCmd.ChainNameFlag)) + commonCmd.ExitOnErr(cmd, "can't parse chain-name: %w", err) + + endpoint := viper.GetString(commonflags.EndpointFlag) + namespace := viper.GetString(namespaceFlag) + + cnrID, err := ResolveContainerID(endpoint, namespace, viper.GetString(containerFlag)) + commonCmd.ExitOnErr(cmd, "can't resolve container id: %w", err) + + inv, policyHash, _ := initReaders(cmd) + 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 := viper.GetBool(decodeChainFlag) + decodeID := viper.GetBool(decodeIDFlag) + + cmd.Println("cid:", cnrID) + cmd.Printf("namespace '%s' policies: %d\n", namespace, 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.String(), string(chainName)) + commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err) + + cmd.Printf("container policies: %d\n", len(res)) + return output.PrintChains(cmd, res, decodeChain, decodeID) +} + +// ResolveContainerID determine container id by resolving NNS name. +func ResolveContainerID(endpoint, namespace, containerName string) (cid.ID, error) { + var cnrID cid.ID + if err := cnrID.DecodeString(containerName); err == nil { + return cnrID, nil + } + + var domain container.Domain + domain.SetName(containerName) + if namespace != "" { + domain.SetZone(namespace + ".ns") + } + + var nns ns.NNS + if err := nns.Dial(endpoint); err != nil { + return cid.ID{}, fmt.Errorf("dial nns %s: %w", endpoint, err) + } + defer nns.Close() + + return nns.ResolveContainerDomain(domain) +} 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 000000000..ca00af0c0 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/chains/list_user.go @@ -0,0 +1,177 @@ +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`, + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(userFlag, cmd.Flags().Lookup(userFlag)) + _ = viper.BindPFlag(namespaceFlag, cmd.Flags().Lookup(namespaceFlag)) + _ = viper.BindPFlag(decodeChainFlag, cmd.Flags().Lookup(decodeChainFlag)) + _ = viper.BindPFlag(decodeIDFlag, cmd.Flags().Lookup(decodeIDFlag)) + }, + RunE: runListCmd, +} + +var errUnknownServiceType = 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, err := parseChainName(viper.GetString(apeCmd.ChainNameFlag)) + commonCmd.ExitOnErr(cmd, "can't parse chain-name: %w", err) + + inv, policyHash, ffsidCli := initReaders(cmd) + subj, err := resolveSubject(ffsidCli, viper.GetString(namespaceFlag), viper.GetString(userFlag)) + 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 := viper.GetBool(decodeChainFlag) + decodeID := viper.GetBool(decodeIDFlag) + + cmd.Printf("user namespace '%s' policies: %d\n", subj.Namespace, 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("user policies: %d\n", len(res)) + err = output.PrintChains(cmd, res, decodeChain, decodeID) + commonCmd.ExitOnErr(cmd, "can't print chains: %w", err) + + cmd.Printf("user group 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(service string) (apechain.Name, error) { + switch service { + case "": + return "", nil + case "s3": + return apechain.S3, nil + case "ingress": + return apechain.Ingress, nil + } + + return "", errUnknownServiceType +} + +func printSubject(cmd *cobra.Command, subj *ffsidclient.SubjectExtended) { + cmd.Println("ns:", subj.Namespace) + cmd.Println("name:", subj.Name) + cmd.Println("key:", hex.EncodeToString(subj.PrimaryKey.Bytes())) + cmd.Println("claims:") + for k, v := range subj.KV { + cmd.Println(k, v) + } +} + +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) + + policyHash, err := helper.NNSResolveHash(inv, nnsCs.Hash, viper.GetString(policyHashFlag)) + commonCmd.ExitOnErr(cmd, "can't resolve NNS policy contract: %w", err) + + frostfsidHash, err := helper.NNSResolveHash(inv, nnsCs.Hash, viper.GetString(frostfsidHashFlag)) + 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 000000000..0aaedd278 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/chains/root.go @@ -0,0 +1,36 @@ +package chains + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" + apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape" + "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)) + _ = viper.BindPFlag(apeCmd.ChainNameFlag, cmd.Flags().Lookup(apeCmd.ChainNameFlag)) + _ = viper.BindPFlag(policyHashFlag, cmd.Flags().Lookup(policyHashFlag)) + _ = viper.BindPFlag(frostfsidHashFlag, cmd.Flags().Lookup(frostfsidHashFlag)) + }, +} + +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 000000000..2ecd01ddb --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_chain_names.go @@ -0,0 +1,79 @@ +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`, + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(kindFlag, cmd.Flags().Lookup(kindFlag)) + _ = viper.BindPFlag(nameFlag, cmd.Flags().Lookup(nameFlag)) + _ = viper.BindPFlag(nameBase64Flag, cmd.Flags().Lookup(nameBase64Flag)) + }, + 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 := viper.GetString(kindFlag) + entity, err := parseTargetKind(kind) + commonCmd.ExitOnErr(cmd, "can't parse target kind: %w", err) + + entityName, err := parseEntityName() + 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() ([]byte, error) { + entityNameStr := viper.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 000000000..e6a4ab68b --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_chains_by_prefix.go @@ -0,0 +1,77 @@ +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" + "github.com/spf13/viper" +) + +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`, + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(kindFlag, cmd.Flags().Lookup(kindFlag)) + _ = viper.BindPFlag(nameFlag, cmd.Flags().Lookup(nameFlag)) + _ = viper.BindPFlag(prefixFlag, cmd.Flags().Lookup(prefixFlag)) + _ = viper.BindPFlag(prefixBase64Flag, cmd.Flags().Lookup(prefixBase64Flag)) + _ = viper.BindPFlag(nameBase64Flag, cmd.Flags().Lookup(nameBase64Flag)) + _ = viper.BindPFlag(decodeChainFlag, cmd.Flags().Lookup(decodeChainFlag)) + _ = viper.BindPFlag(decodeIDFlag, cmd.Flags().Lookup(decodeIDFlag)) + }, + 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 { + typ := viper.GetString(kindFlag) + entity, err := parseTargetKind(typ) + commonCmd.ExitOnErr(cmd, "can't parse target kind: %w", err) + + entityName, err := parseEntityName() + commonCmd.ExitOnErr(cmd, "can't parse name: %w", err) + + prefixStr := viper.GetString(prefixFlag) + var prefix []byte + if viper.GetBool(prefixBase64Flag) { + 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", typ, len(res)) + return output.PrintChains(cmd, res, viper.GetBool(decodeChainFlag), viper.GetBool(decodeIDFlag)) +} 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 000000000..3edc314b3 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/raw/list_targets.go @@ -0,0 +1,90 @@ +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`, + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(kindFlag, cmd.Flags().Lookup(kindFlag)) + }, + 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 := viper.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) + + policyHash, err := helper.NNSResolveHash(inv, nnsCs.Hash, viper.GetString(policyHashFlag)) + 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 000000000..c76c71a24 --- /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.Println(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.Println(printableID, string(raw)) + } else { + cmd.Println(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 000000000..5ae3778af --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape/raw/root.go @@ -0,0 +1,34 @@ +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", + Long: "Helps reading policy information from contact in FrostFS network", + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag)) + _ = viper.BindPFlag(policyHashFlag, cmd.Flags().Lookup(policyHashFlag)) + }, +} + +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 a4746cd2c..cdf413f33 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/internal/common/ape/flags.go b/cmd/internal/common/ape/flags.go index d8b2e88a2..25319beda 100644 --- a/cmd/internal/common/ape/flags.go +++ b/cmd/internal/common/ape/flags.go @@ -7,7 +7,7 @@ const ( TargetNameFlag = "target-name" TargetNameFlagDesc = "Resource name in APE resource name format" TargetTypeFlag = "target-type" - TargetTypeFlagDesc = "Resource type(container/namespace)" + TargetTypeFlagDesc = "Resource type(container/namespace/group/user)" ChainIDFlag = "chain-id" ChainIDFlagDesc = "Chain id" ChainIDHexFlag = "chain-id-hex"