forked from TrueCloudLab/frostfs-node
[#1501] cli: Move APE-chain parser methods to pkg/util
Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
ae31ef3602
commit
ffe9906266
6 changed files with 10 additions and 10 deletions
|
@ -7,9 +7,9 @@ import (
|
|||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
|
||||
parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/ape"
|
||||
apeSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
|
||||
client_sdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
|
@ -88,9 +88,9 @@ func parseChain(cmd *cobra.Command) apeSDK.Chain {
|
|||
chain.ID = apechain.ID(chainIDRaw)
|
||||
|
||||
if rules, _ := cmd.Flags().GetStringArray(ruleFlag); len(rules) > 0 {
|
||||
commonCmd.ExitOnErr(cmd, "parser error: %w", util.ParseAPEChain(chain, rules))
|
||||
commonCmd.ExitOnErr(cmd, "parser error: %w", parseutil.ParseAPEChain(chain, rules))
|
||||
} else if encPath, _ := cmd.Flags().GetString(pathFlag); encPath != "" {
|
||||
commonCmd.ExitOnErr(cmd, "decode binary or json error: %w", util.ParseAPEChainBinaryOrJSON(chain, encPath))
|
||||
commonCmd.ExitOnErr(cmd, "decode binary or json error: %w", parseutil.ParseAPEChainBinaryOrJSON(chain, encPath))
|
||||
} else {
|
||||
commonCmd.ExitOnErr(cmd, "parser error: %w", errors.New("rule is not passed"))
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
"os"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
|
||||
parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/ape"
|
||||
apeSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
"errors"
|
||||
|
||||
"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"
|
||||
apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
||||
parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/ape"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/rpc/client"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -48,9 +48,9 @@ func parseChain(cmd *cobra.Command) *apechain.Chain {
|
|||
chain.ID = apechain.ID(chainIDRaw)
|
||||
|
||||
if rules, _ := cmd.Flags().GetStringArray(ruleFlag); len(rules) > 0 {
|
||||
commonCmd.ExitOnErr(cmd, "parser error: %w", util.ParseAPEChain(chain, rules))
|
||||
commonCmd.ExitOnErr(cmd, "parser error: %w", parseutil.ParseAPEChain(chain, rules))
|
||||
} else if encPath, _ := cmd.Flags().GetString(pathFlag); encPath != "" {
|
||||
commonCmd.ExitOnErr(cmd, "decode binary or json error: %w", util.ParseAPEChainBinaryOrJSON(chain, encPath))
|
||||
commonCmd.ExitOnErr(cmd, "decode binary or json error: %w", parseutil.ParseAPEChainBinaryOrJSON(chain, encPath))
|
||||
} else {
|
||||
commonCmd.ExitOnErr(cmd, "parser error", errors.New("rule is not passed"))
|
||||
}
|
||||
|
|
|
@ -1,321 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidStatementFormat = errors.New("invalid statement format")
|
||||
errInvalidConditionFormat = errors.New("invalid condition format")
|
||||
errUnknownStatus = errors.New("status is not recognized")
|
||||
errUnknownStatusDetail = errors.New("status detail is not recognized")
|
||||
errUnknownAction = errors.New("action is not recognized")
|
||||
errUnknownBinaryOperator = errors.New("binary operator is not recognized")
|
||||
errUnknownCondObjectType = errors.New("condition object type is not recognized")
|
||||
errMixedTypesInRule = errors.New("found mixed type of actions in rule")
|
||||
errNoActionsInRule = errors.New("there are no actions in rule")
|
||||
errUnsupportedResourceFormat = errors.New("unsupported resource format")
|
||||
errFailedToParseAllAny = errors.New("any/all is not parsed")
|
||||
)
|
||||
|
||||
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 fmt.Errorf("invalid format: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseAPEChain parses APE chain rules.
|
||||
func ParseAPEChain(chain *apechain.Chain, rules []string) error {
|
||||
if len(rules) == 0 {
|
||||
return errors.New("no APE rules provided")
|
||||
}
|
||||
|
||||
for _, rule := range rules {
|
||||
r := new(apechain.Rule)
|
||||
if err := ParseAPERule(r, rule); err != nil {
|
||||
return err
|
||||
}
|
||||
chain.Rules = append(chain.Rules, *r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseAPERule parses access-policy-engine statement from the following form:
|
||||
// <status>[:status_detail] <action>... [<condition>...] <resource>...
|
||||
//
|
||||
// Examples:
|
||||
// deny Object.Put *
|
||||
// deny:QuotaLimitReached Object.Put *
|
||||
// allow Object.Put *
|
||||
// allow Object.Get ResourceCondition:Department=HR RequestCondition:Actor=ownerA *
|
||||
// allow Object.Get any ResourceCondition:Department=HR RequestCondition:Actor=ownerA *
|
||||
// allow Object.Get all ResourceCondition:Department=HR RequestCondition:Actor=ownerA *
|
||||
// allow Object.* *
|
||||
// allow Container.* *
|
||||
//
|
||||
//nolint:godot
|
||||
func ParseAPERule(r *apechain.Rule, rule string) error {
|
||||
lexemes, err := shlex.Split(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse rule '%s': %v", rule, err)
|
||||
}
|
||||
return parseRuleLexemes(r, lexemes)
|
||||
}
|
||||
|
||||
func unique(inputSlice []string) []string {
|
||||
uniqueSlice := make([]string, 0, len(inputSlice))
|
||||
seen := make(map[string]bool, len(inputSlice))
|
||||
for _, element := range inputSlice {
|
||||
if !seen[element] {
|
||||
uniqueSlice = append(uniqueSlice, element)
|
||||
seen[element] = true
|
||||
}
|
||||
}
|
||||
return uniqueSlice
|
||||
}
|
||||
|
||||
func parseRuleLexemes(r *apechain.Rule, lexemes []string) error {
|
||||
if len(lexemes) < 2 {
|
||||
return errInvalidStatementFormat
|
||||
}
|
||||
|
||||
var err error
|
||||
r.Status, err = parseStatus(lexemes[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var objectTargeted bool
|
||||
var containerTargeted bool
|
||||
|
||||
for i, lexeme := range lexemes[1:] {
|
||||
anyExpr, anyErr := parseAnyAll(lexeme)
|
||||
if anyErr == nil {
|
||||
r.Any = anyExpr
|
||||
continue
|
||||
}
|
||||
|
||||
var names []string
|
||||
var actionType bool
|
||||
names, 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
|
||||
}
|
||||
r.Condition = append(r.Condition, *condition)
|
||||
} else {
|
||||
if actionType {
|
||||
objectTargeted = true
|
||||
} else {
|
||||
containerTargeted = true
|
||||
}
|
||||
if objectTargeted && containerTargeted {
|
||||
// Actually, APE chain allows to define rules for several resources, for example, if
|
||||
// chain target is namespace, but the parser primitevly compiles verbs,
|
||||
// conditions and resources in one rule. So, for the parser, one rule relates only to
|
||||
// one resource type - object or container.
|
||||
return errMixedTypesInRule
|
||||
}
|
||||
|
||||
r.Actions.Names = append(r.Actions.Names, names...)
|
||||
}
|
||||
}
|
||||
r.Actions.Names = unique(r.Actions.Names)
|
||||
if len(r.Actions.Names) == 0 {
|
||||
return fmt.Errorf("%w:%w", err, errNoActionsInRule)
|
||||
}
|
||||
for _, lexeme := range lexemes {
|
||||
resource, errRes := parseResource(lexeme, objectTargeted)
|
||||
if errRes != nil {
|
||||
return fmt.Errorf("%w:%w", err, errRes)
|
||||
}
|
||||
r.Resources.Names = append(r.Resources.Names, resource)
|
||||
}
|
||||
|
||||
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, errFailedToParseAllAny
|
||||
}
|
||||
}
|
||||
|
||||
func parseStatus(lexeme string) (apechain.Status, error) {
|
||||
action, expression, found := strings.Cut(lexeme, ":")
|
||||
switch strings.ToLower(action) {
|
||||
case "deny":
|
||||
if !found {
|
||||
return apechain.AccessDenied, nil
|
||||
} else if strings.EqualFold(expression, "QuotaLimitReached") {
|
||||
return apechain.QuotaLimitReached, nil
|
||||
} else {
|
||||
return 0, fmt.Errorf("%w: %s", errUnknownStatusDetail, expression)
|
||||
}
|
||||
case "allow":
|
||||
if found {
|
||||
return 0, errUnknownStatusDetail
|
||||
}
|
||||
return apechain.Allow, nil
|
||||
default:
|
||||
return 0, errUnknownStatus
|
||||
}
|
||||
}
|
||||
|
||||
func parseAction(lexeme string) ([]string, bool, error) {
|
||||
switch strings.ToLower(lexeme) {
|
||||
case "object.put":
|
||||
return []string{nativeschema.MethodPutObject}, true, nil
|
||||
case "object.get":
|
||||
return []string{nativeschema.MethodGetObject}, true, nil
|
||||
case "object.head":
|
||||
return []string{nativeschema.MethodHeadObject}, true, nil
|
||||
case "object.delete":
|
||||
return []string{nativeschema.MethodDeleteObject}, true, nil
|
||||
case "object.search":
|
||||
return []string{nativeschema.MethodSearchObject}, true, nil
|
||||
case "object.range":
|
||||
return []string{nativeschema.MethodRangeObject}, true, nil
|
||||
case "object.hash":
|
||||
return []string{nativeschema.MethodHashObject}, true, nil
|
||||
case "object.patch":
|
||||
return []string{nativeschema.MethodPatchObject}, true, nil
|
||||
case "object.*":
|
||||
return []string{
|
||||
nativeschema.MethodPutObject,
|
||||
nativeschema.MethodGetObject,
|
||||
nativeschema.MethodHeadObject,
|
||||
nativeschema.MethodDeleteObject,
|
||||
nativeschema.MethodSearchObject,
|
||||
nativeschema.MethodRangeObject,
|
||||
nativeschema.MethodHashObject,
|
||||
nativeschema.MethodPatchObject,
|
||||
}, true, nil
|
||||
case "container.put":
|
||||
return []string{nativeschema.MethodPutContainer}, false, nil
|
||||
case "container.delete":
|
||||
return []string{nativeschema.MethodDeleteContainer}, false, nil
|
||||
case "container.get":
|
||||
return []string{nativeschema.MethodGetContainer}, false, nil
|
||||
case "container.list":
|
||||
return []string{nativeschema.MethodListContainers}, false, nil
|
||||
case "container.*":
|
||||
return []string{
|
||||
nativeschema.MethodPutContainer,
|
||||
nativeschema.MethodDeleteContainer,
|
||||
nativeschema.MethodGetContainer,
|
||||
nativeschema.MethodListContainers,
|
||||
}, false, nil
|
||||
default:
|
||||
}
|
||||
return nil, false, fmt.Errorf("%w: %s", errUnknownAction, lexeme)
|
||||
}
|
||||
|
||||
func parseResource(lexeme string, isObj bool) (string, error) {
|
||||
if len(lexeme) > 0 && !strings.HasSuffix(lexeme, "/") {
|
||||
if isObj {
|
||||
if lexeme == "*" {
|
||||
return nativeschema.ResourceFormatAllObjects, nil
|
||||
} else if lexeme == "/*" || lexeme == "root/*" {
|
||||
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 "", errUnsupportedResourceFormat
|
||||
}
|
||||
|
||||
const (
|
||||
ResourceCondition = "resourcecondition"
|
||||
RequestCondition = "requestcondition"
|
||||
)
|
||||
|
||||
var typeToCondKindType = map[string]apechain.ConditionKindType{
|
||||
ResourceCondition: apechain.KindResource,
|
||||
RequestCondition: apechain.KindRequest,
|
||||
}
|
||||
|
||||
func parseCondition(lexeme string) (*apechain.Condition, error) {
|
||||
typ, expression, found := strings.Cut(lexeme, ":")
|
||||
typ = strings.ToLower(typ)
|
||||
|
||||
condKindType, ok := typeToCondKindType[typ]
|
||||
if ok {
|
||||
if !found {
|
||||
return nil, fmt.Errorf("%w: %s", errInvalidConditionFormat, lexeme)
|
||||
}
|
||||
|
||||
var cond apechain.Condition
|
||||
cond.Kind = condKindType
|
||||
|
||||
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,319 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseAPERule(t *testing.T) {
|
||||
tests := [...]struct {
|
||||
name string
|
||||
rule string
|
||||
expectErr error
|
||||
expectRule policyengine.Rule
|
||||
}{
|
||||
{
|
||||
name: "Valid allow rule for all objects",
|
||||
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 implicit root namespace",
|
||||
rule: "allow Object.Put /*",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.Allow,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid rule for all objects in explicit root namespace",
|
||||
rule: "allow Object.Put root/*",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.Allow,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
||||
},
|
||||
},
|
||||
{
|
||||
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"),
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid deny rule",
|
||||
rule: "deny Object.Put *",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.AccessDenied,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid deny rule with action detail",
|
||||
rule: "deny:QuotaLimitReached Object.Put *",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.QuotaLimitReached,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid allow rule with conditions",
|
||||
rule: "allow Object.Get ResourceCondition:Department=HR RequestCondition:Actor!=ownerA *",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.Allow,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}},
|
||||
Condition: []policyengine.Condition{
|
||||
{
|
||||
Op: policyengine.CondStringEquals,
|
||||
Kind: policyengine.KindResource,
|
||||
Key: "Department",
|
||||
Value: "HR",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotEquals,
|
||||
Kind: policyengine.KindRequest,
|
||||
Key: "Actor",
|
||||
Value: "ownerA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid rule for object with conditions with action detail",
|
||||
rule: "deny:QuotaLimitReached Object.Get ResourceCondition:Department=HR RequestCondition:Actor!=ownerA *",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.QuotaLimitReached,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatAllObjects}},
|
||||
Condition: []policyengine.Condition{
|
||||
{
|
||||
Op: policyengine.CondStringEquals,
|
||||
Kind: policyengine.KindResource,
|
||||
Key: "Department",
|
||||
Value: "HR",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotEquals,
|
||||
Kind: policyengine.KindRequest,
|
||||
Key: "Actor",
|
||||
Value: "ownerA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid rule with unknown status",
|
||||
rule: "permit Object.Put *",
|
||||
expectErr: errUnknownStatus,
|
||||
},
|
||||
{
|
||||
name: "Invalid rule with unknown action",
|
||||
rule: "allow Object.PutOut *",
|
||||
expectErr: errUnknownAction,
|
||||
},
|
||||
{
|
||||
name: "Invalid rule with unknown status detail",
|
||||
rule: "deny:UnknownActionDetail Object.Put *",
|
||||
expectErr: errUnknownStatusDetail,
|
||||
},
|
||||
{
|
||||
name: "Invalid rule with unknown condition binary operator",
|
||||
rule: "deny Object.Put ResourceCondition:Department<HR *",
|
||||
expectErr: errUnknownBinaryOperator,
|
||||
},
|
||||
{
|
||||
name: "Invalid rule with unknown condition object type",
|
||||
rule: "deny Object.Put ResourSeCondiDion:Department=HR *",
|
||||
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 ResourceCondition: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 ResourceCondition:A=B Container.Put RequestCondition: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,
|
||||
Kind: policyengine.KindResource,
|
||||
Key: "A",
|
||||
Value: "B",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotEquals,
|
||||
Kind: policyengine.KindRequest,
|
||||
Key: "C",
|
||||
Value: "D",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := new(policyengine.Rule)
|
||||
err := ParseAPERule(r, test.rule)
|
||||
require.ErrorIs(t, err, test.expectErr)
|
||||
if test.expectErr == nil {
|
||||
require.Equal(t, test.expectRule, *r)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue