From 410201c89d4880b824de81c71967c141f5f04647 Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Thu, 26 Sep 2024 15:50:10 -0700 Subject: [PATCH] Issue #52, New cedar-go parser and deprecated older parser. Signed-off-by: Phil Hunt --- cmd/hexa/commands.go | 22 +- cmd/hexa/hexa_test.go | 3 +- cmd/hexa/main.go | 2 +- .../bundles/bundle/hexaPolicy.rego | 35 +- .../cedarConditions/map_cedar.go | 492 ++++++------- .../cedarConditions/map_hexa.go | 44 +- .../cedarConditions/map_test.go | 129 +++- models/formats/awsCedar/ReadMe.md | 6 + models/formats/awsCedar/amazon_cedar.go | 8 + models/formats/cedar/cedar_mapper.go | 405 +++++++++++ models/formats/cedar/cedar_test.go | 348 ++++++--- models/formats/cedar/json/json.go | 138 ++++ models/formats/cedar/parse.go | 373 ---------- pkg/hexapolicysupport/hexa_policy_support.go | 9 +- .../avpTestSupport/avp_operations.go | 672 +++++++++--------- providers/aws/avpProvider/avp_provider.go | 37 +- .../aws/avpProvider/avp_provider_test.go | 12 +- sdk/options_test.go | 182 +++-- sdk/provider_integrations.go | 8 +- 19 files changed, 1652 insertions(+), 1273 deletions(-) create mode 100644 models/formats/awsCedar/ReadMe.md create mode 100644 models/formats/cedar/cedar_mapper.go create mode 100644 models/formats/cedar/json/json.go delete mode 100644 models/formats/cedar/parse.go diff --git a/cmd/hexa/commands.go b/cmd/hexa/commands.go index 4bc3daf..31a2433 100644 --- a/cmd/hexa/commands.go +++ b/cmd/hexa/commands.go @@ -16,7 +16,7 @@ import ( "github.com/alecthomas/kong" "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/models/formats/awsCedar" + "github.com/hexa-org/policy-mapper/models/formats/cedar" "github.com/hexa-org/policy-mapper/models/formats/gcpBind" "github.com/hexa-org/policy-mapper/pkg/hexapolicy" "github.com/hexa-org/policy-mapper/pkg/hexapolicysupport" @@ -756,18 +756,15 @@ func (m *MapToCmd) Run(cli *CLI) error { _ = MarshalJsonNoEscape(bindings, outWriter.GetOutput()) outWriter.Close() case "cedar": - cMapper := awsCedar.New(map[string]string{}) + cMapper := cedar.NewCedarMapper(map[string]string{}) - cedar, err := cMapper.MapPoliciesToCedar(policies) + cedarPoliciesString, err := cMapper.MapHexaPolicies(m.File, policies) if err != nil { return err } - for _, v := range cedar.Policies { - policy := v.String() - fmt.Println(policy) - cli.GetOutputWriter().WriteString(policy, false) - } + fmt.Println(cedarPoliciesString) + cli.GetOutputWriter().WriteString(cedarPoliciesString, false) cli.GetOutputWriter().Close() } return nil @@ -802,9 +799,12 @@ func (m *MapFromCmd) Run(cli *CLI) error { } case "cedar": - cMapper := awsCedar.New(map[string]string{}) - - pols, err := cMapper.ParseFile(m.File) + cMapper := cedar.NewCedarMapper(map[string]string{}) + policyBytes, err := os.ReadFile(m.File) + if err != nil { + return err + } + pols, err := cMapper.MapCedarPolicyBytes(m.File, policyBytes) if err != nil { return err } diff --git a/cmd/hexa/hexa_test.go b/cmd/hexa/hexa_test.go index dbae496..4adf394 100644 --- a/cmd/hexa/hexa_test.go +++ b/cmd/hexa/hexa_test.go @@ -487,7 +487,8 @@ func (suite *testSuite) Test08_MapFromCmd() { command = "map from cedar ../../examples/policyExamples/cedarAlice.txt" res, err = suite.executeCommand(command, 0) assert.NoError(suite.T(), err, "Should be successful map of cedar") - assert.Contains(suite.T(), string(res), "cedar:Photo:") + assert.Contains(suite.T(), string(res), "Photo::VacationPhoto94.jpg") + assert.Contains(suite.T(), string(res), "\"Rule\": \"resource in Account::\\\"stacey\\\"\"") command = "map from gcp ../../examples/policyExamples/example_bindings.json" res, err = suite.executeCommand(command, 0) diff --git a/cmd/hexa/main.go b/cmd/hexa/main.go index 573a215..e8d621c 100644 --- a/cmd/hexa/main.go +++ b/cmd/hexa/main.go @@ -17,7 +17,7 @@ import ( "github.com/hexa-org/policy-mapper/sdk" ) -const Version string = "0.7.1" +const Version string = "0.7.2" type ParserData struct { parser *kong.Kong diff --git a/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego b/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego index 8754174..cc486a7 100644 --- a/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego +++ b/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego @@ -1,11 +1,11 @@ package hexaPolicy -# Rego Hexa Policy Interpreter v0.7.0 +# Rego Hexa Policy Interpreter v0.7.1 import rego.v1 import data.bundle.policies -hexa_rego_version := "0.7.0" +hexa_rego_version := "0.7.1" policies_evaluated := count(policies) @@ -60,6 +60,22 @@ allow_set contains policy_id if { condition_match(policy, input) } +# Returns the list of matching policy names based on current request with no actions +allow_set contains policy_id if { + some policy in policies + + # return id of the policy + policy_id := sprintf("%s", [policy.meta.policyId]) + + subject_match(policy.subjects, input.subject, input.req) + + not policy.actions + + is_object_match(policy.object, input.req) + + condition_match(policy, input) +} + scopes contains scope if { some policy in policies policy.meta.policyId in allow_set @@ -79,6 +95,15 @@ action_rights contains name if { name := sprintf("%s:%s", [policy.meta.policyId, action]) } +# Returns the list of possible actions where actions is empty +action_rights contains name if { + some policy in policies + policy.meta.policyId in allow_set + + count(policy.actions) == 0 + name := sprintf("%s:*", [policy.meta.policyId]) +} + # Returns whether the current operation is allowed allow if { count(allow_set) > 0 @@ -136,7 +161,7 @@ subject_member_match(member, _, req) if { actions_match(actions, _) if { # no actions is a match - not actions + count(actions) == 0 } actions_match(actions, req) if { @@ -236,4 +261,6 @@ condition_match(policy, inreq) if { } # Evaluate whether the condition is set to allow -action_allow(val) if lower(val) == "allow" +action_allow(val) if { + lower(val) == "allow" +} diff --git a/models/conditionLangs/cedarConditions/map_cedar.go b/models/conditionLangs/cedarConditions/map_cedar.go index 55bed7c..7e7eaeb 100644 --- a/models/conditionLangs/cedarConditions/map_cedar.go +++ b/models/conditionLangs/cedarConditions/map_cedar.go @@ -1,18 +1,19 @@ package cedarConditions import ( + "encoding/json" "errors" "fmt" "strconv" "strings" - cedarParser "github.com/cedar-policy/cedar-go/x/exp/parser" + "github.com/cedar-policy/cedar-go/types" + cedarjson "github.com/hexa-org/policy-mapper/models/formats/cedar/json" "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions" hexaParser "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions/parser" - log "golang.org/x/exp/slog" ) -func MapCedarConditionToHexa(cedarConditions []cedarParser.Condition) (*conditions.ConditionInfo, error) { +func MapCedarConditionToHexa(cedarConditions []cedarjson.ConditionJSON) (*conditions.ConditionInfo, error) { if cedarConditions == nil { return nil, nil @@ -26,7 +27,7 @@ func MapCedarConditionToHexa(cedarConditions []cedarParser.Condition) (*conditio return nil, nil case 1: condInfo.Rule, err = mapConditionClause(cedarConditions[0], true) - if cedarConditions[0].Type == cedarParser.ConditionWhen { + if cedarConditions[0].Kind == "when" { condInfo.Action = conditions.AAllow } else { condInfo.Action = conditions.ADeny @@ -50,18 +51,18 @@ func MapCedarConditionToHexa(cedarConditions []cedarParser.Condition) (*conditio } // mapConditionClause maps a when/unless clause -func mapConditionClause(cond cedarParser.Condition, isSingle bool) (string, error) { - exp := cond.Expression +func mapConditionClause(cond cedarjson.ConditionJSON, isSingle bool) (string, error) { + body := cond.Body var clause hexaParser.Expression var err error - switch cond.Type { - case cedarParser.ConditionWhen: + switch cond.Kind { + case "when": // if there is only a single condition clause then we don't need precedence brackets - can treat as nested - clause, err = mapCedarExpression(exp, isSingle) + clause, err = mapCedarNode(body, !isSingle) - case cedarParser.ConditionUnless: + case "unless": var notClause hexaParser.Expression - notClause, err = mapCedarExpression(exp, isSingle) + notClause, err = mapCedarNode(body, !isSingle) if isSingle { // The outer when/unless handles it clause = notClause @@ -79,308 +80,269 @@ func mapConditionClause(cond cedarParser.Condition, isSingle bool) (string, erro return res, err } -func mapCedarExpression(expression cedarParser.Expression, isNested bool) (hexaParser.Expression, error) { - switch expression.Type { - case cedarParser.ExpressionOr: - expressions := expression.Or.Ands - if len(expressions) == 1 { - return mapCedarAnd(expressions[0]) - } - return mapCedarOrs(expressions, isNested) - case cedarParser.ExpressionIf: - return nil, errors.New("hexa cedar mapper does not support if clauses") - } - return nil, errors.New(fmt.Sprintf("Unexpected expression type: %v", expression.Type)) +func sliceToStringArray(vals []string) string { + + return fmt.Sprintf("[%s]", strings.Join(vals, ", ")) } -func mapCedarOrs(ands []cedarParser.And, isNested bool) (hexaParser.Expression, error) { - lh, err := mapCedarAnd(ands[0]) - if err != nil { - return nil, err - } +func mapCedarRelationComparator(node cedarjson.NodeJSON) (string, error) { + switch { + case node.Value != nil: + val := node.Value.V + switch item := val.(type) { + case types.String: + return strconv.Quote(val.String()), nil + case types.EntityUID: + return fmt.Sprintf("%s::\"%s\"", item.Type, item.ID), nil + default: + return val.String(), nil + } - if len(ands) == 2 { - rh, err := mapCedarAnd(ands[1]) - if err != nil { - return nil, err + case node.Var != nil: + return *node.Var, nil + + case node.Set != nil: + vals := make([]string, len(node.Set)) + for k, n := range node.Set { + value, err := mapCedarRelationComparator(n) + if err != nil { + return "", err + } + vals[k] = value } - if isNested { - return makeLogical(hexaParser.OR, lh, rh), nil + return sliceToStringArray(vals), nil + case node.Access != nil: + lh, err := mapCedarRelationComparator(node.Access.Left) + if err != nil { + return "", err } - return hexaParser.PrecedenceExpression{ - Expression: makeLogical(hexaParser.OR, lh, rh), - }, nil + return fmt.Sprintf("%s.%s", lh, node.Access.Attr), nil + case node.IfThenElse != nil: + return "", formatNodeParseError(node, "if-then-else not supported by Hexa IDQL: %s") + default: + return "", formatNodeParseError(node, "unknown comparator type: %s") } +} - rh, err := mapCedarOrs(ands[1:], true) +func mapRelation(op hexaParser.CompareOperator, left cedarjson.NodeJSON, right cedarjson.NodeJSON) (hexaParser.Expression, error) { + + lh, err := mapCedarRelationComparator(left) if err != nil { return nil, err } - if isNested { - return makeLogical(hexaParser.OR, lh, rh), nil + rh, err := mapCedarRelationComparator(right) + if err != nil { + return nil, err } - return hexaParser.PrecedenceExpression{ - Expression: makeLogical(hexaParser.OR, lh, rh), + return hexaParser.AttributeExpression{ + AttributePath: lh, + Operator: op, + CompareValue: rh, }, nil } -func mapCedarAnd(and cedarParser.And) (hexaParser.Expression, error) { - relations := and.Relations - switch len(relations) { - case 1: - return mapCedarRelation(relations[0]) - case 2: - lh, err := mapCedarRelation(relations[0]) - if err != nil { - return nil, err +func mapCedarFunc(op hexaParser.CompareOperator, nodes []cedarjson.NodeJSON) (hexaParser.Expression, error) { + var left, right string + for _, node := range nodes { + if node.Value != nil { + right = node.Value.V.String() + } else if node.Access != nil { + attr, _ := mapCedarRelationComparator(node.Access.Left) + left = fmt.Sprintf("%s.%s", attr, node.Access.Attr) } - rh, err := mapCedarRelation(relations[1]) + } + + return hexaParser.AttributeExpression{ + AttributePath: left, + Operator: op, + CompareValue: right, + }, nil +} + +const likeError = "Hexa supports only SW, EW, and CO comparisons: %s" + +func mapCedarNode(node cedarjson.NodeJSON, isNested bool) (hexaParser.Expression, error) { + + switch { + case node.And != nil: + return mapCedarAnd(*node.And) + case node.Or != nil: + orNode := *node.Or + return mapCedarOr(orNode, isNested) + case node.Not != nil: + body := node.Not.Arg + exp, err := mapCedarNode(body, isNested) if err != nil { return nil, err } - return makeLogical(hexaParser.AND, lh, rh), nil - default: - lh, err := mapCedarRelation(relations[0]) + return hexaParser.NotExpression{Expression: exp}, nil + case node.Equals != nil: + return mapRelation(hexaParser.EQ, node.Equals.Left, node.Equals.Right) + case node.NotEquals != nil: + return mapRelation(hexaParser.NE, node.NotEquals.Left, node.NotEquals.Right) + case node.GreaterThan != nil: + return mapRelation(hexaParser.GT, node.GreaterThan.Left, node.GreaterThan.Right) + case node.FuncGreaterThan != nil: + return mapCedarFunc(hexaParser.GT, node.FuncGreaterThan) + case node.GreaterThanOrEqual != nil: + return mapRelation(hexaParser.GE, node.GreaterThanOrEqual.Left, node.GreaterThanOrEqual.Right) + case node.FuncGreaterThanOrEqual != nil: + return mapCedarFunc(hexaParser.GE, node.FuncGreaterThanOrEqual) + case node.LessThan != nil: + return mapRelation(hexaParser.LT, node.LessThan.Left, node.LessThan.Right) + case node.FuncLessThan != nil: + return mapCedarFunc(hexaParser.LT, node.FuncLessThan) + case node.LessThanOrEqual != nil: + return mapRelation(hexaParser.LE, node.LessThanOrEqual.Left, node.LessThanOrEqual.Right) + case node.FuncLessThanOrEqual != nil: + return mapCedarFunc(hexaParser.LE, node.FuncLessThanOrEqual) + case node.In != nil: + return mapRelation(hexaParser.IN, node.In.Left, node.In.Right) + case node.Contains != nil: + return mapRelation(hexaParser.CO, node.Contains.Left, node.Contains.Right) + case node.Is != nil: + lh, err := mapCedarRelationComparator(node.Is.Left) if err != nil { return nil, err } - remainAnd := cedarParser.And{ - Relations: relations[1:], + isExp := hexaParser.AttributeExpression{ + AttributePath: lh, + Operator: hexaParser.IS, + CompareValue: node.Is.EntityType} + + if node.Is.In == nil { + // this is an is expression only + return isExp, nil } - rh, err := mapCedarAnd(remainAnd) + // This is the Is In case + + rh, err := mapCedarRelationComparator(*node.Is.In) + inExp := hexaParser.AttributeExpression{ + AttributePath: lh, + Operator: hexaParser.IN, + CompareValue: rh} + + return hexaParser.LogicalExpression{ + Operator: hexaParser.AND, + Left: isExp, + Right: inExp, + }, nil + + case node.Has != nil: + lh, err := mapCedarRelationComparator(node.Has.Left) if err != nil { return nil, err } - return makeLogical(hexaParser.AND, lh, rh), nil - } -} + hasAttr := node.Has.Attr + if strings.Contains(hasAttr, " ") { + hasAttr = strconv.Quote(hasAttr) -// makeLogical checks for null expressions and either simplifies or returns a logical expression -func makeLogical(andor hexaParser.LogicalOperator, lh hexaParser.Expression, rh hexaParser.Expression) hexaParser.Expression { - if lh == nil { - return rh - } - if rh == nil { - return lh - } - return hexaParser.LogicalExpression{ - Operator: andor, - Left: lh, - Right: rh, - } -} + } + return hexaParser.AttributeExpression{ + AttributePath: fmt.Sprintf("%s.%s", lh, hasAttr), + Operator: hexaParser.PR, + CompareValue: "", + }, nil -func getSubExpression(rel cedarParser.Relation) (*cedarParser.Expression, error) { - add := rel.Add + case node.Like != nil: + lh, _ := mapCedarRelationComparator(node.Like.Left) + + pattern := removeQuotes(string(node.Like.Pattern.MarshalCedar())) + if strings.HasPrefix(pattern, "*") { + if strings.HasSuffix(pattern, "*") { + // Map "**" to Contains + if !strings.Contains(pattern[1:len(pattern)-1], "*") { + return hexaParser.AttributeExpression{ + AttributePath: lh, + Operator: hexaParser.CO, + CompareValue: strconv.Quote(pattern[1 : len(pattern)-1]), + }, nil + } + } + if strings.Contains(pattern[1:], "*") { + // this is a complex pattern + return nil, errors.New(fmt.Sprintf(likeError, pattern)) + } + return hexaParser.AttributeExpression{ + AttributePath: lh, + Operator: hexaParser.EW, + CompareValue: strconv.Quote(pattern[1:]), + }, nil + } + if strings.HasSuffix(pattern, "*") { - primary, err := getPrimary(add) - if err != nil { - return nil, err + if strings.Contains(pattern[0:len(pattern)-1], "*") { + // this is a complex pattern + + return nil, errors.New(fmt.Sprintf(likeError, pattern)) + } + return hexaParser.AttributeExpression{ + AttributePath: lh, + Operator: hexaParser.SW, + CompareValue: strconv.Quote(pattern[0 : len(pattern)-1]), + }, nil + } + return nil, errors.New(fmt.Sprintf(likeError, pattern)) + case node.IfThenElse != nil: + return nil, formatNodeParseError(node, "if-then-else is not supported by Hexa IDQL: %s") + + case node.Negate != nil, node.Subtract != nil, node.Add != nil, node.Multiply != nil: + return nil, formatNodeParseError(node, "calculations (negate, add, multiply, subtract) are not supported by Hexa IDQL: %s") + + default: + return nil, formatNodeParseError(node, "unsupported expression: %s") } - return &primary.Expression, nil + } -func parseRelNone(rel cedarParser.Relation) (hexaParser.Expression, error) { +func formatNodeParseError(node cedarjson.NodeJSON, errMessageFmt string) error { + exp, _ := json.Marshal(node) + return errors.New(fmt.Sprintf(errMessageFmt, string(exp))) +} - unaryExpression, err := getSubExpression(rel) +func mapCedarOr(or cedarjson.BinaryJSON, isNested bool) (hexaParser.Expression, error) { + lh, err := mapCedarNode(or.Left, isNested) if err != nil { return nil, err } - primary, _ := getPrimary(rel.Add) - if primary != nil { - switch primary.Type { - case cedarParser.PrimaryRecInits: - return nil, errors.New("cedar RecInits values not supported") - case cedarParser.PrimaryVar: - accesses := rel.Add.Mults[0].Unaries[0].Member.Accesses - left := primary.Var.String() - op := hexaParser.CO - if accesses != nil { - for _, access := range accesses { - switch access.Type { - case cedarParser.AccessField: - left = left + "." + access.Name - case cedarParser.AccessIndex: - left = left + "[" + strconv.Quote(access.Name) + "]" - case cedarParser.AccessCall: - switch access.Name { - case "contains": - op = hexaParser.CO - case "lessThan": - op = hexaParser.LT - case "greaterThan": - op = hexaParser.GT - case "lessThanOrEqual": - op = hexaParser.LE - case "greaterThanOrEqual": - op = hexaParser.GE - - default: - return nil, errors.New(fmt.Sprintf("comparison function %s not supported by IDQL at this time", access.Name)) - } - sb := strings.Builder{} - for i, e := range access.Expressions { - if i > 0 { - // return nil, errors.New(fmt.Sprintf("set comparisons not supported: %s", access.String())) - sb.WriteString(", ") - } - sb.WriteString(e.String()) - } - return hexaParser.AttributeExpression{ - AttributePath: left, - Operator: op, - CompareValue: sb.String(), - }, nil - - } - } - } - return nil, errors.New(fmt.Sprintf("unknown Cedar PrimaryVar relation (%s)", rel.String())) - default: - // fall through and treat as precedence brackets - } - } - // This is just a set of brackets (precedence) - mapExp, err := mapCedarExpression(*unaryExpression, true) + rh, err := mapCedarNode(or.Right, isNested) if err != nil { return nil, err } - if mapExp == nil { - return nil, nil + if !isNested { + return makeLogical(hexaParser.OR, lh, rh), nil } - return hexaParser.PrecedenceExpression{Expression: mapExp}, nil - + return hexaParser.PrecedenceExpression{ + Expression: makeLogical(hexaParser.OR, lh, rh), + }, nil } -func getPrimary(add cedarParser.Add) (*cedarParser.Primary, error) { - mults := add.Mults - if mults == nil { - return nil, errors.New("no primary found") +func mapCedarAnd(and cedarjson.BinaryJSON) (hexaParser.Expression, error) { + lh, err := mapCedarNode(and.Left, true) + if err != nil { + return nil, err } - return &mults[0].Unaries[0].Member.Primary, nil -} - -func convertPrimary(add cedarParser.Add) (string, error) { - prime, _ := getPrimary(add) - - if prime != nil { - switch prime.Type { - case cedarParser.PrimaryExpr: - exp := prime.Expression - if exp.Type == cedarParser.ExpressionIf { - return "", errors.New("relation expressions containing if not supported") - } - case cedarParser.PrimaryRecInits: - return "", errors.New("relation expressions containing inits {} not supported") - case cedarParser.PrimaryVar: - // This is the case where an index is used to reference sub-attribute principal["name"] - primeAttr := prime.Var.String() - accesses := add.Mults[0].Unaries[0].Member.Accesses - sb := strings.Builder{} - sb.WriteString(primeAttr) - for _, access := range accesses { - sb.WriteRune('.') - sb.WriteString(access.Name) - } - return sb.String(), nil - // otherwise just fall through and return the unmapped value - default: - } + rh, err := mapCedarNode(and.Right, true) + if err != nil { + return nil, err } - return add.String(), nil + return makeLogical(hexaParser.AND, lh, rh), nil } -func mapCedarRelation(rel cedarParser.Relation) (hexaParser.Expression, error) { - switch rel.Type { - case cedarParser.RelationNone: - // this is Precedence ( ) - // exp,err := mapCedarAnd(rel.RelOpRhs) - return parseRelNone(rel) - - case cedarParser.RelationRelOp: - var err error - - left, err := convertPrimary(rel.Add) - if err != nil { - return nil, err - } - - op := rel.RelOp - - right, err := convertPrimary(rel.RelOpRhs) - if err != nil { - return nil, err - } - return hexaParser.AttributeExpression{ - AttributePath: left, - Operator: mapCedarOperator(op), - CompareValue: right, - }, nil - case cedarParser.RelationHasIdent: - attribute := rel.Add.String() + "." + rel.Str - return hexaParser.AttributeExpression{ - AttributePath: attribute, - Operator: hexaParser.PR, - CompareValue: "", - }, nil - case cedarParser.RelationHasLiteral: - // This is the same as HasIdent except that the attribute name has a space in it - attribute := rel.Add.String() + ".\"" + rel.Str + "\"" - return hexaParser.AttributeExpression{ - AttributePath: attribute, - Operator: hexaParser.PR, - CompareValue: "", - }, nil - case cedarParser.RelationIs: - // Closest equivalent would be User co principal - return hexaParser.AttributeExpression{ - AttributePath: rel.Add.String(), - Operator: hexaParser.IS, - CompareValue: rel.Path.String(), - }, nil - - case cedarParser.RelationIsIn: - isExpression := hexaParser.AttributeExpression{ - AttributePath: rel.Add.String(), - Operator: hexaParser.IS, - CompareValue: rel.Path.String(), - } - inExpression := hexaParser.AttributeExpression{ - AttributePath: rel.Add.String(), - Operator: hexaParser.IN, - CompareValue: rel.Entity.String(), - } - return hexaParser.LogicalExpression{ - Operator: hexaParser.AND, - Left: isExpression, - Right: inExpression, - }, nil +// makeLogical checks for null expressions and either simplifies or returns a logical expression +func makeLogical(andor hexaParser.LogicalOperator, lh hexaParser.Expression, rh hexaParser.Expression) hexaParser.Expression { + if lh == nil { + return rh } - return nil, errors.New(fmt.Sprintf("Unknown Relation type: %s, source: %s", rel.Type, rel.String())) -} - -func mapCedarOperator(op cedarParser.RelOp) hexaParser.CompareOperator { - switch op { - case cedarParser.RelOpEq: - return hexaParser.EQ - case cedarParser.RelOpNe: - return hexaParser.NE - case cedarParser.RelOpLt: - return hexaParser.LT - case cedarParser.RelOpLe: - return hexaParser.LE - case cedarParser.RelOpGt: - return hexaParser.GT - case cedarParser.RelOpGe: - return hexaParser.GE - case cedarParser.RelOpIn: - return hexaParser.IN + if rh == nil { + return lh + } + return hexaParser.LogicalExpression{ + Operator: andor, + Left: lh, + Right: rh, } - // Cedar does not appear to have an equivalent of hexaParser.CO - // this should not happen. - log.Error(fmt.Sprintf("Unexpected relation operator encountered: %s", op)) - return hexaParser.CompareOperator(op) } diff --git a/models/conditionLangs/cedarConditions/map_hexa.go b/models/conditionLangs/cedarConditions/map_hexa.go index 6aadccd..86aa6e6 100644 --- a/models/conditionLangs/cedarConditions/map_hexa.go +++ b/models/conditionLangs/cedarConditions/map_hexa.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/cedar-policy/cedar-go/types" "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions" hexaParser "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions/parser" ) @@ -266,27 +267,58 @@ func (mapper *CedarConditionMapper) mapFilterLogical(logicFilter *hexaParser.Log } } +// removeQuotes checks for quotes and removes them if present +func removeQuotes(val string) string { + if strings.HasSuffix(val, "\"") && strings.HasPrefix(val, "\"") { + unquote, err := strconv.Unquote(val) + if err == nil { + return unquote + } + } + return val +} + func (mapper *CedarConditionMapper) mapFilterAttrExpr(attrExpr *hexaParser.AttributeExpression) string { compareValue := prepareValue(attrExpr) mapPath := mapper.NameMapper.GetProviderAttributeName(attrExpr.AttributePath) + isDecimal := false + format := "%s %s %s" + decimalVal, err := types.ParseDecimal(compareValue) + if err == nil { + isDecimal = true + format = "%s.%s(%s)" + } + switch attrExpr.Operator { case hexaParser.NE: return mapPath + " != " + compareValue case hexaParser.LT: - return mapPath + " < " + compareValue + if isDecimal { + return fmt.Sprintf(format, mapPath, "lessThan", decimalVal.String()) + } + return fmt.Sprintf(format, mapPath, "<", compareValue) case hexaParser.LE: - return mapPath + " <= " + compareValue + if isDecimal { + return fmt.Sprintf(format, mapPath, "lessThanOrEqual", decimalVal.String()) + } + return fmt.Sprintf(format, mapPath, "<=", compareValue) case hexaParser.GT: - return mapPath + " > " + compareValue + if isDecimal { + return fmt.Sprintf(format, mapPath, "greaterThan", decimalVal.String()) + } + return fmt.Sprintf(format, mapPath, ">", compareValue) case hexaParser.GE: - return mapPath + " >= " + compareValue + if isDecimal { + return fmt.Sprintf(format, mapPath, "greaterThanOrEqual", decimalVal.String()) + } + return fmt.Sprintf(format, mapPath, ">=", compareValue) case hexaParser.SW: - return mapPath + ".startsWith(" + compareValue + ")" + return fmt.Sprintf("%s like \"%s*\"", mapPath, removeQuotes(compareValue)) case hexaParser.EW: - return mapPath + ".endsWith(" + compareValue + ")" + return fmt.Sprintf("%s like \"*%s\"", mapPath, removeQuotes(compareValue)) case hexaParser.PR: lastIndex := strings.LastIndex(mapPath, ".") left := mapPath[0:lastIndex] diff --git a/models/conditionLangs/cedarConditions/map_test.go b/models/conditionLangs/cedarConditions/map_test.go index d0525a4..5837a44 100644 --- a/models/conditionLangs/cedarConditions/map_test.go +++ b/models/conditionLangs/cedarConditions/map_test.go @@ -1,12 +1,14 @@ package cedarConditions import ( + "encoding/json" "fmt" "reflect" "strings" "testing" - "github.com/cedar-policy/cedar-go/x/exp/parser" + "github.com/cedar-policy/cedar-go" + policyjson "github.com/hexa-org/policy-mapper/models/formats/cedar/json" "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions" ) @@ -22,12 +24,16 @@ func TestMapCedar(t *testing.T) { Rule: "resource.owner eq principal", Action: conditions.AAllow, }, false}, + {"When not(equal)", "when { !(resource.owner == principal) }", &conditions.ConditionInfo{ + Rule: "not (resource.owner eq principal)", + Action: conditions.AAllow, + }, false}, {"Precedence ands", "when { ( resource.owner == principal.id ) && resource.id == \"1234\" && principal.type == \"customer\" }", &conditions.ConditionInfo{ - Rule: "(resource.owner eq principal.id) and resource.id eq \"1234\" and principal.type eq \"customer\"", + Rule: "resource.owner eq principal.id and resource.id eq \"1234\" and principal.type eq \"customer\"", Action: conditions.AAllow, }, false}, {"Precedence ors", "when { ( resource.owner == principal.id ) || resource.id == \"1234\" && principal.type == \"customer\" }", &conditions.ConditionInfo{ - Rule: "(resource.owner eq principal.id) or resource.id eq \"1234\" and principal.type eq \"customer\"", + Rule: "resource.owner eq principal.id or resource.id eq \"1234\" and principal.type eq \"customer\"", Action: conditions.AAllow, }, false}, {"Nested", "when { resource.owner == principal.id && (principal.type == \"staff\" || principal.type == \"employee\") || resource.id == \"1234\" && principal.type == \"customer\" }", &conditions.ConditionInfo{ @@ -62,7 +68,19 @@ func TestMapCedar(t *testing.T) { Rule: "resource.\"literal ident\" pr", Action: conditions.AAllow, }, false}, + {"Starts with", "when { resource like \"Todo::New*\" }", &conditions.ConditionInfo{ + Rule: "resource sw \"Todo::New\"", + Action: conditions.AAllow, + }, false}, + {"Ends with", "when { resource like \"*NewTodo\" }", &conditions.ConditionInfo{ + Rule: "resource ew \"NewTodo\"", + Action: conditions.AAllow, + }, false}, + {"Ends with error", "when { resource like \"*New*Todo\" }", &conditions.ConditionInfo{ + Rule: "resource ew \"NewTodo\"", + Action: conditions.AAllow, + }, true}, {"Is In", "when { principal is User in Group::\"accounting\"}", &conditions.ConditionInfo{ Rule: "principal is User and principal in Group::\"accounting\"", Action: conditions.AAllow, @@ -113,13 +131,43 @@ when { resource.owner != "somebody" } Rule: "principal eq User::\"a1b2c3d4-e5f6-a1b2-c3d4-EXAMPLE11111\"", Action: conditions.AAllow, }, false}, - {"Dot operand test", "when { principal.id.greaterThan(4) || principal.id.greaterThanOrEqual(100) || principal.access.lessThan(4) || principal.name.lessThanOrEqual(\"m\") }", + {"Greater test", "when { principal.id.greaterThan(4) }", &conditions.ConditionInfo{ - Rule: "principal.id gt 4 or principal.id ge 100 or principal.access lt 4 or principal.name le \"m\"", + Rule: "principal.id gt 4", + Action: conditions.AAllow, + }, false}, + {"GreaterEqual test", "when { principal.id.greaterThanOrEqual(100) }", + &conditions.ConditionInfo{ + Rule: "principal.id ge 100", + Action: conditions.AAllow, + }, false}, + {"Lessthan test", "when { principal.access.lessThan(4) }", + &conditions.ConditionInfo{ + Rule: "principal.access lt 4", + Action: conditions.AAllow, + }, false}, + {"LessthanEqual test", "when { principal.name <= \"m\" }", + &conditions.ConditionInfo{ + Rule: "principal.name le \"m\"", + Action: conditions.AAllow, + }, false}, + {"Contains string using like", "when { principal.name like \"*test*\" }", + &conditions.ConditionInfo{ + Rule: "principal.name co \"test\"", Action: conditions.AAllow, }, false}, + // negative tests + {"If then error", "when { if principal.numberOfLaptops < 5 then principal.jobLevel > 6 else false }", nil, true}, + {"Starts with error", "when { resource like \"Todo*::New*\" }", &conditions.ConditionInfo{ + Rule: "resource sw \"Todo::New\"", + Action: conditions.AAllow, + }, true}, + {"Like error", "when { resource like \"New*Todo\" }", &conditions.ConditionInfo{ + Rule: "resource ew \"NewTodo\"", + Action: conditions.AAllow, + }, true}, {"primary-if-test-error", "when {\n (if principal has name then principal.name else \"Joe\") == \"Alice\"\n}", &conditions.ConditionInfo{}, true}, {"expression-if-error", "when { principal.id > 4 && (if principal.id == \"1\" then true else false) }", @@ -130,6 +178,9 @@ when { resource.owner != "somebody" } Rule: "principal.name co \"smith\"", Action: conditions.AAllow, }, true}, + {"Negate test", "when { - ( 3 + 1) }", + nil, true}, + {"Calculation test", "when { ( 3 + 4 * 2 )}", nil, true}, } for _, tt := range tests { @@ -143,14 +194,25 @@ when { resource.owner != "somebody" } sb.WriteString(";") policyInput := sb.String() - tokens, err := parser.Tokenize([]byte(policyInput)) - testutilOK(t, err) - policies, err := parser.Parse(tokens) + pl, err := cedar.NewPolicyListFromBytes("", []byte(policyInput)) testutilOK(t, err) - hexaCond, err := doMapCedar(policies) - testutilEquals(t, err != nil, tt.err) - if err == nil { - testutilEquals(t, hexaCond, tt.out) + + for _, policy := range pl { + jsonBytes, err := policy.MarshalJSON() + testutilOK(t, err) + var jsonPolicy policyjson.PolicyJSON + + err = json.Unmarshal(jsonBytes, &jsonPolicy) + testutilOK(t, err) + + cedarConds := jsonPolicy.Conditions + + hexaCond, err := MapCedarConditionToHexa(cedarConds) + testutilEquals(t, err != nil, tt.err) + if err == nil { + testutilEquals(t, hexaCond, tt.out) + } + } }) @@ -167,21 +229,6 @@ func doMapHexa(hexaCondition string) (string, error) { return mapper.MapConditionToCedar(cond) } -func doMapCedar(policies parser.Policies) (*conditions.ConditionInfo, error) { - if len(policies) == 0 { - return nil, nil - } - - conditions := policies[0].Conditions - if conditions == nil { - return nil, nil - } - hexaCond, err := MapCedarConditionToHexa(conditions) - if err != nil { - return nil, err - } - return hexaCond, err -} func TestMapHexa(t *testing.T) { examples := [][3]string{ @@ -191,14 +238,21 @@ func TestMapHexa(t *testing.T) { {"Contains", "name.familyName co \"O'Malley\"", "when { name.familyName.contains(\"O'Malley\") }"}, {"Precedence", "(username eq \"bjensen\")", "when { username == \"bjensen\" }"}, {"Spacing", "userName eq \"bjensen\"", "when { userName == \"bjensen\" }"}, + {"GreaterThan number", "account.level gt 4", "when { account.level > 4 }"}, + {"GreaterThan decimal", "account.level gt 4.5", "when { account.level.greaterThan(4.5) }"}, + {"GreaterThanEqual decimal", "account.level ge 4.5", "when { account.level.greaterThanOrEqual(4.5) }"}, {"GT Test", "level gt 12", "when { level > 12 }"}, - {"Numeric", "level gt 12.3", "when { level > 12.3 }"}, + {"LessThan Dec", "level lt 12.3", "when { level.lessThan(12.3) }"}, + {"LessThanEqual Dec", "level le 12.3", "when { level.lessThanOrEqual(12.3) }"}, + {"LessThan int", "level lt 12", "when { level < 12 }"}, + {"LessThanEqual int", "level le 12", "when { level <= 12 }"}, + {"Exponential", "level eq 123.45e-5", "when { level == 123.45e-5 }"}, {"Embedded chars", "emails.type eq \"w o(rk)\"", "when { emails.type == \"w o(rk)\" }"}, {"Operator case test", "userName Eq \"bjensen\"", "when { userName == \"bjensen\" }"}, {"Deep Ors", "((userName eq A) or (username eq \"B\")) or username eq C", "when { (userName == \"A\" || username == \"B\") || username == \"C\" }"}, - {"Starts with", "userName sw \"J\"", "when { userName.startsWith(\"J\") }"}, - {"Long Urn", "urn:ietf:params:scim:schemas:core:2.0:User:userName sw \"J\"", "when { urn:ietf:params:scim:schemas:core:2.0:User:userName.startsWith(\"J\") }"}, + {"Starts with", "userName sw \"J\"", "when { userName like \"J*\" }"}, + {"Long Urn", "urn:ietf:params:scim:schemas:core:2.0:User:userName sw \"J\"", "when { urn:ietf:params:scim:schemas:core:2.0:User:userName like \"J*\" }"}, {"Nested precedence or", "userType eq \"Employee\" and (emails co \"example.com\" or emails.value co \"example.org\")", "when { userType == \"Employee\" }\nwhen { emails.contains(\"example.com\") || emails.value.contains(\"example.org\") }"}, {"Nested Not or", "userType eq \"Employee\" and not(emails co \"example.com\" or emails.value co \"example.org\")", "when { userType == \"Employee\" }\nunless { emails.contains(\"example.com\") || emails.value.contains(\"example.org\") }"}, @@ -212,7 +266,12 @@ func TestMapHexa(t *testing.T) { { "Attribute Expressions", "principal.name LT \"z\" or usage le 4 or logincount ge 2 or ( principal.name ew \"smith\" and principal in Groups::\"accounting\" and (account.active eq true or account.enabled ne false ))", - "when { principal.name < \"z\" || usage <= 4 || logincount >= 2 || (principal.name.endsWith(\"smith\") && principal in Groups::\"accounting\" && (account.active == true || account.enabled != false)) }", + "when { principal.name < \"z\" || usage <= 4 || logincount >= 2 || (principal.name like \"*smith\" && principal in Groups::\"accounting\" && (account.active == true || account.enabled != false)) }", + }, + { + "Not / Unless", + "not ( principal.name eq \"Smith\" )", + "unless { principal.name == \"Smith\" }", }, // {"emails[type eq work and value ew \"h[exa].org\"]", "emails[type eq \"work\" and value ew \"h[exa].org\"]"}, } @@ -252,11 +311,3 @@ func testutilOK(t testing.TB, err error) { } t.Fatalf("got %v want nil", err) } - -func testutilError(t testing.TB, err error) { - t.Helper() - if err != nil { - return - } - t.Fatalf("got nil want error") -} diff --git a/models/formats/awsCedar/ReadMe.md b/models/formats/awsCedar/ReadMe.md new file mode 100644 index 0000000..cc9c970 --- /dev/null +++ b/models/formats/awsCedar/ReadMe.md @@ -0,0 +1,6 @@ +# AWS Cedar Parser based on Participle - Deprecated + +This directory contains a parser/mapper for the original published version of Amazon Cedar Policy Language +It uses the [Participle parser from Alec Thomas](github.com/alecthomas/participle/v2). + +This mapper has been replaced by an [implementation](../cedar) based on [Cedar-go](https://github.com/cedar-policy/cedar-go). \ No newline at end of file diff --git a/models/formats/awsCedar/amazon_cedar.go b/models/formats/awsCedar/amazon_cedar.go index 7e86ed8..05cc43a 100644 --- a/models/formats/awsCedar/amazon_cedar.go +++ b/models/formats/awsCedar/amazon_cedar.go @@ -1,3 +1,8 @@ +// Package awsCedar provides parsing and mapping to and from an earlier version of AWS Cedar Policy Language +// +// Deprecated: This package has been deprecated and is replaced by /models/formats/cedar. +// +// This package will be removed shortly in a future update. package awsCedar import ( @@ -17,6 +22,9 @@ type CedarPolicyMapper struct { Parser *participle.Parser[CedarPolicies] } +// New opens an instance of the CedarPolicyMapper. If nameMap is provided attribute +// names in conditions will be converted in the form [hexaName]cedarName. +// Deprecated: This mapper has been replaced by the cedar-go version in https://github.com/hexa-org/policy-mapper/models/formats/cedar func New(nameMap map[string]string) *CedarPolicyMapper { return &CedarPolicyMapper{ConditionMapper: gcpcel.GoogleConditionMapper{NameMapper: policyCond.NewNameMapper(nameMap)}, Parser: participle.MustBuild[CedarPolicies](participle.CaseInsensitive("permit", "forbid", "unless", "when"))} diff --git a/models/formats/cedar/cedar_mapper.go b/models/formats/cedar/cedar_mapper.go new file mode 100644 index 0000000..0d23071 --- /dev/null +++ b/models/formats/cedar/cedar_mapper.go @@ -0,0 +1,405 @@ +package cedar + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/cedar-policy/cedar-go" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions" + "github.com/hexa-org/policy-mapper/pkg/hexapolicysupport" + + "github.com/hexa-org/policy-mapper/models/conditionLangs/cedarConditions" + policyjson "github.com/hexa-org/policy-mapper/models/formats/cedar/json" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" +) + +type CedarMapper struct { + condMap *cedarConditions.CedarConditionMapper +} + +func NewCedarMapper(attrNameMap map[string]string) *CedarMapper { + return &CedarMapper{condMap: &cedarConditions.CedarConditionMapper{conditions.NewNameMapper(attrNameMap)}} +} + +/* +cedar_mapper.go is needed to expose the internal Cedar parser for use by Hexa. In this package we leverage the Cedar Tokenizer +to build up the AST and Policy tree for mapping. +*/ + +type PolicyPair struct { + HexaPolicy *hexapolicy.PolicyInfo + CedarPolicy *cedar.Policy + Ast policyjson.PolicyJSON + res strings.Builder +} + +type ParseSet struct { + IdqlPolicies []hexapolicy.PolicyInfo + Pairs []PolicyPair + + Pos int + loc string + conditionMapper *cedarConditions.CedarConditionMapper +} + +func (c *CedarMapper) MapCedarPolicyBytes(location string, cedarBytes []byte) (*hexapolicy.Policies, error) { + + policies, err := cedar.NewPolicyListFromBytes(location, cedarBytes) + if err != nil { + return nil, err + } + cset := ParseSet{ + Pairs: make([]PolicyPair, 0), + IdqlPolicies: make([]hexapolicy.PolicyInfo, 0), + Pos: 0, + loc: location, + conditionMapper: c.condMap, + } + + for _, cedarPolicy := range policies { + err := cset.MapCedarPolicy(cedarPolicy) + if err != nil { + break + } + + } + + return &hexapolicy.Policies{ + Policies: cset.IdqlPolicies, + App: &location, + }, err + +} + +func (t *ParseSet) MapCedarPolicy(policy *cedar.Policy) error { + var err error + hexaPolicy := hexapolicy.PolicyInfo{Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}} + + jsonBytes, err := policy.MarshalJSON() + if err != nil { + return err + } + + var jsonPolicy policyjson.PolicyJSON + + err = json.Unmarshal(jsonBytes, &jsonPolicy) + if err != nil { + return err + } + pair := PolicyPair{ + HexaPolicy: &hexaPolicy, + CedarPolicy: policy, + Ast: jsonPolicy, + } + + pair.mapCedarAnnotations() + pair.mapCedarSubject() + pair.mapCedarAction() + pair.mapCedarResource() + err = pair.mapCedarConditions() + + if err != nil { + return err + } + + if pair.HexaPolicy != nil { + t.IdqlPolicies = append(t.IdqlPolicies, *pair.HexaPolicy) + } else { + return errors.New("no policy mapped") + } + + return nil +} + +func (c *CedarMapper) MapHexaPolicyBytes(location string, idqlBytes []byte) (string, error) { + policies, err := hexapolicysupport.ParsePolicies(idqlBytes) + if err != nil { + return "", err + } + return c.MapHexaPolicies(location, policies) +} + +func (c *CedarMapper) MapHexaPolicies(location string, policies []hexapolicy.PolicyInfo) (string, error) { + sb := strings.Builder{} + pset := ParseSet{ + Pairs: make([]PolicyPair, 0), + IdqlPolicies: make([]hexapolicy.PolicyInfo, 0), + Pos: 0, + loc: location, + conditionMapper: c.condMap, + } + + for _, hexaPolicy := range policies { + cedarPol, err := pset.MapHexaPolicy(hexaPolicy) + if err != nil { + return "", err + } + + sb.WriteString(cedarPol) + + } + + return sb.String(), nil +} + +func (t *ParseSet) MapHexaPolicy(policy hexapolicy.PolicyInfo) (string, error) { + pp := PolicyPair{ + HexaPolicy: &policy, + res: strings.Builder{}, + } + + annotations := pp.mapHexaAnnotations() + subjects := pp.mapHexaSubjects() + actions := pp.mapHexaAction() + resource := pp.mapHexaResource() + + condition, err := t.conditionMapper.MapConditionToCedar(pp.HexaPolicy.Condition) + if err != nil { + return "", err + } + + // conditions ;= pp.mapConditions() + for _, subject := range subjects { + pp.writeCedarPolicy(annotations, subject, actions, resource, condition) + } + + return pp.res.String(), nil +} + +func (pp *PolicyPair) writeCedarPolicy(annotations, subject, actions, resource, conditions string) { + pp.res.WriteString(annotations) + // Note: IDQL policies are always a permit + pp.res.WriteString("permit (\n ") + pp.res.WriteString(subject) + pp.res.WriteString("\n ") + pp.res.WriteString(actions) + pp.res.WriteString("\n ") + pp.res.WriteString(resource) + pp.res.WriteString("\n)") + if conditions != "" { + pp.res.WriteString("\n") + pp.res.WriteString(conditions) + } + + pp.res.WriteString(";\n") + + return +} + +// For Hexa, we just map annotations to Policy Meta +func (pp *PolicyPair) mapCedarAnnotations() { + meta := pp.HexaPolicy.Meta + + annotations := pp.CedarPolicy.Annotations + aMap := annotations() + if aMap == nil || len(aMap) == 0 { + return + } + if meta.SourceData == nil { + meta.SourceData = make(map[string]interface{}) + } + meta.SourceData["annotations"] = aMap + pp.HexaPolicy.Meta = meta +} + +func (pp *PolicyPair) mapHexaAnnotations() string { + meta := pp.HexaPolicy.Meta + sourceData := meta.SourceData + if sourceData == nil { + return "" + } + annotationMap, ok := sourceData["annotations"].(map[string]interface{}) + if !ok { + return "" + } + sb := strings.Builder{} + for key, val := range annotationMap { + sb.WriteString(fmt.Sprintf("@%s(\"%s\")\n", key, val)) + } + return sb.String() +} + +func mapCedarScope(isSubj bool, scope policyjson.ScopeJSON) []string { + switch scope.Op { + case "All": + if isSubj { + return []string{hexapolicy.SubjectAnyUser} + } + return []string{} + case "==": + if isSubj { + return []string{fmt.Sprintf("%s:%s", scope.Entity.Type, scope.Entity.ID)} + } + return []string{fmt.Sprintf("%s::%s", scope.Entity.Type, scope.Entity.ID)} + case "is": + // This is "principal is User" + // subj = []string{hexapolicy.SubjectAnyAuth} + if scope.In != nil { + // is in + isType := scope.EntityType + inEntity := fmt.Sprintf("%s::%s", scope.In.Entity.Type, scope.In.Entity.ID) + return []string{fmt.Sprintf("Type:%s[%s]", isType, inEntity)} + } else { + return []string{fmt.Sprintf("Type:%s", scope.EntityType)} + } + + case "in": + if scope.Entity != nil { + return []string{fmt.Sprintf("[%s::%s]", scope.Entity.Type, scope.Entity.ID)} + } else { + items := make([]string, len(scope.Entities)) + for i, entity := range scope.Entities { + items[i] = fmt.Sprintf("%s::%s", entity.Type, entity.ID) + } + return items + } + + } + return []string{} +} + +func (pp *PolicyPair) mapCedarSubject() { + + principal := pp.Ast.Principal + + subjs := mapCedarScope(true, principal) + if len(subjs) == 0 { + subjs = hexapolicy.SubjectInfo{hexapolicy.SubjectAnyUser} + } + pp.HexaPolicy.Subjects = subjs +} + +func mapHexaValue(verb string, member string) string { + if verb == "principal" { + if strings.EqualFold(member, "any") { + return "principal," + } + if strings.EqualFold(member, "anyAuthenticated") { + return "principal is User," + } + } + comma := "," + if verb == "resource" { + comma = "" + } + if member == "" { + return fmt.Sprintf("%s%s", verb, comma) + } + // check for is type + if strings.HasPrefix(member, "Type:") { + entity := member[5:] + if strings.Contains(entity, "[") { + openIndex := strings.Index(entity, "[") + isType := entity[0:openIndex] + inEntity := mapHexaToCedarValue(entity[openIndex+1 : len(entity)-1]) + return fmt.Sprintf("%s is %s in %s%s", verb, isType, inEntity, comma) + } else { + return fmt.Sprintf("%s is %s%s", verb, entity, comma) + } + } + // Check for "in" type + if strings.HasPrefix(member, "[") { + return fmt.Sprintf("%s in %s%s", verb, mapHexaToCedarValue(member[1:len(member)-1]), comma) + } + // assume it is an == + + memberFix := mapHexaToCedarValue(member) + + return fmt.Sprintf("%s == %s%s", verb, memberFix, comma) +} + +func mapHexaToCedarValue(item string) string { + item = strings.Replace(item, "::", ":", -1) + itemComps := strings.Split(item, ":") + eId := itemComps[len(itemComps)-1] + eType := itemComps[0] + if len(itemComps) > 2 { // handles the case TestApp::Photos::"vacationPhoto.jpg" + eType = strings.Join(itemComps[0:len(itemComps)-2], "::") + } + itemFix := item + if len(itemComps) >= 2 { + itemFix = fmt.Sprintf("%s::\"%s\"", eType, eId) + } + return itemFix +} + +func (pp *PolicyPair) mapHexaSubjects() []string { + if pp.HexaPolicy == nil || pp.HexaPolicy.Subjects == nil { + return nil + } + members := pp.HexaPolicy.Subjects + if len(members) == 0 || strings.EqualFold(members[0], hexapolicy.SubjectAnyUser) { + return []string{"principal,"} + } + res := make([]string, 0) + for _, member := range members { + res = append(res, mapHexaValue("principal", member)) + } + return res +} + +func (pp *PolicyPair) mapCedarAction() { + action := pp.Ast.Action + + values := mapCedarScope(false, action) + + actions := make([]hexapolicy.ActionInfo, len(values)) + for i, value := range values { + actions[i] = hexapolicy.ActionInfo(value) + } + pp.HexaPolicy.Actions = actions +} + +func (pp *PolicyPair) mapHexaAction() string { + actions := pp.HexaPolicy.Actions + if actions == nil || len(actions) == 0 { + return "action," + } + if len(actions) == 1 { + // if action has a prefix of "Role" then the value is action in xxx + value := mapHexaToCedarValue(string(actions[0])) + if strings.HasPrefix(strings.ToLower(value), "role:") { + return fmt.Sprintf("action in %s,", value[5:]) + } + return fmt.Sprintf("action == %s,", value) + } + var sb strings.Builder + sb.WriteString("action in [") + for i, e := range actions { + if i > 0 { + sb.WriteString(",") + } + sb.WriteString(mapHexaToCedarValue(string(e))) + } + sb.WriteString("],") + return sb.String() +} + +func (pp *PolicyPair) mapCedarResource() { + resource := pp.Ast.Resource + + values := mapCedarScope(false, resource) + if values == nil || len(values) == 0 { + pp.HexaPolicy.Object = hexapolicy.ObjectInfo("") + } else { + pp.HexaPolicy.Object = hexapolicy.ObjectInfo(values[0]) + } + +} + +func (pp *PolicyPair) mapHexaResource() string { + resource := pp.HexaPolicy.Object.String() + + return mapHexaValue("resource", resource) + +} + +func (pp *PolicyPair) mapCedarConditions() error { + hexaCondition, err := cedarConditions.MapCedarConditionToHexa(pp.Ast.Conditions) + if hexaCondition != nil { + pp.HexaPolicy.Condition = hexaCondition + } + return err +} diff --git a/models/formats/cedar/cedar_test.go b/models/formats/cedar/cedar_test.go index 529da7d..00047ad 100644 --- a/models/formats/cedar/cedar_test.go +++ b/models/formats/cedar/cedar_test.go @@ -6,69 +6,12 @@ import ( "reflect" "testing" + "github.com/cedar-policy/cedar-go" "github.com/hexa-org/policy-mapper/pkg/hexapolicy" "github.com/stretchr/testify/assert" ) -const policyCedar = ` -@comment("this is an annotation") -@description("Makes everything permitted") -permit ( - principal, - action, - resource -); - -permit ( - principal == User::"alice", - action == Action::"viewPhoto", - resource == Photo::"VacationPhoto.jpg" -); - -permit ( - principal is User in UserGroup::"AVTeam", - action in [PhotoOp::"view", PhotoOp::"edit", PhotoOp::"delete"], - resource == Photo::"VacationPhoto.jpg" -); - -permit ( - principal in UserGroup::"AVTeam", - action == Action::"viewPhoto", - resource is Photo -) -when { resource in PhotoApp::Account::"stacey" } -unless { principal has parents }; - -permit ( - principal is User, - action == Action::"viewPhoto", - resource -) -when { resource in PhotoShop::"Photo" }; -` - -const policyTemplate = ` -permit( - principal in ?principal, - action in [hexa_avp::Action::"ReadAccount"], - resource -); -` - -const entitiesJSON = `[ - { - "uid": { "type": "User", "id": "alice" }, - "attrs": { "age": 18 }, - "parents": [] - }, - { - "uid": { "type": "Photo", "id": "VacationPhoto.jpg" }, - "attrs": {}, - "parents": [{ "type": "Album", "id": "jane_vacation" }] - } -]` - -func TestMapCedar(t *testing.T) { +func TestMapCedarToHexa(t *testing.T) { tests := []struct { name string cedar string @@ -87,6 +30,7 @@ permit ( );`, idql: `{ "meta": { + "version": "0.7", "sourceData": { "annotations": { "comment": "this is an annotation", @@ -97,126 +41,304 @@ permit ( "subjects": [ "any" ], - "actions": [ "action" ], + "actions": [ ], "object": "" }`, err: false, }, - {"AlicePhoto", `permit ( + { + name: "AlicePhoto", + cedar: `permit ( principal == User::"alice", action == Action::"viewPhoto", resource == Photo::"VacationPhoto.jpg" -);`, `{ - "meta": {}, +);`, + idql: `{ + "meta": { + "version": "0.7" + }, "subjects": [ "User:alice" ], - "actions": [ "viewPhoto" ], - "object": "Photo::\"VacationPhoto.jpg\"" -}`, false}, - {"Multi-Action", `permit ( + "actions": [ "Action::viewPhoto" ], + "object": "Photo::VacationPhoto.jpg" +}`, + err: false}, + { + name: "Multi-Action", + cedar: `permit ( principal is User in Group::"AVTeam", action in [PhotoOp::"view", PhotoOp::"edit", PhotoOp::"delete"], resource == Photo::"VacationPhoto.jpg" -);`, `{ - "meta": {}, +);`, + idql: `{ + "meta": {"version": "0.7"}, "subjects": [ - "Group:\"AVTeam\".(User)" + "Type:User[Group::AVTeam]" ], "actions": [ - "PhotoOp::\"view\"", - "PhotoOp::\"edit\"", - "PhotoOp::\"delete\"" + "PhotoOp::view", + "PhotoOp::edit", + "PhotoOp::delete" ], - "object": "Photo::\"VacationPhoto.jpg\"" -}`, false}, - {"Conditions", `permit ( + "object": "Photo::VacationPhoto.jpg" +}`, err: false}, + { + name: "Conditions", + cedar: `permit ( principal in UserGroup::"AVTeam", action == Action::"viewPhoto", resource is Photo ) when { resource in PhotoApp::Account::"stacey" } unless { principal has parents };`, - `{ - "meta": {}, + idql: `{ + "meta": {"version": "0.7"}, "subjects": [ - "Group:UserGroup::\"AVTeam\"" + "[UserGroup::AVTeam]" ], - "actions": [ "viewPhoto" ], + "actions": [ "Action::viewPhoto" ], "object": "Type:Photo", "Condition": { "Rule": "resource in PhotoApp::Account::\"stacey\" and not (principal.parents pr)", "Action": "allow" } -}`, false}, - {"action equals", `permit ( +}`, + err: false}, + { + name: "action equals", + cedar: `permit ( principal is User, action == Action::"viewPhoto", resource ) -when { resource in PhotoShop::"Photo" };`, `{ - "meta": {}, +when { resource in PhotoShop::"Photo" };`, + idql: `{ + "meta": {"version": "0.7"}, "subjects": [ "Type:User" ], - "actions": [ "viewPhoto" ], + "actions": [ "Action::viewPhoto" ], "object": "", "Condition": { "Rule": "resource in PhotoShop::\"Photo\"", "Action": "allow" } -}`, false}, +}`, + err: false}, } - + mapper := NewCedarMapper(map[string]string{}) for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - fmt.Println("Testing:\n" + tt.cedar) - result, err := MapCedarPolicyBytes("test", []byte(tt.cedar)) + fmt.Println("Testing:\n" + tt.name) + result, err := mapper.MapCedarPolicyBytes("test", []byte(tt.cedar)) testutilEquals(t, tt.err, err != nil) idqlOut := result.Policies[0].String() - fmt.Println("Mapped:\n" + idqlOut) + fmt.Println("Got:\n", idqlOut) + fmt.Println("Want:\n", tt.idql) var want hexapolicy.PolicyInfo err = json.Unmarshal([]byte(tt.idql), &want) assert.NoError(t, err) assert.True(t, want.Equals(result.Policies[0]), "Policies should match") // testutilEquals(t, result.Policies[0], want) + if tt.name == "Annotation" { + assert.Len(t, result.Policies[0].Meta.SourceData["annotations"], 2, "Should be 2 annotations") + } + }) } - } -func TestMapTemplate(t *testing.T) { - /* Looks like this has to be done using Go AVP library - result, err := MapCedarPolicyBytes("test", []byte(policyTemplate)) - assert.NoError(t, err) - assert.Len(t, result.Policies, 1) +func TestHexaToCedar(t *testing.T) { + tests := []struct { + name string + cedar string + idql string + err bool + }{ + { + name: "Annotation", + cedar: `@comment("this is an annotation") +@description("Makes everything permitted") +permit ( + principal, + action, + resource +);`, + idql: `{ + "meta": { + "version": "0.7", + "sourceData": { + "annotations": { + "comment": "this is an annotation", + "description": "Makes everything permitted" + } + } + }, + "subjects": [ + "any" + ], + "actions": [ ], + "object": "" +}`, + err: false, + }, + { + name: "AlicePhoto", + cedar: `permit ( + principal == User::"alice", + action == Action::"viewPhoto", + resource == Photo::"VacationPhoto.jpg" +);`, + idql: `{ + "meta": { + "version": "0.7" + }, + "subjects": [ + "User:alice" + ], + "actions": [ "Action::viewPhoto" ], + "object": "Photo::VacationPhoto.jpg" +}`, + err: false}, + { + name: "Multi-Subject", + cedar: `permit ( + principal == User::"alice", + action == Action::"viewPhoto", + resource == Photo::"VacationPhoto.jpg" +); +permit ( + principal == User::"bob", + action == Action::"viewPhoto", + resource == Photo::"VacationPhoto.jpg" +);`, + idql: `{ + "meta": { + "version": "0.7" + }, + "subjects": [ + "User:alice","User:bob" + ], + "actions": [ "Action::viewPhoto" ], + "object": "Photo::VacationPhoto.jpg" +}`, + err: false}, + { + name: "Multi-Action", + cedar: `permit ( + principal is User in Group::"AVTeam", + action in [PhotoOp::"view", PhotoOp::"edit", PhotoOp::"delete"], + resource == Photo::"VacationPhoto.jpg" +);`, + idql: `{ + "meta": {"version": "0.7"}, + "subjects": [ + "Type:User[Group::AVTeam]" + ], + "actions": [ + "PhotoOp::view", + "PhotoOp::edit", + "PhotoOp::delete" + ], + "object": "Photo::VacationPhoto.jpg" +}`, err: false}, + { + name: "Conditions", + cedar: `permit ( + principal in UserGroup::"AVTeam", + action == Action::"viewPhoto", + resource is Photo +) +when { resource in PhotoApp::Account::"stacey" } +unless { principal has parents };`, + idql: `{ + "meta": {"version": "0.7"}, + "subjects": [ + "[UserGroup::AVTeam]" + ], + "actions": [ "Action::viewPhoto" ], + "object": "Type:Photo", + "Condition": { + "Rule": "resource in PhotoApp::Account::\"stacey\" and not (principal.parents pr)", + "Action": "allow" + } +}`, + err: false}, + { + name: "action equals", + cedar: `permit ( + principal is User, + action == Action::"viewPhoto", + resource +) +when { resource in PhotoShop::"Photo" };`, + idql: `{ + "meta": {"version": "0.7"}, + "subjects": [ + "Type:User" + ], + "actions": [ "Action::viewPhoto" ], + "object": "", + "Condition": { + "Rule": "resource in PhotoShop::\"Photo\"", + "Action": "allow" + } +}`, + err: false}, + } + mapper := NewCedarMapper(map[string]string{}) + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + fmt.Println("Testing:\n" + tt.name) + result, err := mapper.MapHexaPolicyBytes("test", []byte(tt.idql)) + testutilEquals(t, tt.err, err != nil) - */ -} + fmt.Println("Got:") + fmt.Println(result) -func testutilEquals[T any](t testing.TB, a, b T) { - t.Helper() - if reflect.DeepEqual(a, b) { - return - } - t.Fatalf("\ngot %+v\nwant %+v", a, b) -} + var cl1, cl2 cedar.PolicyList + if err = cl1.UnmarshalCedar([]byte(result)); err != nil { + assert.Fail(t, "Failed to parse result: "+err.Error()) + } -func testutilOK(t testing.TB, err error) { - t.Helper() - if err == nil { - return + fmt.Println("Want:") + fmt.Println(tt.cedar) + if err = cl2.UnmarshalCedar([]byte(tt.cedar)); err != nil { + assert.Fail(t, err.Error()) + } + allMatched := true + for _, cp1 := range cl1 { + itemMatch := false + for _, cp2 := range cl2 { + if reflect.DeepEqual(cp1.AST(), cp2.AST()) { + itemMatch = true + break + } + } + if !itemMatch { + allMatched = false + break + } + } + cl1 = nil // nil structures out to prevent anomalous errors + cl2 = nil + // Compare the object to eliminate spacing difference issues + assert.True(t, allMatched, "got/want should match") + + }) } - t.Fatalf("got %v want nil", err) } -func testutilError(t testing.TB, err error) { +func testutilEquals[T any](t testing.TB, a, b T) { t.Helper() - if err != nil { + if reflect.DeepEqual(a, b) { return } - t.Fatalf("got nil want error") + t.Fatalf("\ngot %+v\nwant %+v", a, b) } diff --git a/models/formats/cedar/json/json.go b/models/formats/cedar/json/json.go new file mode 100644 index 0000000..f6f810e --- /dev/null +++ b/models/formats/cedar/json/json.go @@ -0,0 +1,138 @@ +// Package json Note: This code is from github.com/cedar-policy/cedar-go and is used under the APL 2.0 license terms. +package json + +import ( + "github.com/cedar-policy/cedar-go/types" +) + +type PolicyJSON struct { + Annotations map[string]string `json:"annotations,omitempty"` + Effect string `json:"effect"` + Principal ScopeJSON `json:"principal"` + Action ScopeJSON `json:"action"` + Resource ScopeJSON `json:"resource"` + Conditions []ConditionJSON `json:"conditions,omitempty"` +} + +type scopeInJSON struct { + Entity types.EntityUID `json:"entity"` +} + +type ScopeJSON struct { + Op string `json:"op"` + Entity *types.EntityUID `json:"entity,omitempty"` + Entities []types.EntityUID `json:"entities,omitempty"` + EntityType string `json:"entity_type,omitempty"` + In *scopeInJSON `json:"in,omitempty"` +} + +type ConditionJSON struct { + Kind string `json:"kind"` + Body NodeJSON `json:"body"` +} + +type BinaryJSON struct { + Left NodeJSON `json:"left"` + Right NodeJSON `json:"right"` +} + +type unaryJSON struct { + Arg NodeJSON `json:"arg"` +} + +type strJSON struct { + Left NodeJSON `json:"left"` + Attr string `json:"attr"` +} + +type likeJSON struct { + Left NodeJSON `json:"left"` + Pattern types.Pattern `json:"pattern"` +} + +type isJSON struct { + Left NodeJSON `json:"left"` + EntityType string `json:"entity_type"` + In *NodeJSON `json:"in,omitempty"` +} + +type ifThenElseJSON struct { + If NodeJSON `json:"if"` + Then NodeJSON `json:"then"` + Else NodeJSON `json:"else"` +} + +type arrayJSON []NodeJSON + +type recordJSON map[string]NodeJSON + +type extensionJSON map[string]arrayJSON + +type ValueJSON struct { + V types.Value +} + +func (e *ValueJSON) MarshalJSON() ([]byte, error) { + return e.V.ExplicitMarshalJSON() +} +func (e *ValueJSON) UnmarshalJSON(b []byte) error { + return types.UnmarshalJSON(b, &e.V) +} + +type NodeJSON struct { + // Value + Value *ValueJSON `json:"Value,omitempty"` // could be any + + // Var + Var *string `json:"Var,omitempty"` + + // Slot + // Unknown + + // ! or neg operators + Not *unaryJSON `json:"!,omitempty"` + Negate *unaryJSON `json:"neg,omitempty"` + + // Binary operators: ==, !=, in, <, <=, >, >=, &&, ||, +, -, *, contains, containsAll, containsAny + Equals *BinaryJSON `json:"==,omitempty"` + NotEquals *BinaryJSON `json:"!=,omitempty"` + In *BinaryJSON `json:"in,omitempty"` + LessThan *BinaryJSON `json:"<,omitempty"` + FuncLessThan []NodeJSON `json:"lessThan,omitempty"` + LessThanOrEqual *BinaryJSON `json:"<=,omitempty"` + FuncLessThanOrEqual []NodeJSON `json:"lessThanOrEqual,omitempty"` + GreaterThan *BinaryJSON `json:">,omitempty"` + FuncGreaterThan []NodeJSON `json:"greaterThan,omitempty"` + GreaterThanOrEqual *BinaryJSON `json:">=,omitempty"` + FuncGreaterThanOrEqual []NodeJSON `json:"greaterThanOrEqual,omitempty"` + And *BinaryJSON `json:"&&,omitempty"` + Or *BinaryJSON `json:"||,omitempty"` + Add *BinaryJSON `json:"+,omitempty"` + Subtract *BinaryJSON `json:"-,omitempty"` + Multiply *BinaryJSON `json:"*,omitempty"` + Contains *BinaryJSON `json:"contains,omitempty"` + ContainsAll *BinaryJSON `json:"containsAll,omitempty"` + ContainsAny *BinaryJSON `json:"containsAny,omitempty"` + + // ., has + Access *strJSON `json:".,omitempty"` + Has *strJSON `json:"has,omitempty"` + + // is + Is *isJSON `json:"is,omitempty"` + + // like + Like *likeJSON `json:"like,omitempty"` + + // if-then-else + IfThenElse *ifThenElseJSON `json:"if-then-else,omitempty"` + + // Set + Set arrayJSON `json:"Set,omitempty"` + + // Record + Record recordJSON `json:"Record,omitempty"` + + // Any other method: decimal, datetime, duration, ip, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, isIpv4, isIpv6, isLoopback, isMulticast, isInRange, toDate, toTime, toDays, toHours, toMinutes, toSeconds, toMilliseconds, offset, durationSince + ExtensionCall extensionJSON `json:"-"` +} diff --git a/models/formats/cedar/parse.go b/models/formats/cedar/parse.go deleted file mode 100644 index aa113f7..0000000 --- a/models/formats/cedar/parse.go +++ /dev/null @@ -1,373 +0,0 @@ -package cedar - -import ( - "errors" - "fmt" - "strings" - - cedarParser "github.com/cedar-policy/cedar-go/x/exp/parser" - "github.com/hexa-org/policy-mapper/models/conditionLangs/cedarConditions" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" -) - -/* -parse.go is needed to expose the internal Cedar parser for use by Hexa. In this package we leverage the Cedar Tokenizer -to build up the AST and Policy tree for mapping. -*/ - -type PolicyPair struct { - HexaPolicy *hexapolicy.PolicyInfo - CedarPolicy *cedarParser.Policy - res strings.Builder -} - -type ParseSet struct { - IdqlPolicies []hexapolicy.PolicyInfo - Pairs []PolicyPair - Tokens []cedarParser.Token - Pos int - loc string - conditionMapper *cedarConditions.CedarConditionMapper -} - -func MapCedarPolicyBytes(location string, cedarBytes []byte) (*hexapolicy.Policies, error) { - - tokens, err := cedarParser.Tokenize(cedarBytes) - if err != nil { - return nil, err - } - - cset := ParseSet{ - Pairs: make([]PolicyPair, 0), - IdqlPolicies: make([]hexapolicy.PolicyInfo, 0), - Tokens: tokens, - Pos: 0, - loc: location, - conditionMapper: &cedarConditions.CedarConditionMapper{}, - } - - res, err := cedarParser.Parse(tokens) - for _, cedarPolicy := range res { - err := cset.MapCedarPolicy(cedarPolicy) - if err != nil { - break - } - - } - - return &hexapolicy.Policies{ - Policies: cset.IdqlPolicies, - App: &location, - }, err - -} - -func (t *ParseSet) MapCedarPolicy(policy cedarParser.Policy) error { - hexaPolicy := hexapolicy.PolicyInfo{Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}} - pair := PolicyPair{ - HexaPolicy: &hexaPolicy, - CedarPolicy: &policy, - } - - var err error - pair.mapCedarAnnotations() - pair.mapCedarSubject() - pair.mapCedarAction() - pair.mapCedarResource() - err = pair.mapCedarConditions() - - if err != nil { - return err - } - - if pair.HexaPolicy != nil { - t.IdqlPolicies = append(t.IdqlPolicies, *pair.HexaPolicy) - } else { - return errors.New("no policy mapped") - } - - return nil -} - -func (t *ParseSet) MapHexaPolicy(policy hexapolicy.PolicyInfo) (string, error) { - pp := PolicyPair{ - HexaPolicy: &policy, - res: strings.Builder{}, - } - - annotations := pp.mapHexaAnnotations() - subjects := pp.mapHexaSubjects() - actions := pp.mapHexaAction() - resource := pp.mapHexaResource() - - conditions, err := t.conditionMapper.MapConditionToCedar(pp.HexaPolicy.Condition) - if err != nil { - return "", err - } - - // conditions ;= pp.mapConditions() - pp.writeCedarPolicy(annotations, subjects[0], actions, resource, conditions) - return pp.res.String(), nil -} - -func (pp *PolicyPair) writeCedarPolicy(annotations, subject, actions, resource, conditions string) { - pp.res.WriteString(annotations) - pp.res.WriteString("permit (\n ") - pp.res.WriteString(subject) - pp.res.WriteString("\n ") - pp.res.WriteString(actions) - pp.res.WriteString("\n ") - pp.res.WriteString(resource) - pp.res.WriteString("\n)") - pp.res.WriteString(conditions) - pp.res.WriteString(";\n") - - return -} - -// For Hexa, we just map annotations to Policy Meta -func (pp *PolicyPair) mapCedarAnnotations() { - meta := pp.HexaPolicy.Meta - if meta.SourceData == nil { - meta.SourceData = make(map[string]interface{}) - } - annotations := pp.CedarPolicy.Annotations - if annotations == nil || len(annotations) == 0 { - return - } - annotationMap := make(map[string]string) - for _, annotation := range pp.CedarPolicy.Annotations { - annotationMap[annotation.Key] = annotation.Value - } - meta.SourceData["annotations"] = annotationMap - pp.HexaPolicy.Meta = meta -} - -func (pp *PolicyPair) mapHexaAnnotations() string { - meta := pp.HexaPolicy.Meta - sourceData := meta.SourceData - if sourceData == nil { - return "" - } - annotationMap, ok := sourceData["annotations"].(map[string]interface{}) - if !ok { - return "" - } - sb := strings.Builder{} - for key, val := range annotationMap { - sb.WriteString(fmt.Sprintf("@%s(%s)\n", key, val)) - } - return sb.String() -} - -func (pp *PolicyPair) mapCedarSubject() { - principal := pp.CedarPolicy.Principal - - paths := principal.Entity.Path - - var subj hexapolicy.SubjectInfo - switch principal.Type { - case cedarParser.MatchAny: - subj = []string{hexapolicy.SubjectAnyUser} - case cedarParser.MatchEquals: - member := principal.Entity.String() - // This is principal == xxx - if len(paths) > 1 { - member = fmt.Sprintf("%s:%s", paths[0], strings.Join(paths[1:], "::")) - - } - subj = []string{member} - case cedarParser.MatchIs: - // This is "principal is User" - // subj = []string{hexapolicy.SubjectAnyAuth} - subj = []string{fmt.Sprintf("Type:%s", principal.Path.String())} - case cedarParser.MatchIn: - subj = []string{fmt.Sprintf("Group:%s", principal.Entity.String())} - case cedarParser.MatchIsIn: - isType := principal.Path.String() - inEntity := strings.Replace(principal.Entity.String(), "::", ":", 1) - subj = []string{fmt.Sprintf("%s.(%s)", inEntity, isType)} - default: - fmt.Println(fmt.Sprintf("Unexpected principal type: %T, value: %s", principal, principal.String())) - subj = []string{principal.String()} - } - - pp.HexaPolicy.Subjects = subj -} - -func (pp *PolicyPair) HasMultiSubjects() bool { - if pp.HexaPolicy != nil && pp.HexaPolicy.Subjects != nil { - return len(pp.HexaPolicy.Subjects) > 0 - } - return false -} - -func (pp *PolicyPair) mapHexaSubjects() []string { - if pp.HexaPolicy == nil || pp.HexaPolicy.Subjects != nil { - return nil - } - members := pp.HexaPolicy.Subjects - if len(members) == 0 || strings.EqualFold(members[0], hexapolicy.SubjectAnyUser) { - return []string{"principal,"} - } - res := make([]string, 0) - for _, member := range members { - lower := strings.ToLower(member) - if lower == "any" { - res = append(res, "principal,") - continue - } - if len(lower) < 5 { - // not sure what this is, try principal == value - res = append(res, fmt.Sprintf("principal == %s,", member)) - continue - } - switch lower[0:5] { - case "anyauthenticated": - res = append(res, "principal is User,") - // case "user:": - // res = append(res, fmt.Sprintf("principal == User::%s,",member[6:])) - case "group": - value := member[5:] - if strings.Contains(value, ".(") { - parts := strings.Split(value, ".(") - entity := parts[0] - path := parts[1][0 : len(parts[1])-1] - res = append(res, fmt.Sprintf(fmt.Sprintf("principal is %s in %s,", entity, path))) - } else { - // This is an in principal - res = append(res, fmt.Sprintf(fmt.Sprintf("principal in %s,", value))) - } - - case "domai": - // todo - not sure what the cedar equivalence is - res = append(res, "principal is User,") - - case "type:": - res = append(res, fmt.Sprintf("principal is %s,", member[5:])) - default: - // assume this is principal == entity::"account" form - firstColon := strings.Index(member, ":") - res = append(res, fmt.Sprintf("principal == %s::%s,", member[0:firstColon], member[firstColon+1:])) - } - } - return res -} - -func (pp *PolicyPair) mapCedarAction() { - action := pp.CedarPolicy.Action - - var aInfo []hexapolicy.ActionInfo - switch action.Type { - case cedarParser.MatchEquals: - aInfo = append(aInfo, hexapolicy.ActionInfo(mapCedarEntityName(action.Entities[0]))) - case cedarParser.MatchIn: - aInfo = append(aInfo, hexapolicy.ActionInfo("Role:"+mapCedarEntityName(action.Entities[0]))) - case cedarParser.MatchInList: - for _, entity := range action.Entities { - aInfo = append(aInfo, hexapolicy.ActionInfo(mapCedarEntityName(entity))) - } - default: - fmt.Println(fmt.Sprintf("Unexpected action type: %T, value: %s", action, action.String())) - aInfo = append(aInfo, hexapolicy.ActionInfo(action.String())) - } - - pp.HexaPolicy.Actions = aInfo -} - -func (pp *PolicyPair) mapHexaAction() string { - actions := pp.HexaPolicy.Actions - if actions == nil || len(actions) == 0 { - return "action," - } - if len(actions) == 1 { - // if action has a prefix of "Role" then the value is action in xxx - value := string(actions[0]) - if strings.HasPrefix(strings.ToLower(value), "role:") { - return fmt.Sprintf("action in %s,", value[5:]) - } - return fmt.Sprintf("action == %s,", value) - } - var sb strings.Builder - sb.WriteString("action in [") - for i, e := range actions { - if i > 0 { - sb.WriteString(",") - } - sb.WriteString(string(e)) - } - sb.WriteString("],") - return sb.String() -} - -func mapCedarEntityName(entity cedarParser.Entity) string { - paths := entity.Path - if len(paths) < 2 { - return entity.String() - } - - // Drop the Action - - if paths[0] == "Action" { - if len(paths) > 2 { - return fmt.Sprintf( - "%s::%q", - strings.Join(paths[1:len(paths)-1], "::"), - paths[len(paths)-1], - ) - - } - return paths[1] - } - - return entity.String() -} - -func (pp *PolicyPair) mapCedarResource() { - resource := pp.CedarPolicy.Resource - switch resource.Type { - case cedarParser.MatchEquals: - pp.HexaPolicy.Object = hexapolicy.ObjectInfo(resource.Entity.String()) - case cedarParser.MatchAny: - pp.HexaPolicy.Object = "" - case cedarParser.MatchIs: - pp.HexaPolicy.Object = hexapolicy.ObjectInfo(fmt.Sprintf("Type:%s", resource.Path.String())) - case cedarParser.MatchIsIn: - pp.HexaPolicy.Object = hexapolicy.ObjectInfo(fmt.Sprintf("[%s].(%s)", resource.Entity.String(), resource.Path.String())) - case cedarParser.MatchIn: - pp.HexaPolicy.Object = hexapolicy.ObjectInfo(fmt.Sprintf("[%s]", resource.Entity.String())) - default: - fmt.Println(fmt.Sprintf("Unexpected resource type: %T, value: %s", resource, resource.String())) - pp.HexaPolicy.Object = hexapolicy.ObjectInfo(fmt.Sprintf("[%s]", resource.String())) - } -} - -func (pp *PolicyPair) mapHexaResource() string { - resource := pp.HexaPolicy.Object.String() - if resource == "" { - return "resource" - } - if strings.HasPrefix(strings.ToLower(resource), "type:") { - return fmt.Sprintf("resource is %s", resource[5:]) - } - if !strings.HasPrefix(resource, "[") { - return fmt.Sprintf("resource == %s", resource) - } - if !strings.Contains(resource, ".(") { - // this is [entity].(typepath) - entity := resource[1:strings.Index(resource, "]")] - path := resource[strings.Index(resource, ".(")+2 : len(resource)-1] - - return fmt.Sprintf("resource is %s in %s", path, entity) - } - // this is [Entity] - return fmt.Sprintf("resource in %s", resource[1:len(resource)-1]) -} - -func (pp *PolicyPair) mapCedarConditions() error { - hexaCondition, err := cedarConditions.MapCedarConditionToHexa(pp.CedarPolicy.Conditions) - if hexaCondition != nil { - pp.HexaPolicy.Condition = hexaCondition - } - return err -} diff --git a/pkg/hexapolicysupport/hexa_policy_support.go b/pkg/hexapolicysupport/hexa_policy_support.go index eb7f1e5..afbdace 100644 --- a/pkg/hexapolicysupport/hexa_policy_support.go +++ b/pkg/hexapolicysupport/hexa_policy_support.go @@ -22,12 +22,17 @@ func ParsePolicyFile(path string) ([]hexapolicy.PolicyInfo, error) { func ParsePolicies(policyBytes []byte) ([]hexapolicy.PolicyInfo, error) { var policies hexapolicy.Policies err := json.Unmarshal(policyBytes, &policies) - if err != nil { + if err != nil || policies.Policies == nil { // Try array of polcies var pols []hexapolicy.PolicyInfo err = json.Unmarshal(policyBytes, &pols) if err != nil { - return nil, err + var pol hexapolicy.PolicyInfo + err = json.Unmarshal(policyBytes, &pol) + if err != nil { + return nil, err + } + return []hexapolicy.PolicyInfo{pol}, nil } return pols, nil } diff --git a/providers/aws/avpProvider/avpClient/avpTestSupport/avp_operations.go b/providers/aws/avpProvider/avpClient/avpTestSupport/avp_operations.go index 7499384..af07526 100644 --- a/providers/aws/avpProvider/avpClient/avpTestSupport/avp_operations.go +++ b/providers/aws/avpProvider/avpClient/avpTestSupport/avp_operations.go @@ -1,430 +1,430 @@ package avpTestSupport import ( - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" - "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws" ) var AvpApiUrl = fmt.Sprintf("https://verifiedpermissions.%s.amazonaws.com/", TestAwsRegion) func (m *MockVerifiedPermissionsHTTPClient) MockListStores() { - m.MockListStoresWithHttpStatus(http.StatusOK) + m.MockListStoresWithHttpStatus(http.StatusOK) } func (m *MockVerifiedPermissionsHTTPClient) MockListStoresWithHttpStatus(httpStatus int) { - m.AddRequest(http.MethodPost, AvpApiUrl, "ListPolicyStores", httpStatus, ListStoresResponse()) + m.AddRequest(http.MethodPost, AvpApiUrl, "ListPolicyStores", httpStatus, ListStoresResponse()) } type MockPolicyStoresOutput struct { - // The parser in AWS verified permissions client is looking for "policyStores" - PolicyStores []MockPolicyStoreItem `json:"policyStores"` - NextToken *string `json:"nextToken"` + // The parser in AWS verified permissions client is looking for "policyStores" + PolicyStores []MockPolicyStoreItem `json:"policyStores"` + NextToken *string `json:"nextToken"` } type MockPolicyStoreItem struct { - Arn *string `json:"arn"` - PolicyStoreId *string `json:"policyStoreId"` - CreatedDate *time.Time `json:"createdDate"` - LastUpdatedDate *time.Time `json:"lastUpdatedDate"` - Description *string `json:"description"` + Arn *string `json:"arn"` + PolicyStoreId *string `json:"policyStoreId"` + CreatedDate *time.Time `json:"createdDate"` + LastUpdatedDate *time.Time `json:"lastUpdatedDate"` + Description *string `json:"description"` } func ListStoresResponse() []byte { - policyDate := time.Date(2023, 12, 1, 1, 2, 3, 0, time.UTC) - output := MockPolicyStoresOutput{ - PolicyStores: []MockPolicyStoreItem{ - { - Arn: aws.String(TestPolicyStoreArn), - PolicyStoreId: aws.String(TestPolicyStoreId), - CreatedDate: &policyDate, - LastUpdatedDate: &policyDate, - Description: aws.String(TestPolicyStoreDescription), - }, - }, - NextToken: nil, - } - - outBytes, _ := json.Marshal(output) - return outBytes + policyDate := time.Date(2023, 12, 1, 1, 2, 3, 0, time.UTC) + output := MockPolicyStoresOutput{ + PolicyStores: []MockPolicyStoreItem{ + { + Arn: aws.String(TestPolicyStoreArn), + PolicyStoreId: aws.String(TestPolicyStoreId), + CreatedDate: &policyDate, + LastUpdatedDate: &policyDate, + Description: aws.String(TestPolicyStoreDescription), + }, + }, + NextToken: nil, + } + + outBytes, _ := json.Marshal(output) + return outBytes } type EntityIdentifier struct { - // The identifier of an entity. - // - // "entityId":"identifier" - // - // EntityId is a sensitive parameter and its value will be - // replaced with "sensitive" in string returned by EntityIdentifier's - // String and GoString methods. - // - // EntityId is a required field - EntityId *string `json:"entityId" locationName:"entityId" min:"1" type:"string" required:"true" sensitive:"true"` - - // The type of an entity. - // - // Example: "entityType":"typeName" - // - // EntityType is a sensitive parameter and its value will be - // replaced with "sensitive" in string returned by EntityIdentifier's - // String and GoString methods. - // - // EntityType is a required field - EntityType *string `json:"entityType" locationName:"entityType" min:"1" type:"string" required:"true" sensitive:"true"` - // contains filtered or unexported fields + // The identifier of an entity. + // + // "entityId":"identifier" + // + // EntityId is a sensitive parameter and its value will be + // replaced with "sensitive" in string returned by EntityIdentifier's + // String and GoString methods. + // + // EntityId is a required field + EntityId *string `json:"entityId" locationName:"entityId" min:"1" type:"string" required:"true" sensitive:"true"` + + // The type of an entity. + // + // Example: "entityType":"typeName" + // + // EntityType is a sensitive parameter and its value will be + // replaced with "sensitive" in string returned by EntityIdentifier's + // String and GoString methods. + // + // EntityType is a required field + EntityType *string `json:"entityType" locationName:"entityType" min:"1" type:"string" required:"true" sensitive:"true"` + // contains filtered or unexported fields } type StaticPolicyDefinitionItem struct { - // A description of the static policy. - // - // Description is a sensitive parameter and its value will be - // replaced with "sensitive" in string returned by StaticPolicyDefinitionItem's - // String and GoString methods. - Description *string `json:"description" locationName:"description" type:"string" sensitive:"true"` - // contains filtered or unexported fields - - // Statement is a required field - Statement *string `json:"statement" locationName:"statement" min:"1" type:"string" required:"true" sensitive:"true"` - // contains filtered or unexported fields + // A description of the static policy. + // + // Description is a sensitive parameter and its value will be + // replaced with "sensitive" in string returned by StaticPolicyDefinitionItem's + // String and GoString methods. + Description *string `json:"description" locationName:"description" type:"string" sensitive:"true"` + // contains filtered or unexported fields + + // Statement is a required field + Statement *string `json:"statement" locationName:"statement" min:"1" type:"string" required:"true" sensitive:"true"` + // contains filtered or unexported fields } type TemplateLinkedPolicyDefinitionItem struct { - // The unique identifier of the policy template used to create this policy. - // - // PolicyTemplateId is a required field - PolicyTemplateId *string `json:"policyTemplateId" locationName:"policyTemplateId" min:"1" type:"string" required:"true"` - - // The principal associated with this template-linked policy. Verified Permissions - // substitutes this principal for the ?principal placeholder in the policy template - // when it evaluates an authorization request. - Principal *EntityIdentifier `json:"principal" locationName:"principal" type:"structure"` - - // The resource associated with this template-linked policy. Verified Permissions - // substitutes this resource for the ?resource placeholder in the policy template - // when it evaluates an authorization request. - Resource *EntityIdentifier `json:"resource" locationName:"resource" type:"structure"` - // contains filtered or unexported fields + // The unique identifier of the policy template used to create this policy. + // + // PolicyTemplateId is a required field + PolicyTemplateId *string `json:"policyTemplateId" locationName:"policyTemplateId" min:"1" type:"string" required:"true"` + + // The principal associated with this template-linked policy. Verified Permissions + // substitutes this principal for the ?principal placeholder in the policy template + // when it evaluates an authorization request. + Principal *EntityIdentifier `json:"principal" locationName:"principal" type:"structure"` + + // The resource associated with this template-linked policy. Verified Permissions + // substitutes this resource for the ?resource placeholder in the policy template + // when it evaluates an authorization request. + Resource *EntityIdentifier `json:"resource" locationName:"resource" type:"structure"` + // contains filtered or unexported fields } type PolicyDefinitionItem struct { - // Information about a static policy that wasn't created with a policy template. - Static *StaticPolicyDefinitionItem `json:"static" locationName:"static" type:"structure"` + // Information about a static policy that wasn't created with a policy template. + Static *StaticPolicyDefinitionItem `json:"static" locationName:"static" type:"structure"` - // Information about a template-linked policy that was created by instantiating - // a policy template. - TemplateLinked *TemplateLinkedPolicyDefinitionItem `json:"templateLinked" locationName:"templateLinked" type:"structure"` - // contains filtered or unexported fields + // Information about a template-linked policy that was created by instantiating + // a policy template. + TemplateLinked *TemplateLinkedPolicyDefinitionItem `json:"templateLinked" locationName:"templateLinked" type:"structure"` + // contains filtered or unexported fields } type PolicyItem struct { - // The date and time the policy was created. - // - // CreatedDate is a required field - CreatedDate *time.Time `json:"createdDate" locationName:"createdDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` - - // The policy definition of an item in the list of policies returned. - // - // Definition is a required field - Definition *PolicyDefinitionItem `json:"definition" locationName:"definition" type:"structure" required:"true"` - - // The date and time the policy was most recently updated. - // - // LastUpdatedDate is a required field - LastUpdatedDate *time.Time `json:"lastUpdatedDate" locationName:"lastUpdatedDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` - - // The identifier of the policy you want information about. - // - // PolicyId is a required field - PolicyId *string `json:"policyId" locationName:"policyId" min:"1" type:"string" required:"true"` - - // The identifier of the PolicyStore where the policy you want information about - // is stored. - // - // PolicyStoreId is a required field - PolicyStoreId *string `json:"policyStoreId" locationName:"policyStoreId" min:"1" type:"string" required:"true"` - - // The type of the policy. This is one of the following values: - // - // * static - // - // * templateLinked - // - // PolicyType is a required field - PolicyType *string `json:"policyType" locationName:"policyType" type:"string" required:"true" enum:"PolicyType"` - - // The principal associated with the policy. - Principal *EntityIdentifier `json:"principal" locationName:"principal" type:"structure"` - - // The resource associated with the policy. - Resource *EntityIdentifier `json:"resource" locationName:"resource" type:"structure"` - // contains filtered or unexported fields + // The date and time the policy was created. + // + // CreatedDate is a required field + CreatedDate *time.Time `json:"createdDate" locationName:"createdDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` + + // The policy definition of an item in the list of policies returned. + // + // Definition is a required field + Definition *PolicyDefinitionItem `json:"definition" locationName:"definition" type:"structure" required:"true"` + + // The date and time the policy was most recently updated. + // + // LastUpdatedDate is a required field + LastUpdatedDate *time.Time `json:"lastUpdatedDate" locationName:"lastUpdatedDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` + + // The identifier of the policy you want information about. + // + // PolicyId is a required field + PolicyId *string `json:"policyId" locationName:"policyId" min:"1" type:"string" required:"true"` + + // The identifier of the PolicyStore where the policy you want information about + // is stored. + // + // PolicyStoreId is a required field + PolicyStoreId *string `json:"policyStoreId" locationName:"policyStoreId" min:"1" type:"string" required:"true"` + + // The type of the policy. This is one of the following values: + // + // * static + // + // * templateLinked + // + // PolicyType is a required field + PolicyType *string `json:"policyType" locationName:"policyType" type:"string" required:"true" enum:"PolicyType"` + + // The principal associated with the policy. + Principal *EntityIdentifier `json:"principal" locationName:"principal" type:"structure"` + + // The resource associated with the policy. + Resource *EntityIdentifier `json:"resource" locationName:"resource" type:"structure"` + // contains filtered or unexported fields } type GetPolicyTemplateOutput struct { - // The date and time that the policy template was originally created. - // - // CreatedDate is a required field - CreatedDate *time.Time `json:"createdDate" locationName:"createdDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` - - // The description of the policy template. - // - // Description is a sensitive parameter and its value will be - // replaced with "sensitive" in string returned by GetPolicyTemplateOutput's - // String and GoString methods. - Description *string `json:"description" locationName:"description" type:"string" sensitive:"true"` - - // The date and time that the policy template was most recently updated. - // - // LastUpdatedDate is a required field - LastUpdatedDate *time.Time `json:"lastUpdatedDate" locationName:"lastUpdatedDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` - - // The ID of the policy store that contains the policy template. - // - // PolicyStoreId is a required field - PolicyStoreId *string `json:"policyStoreId" locationName:"policyStoreId" min:"1" type:"string" required:"true"` - - // The ID of the policy template. - // - // PolicyTemplateId is a required field - PolicyTemplateId *string `json:"policyTemplateId" locationName:"policyTemplateId" min:"1" type:"string" required:"true"` - - // The content of the body of the policy template written in the Cedar policy - // language. - // - // Statement is a sensitive parameter and its value will be - // replaced with "sensitive" in string returned by GetPolicyTemplateOutput's - // String and GoString methods. - // - // Statement is a required field - Statement *string `json:"statement" locationName:"statement" min:"1" type:"string" required:"true" sensitive:"true"` - // contains filtered or unexported fields + // The date and time that the policy template was originally created. + // + // CreatedDate is a required field + CreatedDate *time.Time `json:"createdDate" locationName:"createdDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` + + // The description of the policy template. + // + // Description is a sensitive parameter and its value will be + // replaced with "sensitive" in string returned by GetPolicyTemplateOutput's + // String and GoString methods. + Description *string `json:"description" locationName:"description" type:"string" sensitive:"true"` + + // The date and time that the policy template was most recently updated. + // + // LastUpdatedDate is a required field + LastUpdatedDate *time.Time `json:"lastUpdatedDate" locationName:"lastUpdatedDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` + + // The ID of the policy store that contains the policy template. + // + // PolicyStoreId is a required field + PolicyStoreId *string `json:"policyStoreId" locationName:"policyStoreId" min:"1" type:"string" required:"true"` + + // The ID of the policy template. + // + // PolicyTemplateId is a required field + PolicyTemplateId *string `json:"policyTemplateId" locationName:"policyTemplateId" min:"1" type:"string" required:"true"` + + // The content of the body of the policy template written in the Cedar policy + // language. + // + // Statement is a sensitive parameter and its value will be + // replaced with "sensitive" in string returned by GetPolicyTemplateOutput's + // String and GoString methods. + // + // Statement is a required field + Statement *string `json:"statement" locationName:"statement" min:"1" type:"string" required:"true" sensitive:"true"` + // contains filtered or unexported fields } type MockListPoliciesOutput struct { - Policies []PolicyItem `json:"policies"` - NextToken *string `json:"nextToken"` + Policies []PolicyItem `json:"policies"` + NextToken *string `json:"nextToken"` } func GenerateStaticPolicyItem(isDetail bool, id string) PolicyItem { - testDate := time.Date(2023, 12, 1, 1, 2, 3, 0, time.UTC) - - var staticItem StaticPolicyDefinitionItem - - staticItem = StaticPolicyDefinitionItem{ - Description: &TestCedarStaticPolicyDescription, - } - if isDetail { - staticItem.Statement = &TestCedarStaticPolicy - } - staticDefinition := PolicyDefinitionItem{Static: &staticItem} - - policyType := "STATIC" - - return PolicyItem{ - CreatedDate: &testDate, - LastUpdatedDate: &testDate, - Principal: nil, - Resource: nil, - Definition: &staticDefinition, - PolicyStoreId: &TestPolicyStoreId, - PolicyType: &policyType, - PolicyId: &id, - } + testDate := time.Date(2023, 12, 1, 1, 2, 3, 0, time.UTC) + + var staticItem StaticPolicyDefinitionItem + + staticItem = StaticPolicyDefinitionItem{ + Description: &TestCedarStaticPolicyDescription, + } + if isDetail { + staticItem.Statement = &TestCedarStaticPolicy + } + staticDefinition := PolicyDefinitionItem{Static: &staticItem} + + policyType := "STATIC" + + return PolicyItem{ + CreatedDate: &testDate, + LastUpdatedDate: &testDate, + Principal: nil, + Resource: nil, + Definition: &staticDefinition, + PolicyStoreId: &TestPolicyStoreId, + PolicyType: &policyType, + PolicyId: &id, + } } func GenerateTemplatePolicyItem(id string) PolicyItem { - testDate := time.Date(2022, 12, 1, 1, 2, 3, 0, time.UTC) - - princId := "joe@example.com" - princType := "hexa_avp::User" - principleIdentifier := EntityIdentifier{ - EntityId: &princId, - EntityType: &princType, - } - resId := "1" - resType := "hexa_avp::account" - resourceIdentifier := EntityIdentifier{ - EntityId: &resId, - EntityType: &resType, - } - templateItem := TemplateLinkedPolicyDefinitionItem{ - PolicyTemplateId: &TestCedarTemplateId, - Principal: &principleIdentifier, - Resource: &resourceIdentifier, - } - templateDefinition := PolicyDefinitionItem{TemplateLinked: &templateItem} - policyType := "TEMPLATE_LINKED" // types.PolicyTypeTemplateLinked - return PolicyItem{ - CreatedDate: &testDate, - LastUpdatedDate: &testDate, - Principal: nil, - Resource: nil, - Definition: &templateDefinition, - PolicyStoreId: &TestPolicyStoreId, - PolicyId: &id, - PolicyType: &policyType, - } + testDate := time.Date(2022, 12, 1, 1, 2, 3, 0, time.UTC) + + princId := "joe@example.com" + princType := "hexa_avp::User" + principleIdentifier := EntityIdentifier{ + EntityId: &princId, + EntityType: &princType, + } + resId := "1" + resType := "hexa_avp::account" + resourceIdentifier := EntityIdentifier{ + EntityId: &resId, + EntityType: &resType, + } + templateItem := TemplateLinkedPolicyDefinitionItem{ + PolicyTemplateId: &TestCedarTemplateId, + Principal: &principleIdentifier, + Resource: &resourceIdentifier, + } + templateDefinition := PolicyDefinitionItem{TemplateLinked: &templateItem} + policyType := "TEMPLATE_LINKED" // types.PolicyTypeTemplateLinked + return PolicyItem{ + CreatedDate: &testDate, + LastUpdatedDate: &testDate, + Principal: nil, + Resource: nil, + Definition: &templateDefinition, + PolicyStoreId: &TestPolicyStoreId, + PolicyId: &id, + PolicyType: &policyType, + } } func (m *MockVerifiedPermissionsHTTPClient) MockGetPolicyTemplateWithHttpStatus(httpStatus int, id string) { - m.AddRequest(http.MethodPost, AvpApiUrl, "GetPolicyTemplate", httpStatus, GetPolicyTemplateResponse(id)) + m.AddRequest(http.MethodPost, AvpApiUrl, "GetPolicyTemplate", httpStatus, GetPolicyTemplateResponse(id)) } func GetPolicyTemplateResponse(id string) []byte { - testDate := time.Date(2023, 2, 1, 1, 2, 3, 0, time.UTC) - description := "Test Hexa Policy Template" - policy := GetPolicyTemplateOutput{ - CreatedDate: &testDate, - Description: &description, - LastUpdatedDate: &testDate, - PolicyStoreId: &TestPolicyStoreId, - PolicyTemplateId: &id, - Statement: &TestCedarTemplatePolicy, - } - outBytes, _ := json.Marshal(policy) - return outBytes + testDate := time.Date(2023, 2, 1, 1, 2, 3, 0, time.UTC) + description := "Test Hexa Policy Template" + policy := GetPolicyTemplateOutput{ + CreatedDate: &testDate, + Description: &description, + LastUpdatedDate: &testDate, + PolicyStoreId: &TestPolicyStoreId, + PolicyTemplateId: &id, + Statement: &TestCedarTemplatePolicy, + } + outBytes, _ := json.Marshal(policy) + return outBytes } func (m *MockVerifiedPermissionsHTTPClient) MockGetPolicyWithHttpStatus(httpStatus int, id string) { - m.AddRequest(http.MethodPost, AvpApiUrl, "GetPolicy", httpStatus, GetPolicyResponse(id)) + m.AddRequest(http.MethodPost, AvpApiUrl, "GetPolicy", httpStatus, GetPolicyResponse(id)) } func GetPolicyResponse(id string) []byte { - policyItem := GenerateStaticPolicyItem(true, id) + policyItem := GenerateStaticPolicyItem(true, id) - outBytes, _ := json.Marshal(policyItem) - return outBytes + outBytes, _ := json.Marshal(policyItem) + return outBytes } func (m *MockVerifiedPermissionsHTTPClient) MockListPolicies() { - m.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) + m.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) } func (m *MockVerifiedPermissionsHTTPClient) MockListPoliciesWithHttpStatus(httpStatus int, staticPolCnt int, templatePolCnt int, nextToken *string) { - if httpStatus != 200 { - m.AddRequest(http.MethodPost, AvpApiUrl, "ListPolicies", httpStatus, []byte{}) - return - } - m.AddRequest(http.MethodPost, AvpApiUrl, "ListPolicies", httpStatus, ListPoliciesResponse(staticPolCnt, templatePolCnt, nextToken)) + if httpStatus != 200 { + m.AddRequest(http.MethodPost, AvpApiUrl, "ListPolicies", httpStatus, []byte{}) + return + } + m.AddRequest(http.MethodPost, AvpApiUrl, "ListPolicies", httpStatus, ListPoliciesResponse(staticPolCnt, templatePolCnt, nextToken)) } func ListPoliciesResponse(staticPolCnt int, templatePolCnt int, nextToken *string) []byte { - var policies = make([]PolicyItem, 0) - statCnt := 0 - templCnt := 0 - - i := 0 - if nextToken != nil { - i, _ = strconv.Atoi(*nextToken) - } - for ; (statCnt+templCnt < 50) && ((statCnt < staticPolCnt) || (templCnt < templatePolCnt)); i++ { - if statCnt < staticPolCnt { - statCnt++ - policies = append(policies, GenerateStaticPolicyItem(false, TestCedarStaticPolicyId+strconv.Itoa(i))) - } - - if templCnt < templatePolCnt { - templCnt++ - policies = append(policies, GenerateTemplatePolicyItem(TestCedarTemplatePolicyId+strconv.Itoa(i))) - } - } - - nextToken = nil // Set to nil if no more results - if templCnt+statCnt >= 50 { - index := strconv.Itoa(i) - nextToken = &index - } - output := MockListPoliciesOutput{ - Policies: policies, - NextToken: nextToken, - } - - outBytes, _ := json.Marshal(output) - return outBytes + var policies = make([]PolicyItem, 0) + statCnt := 0 + templCnt := 0 + + i := 0 + if nextToken != nil { + i, _ = strconv.Atoi(*nextToken) + } + for ; (statCnt+templCnt < 50) && ((statCnt < staticPolCnt) || (templCnt < templatePolCnt)); i++ { + if statCnt < staticPolCnt { + statCnt++ + policies = append(policies, GenerateStaticPolicyItem(false, TestCedarStaticPolicyId+strconv.Itoa(i))) + } + + if templCnt < templatePolCnt { + templCnt++ + policies = append(policies, GenerateTemplatePolicyItem(TestCedarTemplatePolicyId+strconv.Itoa(i))) + } + } + + nextToken = nil // Set to nil if no more results + if templCnt+statCnt >= 50 { + index := strconv.Itoa(i) + nextToken = &index + } + output := MockListPoliciesOutput{ + Policies: policies, + NextToken: nextToken, + } + + outBytes, _ := json.Marshal(output) + return outBytes } func (m *MockVerifiedPermissionsHTTPClient) MockCreatePolicyWithHttpStatus(httpStatus int, id string) { - if httpStatus != 200 { - m.AddRequest(http.MethodPost, AvpApiUrl, "CreatePolicy", httpStatus, []byte{}) - return - } - m.AddRequest(http.MethodPost, AvpApiUrl, "CreatePolicy", httpStatus, CreatePolicyResponse(id)) + if httpStatus != 200 { + m.AddRequest(http.MethodPost, AvpApiUrl, "CreatePolicy", httpStatus, []byte{}) + return + } + m.AddRequest(http.MethodPost, AvpApiUrl, "CreatePolicy", httpStatus, CreatePolicyResponse(id)) } func (m *MockVerifiedPermissionsHTTPClient) MockUpdatePolicyWithHttpStatus(httpStatus int, id string) { - if httpStatus != 200 { - m.AddRequest(http.MethodPost, AvpApiUrl, "UpdatePolicy", httpStatus, []byte{}) - return - } - m.AddRequest(http.MethodPost, AvpApiUrl, "UpdatePolicy", httpStatus, CreatePolicyResponse(id)) + if httpStatus != 200 { + m.AddRequest(http.MethodPost, AvpApiUrl, "UpdatePolicy", httpStatus, []byte{}) + return + } + m.AddRequest(http.MethodPost, AvpApiUrl, "UpdatePolicy", httpStatus, CreatePolicyResponse(id)) } type PolicyOutput struct { - // The date and time the policy was originally created. - // - // CreatedDate is a required field - CreatedDate *time.Time `json:"createdDate" locationName:"createdDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` - - // The date and time the policy was last updated. - // - // LastUpdatedDate is a required field - LastUpdatedDate *time.Time `json:"lastUpdatedDate" locationName:"lastUpdatedDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` - - // The unique ID of the new policy. - // - // PolicyId is a required field - PolicyId *string `json:"policyId" locationName:"policyId" min:"1" type:"string" required:"true"` - - // The ID of the policy store that contains the new policy. - // - // PolicyStoreId is a required field - PolicyStoreId *string `json:"policyStoreId" locationName:"policyStoreId" min:"1" type:"string" required:"true"` - - // The policy type of the new policy. - // - // PolicyType is a required field - PolicyType *string `json:"policyType" locationName:"policyType" type:"string" required:"true" enum:"PolicyType"` - - // The principal specified in the new policy's scope. This response element - // isn't present when principal isn't specified in the policy content. - Principal *EntityIdentifier `json:"principal" locationName:"principal" type:"structure"` - - // The resource specified in the new policy's scope. This response element isn't - // present when the resource isn't specified in the policy content. - Resource *EntityIdentifier `json:"resource" locationName:"resource" type:"structure"` - // contains filtered or unexported fields + // The date and time the policy was originally created. + // + // CreatedDate is a required field + CreatedDate *time.Time `json:"createdDate" locationName:"createdDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` + + // The date and time the policy was last updated. + // + // LastUpdatedDate is a required field + LastUpdatedDate *time.Time `json:"lastUpdatedDate" locationName:"lastUpdatedDate" type:"timestamp" timestampFormat:"iso8601" required:"true"` + + // The unique ID of the new policy. + // + // PolicyId is a required field + PolicyId *string `json:"policyId" locationName:"policyId" min:"1" type:"string" required:"true"` + + // The ID of the policy store that contains the new policy. + // + // PolicyStoreId is a required field + PolicyStoreId *string `json:"policyStoreId" locationName:"policyStoreId" min:"1" type:"string" required:"true"` + + // The policy type of the new policy. + // + // PolicyType is a required field + PolicyType *string `json:"policyType" locationName:"policyType" type:"string" required:"true" enum:"PolicyType"` + + // The principal specified in the new policy's scope. This response element + // isn't present when principal isn't specified in the policy content. + Principal *EntityIdentifier `json:"principal" locationName:"principal" type:"structure"` + + // The resource specified in the new policy's scope. This response element isn't + // present when the resource isn't specified in the policy content. + Resource *EntityIdentifier `json:"resource" locationName:"resource" type:"structure"` + // contains filtered or unexported fields } func CreatePolicyResponse(id string) []byte { - nowTime := time.Now() - ptype := "STATIC" - output := PolicyOutput{ - CreatedDate: &nowTime, - LastUpdatedDate: &nowTime, - PolicyId: &id, - PolicyStoreId: &TestPolicyStoreId, - PolicyType: &ptype, - } - outBytes, _ := json.Marshal(output) - return outBytes + nowTime := time.Now() + ptype := "STATIC" + output := PolicyOutput{ + CreatedDate: &nowTime, + LastUpdatedDate: &nowTime, + PolicyId: &id, + PolicyStoreId: &TestPolicyStoreId, + PolicyType: &ptype, + } + outBytes, _ := json.Marshal(output) + return outBytes } func (m *MockVerifiedPermissionsHTTPClient) MockDeletePolicyWithHttpStatus(httpStatus int) { - if httpStatus != 200 { - m.AddRequest(http.MethodPost, AvpApiUrl, "DeletePolicy", httpStatus, []byte{}) - return - } - emptyJson := "{}" - m.AddRequest(http.MethodPost, AvpApiUrl, "DeletePolicy", httpStatus, []byte(emptyJson)) + if httpStatus != 200 { + m.AddRequest(http.MethodPost, AvpApiUrl, "DeletePolicy", httpStatus, []byte{}) + return + } + emptyJson := "{}" + m.AddRequest(http.MethodPost, AvpApiUrl, "DeletePolicy", httpStatus, []byte(emptyJson)) } diff --git a/providers/aws/avpProvider/avp_provider.go b/providers/aws/avpProvider/avp_provider.go index 17fad01..20b7b08 100644 --- a/providers/aws/avpProvider/avp_provider.go +++ b/providers/aws/avpProvider/avp_provider.go @@ -9,7 +9,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/verifiedpermissions" "github.com/aws/aws-sdk-go-v2/service/verifiedpermissions/types" "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/models/formats/awsCedar" + "github.com/hexa-org/policy-mapper/models/formats/cedar" "github.com/hexa-org/policy-mapper/models/schema" "github.com/hexa-org/policy-mapper/pkg/hexapolicy" "github.com/hexa-org/policy-mapper/providers/aws/avpProvider/avpClient" @@ -55,10 +55,12 @@ func MapAvpTemplate(item *verifiedpermissions.GetPolicyTemplateOutput) hexapolic } } -type AmazonAvpProvider struct { - AwsClientOpts awscommon.AWSClientOptions - CedarMapper *awsCedar.CedarPolicyMapper -} +type ( + AmazonAvpProvider struct { + AwsClientOpts awscommon.AWSClientOptions + CedarMapper *cedar.CedarMapper + } +) func (a AmazonAvpProvider) Name() string { return ProviderTypeAvp @@ -66,7 +68,7 @@ func (a AmazonAvpProvider) Name() string { func (a AmazonAvpProvider) initCedarMapper() { if a.CedarMapper == nil { - a.CedarMapper = awsCedar.New(map[string]string{}) + a.CedarMapper = cedar.NewCedarMapper(map[string]string{}) } } @@ -107,7 +109,7 @@ func (a AmazonAvpProvider) mapAvpPolicyToHexa(avpPolicy types.PolicyItem, client policyDefinition := output.Definition policyStatic := policyDefinition.(*types.PolicyDefinitionDetailMemberStatic).Value cedarPolicy := policyStatic.Statement - mapPols, err := a.CedarMapper.ParseAndMapCedarToHexa([]byte(*cedarPolicy)) + mapPols, err := a.CedarMapper.MapCedarPolicyBytes(applicationInfo.ObjectID, []byte(*cedarPolicy)) if err != nil { return nil, err } @@ -135,7 +137,10 @@ func (a AmazonAvpProvider) mapAvpPolicyToHexa(avpPolicy types.PolicyItem, client // resource == ?resource // ); policyString := *output.Statement - mapPols, err := a.CedarMapper.ParseAndMapCedarToHexa([]byte(policyString)) + policyString = strings.Replace(policyString, "?principal", "Template::\"principal\"", -1) + policyString = strings.Replace(policyString, "?resource", "Template::\"resource\"", -1) + + mapPols, err := a.CedarMapper.MapCedarPolicyBytes(applicationInfo.ObjectID, []byte(policyString)) if err != nil { return nil, err } @@ -375,18 +380,10 @@ func (a AmazonAvpProvider) SetPolicyInfo(info policyprovider.IntegrationInfo, ap } func (a AmazonAvpProvider) convertCedarStatement(hexaPolicy hexapolicy.PolicyInfo) (*string, error) { - cedarPolicies, err := a.CedarMapper.MapPolicyToCedar(hexaPolicy) - if err != nil { - return nil, err - } - var cedarDefinition string - for i, cedarPolicy := range cedarPolicies { - if i != 0 { - cedarDefinition = cedarDefinition + "\n" - } - cedarDefinition = cedarDefinition + cedarPolicy.String() - } - return &cedarDefinition, nil + cedarPolicies, err := a.CedarMapper.MapHexaPolicies("", []hexapolicy.PolicyInfo{hexaPolicy}) + cedarPolicies = strings.Replace(cedarPolicies, "Template::\"principal\"", "?principal", -1) + cedarPolicies = strings.Replace(cedarPolicies, "Template::\"resource\"", "?resource", -1) + return &cedarPolicies, err } func (a AmazonAvpProvider) prepareCreatePolicy(hexaPolicy hexapolicy.PolicyInfo, app policyprovider.ApplicationInfo) (*verifiedpermissions.CreatePolicyInput, error) { diff --git a/providers/aws/avpProvider/avp_provider_test.go b/providers/aws/avpProvider/avp_provider_test.go index a1423e9..00074e3 100644 --- a/providers/aws/avpProvider/avp_provider_test.go +++ b/providers/aws/avpProvider/avp_provider_test.go @@ -14,7 +14,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/verifiedpermissions" "github.com/aws/aws-sdk-go-v2/service/verifiedpermissions/types" "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/models/formats/awsCedar" + "github.com/hexa-org/policy-mapper/models/formats/cedar" "github.com/hexa-org/policy-mapper/pkg/hexapolicy" "github.com/hexa-org/policy-mapper/providers/aws/avpProvider" "github.com/hexa-org/policy-mapper/providers/aws/avpProvider/avpClient" @@ -61,7 +61,7 @@ func initializeOnlineTests() error { info := policyprovider.IntegrationInfo{Name: "avp", Key: []byte(str)} provider := avpProvider.AmazonAvpProvider{AwsClientOpts: awscommon.AWSClientOptions{DisableRetry: true}, - CedarMapper: awsCedar.New(map[string]string{})} + CedarMapper: cedar.NewCedarMapper(map[string]string{})} testData = TestInfo{ Provider: provider, @@ -161,7 +161,7 @@ func TestAvp_1_ListStores(t *testing.T) { HTTPClient: mockClient, DisableRetry: true, }, - CedarMapper: awsCedar.New(map[string]string{})} + CedarMapper: cedar.NewCedarMapper(map[string]string{})} info := avpTestSupport.IntegrationInfo() apps, err := p.DiscoverApplications(info) @@ -186,7 +186,7 @@ func TestAvp_2_GetPolicies(t *testing.T) { HTTPClient: mockClient, DisableRetry: true, }, - CedarMapper: awsCedar.New(map[string]string{})} + CedarMapper: cedar.NewCedarMapper(map[string]string{})} mockClient.MockListStores() info := avpTestSupport.IntegrationInfo() @@ -212,7 +212,7 @@ func TestAvp_3_Reconcile(t *testing.T) { HTTPClient: mockClient, DisableRetry: true, }, - CedarMapper: awsCedar.New(map[string]string{})} + CedarMapper: cedar.NewCedarMapper(map[string]string{})} mockClient.MockListStores() info := avpTestSupport.IntegrationInfo() @@ -306,7 +306,7 @@ func TestAvp_4_SetPolicies(t *testing.T) { HTTPClient: mockClient, DisableRetry: true, }, - CedarMapper: awsCedar.New(map[string]string{})} + CedarMapper: cedar.NewCedarMapper(map[string]string{})} mockClient.MockListStores() info := avpTestSupport.IntegrationInfo() diff --git a/sdk/options_test.go b/sdk/options_test.go index 4ce443b..93990c1 100644 --- a/sdk/options_test.go +++ b/sdk/options_test.go @@ -1,20 +1,20 @@ package sdk import ( - "testing" + "testing" - "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/providers/aws/avpProvider" - "github.com/hexa-org/policy-mapper/providers/aws/avpProvider/avpClient/avpTestSupport" - "github.com/hexa-org/policy-mapper/providers/aws/awscommon" - "github.com/stretchr/testify/assert" + "github.com/hexa-org/policy-mapper/api/policyprovider" + "github.com/hexa-org/policy-mapper/providers/aws/avpProvider" + "github.com/hexa-org/policy-mapper/providers/aws/avpProvider/avpClient/avpTestSupport" + "github.com/hexa-org/policy-mapper/providers/aws/awscommon" + "github.com/stretchr/testify/assert" ) func TestWithIntegrationInfo(t *testing.T) { - info := policyprovider.IntegrationInfo{ - Name: ProviderTypeOpa, - Key: []byte(` + info := policyprovider.IntegrationInfo{ + Name: ProviderTypeOpa, + Key: []byte(` { "project_id": "some aws project", "aws": { @@ -26,105 +26,103 @@ func TestWithIntegrationInfo(t *testing.T) { } } `), - } - integration, err := OpenIntegration(WithIntegrationInfo(info)) - assert.NoError(t, err, "No error on open OPA AWS integration") - assert.NotNil(t, integration, "Integration is defined") - - applications, err := integration.GetPolicyApplicationPoints(nil) - assert.NoError(t, err) - assert.Equal(t, 1, len(applications)) - - assert.Equal(t, "opa-bundles", applications[0].ObjectID) - assert.Equal(t, "OPA aws-s3 Bundle Service", applications[0].Description) - assert.Equal(t, "OPA aws-s3", applications[0].Service) + } + integration, err := OpenIntegration(WithIntegrationInfo(info)) + assert.NoError(t, err, "No error on open OPA AWS integration") + assert.NotNil(t, integration, "Integration is defined") + + applications, err := integration.GetPolicyApplicationPoints(nil) + assert.NoError(t, err) + assert.Equal(t, 1, len(applications)) + + assert.Equal(t, "opa-bundles", applications[0].ObjectID) + assert.Equal(t, "OPA aws-s3 Bundle Service", applications[0].Description) + assert.Equal(t, "OPA aws-s3", applications[0].Service) } func TestWithOpaAwsIntegration(t *testing.T) { - key := []byte(`{"region": "us-west-1"}`) - integration, err := OpenIntegration(WithOpaAwsIntegration("opa-bundles", "bundle.tar.gz", key)) - assert.NoError(t, err, "No error on open OPA AWS integration") - assert.NotNil(t, integration, "Integration is defined") - - applications, err := integration.GetPolicyApplicationPoints(nil) - assert.NoError(t, err) - assert.Equal(t, 1, len(applications)) - - assert.Equal(t, "opa-bundles", applications[0].ObjectID) - assert.Equal(t, "OPA aws-s3 Bundle Service", applications[0].Description) - assert.Equal(t, "OPA aws-s3", applications[0].Service) + key := []byte(`{"region": "us-west-1"}`) + integration, err := OpenIntegration(WithOpaAwsIntegration("opa-bundles", "bundle.tar.gz", key)) + assert.NoError(t, err, "No error on open OPA AWS integration") + assert.NotNil(t, integration, "Integration is defined") + + applications, err := integration.GetPolicyApplicationPoints(nil) + assert.NoError(t, err) + assert.Equal(t, 1, len(applications)) + + assert.Equal(t, "opa-bundles", applications[0].ObjectID) + assert.Equal(t, "OPA aws-s3 Bundle Service", applications[0].Description) + assert.Equal(t, "OPA aws-s3", applications[0].Service) } func TestWithOpaGcpIntegration(t *testing.T) { - key := []byte(`{"type": "service_account"}`) - integration, err := OpenIntegration( - WithOpaGcpIntegration("opa-bundles", "bundle.tar.gz", key)) - assert.NoError(t, err, "No error on open OPA GCP integration") - assert.NotNil(t, integration, "Integration is defined") - - applications, err := integration.GetPolicyApplicationPoints(nil) - assert.NoError(t, err) - assert.Equal(t, 1, len(applications)) - - assert.Equal(t, "opa-bundles", applications[0].ObjectID) - assert.Equal(t, "OPA GCP_Storage Bundle Service", applications[0].Description) - assert.Equal(t, "OPA GCP_Storage", applications[0].Service) + key := []byte(`{"type": "service_account"}`) + integration, err := OpenIntegration( + WithOpaGcpIntegration("opa-bundles", "bundle.tar.gz", key)) + assert.NoError(t, err, "No error on open OPA GCP integration") + assert.NotNil(t, integration, "Integration is defined") + + applications, err := integration.GetPolicyApplicationPoints(nil) + assert.NoError(t, err) + assert.Equal(t, 1, len(applications)) + + assert.Equal(t, "opa-bundles", applications[0].ObjectID) + assert.Equal(t, "OPA GCP_Storage Bundle Service", applications[0].Description) + assert.Equal(t, "OPA GCP_Storage", applications[0].Service) } func TestWithOpaGithubIntegration(t *testing.T) { - key := []byte(`{"accessToken": "some_github_access_token"}`) - integration, err := OpenIntegration( - WithOpaGithubIntegration("hexa-org", "opa-bundles", "bundle.tar.gz", key)) - assert.NoError(t, err, "No error on open OPA Github integration") - assert.NotNil(t, integration, "Integration is defined") - - applications, err := integration.GetPolicyApplicationPoints(nil) - assert.NoError(t, err) - assert.Equal(t, 1, len(applications)) - - assert.Equal(t, "opa-bundles", applications[0].ObjectID) - assert.Equal(t, "OPA Github Bundle Service", applications[0].Description) - assert.Equal(t, "OPA Github", applications[0].Service) + key := []byte(`{"accessToken": "some_github_access_token"}`) + integration, err := OpenIntegration( + WithOpaGithubIntegration("hexa-org", "opa-bundles", "bundle.tar.gz", key)) + assert.NoError(t, err, "No error on open OPA Github integration") + assert.NotNil(t, integration, "Integration is defined") + + applications, err := integration.GetPolicyApplicationPoints(nil) + assert.NoError(t, err) + assert.Equal(t, 1, len(applications)) + + assert.Equal(t, "opa-bundles", applications[0].ObjectID) + assert.Equal(t, "OPA Github Bundle Service", applications[0].Description) + assert.Equal(t, "OPA Github", applications[0].Service) } func TestWithOpaHttpIntegration(t *testing.T) { - integration, err := OpenIntegration( - WithOpaHttpIntegration("aBigUrl", "", nil)) - assert.NoError(t, err, "No error on open OPA Http integration") - assert.NotNil(t, integration, "Integration is defined") + integration, err := OpenIntegration( + WithOpaHttpIntegration("aBigUrl", "", nil)) + assert.NoError(t, err, "No error on open OPA Http integration") + assert.NotNil(t, integration, "Integration is defined") - applications, err := integration.GetPolicyApplicationPoints(nil) - assert.NoError(t, err) - assert.Equal(t, 1, len(applications)) + applications, err := integration.GetPolicyApplicationPoints(nil) + assert.NoError(t, err) + assert.Equal(t, 1, len(applications)) - objectIdMatch := "/aBigUrl" - assert.Equal(t, objectIdMatch, applications[0].ObjectID) - assert.Equal(t, "OPA HTTP Bundle Service", applications[0].Description) - assert.Equal(t, "OPA HTTP", applications[0].Service) + objectIdMatch := "/aBigUrl" + assert.Equal(t, objectIdMatch, applications[0].ObjectID) + assert.Equal(t, "OPA HTTP Bundle Service", applications[0].Description) + assert.Equal(t, "OPA HTTP", applications[0].Service) } func TestWithAttributeMap(t *testing.T) { - mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() - - options := awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true, - } - nameMap := make(map[string]string, 2) - nameMap["username"] = "account" - nameMap["a"] = "123" - info := avpTestSupport.IntegrationInfo() - integration, err := OpenIntegration(WithIntegrationInfo(info), WithProviderOptions(options), WithAttributeMap(nameMap)) - assert.NoError(t, err, "Check no error opening mock provider") - - switch prov := integration.provider.(type) { - case *avpProvider.AmazonAvpProvider: - cedarMapper := prov.CedarMapper - assert.NotNil(t, cedarMapper) - res := cedarMapper.ConditionMapper.NameMapper.GetProviderAttributeName("a") - assert.Equal(t, "123", res) - default: - assert.Fail(t, "Expecting an Amazon AVP Provider!") - } + mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() + + options := awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true, + } + nameMap := make(map[string]string, 2) + nameMap["username"] = "account" + nameMap["a"] = "123" + info := avpTestSupport.IntegrationInfo() + integration, err := OpenIntegration(WithIntegrationInfo(info), WithProviderOptions(options), WithAttributeMap(nameMap)) + assert.NoError(t, err, "Check no error opening mock provider") + + switch prov := integration.provider.(type) { + case *avpProvider.AmazonAvpProvider: + cedarMapper := prov.CedarMapper + assert.NotNil(t, cedarMapper) + default: + assert.Fail(t, "Expecting an Amazon AVP Provider!") + } } diff --git a/sdk/provider_integrations.go b/sdk/provider_integrations.go index f329c6a..cfa6a5d 100644 --- a/sdk/provider_integrations.go +++ b/sdk/provider_integrations.go @@ -7,7 +7,7 @@ import ( "os" "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/models/formats/awsCedar" + "github.com/hexa-org/policy-mapper/models/formats/cedar" "github.com/hexa-org/policy-mapper/models/formats/gcpBind" "github.com/hexa-org/policy-mapper/providers/aws/avpProvider" "github.com/hexa-org/policy-mapper/providers/aws/awsapigwProvider" @@ -164,11 +164,11 @@ func newAvpProvider(options Options) (policyprovider.Provider, error) { fmt.Println("Warning, unexpected ProviderOpts (use awscommon.AWSClientOptions)") } } - var mapper *awsCedar.CedarPolicyMapper + var mapper *cedar.CedarMapper if options.AttributeMap != nil { - mapper = awsCedar.New(options.AttributeMap) + mapper = cedar.NewCedarMapper(options.AttributeMap) } else { - mapper = awsCedar.New(map[string]string{}) + mapper = cedar.NewCedarMapper(map[string]string{}) } return &avpProvider.AmazonAvpProvider{