[#1124] cli: Improve APE rule parsing

* Make APE rule parser to read condition's kind in unambiguous using lexemes
`ResourceCondition`, `RequestCondition` instead confusing `Object.Request`, `Object.Resource`.
* Fix unit-tests.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
Airat Arifullin 2024-05-14 12:23:26 +03:00
parent 20baf6e112
commit 952d13cd2b
8 changed files with 151 additions and 147 deletions

View file

@ -24,7 +24,7 @@ var addRuleCmd = &cobra.Command{
Long: "Add local APE rule to a node with following format:\n<action>[:action_detail] <operation> [<condition1> ...] <resource>",
Example: `control add-rule --endpoint ... -w ... --address ... --chain-id ChainID --cid ... --rule "allow Object.Get *"
--rule "deny Object.Get EbxzAdz5LB4uqxuz6crWKAumBNtZyK2rKsqQP7TdZvwr/*"
--rule "deny:QuotaLimitReached Object.Put Object.Resource:Department=HR *"
--rule "deny:QuotaLimitReached Object.Put ResourceCondition:Department=HR *"
control add-rule --endpoint ... -w ... --address ... --chain-id ChainID --cid ... --path some_chain.json
`,

View file

@ -21,7 +21,7 @@ var (
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 and conditions in rule")
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")
@ -38,10 +38,10 @@ func PrintHumanReadableAPEChain(cmd *cobra.Command, chain *apechain.Chain) {
cmd.Println("\tConditions:")
for _, c := range rule.Condition {
var ot string
switch c.Object {
case apechain.ObjectResource:
switch c.Kind {
case apechain.KindResource:
ot = "Resource"
case apechain.ObjectRequest:
case apechain.KindRequest:
ot = "Request"
default:
panic("unknown object type")
@ -100,9 +100,9 @@ func ParseAPEChain(chain *apechain.Chain, rules []string) error {
// deny Object.Put *
// deny:QuotaLimitReached Object.Put *
// allow Object.Put *
// 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 *
// 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.* *
//
@ -138,7 +138,9 @@ func parseRuleLexemes(r *apechain.Rule, lexemes []string) error {
return err
}
var isObject *bool
var objectTargeted bool
var containerTargeted bool
for i, lexeme := range lexemes[1:] {
anyExpr, anyErr := parseAnyAll(lexeme)
if anyErr == nil {
@ -156,23 +158,30 @@ func parseRuleLexemes(r *apechain.Rule, lexemes []string) error {
lexemes = lexemes[i+1:]
break
}
actionType = condition.Object == apechain.ObjectResource || condition.Object == apechain.ObjectRequest
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...)
}
if isObject == nil {
isObject = &actionType
} else if actionType != *isObject {
return errMixedTypesInRule
}
}
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, *isObject)
resource, errRes := parseResource(lexeme, objectTargeted)
if errRes != nil {
return fmt.Errorf("%w:%w", err, errRes)
}
@ -308,32 +317,27 @@ func parseResource(lexeme string, isObj bool) (string, error) {
}
const (
ObjectResource = "object.resource"
ObjectRequest = "object.request"
ContainerResource = "container.resource"
ContainerRequest = "container.request"
ResourceCondition = "resourcecondition"
RequestCondition = "requestcondition"
)
var typeToCondObject = map[string]apechain.ObjectType{
ObjectResource: apechain.ObjectResource,
ObjectRequest: apechain.ObjectRequest,
ContainerResource: apechain.ContainerResource,
ContainerRequest: apechain.ContainerRequest,
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)
objType, ok := typeToCondObject[typ]
condKindType, ok := typeToCondKindType[typ]
if ok {
if !found {
return nil, fmt.Errorf("%w: %s", errInvalidConditionFormat, lexeme)
}
var cond apechain.Condition
cond.Object = objType
cond.Kind = condKindType
lhs, rhs, binExpFound := strings.Cut(expression, "!=")
if !binExpFound {

View file

@ -109,46 +109,46 @@ func TestParseAPERule(t *testing.T) {
},
{
name: "Valid allow rule with conditions",
rule: "allow Object.Get Object.Resource:Department=HR Object.Request:Actor!=ownerA *",
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,
Object: policyengine.ObjectResource,
Key: "Department",
Value: "HR",
Op: policyengine.CondStringEquals,
Kind: policyengine.KindResource,
Key: "Department",
Value: "HR",
},
{
Op: policyengine.CondStringNotEquals,
Object: policyengine.ObjectRequest,
Key: "Actor",
Value: "ownerA",
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 Object.Resource:Department=HR Object.Request:Actor!=ownerA *",
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,
Object: policyengine.ObjectResource,
Key: "Department",
Value: "HR",
Op: policyengine.CondStringEquals,
Kind: policyengine.KindResource,
Key: "Department",
Value: "HR",
},
{
Op: policyengine.CondStringNotEquals,
Object: policyengine.ObjectRequest,
Key: "Actor",
Value: "ownerA",
Op: policyengine.CondStringNotEquals,
Kind: policyengine.KindRequest,
Key: "Actor",
Value: "ownerA",
},
},
},
@ -170,12 +170,12 @@ func TestParseAPERule(t *testing.T) {
},
{
name: "Invalid rule with unknown condition binary operator",
rule: "deny Object.Put Object.Resource:Department<HR *",
rule: "deny Object.Put ResourceCondition:Department<HR *",
expectErr: errUnknownBinaryOperator,
},
{
name: "Invalid rule with unknown condition object type",
rule: "deny Object.Put Object.ResourZe:Department=HR *",
rule: "deny Object.Put ResourSeCondiDion:Department=HR *",
expectErr: errUnknownCondObjectType,
},
{
@ -185,7 +185,7 @@ func TestParseAPERule(t *testing.T) {
},
{
name: "Invalid rule with no actions",
rule: "allow Container.Resource:A=B *",
rule: "allow ResourceCondition:A=B *",
expectErr: errNoActionsInRule,
},
{
@ -271,7 +271,7 @@ func TestParseAPERule(t *testing.T) {
},
{
name: "Valid rule for container with conditions with action detail",
rule: "allow Container.Get Container.Resource:A=B Container.Put Container.Request:C!=D " +
rule: "allow Container.Get ResourceCondition:A=B Container.Put RequestCondition:C!=D " +
"* /cnt_id",
expectRule: policyengine.Rule{
Status: policyengine.Allow,
@ -282,16 +282,16 @@ func TestParseAPERule(t *testing.T) {
}},
Condition: []policyengine.Condition{
{
Op: policyengine.CondStringEquals,
Object: policyengine.ContainerResource,
Key: "A",
Value: "B",
Op: policyengine.CondStringEquals,
Kind: policyengine.KindResource,
Key: "A",
Value: "B",
},
{
Op: policyengine.CondStringNotEquals,
Object: policyengine.ContainerRequest,
Key: "C",
Value: "D",
Op: policyengine.CondStringNotEquals,
Kind: policyengine.KindRequest,
Key: "C",
Value: "D",
},
},
},