WIP: cli, adm: Improve add rule commands #990
5 changed files with 94 additions and 49 deletions
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue