WIP: cli, adm: Improve add rule commands #990

Closed
aarifullin wants to merge 3 commits from aarifullin/frostfs-node:feat/cli_parse_json_any into master
5 changed files with 94 additions and 49 deletions

View file

@ -14,22 +14,22 @@ import (
) )
const ( const (
namespaceTarget = "namespace" namespaceTarget = "namespace"
containerTarget = "container" containerTarget = "container"
jsonFlag = "json" jsonFlag = "json"
jsonFlagDesc = "Output rule chains in JSON format" jsonFlagDesc = "Output rule chains in JSON format"
chainIDFlag = "chain-id" chainIDFlag = "chain-id"
chainIDDesc = "Rule chain ID" chainIDDesc = "Rule chain ID"
ruleFlag = "rule" ruleFlag = "rule"
ruleFlagDesc = "Rule chain in text format" ruleFlagDesc = "Rule chain in text format"
ruleJSONFlag = "rule-json" pathFlag = "path"
ruleJSONFlagDesc = "Chain rule in JSON format or path to the file" pathFlagDesc = "path to encoded chain in JSON or binary format"
targetNameFlag = "target-name" targetNameFlag = "target-name"
targetNameDesc = "Resource name in APE resource name format" targetNameDesc = "Resource name in APE resource name format"
targetTypeFlag = "target-type" targetTypeFlag = "target-type"
targetTypeDesc = "Resource type(container/namespace)" targetTypeDesc = "Resource type(container/namespace)"
addrAdminFlag = "addr" addrAdminFlag = "addr"
addrAdminDesc = "The address of the admins wallet" addrAdminDesc = "The address of the admins wallet"
) )
var ( var (
@ -97,10 +97,10 @@ func initAddRuleChainCmd() {
addRuleChainCmd.Flags().String(chainIDFlag, "", chainIDDesc) addRuleChainCmd.Flags().String(chainIDFlag, "", chainIDDesc)
_ = addRuleChainCmd.MarkFlagRequired(chainIDFlag) _ = addRuleChainCmd.MarkFlagRequired(chainIDFlag)
addRuleChainCmd.Flags().String(ruleFlag, "", ruleFlagDesc) addRuleChainCmd.Flags().StringArray(ruleFlag, []string{}, ruleFlagDesc)
addRuleChainCmd.Flags().String(ruleJSONFlag, "", ruleJSONFlagDesc) addRuleChainCmd.Flags().String(pathFlag, "", pathFlagDesc)
addRuleChainCmd.MarkFlagsMutuallyExclusive(ruleFlag, ruleJSONFlag) addRuleChainCmd.MarkFlagsMutuallyExclusive(ruleFlag, pathFlag)
} }
func initRemoveRuleChainCmd() { func initRemoveRuleChainCmd() {

View file

@ -1,9 +1,7 @@
package ape package ape
import ( import (
"encoding/json"
"fmt" "fmt"
"os"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
@ -52,29 +50,19 @@ func parseChainID(cmd *cobra.Command) apechain.ID {
func parseChain(cmd *cobra.Command) *apechain.Chain { func parseChain(cmd *cobra.Command) *apechain.Chain {
chain := new(apechain.Chain) chain := new(apechain.Chain)
if ruleStmt, _ := cmd.Flags().GetString(ruleFlag); ruleStmt != "" { if rules, _ := cmd.Flags().GetStringArray(ruleFlag); len(rules) > 0 {
parseErr := parseutil.ParseAPEChain(chain, []string{ruleStmt}) commonCmd.ExitOnErr(cmd, "parser error: %w", parseutil.ParseAPEChain(chain, rules))
commonCmd.ExitOnErr(cmd, "ape chain parser error: %w", parseErr) } else if encPath, _ := cmd.Flags().GetString(pathFlag); encPath != "" {
} else if ruleJSON, _ := cmd.Flags().GetString(ruleJSONFlag); ruleJSON != "" { commonCmd.ExitOnErr(cmd, "decode binary or json error: %w", parseutil.ParseAPEChainBinaryOrJSON(chain, encPath))
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 { } else {
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("rule is not passed")) commonCmd.ExitOnErr(cmd, "parser error: %w", fmt.Errorf("rule is not passed"))
} }
chain.ID = parseChainID(cmd) chain.ID = parseChainID(cmd)
cmd.Println("Parsed chain:")
parseutil.PrintHumanReadableAPEChain(cmd, chain)
return chain return chain
} }

View file

@ -30,6 +30,9 @@ Actions is a regular operations upon FrostFS containers/objects. Like `Object.Pu
In status section it is possible to use `allow`, `deny` or `deny:QuotaLimitReached` actions. In status section it is possible to use `allow`, `deny` or `deny:QuotaLimitReached` actions.
If a statement does not contain lexeme `any`, field `Any` is set to `false` by default. Otherwise, it is set
to `true`. Optionally, `all` can be used - it also sets `Any=false`.
It is prohibited to mix operation under FrostFS container and object in one rule. It is prohibited to mix operation under FrostFS container and object in one rule.
The same statement is equal for conditions and resources - one rule is for one type of items. The same statement is equal for conditions and resources - one rule is for one type of items.

View file

@ -2,6 +2,7 @@ package control
import ( import (
"encoding/hex" "encoding/hex"
"errors"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
@ -14,6 +15,7 @@ import (
const ( const (
ruleFlag = "rule" ruleFlag = "rule"
pathFlag = "path"
) )
var addRuleCmd = &cobra.Command{ var addRuleCmd = &cobra.Command{
@ -23,15 +25,13 @@ var addRuleCmd = &cobra.Command{
Example: `control add-rule --endpoint ... -w ... --address ... --chain-id ChainID --cid ... --rule "allow Object.Get *" Example: `control add-rule --endpoint ... -w ... --address ... --chain-id ChainID --cid ... --rule "allow Object.Get *"
--rule "deny Object.Get EbxzAdz5LB4uqxuz6crWKAumBNtZyK2rKsqQP7TdZvwr/*" --rule "deny Object.Get EbxzAdz5LB4uqxuz6crWKAumBNtZyK2rKsqQP7TdZvwr/*"
--rule "deny:QuotaLimitReached Object.Put Object.Resource:Department=HR *" --rule "deny:QuotaLimitReached Object.Put Object.Resource:Department=HR *"
control add-rule --endpoint ... -w ... --address ... --chain-id ChainID --cid ... --path some_chain.json
`, `,
Run: addRule, Run: addRule,
} }
func addRule(cmd *cobra.Command, _ []string) { func parseChain(cmd *cobra.Command) *apechain.Chain {
pk := key.Get(cmd)
target := parseTarget(cmd)
chainID, _ := cmd.Flags().GetString(chainIDFlag) chainID, _ := cmd.Flags().GetString(chainIDFlag)
hexEncoded, _ := cmd.Flags().GetBool(chainIDHexFlag) hexEncoded, _ := cmd.Flags().GetBool(chainIDHexFlag)
@ -43,20 +43,34 @@ func addRule(cmd *cobra.Command, _ []string) {
commonCmd.ExitOnErr(cmd, "can't decode chain ID as hex: %w", err) commonCmd.ExitOnErr(cmd, "can't decode chain ID as hex: %w", err)
} }
rule, _ := cmd.Flags().GetStringArray(ruleFlag)
chain := new(apechain.Chain) chain := new(apechain.Chain)
commonCmd.ExitOnErr(cmd, "parser error: %w", util.ParseAPEChain(chain, rule))
chain.ID = apechain.ID(chainIDRaw) chain.ID = apechain.ID(chainIDRaw)
serializedChain := chain.Bytes()
if rules, _ := cmd.Flags().GetStringArray(ruleFlag); len(rules) > 0 {
commonCmd.ExitOnErr(cmd, "parser error: %w", util.ParseAPEChain(chain, rules))
} else if encPath, _ := cmd.Flags().GetString(pathFlag); encPath != "" {
commonCmd.ExitOnErr(cmd, "decode binary or json error: %w", util.ParseAPEChainBinaryOrJSON(chain, encPath))
} else {
commonCmd.ExitOnErr(cmd, "parser error", errors.New("rule is not passed"))
}
cmd.Println("Parsed chain:") cmd.Println("Parsed chain:")
util.PrintHumanReadableAPEChain(cmd, chain) util.PrintHumanReadableAPEChain(cmd, chain)
return chain
}
func addRule(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
target := parseTarget(cmd)
parsed := parseChain(cmd)
req := &control.AddChainLocalOverrideRequest{ req := &control.AddChainLocalOverrideRequest{
Body: &control.AddChainLocalOverrideRequest_Body{ Body: &control.AddChainLocalOverrideRequest_Body{
Target: target, Target: target,
Chain: serializedChain, Chain: parsed.Bytes(),
}, },
} }
@ -81,9 +95,12 @@ func initControlAddRuleCmd() {
ff := addRuleCmd.Flags() ff := addRuleCmd.Flags()
ff.StringArray(ruleFlag, []string{}, "Rule statement") ff.StringArray(ruleFlag, []string{}, "Rule statement")
ff.String(pathFlag, "", "Path to encoded chain in JSON or binary format")
ff.String(chainIDFlag, "", "Assign ID to the parsed chain") ff.String(chainIDFlag, "", "Assign ID to the parsed chain")
ff.String(targetNameFlag, "", targetNameDesc) ff.String(targetNameFlag, "", targetNameDesc)
ff.String(targetTypeFlag, "", targetTypeDesc) ff.String(targetTypeFlag, "", targetTypeDesc)
_ = addRuleCmd.MarkFlagRequired(targetTypeFlag) _ = addRuleCmd.MarkFlagRequired(targetTypeFlag)
ff.Bool(chainIDHexFlag, false, "Flag to parse chain ID as hex") ff.Bool(chainIDHexFlag, false, "Flag to parse chain ID as hex")
addRuleCmd.MarkFlagsMutuallyExclusive(pathFlag, ruleFlag)
} }

View file

@ -3,6 +3,7 @@ package util
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"strconv" "strconv"
"strings" "strings"
@ -57,6 +58,23 @@ func PrintHumanReadableAPEChain(cmd *cobra.Command, chain *apechain.Chain) {
} }
} }
func ParseAPEChainBinaryOrJSON(chain *apechain.Chain, path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read file <%s>: %w", path, err)
}
err = chain.UnmarshalBinary(data)
if err != nil {
err = chain.UnmarshalJSON(data)
if err != nil {
return errors.New("invalid format")
}
}
return nil
}
// ParseAPEChain parses APE chain rules. // ParseAPEChain parses APE chain rules.
func ParseAPEChain(chain *apechain.Chain, rules []string) error { func ParseAPEChain(chain *apechain.Chain, rules []string) error {
if len(rules) == 0 { if len(rules) == 0 {
@ -82,6 +100,8 @@ func ParseAPEChain(chain *apechain.Chain, rules []string) error {
// deny:QuotaLimitReached Object.Put * // deny:QuotaLimitReached Object.Put *
// allow Object.Put * // allow Object.Put *
// allow Object.Get Object.Resource:Department=HR Object.Request:Actor=ownerA * // allow Object.Get Object.Resource:Department=HR Object.Request:Actor=ownerA *
// allow Object.Get any Object.Resource:Department=HR Object.Request:Actor=ownerA *
// allow Object.Get all Object.Resource:Department=HR Object.Request:Actor=ownerA *
// //
//nolint:godot //nolint:godot
func ParseAPERule(r *apechain.Rule, rule string) error { func ParseAPERule(r *apechain.Rule, rule string) error {
@ -105,6 +125,12 @@ func parseRuleLexemes(r *apechain.Rule, lexemes []string) error {
var isObject *bool var isObject *bool
for i, lexeme := range lexemes[1:] { for i, lexeme := range lexemes[1:] {
anyExpr, anyErr := parseAnyAll(lexeme)
if anyErr == nil {
r.Any = anyExpr
continue
}
var name string var name string
var actionType bool var actionType bool
name, actionType, err = parseAction(lexeme) name, actionType, err = parseAction(lexeme)
@ -140,6 +166,17 @@ func parseRuleLexemes(r *apechain.Rule, lexemes []string) error {
return nil return nil
} }
func parseAnyAll(lexeme string) (bool, error) {
switch strings.ToLower(lexeme) {
case "any":
return true, nil
case "all":
return false, nil
default:
return false, fmt.Errorf("any/all is not parsed")
}
}
func parseStatus(lexeme string) (apechain.Status, error) { func parseStatus(lexeme string) (apechain.Status, error) {
action, expression, found := strings.Cut(lexeme, ":") action, expression, found := strings.Cut(lexeme, ":")
switch strings.ToLower(action) { switch strings.ToLower(action) {