From 32f4e72e6a2c0fcc5569b131021d1ada5f26dcfb Mon Sep 17 00:00:00 2001 From: Anton Nikiforov Date: Wed, 20 Dec 2023 15:44:12 +0300 Subject: [PATCH] [#834] adm: Add commands to invoke methods of `policy` contract Signed-off-by: Anton Nikiforov --- cmd/frostfs-adm/internal/modules/morph/ape.go | 162 ++++++++++++++++++ .../internal/modules/morph/ape_util.go | 103 +++++++++++ .../internal/modules/morph/root.go | 4 + cmd/frostfs-cli/modules/util/ape.go | 33 ++++ 4 files changed, 302 insertions(+) create mode 100644 cmd/frostfs-adm/internal/modules/morph/ape.go create mode 100644 cmd/frostfs-adm/internal/modules/morph/ape_util.go diff --git a/cmd/frostfs-adm/internal/modules/morph/ape.go b/cmd/frostfs-adm/internal/modules/morph/ape.go new file mode 100644 index 000000000..6dd2bd185 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape.go @@ -0,0 +1,162 @@ +package morph + +import ( + "bytes" + "encoding/json" + + parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const ( + namespaceTarget = "namespace" + containerTarget = "container" + jsonFlag = "json" + jsonFlagDesc = "Output rule chains in JSON format" + chainIDFlag = "chain-id" + chainIDDesc = "Rule chain ID" + ruleFlag = "rule" + ruleFlagDesc = "Rule chain in text format" + ruleJSONFlag = "rule-json" + ruleJSONFlagDesc = "Chain rule in JSON format or path to the file" + targetNameFlag = "target-name" + targetNameDesc = "Resource name in APE resource name format" + targetTypeFlag = "target-type" + targetTypeDesc = "Resource type(container/namespace)" +) + +var ( + apeCmd = &cobra.Command{ + Use: "ape", + Short: "Section for APE configuration commands", + } + + addRuleChainCmd = &cobra.Command{ + Use: "add-rule-chain", + Short: "Add rule chain", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + }, + Run: addRuleChain, + } + + removeRuleChainCmd = &cobra.Command{ + Use: "rm-rule-chain", + Short: "Remove rule chain", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + }, + Run: removeRuleChain, + } + + listRuleChainsCmd = &cobra.Command{ + Use: "list-rule-chains", + Short: "List rule chains", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + }, + Run: listRuleChains, + } +) + +func initAddRuleChainCmd() { + apeCmd.AddCommand(addRuleChainCmd) + + addRuleChainCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) + addRuleChainCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc) + + addRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc) + _ = addRuleChainCmd.MarkFlagRequired(targetTypeFlag) + addRuleChainCmd.Flags().String(targetNameFlag, "", targetNameDesc) + _ = addRuleChainCmd.MarkFlagRequired(targetNameFlag) + + addRuleChainCmd.Flags().String(chainIDFlag, "", chainIDDesc) + _ = addRuleChainCmd.MarkFlagRequired(chainIDFlag) + addRuleChainCmd.Flags().String(ruleFlag, "", ruleFlagDesc) + addRuleChainCmd.Flags().String(ruleJSONFlag, "", ruleJSONFlagDesc) + + addRuleChainCmd.MarkFlagsMutuallyExclusive(ruleFlag, ruleJSONFlag) +} + +func initRemoveRuleChainCmd() { + apeCmd.AddCommand(removeRuleChainCmd) + + removeRuleChainCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) + removeRuleChainCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc) + + removeRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc) + _ = removeRuleChainCmd.MarkFlagRequired(targetTypeFlag) + removeRuleChainCmd.Flags().String(targetNameFlag, "", targetNameDesc) + _ = removeRuleChainCmd.MarkFlagRequired(targetNameFlag) + removeRuleChainCmd.Flags().String(chainIDFlag, "", chainIDDesc) + _ = removeRuleChainCmd.MarkFlagRequired(chainIDFlag) +} + +func initListRuleChainsCmd() { + apeCmd.AddCommand(listRuleChainsCmd) + + listRuleChainsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) + listRuleChainsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc) + listRuleChainsCmd.Flags().StringP(targetTypeFlag, "t", "", targetTypeDesc) + _ = listRuleChainsCmd.MarkFlagRequired(targetTypeFlag) + listRuleChainsCmd.Flags().String(targetNameFlag, "", targetNameDesc) + _ = listRuleChainsCmd.MarkFlagRequired(targetNameFlag) + listRuleChainsCmd.Flags().Bool(jsonFlag, false, jsonFlagDesc) +} + +func addRuleChain(cmd *cobra.Command, _ []string) { + chain := parseChain(cmd) + target := parseTarget(cmd) + pci, ac := newPolicyContractInterface(cmd) + h, vub, err := pci.AddMorphRuleChain(apechain.Ingress, target, chain) + cmd.Println("Waiting for transaction to persist...") + _, err = ac.Wait(h, vub, err) + commonCmd.ExitOnErr(cmd, "add rule chain error: %w", err) + cmd.Println("Rule chain added successfully") +} + +func removeRuleChain(cmd *cobra.Command, _ []string) { + chainID := parseChainID(cmd) + target := parseTarget(cmd) + pci, ac := newPolicyContractInterface(cmd) + h, vub, err := pci.RemoveMorphRuleChain(apechain.Ingress, target, chainID) + cmd.Println("Waiting for transaction to persist...") + _, err = ac.Wait(h, vub, err) + commonCmd.ExitOnErr(cmd, "remove rule chain error: %w", err) + cmd.Println("Rule chain removed successfully") +} + +func listRuleChains(cmd *cobra.Command, _ []string) { + target := parseTarget(cmd) + pci, _ := newPolicyContractInterface(cmd) + chains, err := pci.ListMorphRuleChains(apechain.Ingress, target) + commonCmd.ExitOnErr(cmd, "list rule chains error: %w", err) + if len(chains) == 0 { + return + } + + toJSON, _ := cmd.Flags().GetBool(jsonFlag) + if toJSON { + prettyJSONFormat(cmd, chains) + } else { + for _, c := range chains { + parseutil.PrintHumanReadableAPEChain(cmd, c) + } + } +} + +func prettyJSONFormat(cmd *cobra.Command, chains []*apechain.Chain) { + wr := bytes.NewBufferString("") + data, err := json.Marshal(chains) + if err == nil { + err = json.Indent(wr, data, "", " ") + } + commonCmd.ExitOnErr(cmd, "print rule chain error: %w", err) + cmd.Println(wr) +} diff --git a/cmd/frostfs-adm/internal/modules/morph/ape_util.go b/cmd/frostfs-adm/internal/modules/morph/ape_util.go new file mode 100644 index 000000000..e50522f2d --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/ape_util.go @@ -0,0 +1,103 @@ +package morph + +import ( + "encoding/json" + "fmt" + "os" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" + parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" + policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" + morph "git.frostfs.info/TrueCloudLab/policy-engine/pkg/morph/policy" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "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" +) + +func parseTarget(cmd *cobra.Command) policyengine.Target { + var targetType policyengine.TargetType + typ, _ := cmd.Flags().GetString(targetTypeFlag) + switch typ { + case namespaceTarget: + targetType = policyengine.Namespace + case containerTarget: + targetType = policyengine.Container + default: + commonCmd.ExitOnErr(cmd, "read target type error: %w", fmt.Errorf("unknown target type")) + } + name, _ := cmd.Flags().GetString(targetNameFlag) + + return policyengine.Target{ + Name: name, + Type: targetType, + } +} + +func parseChainID(cmd *cobra.Command) apechain.ID { + chainID, _ := cmd.Flags().GetString(chainIDFlag) + if chainID == "" { + commonCmd.ExitOnErr(cmd, "read chain id error: %w", + fmt.Errorf("chain id cannot be empty")) + } + return apechain.ID(chainID) +} + +func parseChain(cmd *cobra.Command) *apechain.Chain { + chain := new(apechain.Chain) + + if ruleStmt, _ := cmd.Flags().GetString(ruleFlag); ruleStmt != "" { + parseErr := parseutil.ParseAPEChain(chain, []string{ruleStmt}) + commonCmd.ExitOnErr(cmd, "ape chain parser error: %w", parseErr) + } else if ruleJSON, _ := cmd.Flags().GetString(ruleJSONFlag); ruleJSON != "" { + var rule []byte + if _, err := os.Stat(ruleJSON); err == nil { + rule, err = os.ReadFile(ruleJSON) + commonCmd.ExitOnErr(cmd, "read file error: %w", err) + } else { + rule = []byte(ruleJSON) + if !json.Valid(rule) { + commonCmd.ExitOnErr(cmd, "read raw rule error: %w", + fmt.Errorf("invalid JSON")) + } + } + err := chain.DecodeBytes(rule) + commonCmd.ExitOnErr(cmd, "chain decode error: %w", err) + } else { + commonCmd.ExitOnErr(cmd, "", fmt.Errorf("rule is not passed")) + } + + chain.ID = parseChainID(cmd) + + return chain +} + +func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *actor.Actor) { + v := viper.GetViper() + c, err := getN3Client(v) + commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err) + + walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag)) + wallets, err := getAlphabetWallets(v, walletDir) + commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err) + + committeeAcc, err := getWalletAccount(wallets[0], committeeAccountName) + commonCmd.ExitOnErr(cmd, "can't find committee account: %w", err) + + ac, err := newActor(c, committeeAcc) + commonCmd.ExitOnErr(cmd, "can't create actor: %w", err) + + inv := &ac.Invoker + var ch util.Uint160 + r := management.NewReader(inv) + nnsCs, err := r.GetContractByID(1) + commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err) + + ch, err = nnsResolveHash(inv, nnsCs.Hash, policyContract+".frostfs") + commonCmd.ExitOnErr(cmd, "unable to resolve policy contract hash: %w", err) + + return morph.NewContractStorage(ac, ch), ac +} diff --git a/cmd/frostfs-adm/internal/modules/morph/root.go b/cmd/frostfs-adm/internal/modules/morph/root.go index 1a0cd8c8b..475bf4b06 100644 --- a/cmd/frostfs-adm/internal/modules/morph/root.go +++ b/cmd/frostfs-adm/internal/modules/morph/root.go @@ -263,6 +263,10 @@ func init() { initRefillGasCmd() initDepositoryNotaryCmd() initNetmapCandidatesCmd() + RootCmd.AddCommand(apeCmd) + initAddRuleChainCmd() + initRemoveRuleChainCmd() + initListRuleChainsCmd() } func initNetmapCandidatesCmd() { diff --git a/cmd/frostfs-cli/modules/util/ape.go b/cmd/frostfs-cli/modules/util/ape.go index c5f8526e2..79966613d 100644 --- a/cmd/frostfs-cli/modules/util/ape.go +++ b/cmd/frostfs-cli/modules/util/ape.go @@ -3,11 +3,13 @@ package util import ( "errors" "fmt" + "strconv" "strings" apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" "github.com/flynn-archive/go-shlex" + "github.com/spf13/cobra" ) var ( @@ -20,6 +22,37 @@ var ( errUnknownCondObjectType = errors.New("condition object type is not recognized") ) +// PrintHumanReadableAPEChain print APE chain rules. +func PrintHumanReadableAPEChain(cmd *cobra.Command, chain *apechain.Chain) { + cmd.Println("ChainID: " + string(chain.ID)) + cmd.Println("Rules:") + for _, rule := range chain.Rules { + cmd.Println("\tStatus: " + rule.Status.String()) + cmd.Println("\tAny: " + strconv.FormatBool(rule.Any)) + cmd.Println("\tConditions:") + for _, c := range rule.Condition { + var ot string + switch c.Object { + case apechain.ObjectResource: + ot = "Resource" + case apechain.ObjectRequest: + ot = "Request" + default: + panic("unknown object type") + } + cmd.Println(fmt.Sprintf("\t\t%s %s %s %s", ot, c.Key, c.Op, c.Value)) + } + cmd.Println("\tActions:\tInverted:" + strconv.FormatBool(rule.Actions.Inverted)) + for _, name := range rule.Actions.Names { + cmd.Println("\t\t" + name) + } + cmd.Println("\tResources:\tInverted:" + strconv.FormatBool(rule.Resources.Inverted)) + for _, name := range rule.Resources.Names { + cmd.Println("\t\t" + name) + } + } +} + // ParseAPEChain parses APE chain rules. func ParseAPEChain(chain *apechain.Chain, rules []string) error { if len(rules) == 0 {