forked from TrueCloudLab/frostfs-node
[#876] cli: Add support for container
in local rules
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
This commit is contained in:
parent
b6fc3321c5
commit
51d1d935ef
5 changed files with 351 additions and 117 deletions
|
@ -1,10 +1,8 @@
|
||||||
package control
|
package control
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"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/commonflags"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
|
@ -25,20 +23,13 @@ var addRuleCmd = &cobra.Command{
|
||||||
Use: "add-rule",
|
Use: "add-rule",
|
||||||
Short: "Add local override",
|
Short: "Add local override",
|
||||||
Long: "Add local APE rule to a node with following format:\n<action>[:action_detail] <operation> [<condition1> ...] <resource>",
|
Long: "Add local APE rule to a node with following format:\n<action>[:action_detail] <operation> [<condition1> ...] <resource>",
|
||||||
Example: `allow Object.Get *
|
Example: `control add-rule --endpoint ... -w ... --address ... --chain-id ChainID --cid ... --rule "allow Object.Get *"
|
||||||
deny Object.Get EbxzAdz5LB4uqxuz6crWKAumBNtZyK2rKsqQP7TdZvwr/*
|
--rule "deny Object.Get EbxzAdz5LB4uqxuz6crWKAumBNtZyK2rKsqQP7TdZvwr/*"
|
||||||
deny:QuotaLimitReached Object.Put Object.Resource:Department=HR *
|
--rule "deny:QuotaLimitReached Object.Put Object.Resource:Department=HR *"
|
||||||
`,
|
`,
|
||||||
Run: addRule,
|
Run: addRule,
|
||||||
}
|
}
|
||||||
|
|
||||||
func prettyJSONFormat(cmd *cobra.Command, serializedChain []byte) string {
|
|
||||||
wr := bytes.NewBufferString("")
|
|
||||||
err := json.Indent(wr, serializedChain, "", " ")
|
|
||||||
commonCmd.ExitOnErr(cmd, "%w", err)
|
|
||||||
return wr.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func addRule(cmd *cobra.Command, _ []string) {
|
func addRule(cmd *cobra.Command, _ []string) {
|
||||||
pk := key.Get(cmd)
|
pk := key.Get(cmd)
|
||||||
|
|
||||||
|
@ -60,15 +51,15 @@ func addRule(cmd *cobra.Command, _ []string) {
|
||||||
rawCID := make([]byte, sha256.Size)
|
rawCID := make([]byte, sha256.Size)
|
||||||
cnr.Encode(rawCID)
|
cnr.Encode(rawCID)
|
||||||
|
|
||||||
rule, _ := cmd.Flags().GetString(ruleFlag)
|
rule, _ := cmd.Flags().GetStringArray(ruleFlag)
|
||||||
|
|
||||||
chain := new(apechain.Chain)
|
chain := new(apechain.Chain)
|
||||||
commonCmd.ExitOnErr(cmd, "parser error: %w", util.ParseAPEChain(chain, []string{rule}))
|
commonCmd.ExitOnErr(cmd, "parser error: %w", util.ParseAPEChain(chain, rule))
|
||||||
chain.ID = apechain.ID(chainIDRaw)
|
chain.ID = apechain.ID(chainIDRaw)
|
||||||
serializedChain := chain.Bytes()
|
serializedChain := chain.Bytes()
|
||||||
|
|
||||||
cmd.Println("CID: " + cidStr)
|
cmd.Println("Parsed chain:")
|
||||||
cmd.Println("Parsed chain:\n" + prettyJSONFormat(cmd, serializedChain))
|
util.PrintHumanReadableAPEChain(cmd, chain)
|
||||||
|
|
||||||
req := &control.AddChainLocalOverrideRequest{
|
req := &control.AddChainLocalOverrideRequest{
|
||||||
Body: &control.AddChainLocalOverrideRequest_Body{
|
Body: &control.AddChainLocalOverrideRequest_Body{
|
||||||
|
@ -93,9 +84,7 @@ func addRule(cmd *cobra.Command, _ []string) {
|
||||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||||
|
|
||||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||||
|
cmd.Println("\nRule has been added.")
|
||||||
chainIDRaw = resp.GetBody().GetChainId()
|
|
||||||
cmd.Printf("Rule has been added.\nChain id: '%s'\nChain id hex: '%x'\n", string(chainIDRaw), chainIDRaw)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initControlAddRuleCmd() {
|
func initControlAddRuleCmd() {
|
||||||
|
@ -103,7 +92,7 @@ func initControlAddRuleCmd() {
|
||||||
|
|
||||||
ff := addRuleCmd.Flags()
|
ff := addRuleCmd.Flags()
|
||||||
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||||
ff.String(ruleFlag, "", "Rule statement")
|
ff.StringArray(ruleFlag, []string{}, "Rule statement")
|
||||||
ff.String(chainIDFlag, "", "Assign ID to the parsed chain")
|
ff.String(chainIDFlag, "", "Assign ID to the parsed chain")
|
||||||
ff.Bool(chainIDHexFlag, false, "Flag to parse chain ID as hex")
|
ff.Bool(chainIDHexFlag, false, "Flag to parse chain ID as hex")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"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/commonflags"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
@ -66,9 +67,7 @@ func getRule(cmd *cobra.Command, _ []string) {
|
||||||
|
|
||||||
var chain apechain.Chain
|
var chain apechain.Chain
|
||||||
commonCmd.ExitOnErr(cmd, "decode error: %w", chain.DecodeBytes(resp.GetBody().GetChain()))
|
commonCmd.ExitOnErr(cmd, "decode error: %w", chain.DecodeBytes(resp.GetBody().GetChain()))
|
||||||
|
util.PrintHumanReadableAPEChain(cmd, &chain)
|
||||||
// TODO (aarifullin): make pretty-formatted output for chains.
|
|
||||||
cmd.Printf("Parsed chain (chain id hex: '%x'):\n%s\n", chain.ID, prettyJSONFormat(cmd, chain.Bytes()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initControGetRuleCmd() {
|
func initControGetRuleCmd() {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"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/commonflags"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
@ -60,10 +61,9 @@ func listRules(cmd *cobra.Command, _ []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range chains {
|
for _, c := range chains {
|
||||||
// TODO (aarifullin): make pretty-formatted output for chains.
|
|
||||||
var chain apechain.Chain
|
var chain apechain.Chain
|
||||||
commonCmd.ExitOnErr(cmd, "decode error: %w", chain.DecodeBytes(c))
|
commonCmd.ExitOnErr(cmd, "decode error: %w", chain.DecodeBytes(c))
|
||||||
cmd.Printf("Parsed chain (chain id hex: '%x'):\n%s\n", chain.ID, prettyJSONFormat(cmd, chain.Bytes()))
|
util.PrintHumanReadableAPEChain(cmd, &chain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,21 +13,25 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errInvalidStatementFormat = errors.New("invalid statement format")
|
errInvalidStatementFormat = errors.New("invalid statement format")
|
||||||
errInvalidConditionFormat = errors.New("invalid condition format")
|
errInvalidConditionFormat = errors.New("invalid condition format")
|
||||||
errUnknownAction = errors.New("action is not recognized")
|
errUnknownStatus = errors.New("status is not recognized")
|
||||||
errUnknownOperation = errors.New("operation is not recognized")
|
errUnknownStatusDetail = errors.New("status detail is not recognized")
|
||||||
errUnknownActionDetail = errors.New("action detail is not recognized")
|
errUnknownAction = errors.New("action is not recognized")
|
||||||
errUnknownBinaryOperator = errors.New("binary operator is not recognized")
|
errUnknownBinaryOperator = errors.New("binary operator is not recognized")
|
||||||
errUnknownCondObjectType = errors.New("condition object type is not recognized")
|
errUnknownCondObjectType = errors.New("condition object type is not recognized")
|
||||||
|
errMixedTypesInRule = errors.New("found mixed type of actions and conditions in rule")
|
||||||
|
errNoActionsInRule = errors.New("there are no actions in rule")
|
||||||
|
errUnsupportedResourceFormat = errors.New("unsupported resource format")
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrintHumanReadableAPEChain print APE chain rules.
|
// PrintHumanReadableAPEChain print APE chain rules.
|
||||||
func PrintHumanReadableAPEChain(cmd *cobra.Command, chain *apechain.Chain) {
|
func PrintHumanReadableAPEChain(cmd *cobra.Command, chain *apechain.Chain) {
|
||||||
cmd.Println("ChainID: " + string(chain.ID))
|
cmd.Println("Chain ID: " + string(chain.ID))
|
||||||
|
cmd.Printf(" HEX: %x\n", chain.ID)
|
||||||
cmd.Println("Rules:")
|
cmd.Println("Rules:")
|
||||||
for _, rule := range chain.Rules {
|
for _, rule := range chain.Rules {
|
||||||
cmd.Println("\tStatus: " + rule.Status.String())
|
cmd.Println("\n\tStatus: " + rule.Status.String())
|
||||||
cmd.Println("\tAny: " + strconv.FormatBool(rule.Any))
|
cmd.Println("\tAny: " + strconv.FormatBool(rule.Any))
|
||||||
cmd.Println("\tConditions:")
|
cmd.Println("\tConditions:")
|
||||||
for _, c := range rule.Condition {
|
for _, c := range rule.Condition {
|
||||||
|
@ -71,7 +75,7 @@ func ParseAPEChain(chain *apechain.Chain, rules []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAPERule parses access-policy-engine statement from the following form:
|
// ParseAPERule parses access-policy-engine statement from the following form:
|
||||||
// <action>[:action_detail] <operation> [<condition1> ...] <resource>
|
// <status>[:status_detail] <action>... [<condition>...] <resource>...
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
// deny Object.Put *
|
// deny Object.Put *
|
||||||
|
@ -99,116 +103,178 @@ func parseRuleLexemes(r *apechain.Rule, lexemes []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Actions, err = parseAction(lexemes[1])
|
var isObject *bool
|
||||||
if err != nil {
|
for i, lexeme := range lexemes[1:] {
|
||||||
return err
|
var name string
|
||||||
|
var actionType bool
|
||||||
|
name, actionType, err = parseAction(lexeme)
|
||||||
|
if err != nil {
|
||||||
|
condition, errCond := parseCondition(lexeme)
|
||||||
|
if errCond != nil {
|
||||||
|
err = fmt.Errorf("%w:%w", err, errCond)
|
||||||
|
lexemes = lexemes[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
actionType = condition.Object == apechain.ObjectResource || condition.Object == apechain.ObjectRequest
|
||||||
|
r.Condition = append(r.Condition, *condition)
|
||||||
|
} else {
|
||||||
|
r.Actions.Names = append(r.Actions.Names, name)
|
||||||
|
}
|
||||||
|
if isObject == nil {
|
||||||
|
isObject = &actionType
|
||||||
|
} else if actionType != *isObject {
|
||||||
|
return errMixedTypesInRule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(r.Actions.Names) == 0 {
|
||||||
|
return fmt.Errorf("%w:%w", err, errNoActionsInRule)
|
||||||
|
}
|
||||||
|
for _, lexeme := range lexemes {
|
||||||
|
resource, errRes := parseResource(lexeme, *isObject)
|
||||||
|
if errRes != nil {
|
||||||
|
return fmt.Errorf("%w:%w", err, errRes)
|
||||||
|
}
|
||||||
|
r.Resources.Names = append(r.Resources.Names, resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Condition, err = parseConditions(lexemes[2 : len(lexemes)-1])
|
return nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Resources, err = parseResource(lexemes[len(lexemes)-1])
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 action = strings.ToLower(action); action {
|
switch strings.ToLower(action) {
|
||||||
case "deny":
|
case "deny":
|
||||||
if !found {
|
if !found {
|
||||||
return apechain.AccessDenied, nil
|
return apechain.AccessDenied, nil
|
||||||
} else if strings.EqualFold(expression, "QuotaLimitReached") {
|
} else if strings.EqualFold(expression, "QuotaLimitReached") {
|
||||||
return apechain.QuotaLimitReached, nil
|
return apechain.QuotaLimitReached, nil
|
||||||
} else {
|
} else {
|
||||||
return 0, fmt.Errorf("%w: %s", errUnknownActionDetail, expression)
|
return 0, fmt.Errorf("%w: %s", errUnknownStatusDetail, expression)
|
||||||
}
|
}
|
||||||
case "allow":
|
case "allow":
|
||||||
if found {
|
if found {
|
||||||
return 0, errUnknownActionDetail
|
return 0, errUnknownStatusDetail
|
||||||
}
|
}
|
||||||
return apechain.Allow, nil
|
return apechain.Allow, nil
|
||||||
default:
|
default:
|
||||||
return 0, errUnknownAction
|
return 0, errUnknownStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAction(lexeme string) (apechain.Actions, error) {
|
func parseAction(lexeme string) (string, bool, error) {
|
||||||
switch strings.ToLower(lexeme) {
|
switch strings.ToLower(lexeme) {
|
||||||
case "object.put":
|
case "object.put":
|
||||||
return apechain.Actions{Names: []string{nativeschema.MethodPutObject}}, nil
|
return nativeschema.MethodPutObject, true, nil
|
||||||
case "object.get":
|
case "object.get":
|
||||||
return apechain.Actions{Names: []string{nativeschema.MethodGetObject}}, nil
|
return nativeschema.MethodGetObject, true, nil
|
||||||
case "object.head":
|
case "object.head":
|
||||||
return apechain.Actions{Names: []string{nativeschema.MethodHeadObject}}, nil
|
return nativeschema.MethodHeadObject, true, nil
|
||||||
case "object.delete":
|
case "object.delete":
|
||||||
return apechain.Actions{Names: []string{nativeschema.MethodDeleteObject}}, nil
|
return nativeschema.MethodDeleteObject, true, nil
|
||||||
case "object.search":
|
case "object.search":
|
||||||
return apechain.Actions{Names: []string{nativeschema.MethodSearchObject}}, nil
|
return nativeschema.MethodSearchObject, true, nil
|
||||||
case "object.range":
|
case "object.range":
|
||||||
return apechain.Actions{Names: []string{nativeschema.MethodRangeObject}}, nil
|
return nativeschema.MethodRangeObject, true, nil
|
||||||
case "object.hash":
|
case "object.hash":
|
||||||
return apechain.Actions{Names: []string{nativeschema.MethodHashObject}}, nil
|
return nativeschema.MethodHashObject, true, nil
|
||||||
|
case "container.put":
|
||||||
|
return nativeschema.MethodPutContainer, false, nil
|
||||||
|
case "container.delete":
|
||||||
|
return nativeschema.MethodDeleteContainer, false, nil
|
||||||
|
case "container.get":
|
||||||
|
return nativeschema.MethodGetContainer, false, nil
|
||||||
|
case "container.setcontainereacl":
|
||||||
|
return nativeschema.MethodSetContainerEACL, false, nil
|
||||||
|
case "container.getcontainereacl":
|
||||||
|
return nativeschema.MethodGetContainerEACL, false, nil
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
return apechain.Actions{}, fmt.Errorf("%w: %s", errUnknownOperation, lexeme)
|
return "", false, fmt.Errorf("%w: %s", errUnknownAction, lexeme)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseResource(lexeme string) (apechain.Resources, error) {
|
func parseResource(lexeme string, isObj bool) (string, error) {
|
||||||
if lexeme == "*" {
|
if len(lexeme) > 0 && !strings.HasSuffix(lexeme, "/") {
|
||||||
return apechain.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}}, nil
|
if isObj {
|
||||||
|
if lexeme == "*" {
|
||||||
|
return nativeschema.ResourceFormatAllObjects, nil
|
||||||
|
} else if lexeme == "/*" {
|
||||||
|
return nativeschema.ResourceFormatRootObjects, nil
|
||||||
|
} else if strings.HasPrefix(lexeme, "/") {
|
||||||
|
lexeme = lexeme[1:]
|
||||||
|
delimCount := strings.Count(lexeme, "/")
|
||||||
|
if delimCount == 1 && len(lexeme) >= 3 { // container/object
|
||||||
|
return nativeschema.ObjectPrefix + "//" + lexeme, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delimCount := strings.Count(lexeme, "/")
|
||||||
|
if delimCount == 1 && len(lexeme) >= 3 ||
|
||||||
|
delimCount == 2 && len(lexeme) >= 5 { // namespace/container/object
|
||||||
|
return nativeschema.ObjectPrefix + "/" + lexeme, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if lexeme == "*" {
|
||||||
|
return nativeschema.ResourceFormatAllContainers, nil
|
||||||
|
} else if lexeme == "/*" {
|
||||||
|
return nativeschema.ResourceFormatRootContainers, nil
|
||||||
|
} else if strings.HasPrefix(lexeme, "/") && len(lexeme) > 1 {
|
||||||
|
lexeme = lexeme[1:]
|
||||||
|
delimCount := strings.Count(lexeme, "/")
|
||||||
|
if delimCount == 0 {
|
||||||
|
return nativeschema.ContainerPrefix + "//" + lexeme, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delimCount := strings.Count(lexeme, "/")
|
||||||
|
if delimCount == 1 && len(lexeme) > 3 { // namespace/container
|
||||||
|
return nativeschema.ContainerPrefix + "/" + lexeme, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return apechain.Resources{Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, lexeme)}}, nil
|
return "", errUnsupportedResourceFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ObjectResource = "object.resource"
|
ObjectResource = "object.resource"
|
||||||
ObjectRequest = "object.request"
|
ObjectRequest = "object.request"
|
||||||
|
|
||||||
|
ContainerResource = "container.resource"
|
||||||
|
ContainerRequest = "container.request"
|
||||||
)
|
)
|
||||||
|
|
||||||
var typeToCondObject = map[string]apechain.ObjectType{
|
var typeToCondObject = map[string]apechain.ObjectType{
|
||||||
ObjectResource: apechain.ObjectResource,
|
ObjectResource: apechain.ObjectResource,
|
||||||
ObjectRequest: apechain.ObjectRequest,
|
ObjectRequest: apechain.ObjectRequest,
|
||||||
|
ContainerResource: apechain.ContainerResource,
|
||||||
|
ContainerRequest: apechain.ContainerRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConditions(lexemes []string) ([]apechain.Condition, error) {
|
func parseCondition(lexeme string) (*apechain.Condition, error) {
|
||||||
conds := make([]apechain.Condition, 0)
|
typ, expression, found := strings.Cut(lexeme, ":")
|
||||||
|
typ = strings.ToLower(typ)
|
||||||
|
|
||||||
for _, lexeme := range lexemes {
|
objType, ok := typeToCondObject[typ]
|
||||||
typ, expression, found := strings.Cut(lexeme, ":")
|
if ok {
|
||||||
typ = strings.ToLower(typ)
|
if !found {
|
||||||
|
return nil, fmt.Errorf("%w: %s", errInvalidConditionFormat, lexeme)
|
||||||
objType, ok := typeToCondObject[typ]
|
|
||||||
if ok {
|
|
||||||
if !found {
|
|
||||||
return nil, fmt.Errorf("%w: %s", errInvalidConditionFormat, lexeme)
|
|
||||||
}
|
|
||||||
|
|
||||||
var lhs, rhs string
|
|
||||||
var binExpFound bool
|
|
||||||
|
|
||||||
var cond apechain.Condition
|
|
||||||
cond.Object = objType
|
|
||||||
|
|
||||||
lhs, rhs, binExpFound = strings.Cut(expression, "!=")
|
|
||||||
if !binExpFound {
|
|
||||||
lhs, rhs, binExpFound = strings.Cut(expression, "=")
|
|
||||||
if !binExpFound {
|
|
||||||
return nil, fmt.Errorf("%w: %s", errUnknownBinaryOperator, expression)
|
|
||||||
}
|
|
||||||
cond.Op = apechain.CondStringEquals
|
|
||||||
} else {
|
|
||||||
cond.Op = apechain.CondStringNotEquals
|
|
||||||
}
|
|
||||||
|
|
||||||
cond.Key, cond.Value = lhs, rhs
|
|
||||||
|
|
||||||
conds = append(conds, cond)
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("%w: %s", errUnknownCondObjectType, typ)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return conds, nil
|
var cond apechain.Condition
|
||||||
|
cond.Object = objType
|
||||||
|
|
||||||
|
lhs, rhs, binExpFound := strings.Cut(expression, "!=")
|
||||||
|
if !binExpFound {
|
||||||
|
lhs, rhs, binExpFound = strings.Cut(expression, "=")
|
||||||
|
if !binExpFound {
|
||||||
|
return nil, fmt.Errorf("%w: %s", errUnknownBinaryOperator, expression)
|
||||||
|
}
|
||||||
|
cond.Op = apechain.CondStringEquals
|
||||||
|
} else {
|
||||||
|
cond.Op = apechain.CondStringNotEquals
|
||||||
|
}
|
||||||
|
|
||||||
|
cond.Key, cond.Value = lhs, rhs
|
||||||
|
return &cond, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%w: %s", errUnknownCondObjectType, typ)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||||
|
@ -16,13 +17,76 @@ func TestParseAPERule(t *testing.T) {
|
||||||
expectRule policyengine.Rule
|
expectRule policyengine.Rule
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Valid allow rule",
|
name: "Valid allow rule for all objects",
|
||||||
rule: "allow Object.Put *",
|
rule: "allow Object.Put *",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for all objects in root namespace",
|
||||||
|
rule: "allow Object.Put /*",
|
||||||
expectRule: policyengine.Rule{
|
expectRule: policyengine.Rule{
|
||||||
Status: policyengine.Allow,
|
Status: policyengine.Allow,
|
||||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
||||||
Condition: []policyengine.Condition{},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for all objects in root namespace and container",
|
||||||
|
rule: "allow Object.Put /cid/*",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, "cid"),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for object in root namespace and container",
|
||||||
|
rule: "allow Object.Put /cid/oid",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatRootContainerObject, "cid", "oid"),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for all objects in namespace",
|
||||||
|
rule: "allow Object.Put ns/*",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceObjects, "ns"),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for all objects in namespace and container",
|
||||||
|
rule: "allow Object.Put ns/cid/*",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainerObjects, "ns", "cid"),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for object in namespace and container",
|
||||||
|
rule: "allow Object.Put ns/cid/oid",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainerObject, "ns", "cid", "oid"),
|
||||||
|
}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -31,8 +95,7 @@ func TestParseAPERule(t *testing.T) {
|
||||||
expectRule: policyengine.Rule{
|
expectRule: policyengine.Rule{
|
||||||
Status: policyengine.AccessDenied,
|
Status: policyengine.AccessDenied,
|
||||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}},
|
||||||
Condition: []policyengine.Condition{},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -41,8 +104,7 @@ func TestParseAPERule(t *testing.T) {
|
||||||
expectRule: policyengine.Rule{
|
expectRule: policyengine.Rule{
|
||||||
Status: policyengine.QuotaLimitReached,
|
Status: policyengine.QuotaLimitReached,
|
||||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}},
|
||||||
Condition: []policyengine.Condition{},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -51,7 +113,7 @@ func TestParseAPERule(t *testing.T) {
|
||||||
expectRule: policyengine.Rule{
|
expectRule: policyengine.Rule{
|
||||||
Status: policyengine.Allow,
|
Status: policyengine.Allow,
|
||||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetObject}},
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetObject}},
|
||||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}},
|
||||||
Condition: []policyengine.Condition{
|
Condition: []policyengine.Condition{
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEquals,
|
Op: policyengine.CondStringEquals,
|
||||||
|
@ -69,12 +131,12 @@ func TestParseAPERule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Valid rule with conditions with action detail",
|
name: "Valid rule for object with conditions with action detail",
|
||||||
rule: "deny:QuotaLimitReached Object.Get Object.Resource:Department=HR Object.Request:Actor!=ownerA *",
|
rule: "deny:QuotaLimitReached Object.Get Object.Resource:Department=HR Object.Request:Actor!=ownerA *",
|
||||||
expectRule: policyengine.Rule{
|
expectRule: policyengine.Rule{
|
||||||
Status: policyengine.QuotaLimitReached,
|
Status: policyengine.QuotaLimitReached,
|
||||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetObject}},
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetObject}},
|
||||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}},
|
||||||
Condition: []policyengine.Condition{
|
Condition: []policyengine.Condition{
|
||||||
{
|
{
|
||||||
Op: policyengine.CondStringEquals,
|
Op: policyengine.CondStringEquals,
|
||||||
|
@ -92,19 +154,19 @@ func TestParseAPERule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid rule with unknown action",
|
name: "Invalid rule with unknown status",
|
||||||
rule: "permit Object.Put *",
|
rule: "permit Object.Put *",
|
||||||
|
expectErr: errUnknownStatus,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid rule with unknown action",
|
||||||
|
rule: "allow Object.PutOut *",
|
||||||
expectErr: errUnknownAction,
|
expectErr: errUnknownAction,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid rule with unknown operation",
|
name: "Invalid rule with unknown status detail",
|
||||||
rule: "allow Object.PutOut *",
|
|
||||||
expectErr: errUnknownOperation,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid rule with unknown action detail",
|
|
||||||
rule: "deny:UnknownActionDetail Object.Put *",
|
rule: "deny:UnknownActionDetail Object.Put *",
|
||||||
expectErr: errUnknownActionDetail,
|
expectErr: errUnknownStatusDetail,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid rule with unknown condition binary operator",
|
name: "Invalid rule with unknown condition binary operator",
|
||||||
|
@ -116,6 +178,124 @@ func TestParseAPERule(t *testing.T) {
|
||||||
rule: "deny Object.Put Object.ResourZe:Department=HR *",
|
rule: "deny Object.Put Object.ResourZe:Department=HR *",
|
||||||
expectErr: errUnknownCondObjectType,
|
expectErr: errUnknownCondObjectType,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid rule with mixed types of actions",
|
||||||
|
rule: "allow Object.Put Container.Put *",
|
||||||
|
expectErr: errMixedTypesInRule,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid rule with no actions",
|
||||||
|
rule: "allow Container.Resource:A=B *",
|
||||||
|
expectErr: errNoActionsInRule,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid rule with invalid resource for object nm/cnt/obj/err",
|
||||||
|
rule: "allow Object.Put nm/cnt/obj/err",
|
||||||
|
expectErr: errUnsupportedResourceFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid rule with invalid resource for container nm/cnt/err",
|
||||||
|
rule: "allow Container.Put nm/cnt/err",
|
||||||
|
expectErr: errUnsupportedResourceFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid rule with invalid resource for container /nm/cnt/err",
|
||||||
|
rule: "allow Container.Put /nm/cnt/err",
|
||||||
|
expectErr: errUnsupportedResourceFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid rule with invalid resource for container /nm/cnt/",
|
||||||
|
rule: "allow Container.Put /nm/cnt/",
|
||||||
|
expectErr: errUnsupportedResourceFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid rule with invalid resource for container /nm/cnt",
|
||||||
|
rule: "allow Container.Put /nm/cnt",
|
||||||
|
expectErr: errUnsupportedResourceFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid rule with invalid resource for container /nm/",
|
||||||
|
rule: "allow Container.Put /nm/",
|
||||||
|
expectErr: errUnsupportedResourceFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for all containers",
|
||||||
|
rule: "allow Container.Put *",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutContainer}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllContainers}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for all containers in root namespace",
|
||||||
|
rule: "allow Container.Put /*",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutContainer}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootContainers}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for container in root namespace",
|
||||||
|
rule: "allow Container.Put /cid",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutContainer}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatRootContainer, "cid"),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for all container in namespace",
|
||||||
|
rule: "allow Container.Put ns/*",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutContainer}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, "ns"),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for container in namespace",
|
||||||
|
rule: "allow Container.Put ns/cid",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutContainer}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainer, "ns", "cid"),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid rule for container with conditions with action detail",
|
||||||
|
rule: "allow Container.Get Container.Resource:A=B Container.Put Container.Request:C!=D " +
|
||||||
|
"* /cnt_id",
|
||||||
|
expectRule: policyengine.Rule{
|
||||||
|
Status: policyengine.Allow,
|
||||||
|
Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetContainer, nativeschema.MethodPutContainer}},
|
||||||
|
Resources: policyengine.Resources{Names: []string{
|
||||||
|
nativeschema.ResourceFormatAllContainers,
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatRootContainer, "cnt_id"),
|
||||||
|
}},
|
||||||
|
Condition: []policyengine.Condition{
|
||||||
|
{
|
||||||
|
Op: policyengine.CondStringEquals,
|
||||||
|
Object: policyengine.ContainerResource,
|
||||||
|
Key: "A",
|
||||||
|
Value: "B",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: policyengine.CondStringNotEquals,
|
||||||
|
Object: policyengine.ContainerRequest,
|
||||||
|
Key: "C",
|
||||||
|
Value: "D",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue