From 23a356e95ad417fa86c1fce3a345f421ee4baa62 Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Thu, 1 Aug 2024 10:12:22 -0700 Subject: [PATCH 01/13] Issue #57, initial drop of policy schema and tests Signed-off-by: Phil Hunt --- models/schema/schema.go | 69 ++++++++++++++ models/schema/schema_test.go | 71 +++++++++++++++ models/schema/test/healthSchema.json | 110 ++++++++++++++++++++++ models/schema/test/photoSchema.json | 131 +++++++++++++++++++++++++++ 4 files changed, 381 insertions(+) create mode 100644 models/schema/schema.go create mode 100644 models/schema/schema_test.go create mode 100644 models/schema/test/healthSchema.json create mode 100644 models/schema/test/photoSchema.json diff --git a/models/schema/schema.go b/models/schema/schema.go new file mode 100644 index 0000000..4e29558 --- /dev/null +++ b/models/schema/schema.go @@ -0,0 +1,69 @@ +package schema + +import "encoding/json" + +type LongType struct { + Type string `json:"type"` // is "Long" +} + +type SetType struct { + Type string `json:"type"` // is "Set" + Element []interface{} `json:"element"` +} + +type AttrType struct { + Type string `json:"type"` + Name string `json:"name,omitempty"` + Required bool `json:"required"` +} + +type RecordType struct { + Type string `json:"type"` // fixed as "RecordType" + Attributes map[string]AttrType `json:"attributes"` +} + +type ContextType struct { + Type string `json:"type"` // fixed as "RecordType" + Attributes map[string]AttrType `json:"attributes"` +} + +type ResourceTypes []string +type PrincipalTypes []string + +type AppliesType struct { + PrincipalTypes *PrincipalTypes `json:"principalTypes,omitempty"` + ResourceTypes *ResourceTypes `json:"resourceTypes,omitempty"` + Context *ContextType `json:"context,omitempty"` +} + +// Action ::= STR ':' '{' [ '"memberOf"' ':' '[' [ STR { ',' STR } ] ']' ] ',' [ '"appliesTo"' ':' '{' [PrincipalTypes] ',' [ResourceTypes] ',' [Context] '}' ] '}' +type ActionType struct { + MemberOf []string `json:"memberOf,omitempty"` + AppliesTo AppliesType `json:"appliesTo,omitempty"` +} + +type ShapeTypes struct { + Type string `json:"type"` + Attributes map[string]AttrType `json:"attributes"` +} + +// EntityType ::= IDENT ':' '{' [ 'memberOfTypes' ':' '[' [ IDENT { ',' IDENT } ] ']' ] ',' [ 'shape': TypeJson ] '}' +type EntityType struct { + MemberOfTypes []string `json:"memberOfTypes,omitempty"` + Shape ShapeTypes `json:"shape,omitempty"` +} + +type SchemaType struct { + EntityTypes map[string]EntityType `json:"entityTypes,omitempty"` + Actions map[string]ActionType `json:"actions,omitempty"` + CommonTypes map[string]ContextType `json:"commonTypes,omitempty"` +} + +type Namespaces map[string]SchemaType + +func ParseSchemaFile(schemaBytes []byte) (*Namespaces, error) { + + var namespaces Namespaces + err := json.Unmarshal(schemaBytes, &namespaces) + return &namespaces, err +} diff --git a/models/schema/schema_test.go b/models/schema/schema_test.go new file mode 100644 index 0000000..7fe034c --- /dev/null +++ b/models/schema/schema_test.go @@ -0,0 +1,71 @@ +package schema + +import ( + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParsePhotoSchema(t *testing.T) { + _, file, _, _ := runtime.Caller(0) + fileBytes, err := os.ReadFile(filepath.Join(file, "../test", "photoSchema.json")) + assert.NoError(t, err) + + namespace, err := ParseSchemaFile(fileBytes) + assert.NoError(t, err) + assert.NotNil(t, namespace) + + schemas := *namespace + app, ok := schemas["PhotoApp"] + if !ok { + assert.Fail(t, "Expected PhotoApp Schema") + } + assert.NotNil(t, app) + + principleTypes := *app.Actions["viewPhoto"].AppliesTo.PrincipalTypes + assert.Contains(t, principleTypes, "User") + + photoType := app.EntityTypes["Photo"] + assert.NotNil(t, photoType) + assert.Equal(t, "Record", photoType.Shape.Type) + assert.Equal(t, "Account", photoType.Shape.Attributes["account"].Name) + + personType := app.CommonTypes["PersonType"] + assert.NotNil(t, personType) + assert.Equal(t, "Long", personType.Attributes["age"].Type) +} + +func TestParseHealthSchema(t *testing.T) { + _, file, _, _ := runtime.Caller(0) + fileBytes, err := os.ReadFile(filepath.Join(file, "../test", "healthSchema.json")) + assert.NoError(t, err) + + namespace, err := ParseSchemaFile(fileBytes) + assert.NoError(t, err) + assert.NotNil(t, namespace) + + schemas := *namespace + app, ok := schemas["HealthCareApp"] + if !ok { + assert.Fail(t, "Expected HealthcareApp Schema") + } + assert.NotNil(t, app) + + userType := app.EntityTypes["User"] + assert.NotNil(t, userType) + assert.Contains(t, userType.MemberOfTypes, "Role") + assert.Equal(t, "Record", userType.Shape.Type) + assert.Empty(t, userType.Shape.Attributes) + + creatAppointmentAction := app.Actions["createAppointment"] + assert.NotNil(t, creatAppointmentAction) + assert.Contains(t, *creatAppointmentAction.AppliesTo.PrincipalTypes, "User") + + context := creatAppointmentAction.AppliesTo.Context + assert.NotNil(t, context) + assert.Equal(t, "Record", context.Type) + assert.Equal(t, "User", context.Attributes["referrer"].Name) +} diff --git a/models/schema/test/healthSchema.json b/models/schema/test/healthSchema.json new file mode 100644 index 0000000..fabc4f0 --- /dev/null +++ b/models/schema/test/healthSchema.json @@ -0,0 +1,110 @@ +{ + "HealthCareApp": { + "entityTypes": { + "User": { + "shape": { + "type": "Record", + "attributes": {} + }, + "memberOfTypes": [ + "Role" + ] + }, + "Role": { + "shape": { + "type": "Record", + "attributes": {} + }, + "memberOfTypes": [] + }, + "Info": { + "shape": { + "type": "Record", + "attributes": { + "patient": { + "type": "Entity", + "name": "User" + }, + "subject": { + "type": "Entity", + "name": "User" + } + } + }, + "memberOfTypes": [ + "InfoType" + ] + }, + "InfoType": { + "shape": { + "type": "Record", + "attributes": {} + }, + "memberOfTypes": [] + } + }, + "actions": { + "createAppointment": { + "appliesTo": { + "principalTypes": [ + "User" + ], + "resourceTypes": [ + "Info" + ], + "context": { + "type": "Record", + "attributes": { + "referrer": { + "type": "Entity", + "name": "User" + } + } + } + } + }, + "updateAppointment": { + "appliesTo": { + "principalTypes": [ + "User" + ], + "resourceTypes": [ + "Info" + ], + "context": { + "type": "Record", + "attributes": {} + } + } + }, + "deleteAppointment": { + "appliesTo": { + "principalTypes": [ + "User" + ], + "resourceTypes": [ + "Info" + ], + "context": { + "type": "Record", + "attributes": {} + } + } + }, + "listAppointments": { + "appliesTo": { + "principalTypes": [ + "User" + ], + "resourceTypes": [ + "Info" + ], + "context": { + "type": "Record", + "attributes": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/models/schema/test/photoSchema.json b/models/schema/test/photoSchema.json new file mode 100644 index 0000000..d04c0fc --- /dev/null +++ b/models/schema/test/photoSchema.json @@ -0,0 +1,131 @@ +{ + "PhotoApp": { + "commonTypes": { + "PersonType": { + "type": "Record", + "attributes": { + "age": { + "type": "Long" + }, + "name": { + "type": "String" + } + } + }, + "ContextType": { + "type": "Record", + "attributes": { + "ip": { + "type": "Extension", + "name": "ipaddr", + "required": false + }, + "authenticated": { + "type": "Boolean", + "required": true + } + } + } + }, + "entityTypes": { + "User": { + "shape": { + "type": "Record", + "attributes": { + "userId": { + "type": "String" + }, + "personInformation": { + "type": "PersonType" + } + } + }, + "memberOfTypes": [ + "UserGroup" + ] + }, + "UserGroup": { + "shape": { + "type": "Record", + "attributes": {} + } + }, + "Photo": { + "shape": { + "type": "Record", + "attributes": { + "account": { + "type": "Entity", + "name": "Account", + "required": true + }, + "private": { + "type": "Boolean", + "required": true + } + } + }, + "memberOfTypes": [ + "Album", + "Account" + ] + }, + "Album": { + "shape": { + "type": "Record", + "attributes": {} + } + }, + "Account": { + "shape": { + "type": "Record", + "attributes": {} + } + } + }, + "actions": { + "viewPhoto": { + "appliesTo": { + "principalTypes": [ + "User", + "UserGroup" + ], + "resourceTypes": [ + "Photo" + ], + "context": { + "type": "ContextType" + } + } + }, + "createPhoto": { + "appliesTo": { + "principalTypes": [ + "User", + "UserGroup" + ], + "resourceTypes": [ + "Photo" + ], + "context": { + "type": "ContextType" + } + } + }, + "listPhotos": { + "appliesTo": { + "principalTypes": [ + "User", + "UserGroup" + ], + "resourceTypes": [ + "Photo" + ], + "context": { + "type": "ContextType" + } + } + } + } + } +} \ No newline at end of file From aa8ca516e589b61d7e0510fb6e4ea91bc67a088f Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Thu, 1 Aug 2024 12:35:56 -0700 Subject: [PATCH 02/13] Issue #65, initial JSON-Schema representation of IDQL Signed-off-by: Phil Hunt --- pkg/hexapolicy/schema/README.md | 5 ++ pkg/hexapolicy/schema/idql.jschema | 120 +++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 pkg/hexapolicy/schema/README.md create mode 100644 pkg/hexapolicy/schema/idql.jschema diff --git a/pkg/hexapolicy/schema/README.md b/pkg/hexapolicy/schema/README.md new file mode 100644 index 0000000..fe41b97 --- /dev/null +++ b/pkg/hexapolicy/schema/README.md @@ -0,0 +1,5 @@ +# JSON Schema for IDQL + +This directory defines JSON schema for IDQL which can be used in javascript editors such as [Json-Editor](https://json-editor.github.io/json-editor/). + +To see a demo of IDQL editing with JSON schema [click here](https://json-editor.github.io/json-editor/?data=N4Ig9gDgLglmB2BnEAuUBXRBTA+vAhgLa75RQBOMARulFsihelgDQgTliHQ5VYBmYcrgAmWADZY6qJqxCZRA/OnFQcAN3zjmDWW0L4AHjjHQAFqgAMbGmQQ5EUUrkJgxqAIxsAxvmwOsJBhYdVwOSCxyKABPAPxybwtGcmY2RDMwAHccSM5yBhAYeDpyfG9YBBA2cO41CD86HANjSXgAcygzHGEysywRVH4tbDZhV1CcIfF/cIhI2HpB4blAwQTcbwRHJem5GE34cWpUEEFi/Ez6LiwAVirwKgArLHKccXxosFoT+CEDcXuiESWAMqFAABIgX1QSgQGYyBAUAB6JGPRAIAC0UJB+AAdEI2kiRKV+FAkQAmSyUjEeclI7GgtgxOYnMBPF5Qe6zeYwRZoEDEJxgkDMrCs9nlLmcOZRXkMUChfJweDC0UnRyUdr3WBQSQnACSABEAIoAGQABAAFMBHbzRc0ANUiiGV9zEQMo0FdsKdSoQ5rA/HNRrN5ogNv29oUIlx9ywhiIEEkDAA2iBLLiAGy4jx3AC6bDEQxUnNhGezuZAAF80l91obSPhVdEWbC2c9JdVpTy+cAayLgnrYQBZfAQANB0gUai0ehh+KwbwqeLmqBgc34MOcdQwMTkc0ACiwuLauNXIKTznNu4AlNXC/RvJ6Kir+WrYRqim1tYOxbDDY+z7KhOq59GGEZ2oex6nuamRmKQ15QNeiDmmsAb7iIYD0He/ZPlgzgDG+LZ/iAn5akyv4nAAwj0L7miIzhuoBMBepUsIACowMQjiJqhfxTv017wOaABKABiVEAMzSQAnLBwRmOaBC/NgBwiFuLwwC6CCxmw8aJsmqBph4MkABw3BilgACw0uS7HkpJKCUigNwZjc5IAFogHm/auCIMD8LyhGgO+pHTuRA66iRpoNOafkBUF9GMQ+HosS+JycdxTjcHx5AGGQglFKJEnSZJcmZApSn4CpLwIOpHCadp8C6SA+ncIZKDGWZFnWbZ9mOc5rm4u5Xk+XpTjfkRrZhZq34UVFJwAKLsfgbTmgAEn4FgpU+aXeiAVFaEu7x0Opy2rea8HpOamjaFgIGdPd4a2va8beNo/ntHFUibkUaz5cqZ4AKrYOpaHPZG5qbNw8RacBSLmlgACO6BaME9oI4k1VtPdYh0OUrqjPhIgAPKHNEMgpFg/YQ3a+rBSKxHquFc2RUOIDWi9wZiMUCWRExqWsa+IAAILmug8AwCj927oEsCBZEuWgU9EEUzTY708201kazOrs5zkMixASb7KQwHWkUSH03LfPkPe7DbrL5DsUzU0kTrP4LbC636Vajt7quxFQ24Yp6Qm7V8oUY4sBu6gQNW/Z6yRw4/UJ/1m/6+BUF8SG02raToBKpYha7DwdpyXYRLKvYCiCfD5FrJHxKUFPzezKeEPX5qIIX5fIGwwQgvKjPayz9vurtQsnGT91CHFQj3cQnfOjdWg6Ku658HFpDAupa7K+BL1xuHSZ8mmCjkCgOPkOQ0QAAJtafuLQ95VaJ5RsL22UL7D6FzcfPcQehBf6l3bByKUVcFjD2/sqIGlBG7M1mp7dmMCEBwJgALSe6UOJgVQUgMMkRCDBFOuaKg9pHqH0jGeF2EBTbiHEPaTcQMRL6mPgZM+IB8AiBEOxNwYB7jwigBARAKAADiy1kRmHQAYeAwh0ToHWMgHyb9+xgMlG7cU5cIEyigcKORdZvC4F3Agj8Y824kRElcBRhjuY2wVnbHaQE2IgGtrzexSsKH6OsfdVa+AiiOFgmYfYikKF5w3MbI49BYxvyJijGAwhCJplkK/HwdVgjehLtNNRFcHaQLlMKESKg/yZPdmPfsIsCZsRKYgr8Cd+xAgiCYsu4DK46PyfyQKqh+YaNMUg/sU5KC2Brn/G+ACB50GAU0j2KiYlsxIiGC0Bs7SYKccLAAUgAZRJgAOXNBs4EBhzQAUCpLOigZgwmkWare2j1iAnGzmAKAGoxxWQTkAA==). \ No newline at end of file diff --git a/pkg/hexapolicy/schema/idql.jschema b/pkg/hexapolicy/schema/idql.jschema new file mode 100644 index 0000000..ccc826a --- /dev/null +++ b/pkg/hexapolicy/schema/idql.jschema @@ -0,0 +1,120 @@ +{ + "$schema": "http://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "version": { + "type": "string", + "title": "IDQL Policy Version", + "description": "Version of IDQL policy used.", + "examples": ["0.6.15"], + "default": "0.6.15" + }, + "sourceData": { + "type": "object", + "properties": {}, + "title": "Map of attributes particular to a provider (e.g. template id)" + }, + "description": { + "type": "string", + "title": "Description of the policy (e.g. what it is for or does)" + }, + "created": { + "type": "string", + "title": "Creation date", + "description": "Timestamp formatted in RFC3339 with nanosecond precision.", + "examples": ["1985-04-12T23:20:50.52Z"] + }, + "modified": { + "type": "string", + "title": "Last modified date", + "description": "Timestamp formatted in RFC3339 with nanosecond precision.", + "examples": ["1985-04-12T23:20:50.52Z"] + }, + "etag": { + "type": "string", + "title": "ETag Hash", + "description": "Calculated ETag hash value of the policy excluding meta information. Used for policy comparision / equality / change detection", + "readOnly": true + }, + "policyId": { + "type": "string", + "title": "Policy Identifier", + "description": "A unique identifier for the policy" + }, + "papId": { + "type": "string", + "title": "Policy Application Point Identifier" + }, + "providerType": { + "type": "string", + "title": "Hexa Provider type code", + "examples": "iap, avp" + } + }, + "title": "Meta information about policy" + }, + "subject": { + "type": "object", + "properties": { + "members": { + "type": "array", + "title": "Member subjects", + "items": {"type": "string"}, + "description": "One or more members values to be matched to the policy", + "examples": ["user:gerry@example.com"] + } + }, + "title": "" + }, + "actions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "actionUri": { + "type": "string", + "title": "actionUri", + "description": "The actions permitted by the policy. Typically a URI", + "examples": [ + "addTodo", + "https:GET:/humanresources" + ] + } + } + } + }, + "object": { + "type": "object", + "properties": { + "resource_id": { + "type": "string", + "title": "Resource Identifier", + "description": "Identifier for the resource against which the policy applies." + } + }, + "required": ["true"] + }, + "condition": { + "type": "object", + "properties": { + "Rule": {"type": "string"}, + "Action": {"type": "string"} + } + }, + "scope": { + "type": "object", + "properties": { + "filter": {"type": "string"}, + "attributes": { + "type": "array", + "items": {"type": "string"} + } + } + } + }, + "title": "IDQL Policy", + "description": "JSON Schema Definition of IDQL Policy" +} \ No newline at end of file From 60abf69c77c33fd58b478033c5a71d2fdbdc6188 Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Thu, 22 Aug 2024 18:05:39 -0700 Subject: [PATCH 03/13] Issue #52, initial drop of new Cedar Conditions mapper and tests. Updated go.mod Updated hexa condition to reduce unnecessary quoting Signed-off-by: Phil Hunt --- go.mod | 100 ++--- go.sum | 200 ++++----- .../cedarConditions/map_cedar.go | 386 ++++++++++++++++++ .../cedarConditions/map_hexa.go | 360 ++++++++++++++++ .../cedarConditions/map_test.go | 262 ++++++++++++ .../gcpcel/gcp_condition_mapper_test.go | 314 +++++++------- models/formats/awsCedar/amazon_cedar_test.go | 280 ++++++------- models/formats/cedar/cedar_test.go | 252 ++++++++++++ models/formats/cedar/entity.go | 210 ++++++++++ models/formats/cedar/parse.go | 365 +++++++++++++++++ .../formats/cedar/test/cedarPhotoPolicy.txt | 12 + models/formats/cedar/test/healthEntities.json | 67 +++ models/formats/cedar/test/healthSchema.json | 110 +++++ models/formats/cedar/test/photoEntities.json | 57 +++ models/formats/cedar/test/photoSchema.json | 131 ++++++ .../conditions/parser/parser_test.go | 311 +++++++------- pkg/hexapolicy/conditions/parser/types.go | 135 +++--- .../resources/bundles/bundle/hexaPolicy.rego | 2 +- 18 files changed, 2893 insertions(+), 661 deletions(-) create mode 100644 models/conditionLangs/cedarConditions/map_cedar.go create mode 100644 models/conditionLangs/cedarConditions/map_hexa.go create mode 100644 models/conditionLangs/cedarConditions/map_test.go create mode 100644 models/formats/cedar/cedar_test.go create mode 100644 models/formats/cedar/entity.go create mode 100644 models/formats/cedar/parse.go create mode 100644 models/formats/cedar/test/cedarPhotoPolicy.txt create mode 100644 models/formats/cedar/test/healthEntities.json create mode 100644 models/formats/cedar/test/healthSchema.json create mode 100644 models/formats/cedar/test/photoEntities.json create mode 100644 models/formats/cedar/test/photoSchema.json diff --git a/go.mod b/go.mod index 3dfbfd0..42a3132 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/hexa-org/policy-mapper -go 1.22 +go 1.23 -toolchain go1.22.4 +toolchain go1.23.0 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/MicahParks/jwkset v0.5.18 github.com/MicahParks/keyfunc/v3 v3.3.3 @@ -13,79 +13,81 @@ require ( github.com/alecthomas/kong v0.9.0 github.com/alecthomas/participle/v2 v2.1.1 github.com/alexedwards/scs/v2 v2.8.0 - github.com/aws/aws-sdk-go-v2 v1.30.3 - github.com/aws/aws-sdk-go-v2/config v1.27.26 - github.com/aws/aws-sdk-go-v2/credentials v1.17.26 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.9 - github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.41.4 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.3 - github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 - github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.3 + github.com/aws/aws-sdk-go-v2 v1.30.4 + github.com/aws/aws-sdk-go-v2/config v1.27.29 + github.com/aws/aws-sdk-go-v2/credentials v1.17.29 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.11 + github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.2 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5 + github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1 + github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.4 + github.com/cedar-policy/cedar-go v0.1.0 github.com/chzyer/readline v1.5.1 github.com/coreos/go-oidc/v3 v3.11.0 - github.com/envoyproxy/go-control-plane v0.12.0 + github.com/envoyproxy/go-control-plane v0.13.0 github.com/go-playground/validator/v10 v10.22.0 - github.com/gofrs/flock v0.12.0 + github.com/gofrs/flock v0.12.1 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/cel-go v0.20.1 + github.com/google/cel-go v0.21.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/hhsnopek/etag v0.0.0-20171206181245-aea95f647346 - github.com/prometheus/client_golang v1.19.1 + github.com/prometheus/client_golang v1.20.1 + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 github.com/stretchr/testify v1.9.0 - golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 - golang.org/x/oauth2 v0.21.0 - google.golang.org/api v0.188.0 - google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d + golang.org/x/exp v0.0.0-20240822175202-778ce7bba035 + golang.org/x/oauth2 v0.22.0 + google.golang.org/api v0.194.0 + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd ) require ( - cloud.google.com/go/auth v0.7.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect + cloud.google.com/go/auth v0.9.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/compute/metadata v0.5.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/alecthomas/repr v0.4.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.16 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect - github.com/aws/smithy-go v1.20.3 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect + github.com/aws/smithy-go v1.20.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect + github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/gabriel-vasile/mimetype v1.4.4 // indirect - github.com/go-jose/go-jose/v4 v4.0.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/s2a-go v0.1.7 // indirect + github.com/google/s2a-go v0.1.8 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.5 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect @@ -98,12 +100,12 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/time v0.6.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index d69739e..e5f744d 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/auth v0.7.1 h1:Iv1bbpzJ2OIg16m94XI9/tlzZZl3cdeR3nGVGj78N7s= -cloud.google.com/go/auth v0.7.1/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= -cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= -cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= +cloud.google.com/go/auth v0.9.1 h1:+pMtLEV2k0AXKvs/tGZojuj6QaioxfUjOpMsG5Gtx+w= +cloud.google.com/go/auth v0.9.1/go.mod h1:Sw8ocT5mhhXxFklyhT12Eiy0ed6tTrPMCJjSI8KhYLk= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= @@ -30,56 +30,58 @@ github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZx github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= -github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= -github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= -github.com/aws/aws-sdk-go-v2/config v1.27.26 h1:T1kAefbKuNum/AbShMsZEro6eRkeOT8YILfE9wyjAYQ= -github.com/aws/aws-sdk-go-v2/config v1.27.26/go.mod h1:ivWHkAWFrw/nxty5Fku7soTIVdqZaZ7dw+tc5iGW3GA= -github.com/aws/aws-sdk-go-v2/credentials v1.17.26 h1:tsm8g/nJxi8+/7XyJJcP2dLrnK/5rkFp6+i2nhmz5fk= -github.com/aws/aws-sdk-go-v2/credentials v1.17.26/go.mod h1:3vAM49zkIa3q8WT6o9Ve5Z0vdByDMwmdScO0zvThTgI= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.9 h1:aVVgQDwvAGq8Olf9nb+sQgSujPEybAg4ptxm+L2zisY= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.9/go.mod h1:uCzvi36pXcTcGHwWXPHXkhaK9F4AjNo+IByRSv7BRe4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg= -github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.41.4 h1:jkvdmVYoVWVrAIjgt9aiR9e7GRK2DnxrMnvKjA5EJd0= -github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.41.4/go.mod h1:aynIysFCBIq18wfN2GrIYAeofOnQKV3LtkjyrQKfaFY= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.3 h1:nEhZKd1JQ4EB1tekcqW1oIVpDC1ZFrjrp/cLC5MXjFQ= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.3/go.mod h1:q9vzW3Xr1KEXa8n4waHiFt1PrppNDlMymlYP+xpsFbY= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.3 h1:r27/FnxLPixKBRIlslsvhqscBuMK8uysCYG9Kfgm098= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.3/go.mod h1:jqOFyN+QSWSoQC+ppyc4weiO8iNQXbzRbxDjQ1ayYd4= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.16 h1:lhAX5f7KpgwyieXjbDnRTjPEUI0l3emSRyxXj1PXP8w= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.16/go.mod h1:AblAlCwvi7Q/SFowvckgN+8M3uFPlopSYeLlbNDArhA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg= -github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE= -github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 h1:Fv1vD2L65Jnp5QRsdiM64JvUM4Xe+E0JyVsRQKv6IeA= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.3/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= -github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.3 h1:RvKL61+VcqZIL9dS3BE0bQTyN1lCrDCv3cz9kdkNm6k= -github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.3/go.mod h1:AmO4nIKOKHzJCbVn467c4keHpzmZwy7s98zEsLjcJos= -github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= -github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= +github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw= +github.com/aws/aws-sdk-go-v2/config v1.27.29 h1:+ZPKb3u9Up4KZWLGTtpTmC5T3XmRD1ZQ8XQjRCHUvJw= +github.com/aws/aws-sdk-go-v2/config v1.27.29/go.mod h1:yxqvuubha9Vw8stEgNiStO+yZpP68Wm9hLmcm+R/Qk4= +github.com/aws/aws-sdk-go-v2/credentials v1.17.29 h1:CwGsupsXIlAFYuDVHv1nnK0wnxO0wZ/g1L8DSK/xiIw= +github.com/aws/aws-sdk-go-v2/credentials v1.17.29/go.mod h1:BPJ/yXV92ZVq6G8uYvbU0gSl8q94UB63nMT5ctNO38g= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.11 h1:KUHQows9JhDp+RJRs9KLN+ljsK5D+oLV13Wr/TwlSr4= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.11/go.mod h1:4kdmcGnKW4R9l2ddj6hNgKnJoxztjvJNCoI9eikMgvI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 h1:mimdLQkIX1zr8GIPY1ZtALdBQGxcASiBd2MOp8m/dMc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16/go.mod h1:YHk6owoSwrIsok+cAH9PENCOGoH5PU2EllX4vLtSrsY= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.2 h1:DolLrk9um5/oj6k8p0sKc5A9eiW+DhFmc/Ip64LNktU= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.2/go.mod h1:PUxIbGvs00Dw/BBqPPxqDpE5k2DvFHPVlNMXgChv0Co= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5 h1:Cm77yt+/CV7A6DglkENsWA3H1hq8+4ItJnFKrhxHkvg= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5/go.mod h1:s2fYaueBuCnwv1XQn6T8TfShxJWusv5tWPMcL+GY6+g= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.5 h1:sM/SaWUKPtsCcXE0bHZPUG4jjCbFbxakyptXQbYLrdU= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.5/go.mod h1:3YxVsEoCNYOLIbdA+cCXSp1fom9hrhyB1DsCiYryCaQ= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 h1:GckUnpm4EJOAio1c8o25a+b3lVfwVzC9gnSBqiiNmZM= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18/go.mod h1:Br6+bxfG33Dk3ynmkhsW2Z/t9D4+lRqdLDNCKi85w0U= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 h1:HDJGz1jlV7RokVgTPfx1UHBHANC0N5Uk++xgyYgz5E0= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17/go.mod h1:5szDu6TWdRDytfDxUQVv2OYfpTQMKApVFyqpm+TcA98= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 h1:jg16PhLPUiHIj8zYIW6bqzeQSuHVEiWnGA0Brz5Xv2I= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16/go.mod h1:Uyk1zE1VVdsHSU7096h/rwnXDzOzYQVl+FNPhPw7ShY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1 h1:mx2ucgtv+MWzJesJY9Ig/8AFHgoE5FwLXwUVgW/FGdI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wINh+4UK+k/0Yo/q8= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= +github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.4 h1:vqIZR0Mo6u0Lx/Ep5ea4kaxalsKY1+Um1tJ6UvoDArs= +github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.4/go.mod h1:lmvSNrXkQPdl9SaIi+yvK9UQ3USZC8N3iImoCu1ADo0= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cedar-policy/cedar-go v0.1.0 h1:2tZwWn8tNO/896YAM7OQmH3vn98EeHEA3g9anwdVZvA= +github.com/cedar-policy/cedar-go v0.1.0/go.mod h1:pEgiK479O5dJfzXnTguOMm+bCplzy5rEEFPGdZKPWz4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -91,26 +93,28 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw= -github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59 h1:fLZ97KE86ELjEYJCEUVzmbhfzDxHHGwBrDVMd4XL6Bs= +github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI= -github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= +github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les= +github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= -github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= -github.com/go-jose/go-jose/v4 v4.0.3 h1:o8aphO8Hv6RPmH+GfzVuyf7YXSBibp+8YyHdOoDESGo= -github.com/go-jose/go-jose/v4 v4.0.3/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -124,8 +128,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/gofrs/flock v0.12.0 h1:xHW8t8GPAiGtqz7KxiSqfOEXwpOaqhpYZrTE2MQBgXY= -github.com/gofrs/flock v0.12.0/go.mod h1:FirDy1Ing0mI2+kB6wk+vyyAH+e6xiE+EYA0jnzV9jc= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -142,10 +146,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= -github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -154,8 +156,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -163,8 +165,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= -github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -175,6 +177,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -187,11 +191,13 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.1 h1:IMJXHOD6eARkQpxo8KkhgEVFlBNm+nkrFUyGlIu7Na8= +github.com/prometheus/client_golang v1.20.1/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= @@ -201,6 +207,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -228,11 +236,11 @@ go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+ go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 h1:wDLEX9a7YQoKdKNQt88rtydkqDxeGaBUTnIYc3iG/mA= -golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240822175202-778ce7bba035 h1:VkSUcpKXdGwUpn/JsiWXwSNnIJVXRfMA4ThL5vwljWg= +golang.org/x/exp v0.0.0-20240822175202-778ce7bba035/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -242,47 +250,47 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw= -google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= +google.golang.org/api v0.194.0 h1:dztZKG9HgtIpbI35FhfuSNR/zmaMVdxNlntHj1sIS4s= +google.golang.org/api v0.194.0/go.mod h1:AgvUFdojGANh3vI+P7EVnxj3AISHllxGCJSFmggmnd0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/models/conditionLangs/cedarConditions/map_cedar.go b/models/conditionLangs/cedarConditions/map_cedar.go new file mode 100644 index 0000000..55bed7c --- /dev/null +++ b/models/conditionLangs/cedarConditions/map_cedar.go @@ -0,0 +1,386 @@ +package cedarConditions + +import ( + "errors" + "fmt" + "strconv" + "strings" + + cedarParser "github.com/cedar-policy/cedar-go/x/exp/parser" + "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) { + + if cedarConditions == nil { + return nil, nil + } + + condInfo := &conditions.ConditionInfo{} + + var err error + switch len(cedarConditions) { + case 0: + return nil, nil + case 1: + condInfo.Rule, err = mapConditionClause(cedarConditions[0], true) + if cedarConditions[0].Type == cedarParser.ConditionWhen { + condInfo.Action = conditions.AAllow + } else { + condInfo.Action = conditions.ADeny + } + default: + merge := "" + for i, cond := range cedarConditions { + var clause string + clause, err = mapConditionClause(cond, false) + if i == 0 { + merge = clause + } else { + merge = fmt.Sprintf("%s %v %s", merge, hexaParser.AND, clause) + } + } + condInfo.Rule = merge + condInfo.Action = conditions.AAllow + } + + return condInfo, err +} + +// mapConditionClause maps a when/unless clause +func mapConditionClause(cond cedarParser.Condition, isSingle bool) (string, error) { + exp := cond.Expression + var clause hexaParser.Expression + var err error + switch cond.Type { + case cedarParser.ConditionWhen: + // if there is only a single condition clause then we don't need precedence brackets - can treat as nested + clause, err = mapCedarExpression(exp, isSingle) + + case cedarParser.ConditionUnless: + var notClause hexaParser.Expression + notClause, err = mapCedarExpression(exp, isSingle) + if isSingle { + // The outer when/unless handles it + clause = notClause + } else { + clause = hexaParser.NotExpression{ + Expression: notClause, + } + } + + } + res := "" + if clause != nil { + res = clause.String() + } + 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 mapCedarOrs(ands []cedarParser.And, isNested bool) (hexaParser.Expression, error) { + lh, err := mapCedarAnd(ands[0]) + if err != nil { + return nil, err + } + + if len(ands) == 2 { + rh, err := mapCedarAnd(ands[1]) + if err != nil { + return nil, err + } + if isNested { + return makeLogical(hexaParser.OR, lh, rh), nil + } + return hexaParser.PrecedenceExpression{ + Expression: makeLogical(hexaParser.OR, lh, rh), + }, nil + } + + rh, err := mapCedarOrs(ands[1:], true) + if err != nil { + return nil, err + } + if isNested { + return makeLogical(hexaParser.OR, lh, rh), nil + } + return hexaParser.PrecedenceExpression{ + Expression: makeLogical(hexaParser.OR, lh, 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 + } + rh, err := mapCedarRelation(relations[1]) + if err != nil { + return nil, err + } + return makeLogical(hexaParser.AND, lh, rh), nil + default: + lh, err := mapCedarRelation(relations[0]) + if err != nil { + return nil, err + } + remainAnd := cedarParser.And{ + Relations: relations[1:], + } + rh, err := mapCedarAnd(remainAnd) + if err != nil { + return nil, err + } + return makeLogical(hexaParser.AND, lh, rh), 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 + } + if rh == nil { + return lh + } + return hexaParser.LogicalExpression{ + Operator: andor, + Left: lh, + Right: rh, + } +} + +func getSubExpression(rel cedarParser.Relation) (*cedarParser.Expression, error) { + add := rel.Add + + primary, err := getPrimary(add) + if err != nil { + return nil, err + } + return &primary.Expression, nil +} + +func parseRelNone(rel cedarParser.Relation) (hexaParser.Expression, error) { + + unaryExpression, err := getSubExpression(rel) + 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) + if err != nil { + return nil, err + } + if mapExp == nil { + return nil, nil + } + return hexaParser.PrecedenceExpression{Expression: mapExp}, nil + +} + +func getPrimary(add cedarParser.Add) (*cedarParser.Primary, error) { + mults := add.Mults + if mults == nil { + return nil, errors.New("no primary found") + } + 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: + } + + } + return add.String(), 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 + } + 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 + } + // 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 new file mode 100644 index 0000000..4faf520 --- /dev/null +++ b/models/conditionLangs/cedarConditions/map_hexa.go @@ -0,0 +1,360 @@ +package cedarConditions + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions" + hexaParser "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions/parser" +) + +type CedarConditionMapper struct { + NameMapper *conditions.AttributeMap +} + +// MultiLogicalExpression is used to flatten an inbound IDQL statement in cases where there are ands with more than 2 values +type MultiLogicalExpression struct { + Operator hexaParser.LogicalOperator + Expressions []Expression +} + +type Expression interface { + String() string +} + +func (m *MultiLogicalExpression) exprNode() {} + +func (m *MultiLogicalExpression) String() string { + return "" +} + +func makeFlattenedLogical(logical hexaParser.LogicalExpression) Expression { + res := MultiLogicalExpression{} + res.Operator = logical.Operator + res.flatten(logical) + return &res +} + +func (m *MultiLogicalExpression) flatten(logical hexaParser.LogicalExpression) { + + switch exp := logical.Left.(type) { + case hexaParser.LogicalExpression: + if exp.Operator == logical.Operator { + m.flatten(exp) + } else { + flattened := makeFlattenedLogical(exp) + m.Expressions = append(m.Expressions, flattened) + } + default: + m.Expressions = append(m.Expressions, exp) + } + + switch exp := logical.Right.(type) { + case hexaParser.LogicalExpression: + if exp.Operator == logical.Operator { + m.flatten(exp) + } else { + flattened := makeFlattenedLogical(exp) + m.Expressions = append(m.Expressions, flattened) + } + default: + m.Expressions = append(m.Expressions, exp) + } +} + +func checkAndFlatten(expression Expression) Expression { + switch exp := expression.(type) { + case hexaParser.LogicalExpression: + return makeFlattenedLogical(exp) + + default: + return exp + } +} + +func (mapper *CedarConditionMapper) MapConditionToCedar(condition *conditions.ConditionInfo) (string, error) { + // assumes https://github.com/google/cel-spec/blob/master/doc/langdef.md#logical-operators + if condition == nil { + return "", nil + } + ast, err := conditions.ParseConditionRuleAst(*condition) + if err != nil { + return "", err + } + root := *ast + + err = checkCompatibility(root) + if err != nil { + return "", err + } + + isUnless := false + if condition.Action == conditions.ADeny { + isUnless = true + } + // This logic needs to decide whether to start with multiple whens/unlesses + + // we have a lot of layers of ands/ors need to split into multiple clauses + switch exp := root.(type) { + case hexaParser.NotExpression: + flattened := checkAndFlatten(exp.Expression) + return mapper.mapFlattenedClause(flattened, !isUnless), nil + case hexaParser.PrecedenceExpression: + flattened := checkAndFlatten(exp.Expression) + return mapper.mapFlattenedClause(flattened, isUnless), nil + case hexaParser.LogicalExpression: + flattened := checkAndFlatten(root) + return mapper.mapFlattenedClause(flattened, isUnless), nil + default: + return mapper.mapToConditionClause(exp, isUnless), nil + } +} + +// mapFlattenedClause attempts to break condition into multiple when/unless fragments (implied and) +func (mapper *CedarConditionMapper) mapFlattenedClause(exp Expression, isUnless bool) string { + + switch node := exp.(type) { + case *MultiLogicalExpression: + if node.Operator == hexaParser.AND { + sb := strings.Builder{} + for i, expression := range node.Expressions { + if i > 0 { + sb.WriteRune('\n') + } + clauseLine := mapper.mapFlattenedClause(expression, isUnless) + sb.WriteString(clauseLine) + } + return sb.String() + } + // Convert the or clauses in one when/unless + sb := strings.Builder{} + + if isUnless { + sb.WriteString("unless { ") + } else { + sb.WriteString("when { ") + } + for i, expression := range node.Expressions { + if i > 0 { + sb.WriteString(" || ") + } + switch exp := expression.(type) { + case *MultiLogicalExpression: + // this is likely a nested and + + sb.WriteString(" (") + for j, expAnd := range exp.Expressions { + clause := mapper.mapFilterExpression(expAnd.(hexaParser.Expression), true) + if j > 0 { + sb.WriteString(" && ") + } + sb.WriteString(clause) + } + sb.WriteString(")") + + default: + clause := mapper.mapFilterExpression(exp.(hexaParser.Expression), true) + sb.WriteString(clause) + } + } + sb.WriteString(" }") + return sb.String() + + default: + return mapper.mapToConditionClause(node.(hexaParser.Expression), isUnless) + } +} + +// mapToConditionClause creates a top-level cedar `when` or `unless` clause +func (mapper *CedarConditionMapper) mapToConditionClause(exp hexaParser.Expression, isUnless bool) string { + sb := strings.Builder{} + + var clause string + // This removes redundant top leve precedence or not expression + switch node := exp.(type) { + case hexaParser.PrecedenceExpression: + clause = mapper.mapFilterExpression(node.Expression, false) + case hexaParser.NotExpression: + isUnless = !isUnless + clause = mapper.mapFilterExpression(node.Expression, false) + default: + clause = mapper.mapFilterExpression(exp, false) + } + if isUnless { + sb.WriteString("unless { ") + } else { + sb.WriteString("when { ") + } + sb.WriteString(clause) + sb.WriteString(" }") + return sb.String() +} + +func (mapper *CedarConditionMapper) mapFilterExpression(node hexaParser.Expression, isChild bool) string { + + switch element := node.(type) { + case hexaParser.NotExpression: + return mapper.mapFilterNot(&element, isChild) + case hexaParser.PrecedenceExpression: + return mapper.mapFilterPrecedence(&element, true) + + case hexaParser.LogicalExpression: + return mapper.mapFilterLogical(&element, isChild) + + default: + attrExpression := node.(hexaParser.AttributeExpression) + return mapper.mapFilterAttrExpr(&attrExpression) + } + +} + +func (mapper *CedarConditionMapper) mapFilterNot(notFilter *hexaParser.NotExpression, isChild bool) string { + subExpression := notFilter.Expression + var clause string + switch subFilter := subExpression.(type) { + case hexaParser.LogicalExpression: + // For the purpose of a not idqlCondition, the logical expression is not a child + clause = mapper.mapFilterLogical(&subFilter, false) + default: + clause = mapper.mapFilterExpression(subFilter, false) + } + + return fmt.Sprintf("!( %v )", clause) +} + +func (mapper *CedarConditionMapper) mapFilterPrecedence(pfilter *hexaParser.PrecedenceExpression, isChild bool) string { + subExpression := pfilter.Expression + var clause string + switch subFilter := subExpression.(type) { + case hexaParser.LogicalExpression: + // For the purpose of a not idqlCondition, the logical expression is not a child + clause = mapper.mapFilterLogical(&subFilter, false) + clause = "(" + clause + ")" + break + default: + clause = mapper.mapFilterExpression(subFilter, false) + } + return fmt.Sprintf("%v", clause) +} + +func (mapper *CedarConditionMapper) mapFilterLogical(logicFilter *hexaParser.LogicalExpression, isChild bool) string { + isDouble := false + var celLeft, celRight string + switch subFilter := logicFilter.Left.(type) { + case hexaParser.LogicalExpression: + if subFilter.Operator == logicFilter.Operator { + isDouble = true + } + } + + celLeft = mapper.mapFilterExpression(logicFilter.Left, !isDouble) + + celRight = mapper.mapFilterExpression(logicFilter.Right, !isDouble) + + switch logicFilter.Operator { + default: + return fmt.Sprintf("%v && %v", celLeft, celRight) + case hexaParser.OR: + if isChild { + // Add precedence to preserve order + return fmt.Sprintf("(%v || %v)", celLeft, celRight) + } else { + return fmt.Sprintf("%v || %v", celLeft, celRight) + } + } +} + +func (mapper *CedarConditionMapper) mapFilterAttrExpr(attrExpr *hexaParser.AttributeExpression) string { + compareValue := prepareValue(attrExpr) + + mapPath := mapper.NameMapper.GetProviderAttributeName(attrExpr.AttributePath) + + switch attrExpr.Operator { + + case hexaParser.NE: + return mapPath + " != " + compareValue + case hexaParser.LT: + return mapPath + " < " + compareValue + case hexaParser.LE: + return mapPath + " <= " + compareValue + case hexaParser.GT: + return mapPath + " > " + compareValue + case hexaParser.GE: + return mapPath + " >= " + compareValue + case hexaParser.SW: + return mapPath + ".startsWith(" + compareValue + ")" + case hexaParser.EW: + return mapPath + ".endsWith(" + compareValue + ")" + case hexaParser.PR: + lastIndex := strings.LastIndex(mapPath, ".") + left := mapPath[0:lastIndex] + right := mapPath[lastIndex+1:] + return fmt.Sprintf("%s has %s", left, right) + case hexaParser.CO: + return mapPath + ".contains(" + compareValue + ")" + case hexaParser.IN: + return mapPath + " in " + compareValue + default: + return mapPath + " == " + compareValue + } + +} + +/* +If the value type is string, it needs to be quoted. +*/ +func prepareValue(attrExpr *hexaParser.AttributeExpression) string { + compValue := attrExpr.CompareValue + if compValue == "" { + return "" + } + + // Check for integer + + if _, err := strconv.ParseInt(compValue, 10, 64); err == nil { + return compValue + } + + if _, err := strconv.ParseFloat(compValue, 64); err == nil { + return compValue + } + + if compValue == "true" || compValue == "false" { + return compValue + } + + // if it has a quote embedded leave it alone + if strings.Index(compValue, "\"") >= 0 { + return compValue + } + + // assume it is a string and return with quotes + return fmt.Sprintf("\"%s\"", attrExpr.CompareValue) + +} + +func checkCompatibility(e hexaParser.Expression) error { + var err error + switch v := e.(type) { + case hexaParser.LogicalExpression: + err = checkCompatibility(v.Left) + if err != nil { + return err + } + err = checkCompatibility(v.Right) + if err != nil { + return err + } + case hexaParser.NotExpression: + return checkCompatibility(v.Expression) + case hexaParser.PrecedenceExpression: + return checkCompatibility(v.Expression) + case hexaParser.ValuePathExpression: + return errors.New("IDQL ValuePath expression mapping to Google CEL currently not supported") + case hexaParser.AttributeExpression: + return nil + } + return nil +} diff --git a/models/conditionLangs/cedarConditions/map_test.go b/models/conditionLangs/cedarConditions/map_test.go new file mode 100644 index 0000000..d0525a4 --- /dev/null +++ b/models/conditionLangs/cedarConditions/map_test.go @@ -0,0 +1,262 @@ +package cedarConditions + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/cedar-policy/cedar-go/x/exp/parser" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions" +) + +func TestMapCedar(t *testing.T) { + tests := []struct { + name string + in string + out *conditions.ConditionInfo + err bool + }{ + {"Empty test", "", nil, false}, + {"When equal", "when { resource.owner == principal }", &conditions.ConditionInfo{ + Rule: "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\"", + 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\"", + Action: conditions.AAllow, + }, false}, + {"Nested", "when { resource.owner == principal.id && (principal.type == \"staff\" || principal.type == \"employee\") || resource.id == \"1234\" && principal.type == \"customer\" }", &conditions.ConditionInfo{ + Rule: "resource.owner eq principal.id and (principal.type eq \"staff\" or principal.type eq \"employee\") or resource.id eq \"1234\" and principal.type eq \"customer\"", + Action: conditions.AAllow, + }, false}, + {"Unless equal", "unless { resource.owner == principal }", &conditions.ConditionInfo{ + Rule: "resource.owner eq principal", + Action: conditions.ADeny, + }, false}, + {"Unless compound", "unless { resource.owner == principal.id && (principal.type == \"staff\" || principal.type == \"employee\") || resource.id == \"1234\" && principal.type == \"customer\" }", &conditions.ConditionInfo{ + Rule: "resource.owner eq principal.id and (principal.type eq \"staff\" or principal.type eq \"employee\") or resource.id eq \"1234\" and principal.type eq \"customer\"", + Action: conditions.ADeny, + }, false}, + {"Is test", "when { resource is Photo }", &conditions.ConditionInfo{ + Rule: "resource is Photo", + Action: conditions.AAllow, + }, false}, + {"In test", "when { resource in Album::\"alice_vacation\" }", &conditions.ConditionInfo{ + Rule: "resource in Album::\"alice_vacation\"", + Action: conditions.AAllow, + }, false}, + {"In set", "when { resource.id in [\"a\",\"b\"] }", &conditions.ConditionInfo{ + Rule: "resource.id in [\"a\", \"b\"]", + Action: conditions.AAllow, + }, false}, + {"Has Ident", "when { resource has ident }", &conditions.ConditionInfo{ + Rule: "resource.ident pr", + Action: conditions.AAllow, + }, false}, + {"Has Literal Ident", "when { resource has \"literal ident\" }", &conditions.ConditionInfo{ + Rule: "resource.\"literal ident\" pr", + Action: conditions.AAllow, + }, false}, + + {"Is In", "when { principal is User in Group::\"accounting\"}", &conditions.ConditionInfo{ + Rule: "principal is User and principal in Group::\"accounting\"", + Action: conditions.AAllow, + }, false}, + {"Multi-or", "when { principal.id > 4 || principal.type >= \"c\" || resource.id < 100 || resource.name <= \"m\" }", + &conditions.ConditionInfo{ + Rule: "principal.id gt 4 or principal.type ge \"c\" or resource.id lt 100 or resource.name le \"m\"", + Action: conditions.AAllow, + }, false}, + {"Multi Clause", ` +when { resource is Photo } +when { resource has ident } +unless { resource.owner == principal.id } +`, &conditions.ConditionInfo{ + Rule: "resource is Photo and resource.ident pr and not (resource.owner eq principal.id)", + Action: conditions.AAllow, + }, false}, + {"Operand test", ` +when { principal.id > 4 && principal.type >= "c" && resource.id < 100 && resource.name <= "m"} +when { resource.docid > 1000 || resource.docid <= 100 } +when { resource.owner != "somebody" } +`, &conditions.ConditionInfo{ + Rule: "principal.id gt 4 and principal.type ge \"c\" and resource.id lt 100 and resource.name le \"m\" and (resource.docid gt 1000 or resource.docid le 100) and resource.owner ne \"somebody\"", + Action: conditions.AAllow, + }, false}, + {"Contains", "when { principal.name.contains(\"smith\") }", + &conditions.ConditionInfo{ + Rule: "principal.name co \"smith\"", + Action: conditions.AAllow, + }, false}, + {"Record set addressing", "when { principal[\"name\"] == \"smith\" }", + &conditions.ConditionInfo{ + Rule: "principal.name eq \"smith\"", + Action: conditions.AAllow, + }, false}, + {"Record multi-set addressing", "when { principal[\"name\"][\"last\"] == \"smith\" }", + &conditions.ConditionInfo{ + Rule: "principal.name.last eq \"smith\"", + Action: conditions.AAllow, + }, false}, + {"Record mix-set addressing", "when { principal[\"name\"].last == \"smith\" }", + &conditions.ConditionInfo{ + Rule: "principal.name.last eq \"smith\"", + Action: conditions.AAllow, + }, false}, + {"Entity addressing", "when { principal == User::\"a1b2c3d4-e5f6-a1b2-c3d4-EXAMPLE11111\" }", + &conditions.ConditionInfo{ + 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\") }", + &conditions.ConditionInfo{ + Rule: "principal.id gt 4 or principal.id ge 100 or principal.access lt 4 or principal.name le \"m\"", + Action: conditions.AAllow, + }, false}, + + // negative tests + {"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) }", + &conditions.ConditionInfo{}, true}, + {"recinit-error", "when { {\"key\": \"some value\", id: \"another value\"} }", &conditions.ConditionInfo{}, true}, + {"ContainsAll-error", "when { principal.name.containsAll([\"a\",\"b\"]) }", + &conditions.ConditionInfo{ + Rule: "principal.name co \"smith\"", + Action: conditions.AAllow, + }, true}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + sb := strings.Builder{} + sb.WriteString("permit ( principal, action, resource )\n") + sb.WriteString(tt.in) + sb.WriteString(";") + policyInput := sb.String() + + tokens, err := parser.Tokenize([]byte(policyInput)) + testutilOK(t, err) + policies, err := parser.Parse(tokens) + testutilOK(t, err) + hexaCond, err := doMapCedar(policies) + testutilEquals(t, err != nil, tt.err) + if err == nil { + testutilEquals(t, hexaCond, tt.out) + } + + }) + } +} + +var mapper = CedarConditionMapper{NameMapper: conditions.NewNameMapper(map[string]string{})} + +func doMapHexa(hexaCondition string) (string, error) { + cond := &conditions.ConditionInfo{ + Rule: hexaCondition, + Action: conditions.AAllow, + } + + 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{ + {"ManyOrs", "((userName eq A) or (username eq \"B\")) or username eq C", "when { (userName == \"A\" || username == \"B\") || username == \"C\" }"}, + {"Pr test", "principal.title pr", "when { principal has title }"}, + {"MultiAnd", "principal.name pr and principal.username pr and principal.title pr", "when { principal has name }\nwhen { principal has username }\nwhen { principal has title }"}, + {"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\" }"}, + {"GT Test", "level gt 12", "when { level > 12 }"}, + {"Numeric", "level gt 12.3", "when { level > 12.3 }"}, + {"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\") }"}, + + {"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\") }"}, + // {"emails[type eq \"work\" and value co \"@example.com\"] or ims[type eq \"xmpp\" and value co \"@foo.com\"]"}, + {"NotExpression", "not(principal.name eq gerry)", "unless { principal.name == \"gerry\" }"}, + {"Or NotExpression", "principal.title pr or not(principal.name eq gerry)", "when { principal has title || !( principal.name == \"gerry\" ) }"}, + {"Or Not LogicalExpression", "principal.title pr or not(principal.name eq phil or principal.name eq gerry)", "when { principal has title || !( principal.name == \"phil\" || principal.name == \"gerry\" ) }"}, + {"And Not And", "principal.name pr and not (first eq \"test\") and another ne \"test\"", "when { principal has name }\nunless { first == \"test\" }\nwhen { another != \"test\" }"}, + {"ands with embedded or", "principal.name pr and first eq \"test\" and ( another ne \"test\" or def lt 123 or abc eq \"123\")", "when { principal has name }\nwhen { first == \"test\" }\nwhen { another != \"test\" || def < 123 || abc == 123 }"}, + + { + "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)) }", + }, + // {"emails[type eq work and value ew \"h[exa].org\"]", "emails[type eq \"work\" and value ew \"h[exa].org\"]"}, + } + + for _, example := range examples { + t.Run(example[0], func(t *testing.T) { + + fmt.Println(fmt.Sprintf("Input:\n%s", example[1])) + + result, err := doMapHexa(example[1]) + testutilOK(t, err) + fmt.Println(fmt.Sprintf("Result:\n%s", result)) + testUtilCederConditionEquals(t, result, example[2]) + }) + } +} + +func testUtilCederConditionEquals(t testing.TB, got string, want string) { + t.Helper() + if got != want { + t.Fatalf("\ngot \t%+v\nwant\t%+v", got, want) + } +} + +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) +} + +func testutilOK(t testing.TB, err error) { + t.Helper() + if err == nil { + return + } + 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/conditionLangs/gcpcel/gcp_condition_mapper_test.go b/models/conditionLangs/gcpcel/gcp_condition_mapper_test.go index b9a24ce..af58a56 100644 --- a/models/conditionLangs/gcpcel/gcp_condition_mapper_test.go +++ b/models/conditionLangs/gcpcel/gcp_condition_mapper_test.go @@ -1,185 +1,185 @@ package gcpcel_test import ( - "fmt" - "testing" + "fmt" + "testing" - "github.com/hexa-org/policy-mapper/models/conditionLangs/gcpcel" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions" - "github.com/stretchr/testify/assert" + "github.com/hexa-org/policy-mapper/models/conditionLangs/gcpcel" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions" + "github.com/stretchr/testify/assert" ) var mapper = gcpcel.GoogleConditionMapper{ - NameMapper: conditions.NewNameMapper(map[string]string{ - "a": "b", - "c": "d", - "username": "account.userid", - }), + NameMapper: conditions.NewNameMapper(map[string]string{ + "a": "b", + "c": "d", + "username": "account.userid", + }), } func TestParseFilter(t *testing.T) { - examples := [][2]string{ - { - "principal.numberOfLaptops lt 5 and principal.joblevel gt 6", - "principal.numberOfLaptops lt 5 and principal.joblevel gt 6", - }, - {"request.auth pr", "request.auth pr"}, - {"emails ew strata.io", "emails ew \"strata.io\""}, - {"username in crmUsers", "username in \"crmUsers\""}, - {"account.active eq true", "account.active eq true"}, - // Note: PR only works for compound attributes like account.userid in Google - {"username pr", "username pr"}, - - {"userName sw \"J\"", "username sw \"J\""}, - {"test.name sw \"J\"", "test.name sw \"J\""}, - { - "username eq \"june\" or username eq fred or username eq alice", - "username eq \"june\" or username eq \"fred\" or username eq \"alice\"", - }, - { - "username eq \"june\" and username eq fred and username eq alice", - "username eq \"june\" and username eq \"fred\" and username eq \"alice\"", - }, - { - "subject.common_name eq \"gcpbind.com\" and subject.country_code eq \"US\" or subject.country_code eq \"IR\"", - "subject.common_name eq \"gcpbind.com\" and subject.country_code eq \"US\" or subject.country_code eq \"IR\"", - }, { - "subject.common_name eq \"gcpbind.com\" and (subject.country_code eq \"US\" or subject.country_code eq \"IR\")", - "subject.common_name eq \"gcpbind.com\" and (subject.country_code eq \"US\" or subject.country_code eq \"IR\")", - }, - - // Following tests that parenthesis and logic preserved - { - "subject.common_name eq \"gcpbind.com\" and (subject.country_code eq \"US\" or subject.country_code eq \"IR\")", - "subject.common_name eq \"gcpbind.com\" and (subject.country_code eq \"US\" or subject.country_code eq \"IR\")", - }, { - "subject.common_name eq \"gcpbind.com\" and (subject.country_code eq \"US\" or not (subject.country_code eq \"IR\"))", - "subject.common_name eq \"gcpbind.com\" and (subject.country_code eq \"US\" or not(subject.country_code eq \"IR\"))", - }, { - "userName eq \"bjensen\"", "username eq \"bjensen\"", - }, { - "userName Eq \"bjensen\"", "username eq \"bjensen\"", - }, { // this should not correct case because name.familityName not in attribute name list - "name.familyName co \"O'Malley\"", "name.familyName co \"O'Malley\"", - }, - - // Following tests confirm > < >= <= - {"meta.lastModified gt \"2011-05-13T04:42:34Z\"", "meta.lastModified gt \"2011-05-13T04:42:34Z\""}, - {"meta.lastModified ge \"2011-05-13T04:42:34Z\"", "meta.lastModified ge \"2011-05-13T04:42:34Z\""}, - {"meta.lastModified lt \"2011-05-13T04:42:34Z\"", "meta.lastModified lt \"2011-05-13T04:42:34Z\""}, - {"meta.lastModified le \"2011-05-13T04:42:34Z\"", "meta.lastModified le \"2011-05-13T04:42:34Z\""}, - {"username pr and userType eq \"Employee\"", "username pr and userType eq \"Employee\""}, - {"username pr or userType eq \"Intern\"", "username pr or userType eq \"Intern\""}, - - { - "userType eq \"Employee\" and (emails co \"example.com\" or emails.value co \"example.org\")", - "userType eq \"Employee\" and (emails co \"example.com\" or emails.value co \"example.org\")", - }, - { - "userType eq \"Employee\" and (emails.type eq \"work\")", - "userType eq \"Employee\" and emails.type eq \"work\"", - }, - { - // Confirms proper handling of not brackets - "userType ne \"Employee\" and not (emails co \"example.com\" or emails.value co \"example.org\")", - "userType ne \"Employee\" and not(emails co \"example.com\" or emails.value co \"example.org\")", - }, - // "userType eq \"Employee\" and emails[type eq \"work\" and value co \"@example.com\"]", // ValueFilter not implemented - // "emails[type eq \"work\" and value co \"@example.com\"] or ims[type eq \"xmpp\" and value co \"@foo.com\"]", - - } - for _, example := range examples { - t.Run(example[0], func(t *testing.T) { - - condition := conditions.ConditionInfo{ - Rule: example[0], - Action: "allow", - } - fmt.Println("Test:\t" + example[0]) - celString, err := mapper.MapConditionToProvider(condition) - assert.NoError(t, err, "error mapping IDQL Condition to Google CEL") - fmt.Println("=>GCP:\t" + celString) - - conditionBack, err := mapper.MapProviderToCondition(celString) - assert.NoError(t, err, "error parsing/mapping CEL statement to IDQL Condition") - returnExample := conditionBack.Rule - fmt.Println("=>IDQL:\t" + returnExample) - - assert.Equal(t, example[1], returnExample, "Check expected result matches: &s", example[1]) - // Because of case-insensitive tests (Eq vs eq vs EQ) round trip may not match in mixed cases. - // assert.Equal(t, strings.ToLower(example), strings.ToLower(returnExample), "Round-trip map test failure") - // assert.Equal(t, example, conditionBack.Rule, "Round-trip map test failure") - - }) - } + examples := [][2]string{ + { + "principal.numberOfLaptops lt 5 and principal.joblevel gt 6", + "principal.numberOfLaptops lt 5 and principal.joblevel gt 6", + }, + {"request.auth pr", "request.auth pr"}, + {"emails ew strata.io", "emails ew strata.io"}, + {"username in crmUsers", "username in crmUsers"}, + {"account.active eq true", "account.active eq true"}, + // Note: PR only works for compound attributes like account.userid in Google + {"username pr", "username pr"}, + + {"userName sw \"J\"", "username sw J"}, + {"test.name sw \"J\"", "test.name sw J"}, + { + "username eq \"june\" or username eq fred or username eq alice", + "username eq june or username eq fred or username eq alice", + }, + { + "username eq \"june\" and username eq fred and username eq alice", + "username eq june and username eq fred and username eq alice", + }, + { + "subject.common_name eq \"gcpbind.com\" and subject.country_code eq \"US\" or subject.country_code eq \"IR\"", + "subject.common_name eq gcpbind.com and subject.country_code eq US or subject.country_code eq IR", + }, { + "subject.common_name eq \"gcpbind.com\" and (subject.country_code eq \"US\" or subject.country_code eq \"IR\")", + "subject.common_name eq gcpbind.com and (subject.country_code eq US or subject.country_code eq IR)", + }, + + // Following tests that parenthesis and logic preserved + { + "subject.common_name eq \"gcpbind.com\" and (subject.country_code eq \"US\" or subject.country_code eq \"IR\")", + "subject.common_name eq gcpbind.com and (subject.country_code eq US or subject.country_code eq IR)", + }, { + "subject.common_name eq \"gcpbind.com\" and (subject.country_code eq \"US\" or not (subject.country_code eq IR))", + "subject.common_name eq gcpbind.com and (subject.country_code eq US or not(subject.country_code eq IR))", + }, { + "userName eq \"bjensen\"", "username eq bjensen", + }, { + "userName Eq \"bjensen\"", "username eq bjensen", + }, { // this should not correct case because name.familityName not in attribute name list + "name.familyName co \"O'Malley\"", "name.familyName co O'Malley", + }, + + // Following tests confirm > < >= <= + {"meta.lastModified gt \"2011-05-13T04:42:34Z\"", "meta.lastModified gt 2011-05-13T04:42:34Z"}, + {"meta.lastModified ge \"2011-05-13T04:42:34Z\"", "meta.lastModified ge 2011-05-13T04:42:34Z"}, + {"meta.lastModified lt \"2011-05-13T04:42:34Z\"", "meta.lastModified lt 2011-05-13T04:42:34Z"}, + {"meta.lastModified le \"2011-05-13T04:42:34Z\"", "meta.lastModified le 2011-05-13T04:42:34Z"}, + {"username pr and userType eq \"Employee\"", "username pr and userType eq Employee"}, + {"username pr or userType eq \"Intern\"", "username pr or userType eq Intern"}, + + { + "userType eq \"Employee\" and (emails co \"example.com\" or emails.value co \"example.org\")", + "userType eq Employee and (emails co example.com or emails.value co example.org)", + }, + { + "userType eq \"Employee\" and (emails.type eq \"work\")", + "userType eq Employee and emails.type eq work", + }, + { + // Confirms proper handling of not brackets + "userType ne \"Employee\" and not (emails co \"example.com\" or emails.value co \"example.org\")", + "userType ne Employee and not(emails co example.com or emails.value co example.org)", + }, + // "userType eq \"Employee\" and emails[type eq \"work\" and value co \"@example.com\"]", // ValueFilter not implemented + // "emails[type eq \"work\" and value co \"@example.com\"] or ims[type eq \"xmpp\" and value co \"@foo.com\"]", + + } + for _, example := range examples { + t.Run(example[0], func(t *testing.T) { + + condition := conditions.ConditionInfo{ + Rule: example[0], + Action: "allow", + } + fmt.Println("Test:\t" + example[0]) + celString, err := mapper.MapConditionToProvider(condition) + assert.NoError(t, err, "error mapping IDQL Condition to Google CEL") + fmt.Println("=>GCP:\t" + celString) + + conditionBack, err := mapper.MapProviderToCondition(celString) + assert.NoError(t, err, "error parsing/mapping CEL statement to IDQL Condition") + returnExample := conditionBack.Rule + fmt.Println("=>IDQL:\t" + returnExample) + + assert.Equal(t, example[1], returnExample, "Check expected result matches: &s", example[1]) + // Because of case-insensitive tests (Eq vs eq vs EQ) round trip may not match in mixed cases. + // assert.Equal(t, strings.ToLower(example), strings.ToLower(returnExample), "Round-trip map test failure") + // assert.Equal(t, example, conditionBack.Rule, "Round-trip map test failure") + + }) + } } func TestNegToProvider(t *testing.T) { - condition := conditions.ConditionInfo{ - Rule: "bleh is bad", - } - celString, err := mapper.MapConditionToProvider(condition) - assert.Errorf(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: is") - assert.Equal(t, "", celString, "Should be empty string") - - valuePath := conditions.ConditionInfo{Rule: "emails[type eq \"work\" and value ew \"strata.io\""} - celString, err = mapper.MapConditionToProvider(valuePath) - assert.Errorf(t, err, "invalid IDQL idqlCondition: Missing close ']' bracket") - assert.Equal(t, "", celString, "Should be empty string") - - valuePath = conditions.ConditionInfo{Rule: "emails[type eq \"work\" and value ew \"strata.io\"]"} - celString, err = mapper.MapConditionToProvider(valuePath) - assert.Errorf(t, err, "IDQL ValuePath expression mapping to Google CEL currently not supported") - assert.Equal(t, "", celString, "Empty, value path not supported") - - valuePath = conditions.ConditionInfo{Rule: "level GT 5 and emails[type eq \"work\" and value ew \"strata.io\"]"} - celString, err = mapper.MapConditionToProvider(valuePath) - assert.Errorf(t, err, "IDQL ValuePath expression mapping to Google CEL currently not supported") - assert.Equal(t, "", celString, "Empty, value path not supported") - - valuePath = conditions.ConditionInfo{Rule: "emails[type eq \"work\" and value ew \"strata.io\"] and level gt 5"} - celString, err = mapper.MapConditionToProvider(valuePath) - assert.Errorf(t, err, "IDQL ValuePath expression mapping to Google CEL currently not supported") - assert.Equal(t, "", celString, "Empty, value path not supported") - - badCompare := conditions.ConditionInfo{Rule: "level GT 3 and abc GR 2"} - celString, err = mapper.MapConditionToProvider(badCompare) - assert.Errorf(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: GR") - assert.Equal(t, "", celString, "Should be empty string") + condition := conditions.ConditionInfo{ + Rule: "bleh is bad", + } + celString, err := mapper.MapConditionToProvider(condition) + assert.Errorf(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: is") + assert.Equal(t, "", celString, "Should be empty string") + + valuePath := conditions.ConditionInfo{Rule: "emails[type eq \"work\" and value ew \"strata.io\""} + celString, err = mapper.MapConditionToProvider(valuePath) + assert.Errorf(t, err, "invalid IDQL idqlCondition: Missing close ']' bracket") + assert.Equal(t, "", celString, "Should be empty string") + + valuePath = conditions.ConditionInfo{Rule: "emails[type eq \"work\" and value ew \"strata.io\"]"} + celString, err = mapper.MapConditionToProvider(valuePath) + assert.Errorf(t, err, "IDQL ValuePath expression mapping to Google CEL currently not supported") + assert.Equal(t, "", celString, "Empty, value path not supported") + + valuePath = conditions.ConditionInfo{Rule: "level GT 5 and emails[type eq \"work\" and value ew \"strata.io\"]"} + celString, err = mapper.MapConditionToProvider(valuePath) + assert.Errorf(t, err, "IDQL ValuePath expression mapping to Google CEL currently not supported") + assert.Equal(t, "", celString, "Empty, value path not supported") + + valuePath = conditions.ConditionInfo{Rule: "emails[type eq \"work\" and value ew \"strata.io\"] and level gt 5"} + celString, err = mapper.MapConditionToProvider(valuePath) + assert.Errorf(t, err, "IDQL ValuePath expression mapping to Google CEL currently not supported") + assert.Equal(t, "", celString, "Empty, value path not supported") + + badCompare := conditions.ConditionInfo{Rule: "level GT 3 and abc GR 2"} + celString, err = mapper.MapConditionToProvider(badCompare) + assert.Errorf(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: GR") + assert.Equal(t, "", celString, "Should be empty string") } func TestNegToIdql(t *testing.T) { - celString := "document.summary.size() < 100" - cond, err := mapper.MapProviderToCondition(celString) - assert.Errorf(t, err, "IDQL condition mapTool error: unimplemented CEL function: size") + celString := "document.summary.size() < 100" + cond, err := mapper.MapProviderToCondition(celString) + assert.Errorf(t, err, "IDQL condition mapTool error: unimplemented CEL function: size") - assert.Equal(t, "", cond.Rule, "Condition should be empty") + assert.Equal(t, "", cond.Rule, "Condition should be empty") - // THis should invoke the error within a logic idqlCondition - celString = "(level > 3 || not(document.summary.size() < 100)) && level < 10" - cond, err = mapper.MapProviderToCondition(celString) - assert.Errorf(t, err, "IDQL condition mapTool error: unimplemented CEL function: size") + // THis should invoke the error within a logic idqlCondition + celString = "(level > 3 || not(document.summary.size() < 100)) && level < 10" + cond, err = mapper.MapProviderToCondition(celString) + assert.Errorf(t, err, "IDQL condition mapTool error: unimplemented CEL function: size") - assert.Equal(t, "", cond.Rule, "Condition should be empty") + assert.Equal(t, "", cond.Rule, "Condition should be empty") - celString = "(level > 3 or not(document.summary.size() < 100)) && level < 10" - cond, err = mapper.MapProviderToCondition(celString) - assert.Errorf(t, err, "CEL Mapping Error: ERROR: :1:12: Syntax error: mismatched input 'or' expecting ')'\n | (level ") + celString = "(level > 3 or not(document.summary.size() < 100)) && level < 10" + cond, err = mapper.MapProviderToCondition(celString) + assert.Errorf(t, err, "CEL Mapping Error: ERROR: :1:12: Syntax error: mismatched input 'or' expecting ')'\n | (level ") - assert.Equal(t, "", cond.Rule, "Condition should be empty") + assert.Equal(t, "", cond.Rule, "Condition should be empty") - // This tests for ? operator - celString = "level > 3 ? document.path.startsWith(\"/abc\") : level < 10" - cond, err = mapper.MapProviderToCondition(celString) - assert.Errorf(t, err, "IDQL condition mapTool error: unimplemented CEL expression operand: _?_:_") + // This tests for ? operator + celString = "level > 3 ? document.path.startsWith(\"/abc\") : level < 10" + cond, err = mapper.MapProviderToCondition(celString) + assert.Errorf(t, err, "IDQL condition mapTool error: unimplemented CEL expression operand: _?_:_") - assert.Equal(t, "", cond.Rule, "Condition should be empty") + assert.Equal(t, "", cond.Rule, "Condition should be empty") - celString = "emails.exists(emails,type == \"work\" && value.endsWith(\"strata.io\"))" - cond, err = mapper.MapProviderToCondition(celString) - assert.Contains(t, err.Error(), "unimplemented CEL expression:") - assert.Equal(t, "", cond.Rule, "Empty rule returned") + celString = "emails.exists(emails,type == \"work\" && value.endsWith(\"strata.io\"))" + cond, err = mapper.MapProviderToCondition(celString) + assert.Contains(t, err.Error(), "unimplemented CEL expression:") + assert.Equal(t, "", cond.Rule, "Empty rule returned") } diff --git a/models/formats/awsCedar/amazon_cedar_test.go b/models/formats/awsCedar/amazon_cedar_test.go index 70533ac..a879be4 100644 --- a/models/formats/awsCedar/amazon_cedar_test.go +++ b/models/formats/awsCedar/amazon_cedar_test.go @@ -1,196 +1,196 @@ package awsCedar_test import ( - "fmt" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/hexa-org/policy-mapper/models/formats/awsCedar" - "github.com/hexa-org/policy-mapper/pkg/hexapolicysupport" - "github.com/stretchr/testify/assert" + "fmt" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/hexa-org/policy-mapper/models/formats/awsCedar" + "github.com/hexa-org/policy-mapper/pkg/hexapolicysupport" + "github.com/stretchr/testify/assert" ) var cedarMapper = awsCedar.New(map[string]string{}) func getTestFile(name string) string { - _, file, _, _ := runtime.Caller(0) - return filepath.Join(file, name) + _, file, _, _ := runtime.Caller(0) + return filepath.Join(file, name) } func TestProduceAndParseCedar(t *testing.T) { - var err error - policies, err := hexapolicysupport.ParsePolicyFile(getTestFile("../test/data.json")) - assert.NoError(t, err, "File %s not parsed", getTestFile("../test/data.json")) + var err error + policies, err := hexapolicysupport.ParsePolicyFile(getTestFile("../test/data.json")) + assert.NoError(t, err, "File %s not parsed", getTestFile("../test/data.json")) - cedarPols, err := cedarMapper.MapPoliciesToCedar(policies) + cedarPols, err := cedarMapper.MapPoliciesToCedar(policies) - assert.Equal(t, 7, len(cedarPols.Policies), "Should be 5 policies generated") + assert.Equal(t, 7, len(cedarPols.Policies), "Should be 5 policies generated") - fmt.Printf("%v Cedar Policies Returned\n", len(cedarPols.Policies)) - for k, v := range cedarPols.Policies { - fmt.Printf("Policy# %v\n", k) - polString := v.String() - fmt.Println(polString) - } + fmt.Printf("%v Cedar Policies Returned\n", len(cedarPols.Policies)) + for k, v := range cedarPols.Policies { + fmt.Printf("Policy# %v\n", k) + polString := v.String() + fmt.Println(polString) + } - res := cedarPols.Policies[0].Head.Resource + res := cedarPols.Policies[0].Head.Resource - assert.Equal(t, "==", cedarPols.Policies[0].Head.Actions.Operator, "Should be ==") - assert.Equal(t, "Action::\"view\"", cedarPols.Policies[0].Head.Actions.Action, "Should be Action::\"view\"") - assert.Equal(t, 1, len(cedarPols.Policies[0].Conditions), "Should be 1 condition") - assert.Nil(t, res, "Resource should be nil") + assert.Equal(t, "==", cedarPols.Policies[0].Head.Actions.Operator, "Should be ==") + assert.Equal(t, "Action::\"view\"", cedarPols.Policies[0].Head.Actions.Action, "Should be Action::\"view\"") + assert.Equal(t, 1, len(cedarPols.Policies[0].Conditions), "Should be 1 condition") + assert.Nil(t, res, "Resource should be nil") } func TestParserSingle(t *testing.T) { - file := getTestFile("../test/cedarSingle.txt") - cedarBytes, err := os.ReadFile(file) - if err != nil { - assert.Fail(t, "Error opening cedar test file: "+err.Error()) - } + file := getTestFile("../test/cedarSingle.txt") + cedarBytes, err := os.ReadFile(file) + if err != nil { + assert.Fail(t, "Error opening cedar test file: "+err.Error()) + } - cedarAst, err := cedarMapper.ParseCedarBytes(cedarBytes) - if err != nil { - fmt.Println(err.Error()) - } - assert.NoError(t, err) + cedarAst, err := cedarMapper.ParseCedarBytes(cedarBytes) + if err != nil { + fmt.Println(err.Error()) + } + assert.NoError(t, err) - fmt.Printf("Polcies returned: %v\n", len(cedarAst.Policies)) + fmt.Printf("Polcies returned: %v\n", len(cedarAst.Policies)) - fmt.Printf("%v Cedar Policies Returned\n", len(cedarAst.Policies)) - for k, v := range cedarAst.Policies { - fmt.Printf("Policy# %v\n", k) - polString := v.String() - fmt.Println(polString) - } + fmt.Printf("%v Cedar Policies Returned\n", len(cedarAst.Policies)) + for k, v := range cedarAst.Policies { + fmt.Printf("Policy# %v\n", k) + polString := v.String() + fmt.Println(polString) + } - assert.Equal(t, 2, len(cedarAst.Policies[0].Head.Actions.Actions), "Should be two actions") + assert.Equal(t, 2, len(cedarAst.Policies[0].Head.Actions.Actions), "Should be two actions") } func TestParserTemplate(t *testing.T) { - file := getTestFile("../test/cedarTemplate.txt") - cedarBytes, err := os.ReadFile(file) - if err != nil { - assert.Fail(t, "Error opening cedar test file: "+err.Error()) - } - - cedarAst, err := cedarMapper.ParseCedarBytes(cedarBytes) - if err != nil { - fmt.Println(err.Error()) - } - assert.NoError(t, err) - - fmt.Printf("Polcies returned: %v\n", len(cedarAst.Policies)) - - fmt.Printf("%v Cedar Policies Returned\n", len(cedarAst.Policies)) - for k, v := range cedarAst.Policies { - fmt.Printf("Policy# %v\n", k) - polString := v.String() - fmt.Println(polString) - } - - assert.Equal(t, 1, len(cedarAst.Policies[0].Head.Actions.Actions), "Should be one action") + file := getTestFile("../test/cedarTemplate.txt") + cedarBytes, err := os.ReadFile(file) + if err != nil { + assert.Fail(t, "Error opening cedar test file: "+err.Error()) + } + + cedarAst, err := cedarMapper.ParseCedarBytes(cedarBytes) + if err != nil { + fmt.Println(err.Error()) + } + assert.NoError(t, err) + + fmt.Printf("Polcies returned: %v\n", len(cedarAst.Policies)) + + fmt.Printf("%v Cedar Policies Returned\n", len(cedarAst.Policies)) + for k, v := range cedarAst.Policies { + fmt.Printf("Policy# %v\n", k) + polString := v.String() + fmt.Println(polString) + } + + assert.Equal(t, 1, len(cedarAst.Policies[0].Head.Actions.Actions), "Should be one action") } func TestParserWhenTrue(t *testing.T) { - file := getTestFile("../test/cedarWhenTrue.txt") - cedarBytes, err := os.ReadFile(file) - if err != nil { - assert.Fail(t, "Error opening cedar test file: "+err.Error()) - } - - cedarAst, err := cedarMapper.ParseCedarBytes(cedarBytes) - if err != nil { - fmt.Println(err.Error()) - } - assert.NoError(t, err) - - fmt.Printf("Polcies returned: %v\n", len(cedarAst.Policies)) - - fmt.Printf("%v Cedar Policies Returned\n", len(cedarAst.Policies)) - for k, v := range cedarAst.Policies { - fmt.Printf("Policy# %v\n", k) - polString := v.String() - fmt.Println(polString) - } - - hexaPols, err := cedarMapper.MapCedarPoliciesToIdql(cedarAst) - assert.NoError(t, err, "Should be no error mapping to idql") - assert.Len(t, hexaPols.Policies, 1, "Expect 1 policy") - assert.Equal(t, 4, len(cedarAst.Policies[0].Head.Actions.Actions), "Should be one action") + file := getTestFile("../test/cedarWhenTrue.txt") + cedarBytes, err := os.ReadFile(file) + if err != nil { + assert.Fail(t, "Error opening cedar test file: "+err.Error()) + } + + cedarAst, err := cedarMapper.ParseCedarBytes(cedarBytes) + if err != nil { + fmt.Println(err.Error()) + } + assert.NoError(t, err) + + fmt.Printf("Polcies returned: %v\n", len(cedarAst.Policies)) + + fmt.Printf("%v Cedar Policies Returned\n", len(cedarAst.Policies)) + for k, v := range cedarAst.Policies { + fmt.Printf("Policy# %v\n", k) + polString := v.String() + fmt.Println(polString) + } + + hexaPols, err := cedarMapper.MapCedarPoliciesToIdql(cedarAst) + assert.NoError(t, err, "Should be no error mapping to idql") + assert.Len(t, hexaPols.Policies, 1, "Expect 1 policy") + assert.Equal(t, 4, len(cedarAst.Policies[0].Head.Actions.Actions), "Should be one action") } func TestParserMulti(t *testing.T) { - file := getTestFile("../test/cedarMulti.txt") - cedarBytes, err := os.ReadFile(file) - if err != nil { - assert.Fail(t, "Error opening cedar test file: "+err.Error()) - } - - cedarAst, err := cedarMapper.ParseCedarBytes(cedarBytes) - if err != nil { - fmt.Println(err.Error()) - } - assert.NoError(t, err) - - fmt.Printf("Polcies returned: %v\n", len(cedarAst.Policies)) - - fmt.Printf("%v Cedar Policies Returned\n", len(cedarAst.Policies)) - for k, v := range cedarAst.Policies { - fmt.Printf("Policy# %v\n", k) - polString := v.String() - fmt.Println(polString) - } - assert.Equal(t, 4, len(cedarAst.Policies), "Should be 4 policies parsed") - assert.Equal(t, 2, len(cedarAst.Policies[0].Head.Actions.Actions), "Should be two actions") - - condString := cedarAst.Policies[3].Conditions[0].String() - assert.Contains(t, condString, " true ", "Check boolean not quoted") - assert.Contains(t, condString, " < ", "Check less than present") + file := getTestFile("../test/cedarMulti.txt") + cedarBytes, err := os.ReadFile(file) + if err != nil { + assert.Fail(t, "Error opening cedar test file: "+err.Error()) + } + + cedarAst, err := cedarMapper.ParseCedarBytes(cedarBytes) + if err != nil { + fmt.Println(err.Error()) + } + assert.NoError(t, err) + + fmt.Printf("Polcies returned: %v\n", len(cedarAst.Policies)) + + fmt.Printf("%v Cedar Policies Returned\n", len(cedarAst.Policies)) + for k, v := range cedarAst.Policies { + fmt.Printf("Policy# %v\n", k) + polString := v.String() + fmt.Println(polString) + } + assert.Equal(t, 4, len(cedarAst.Policies), "Should be 4 policies parsed") + assert.Equal(t, 2, len(cedarAst.Policies[0].Head.Actions.Actions), "Should be two actions") + + condString := cedarAst.Policies[3].Conditions[0].String() + assert.Contains(t, condString, " true ", "Check boolean not quoted") + assert.Contains(t, condString, " < ", "Check less than present") } func TestParserToHexa(t *testing.T) { - file := getTestFile("../test/cedarMulti.txt") + file := getTestFile("../test/cedarMulti.txt") - idql, err := cedarMapper.ParseFile(file) - if err != nil { - assert.NoError(t, err, "error parsing and mapping of cedar bytes") + idql, err := cedarMapper.ParseFile(file) + if err != nil { + assert.NoError(t, err, "error parsing and mapping of cedar bytes") - } + } - condString := idql.Policies[3].Condition.Rule - assert.Contains(t, condString, " true", "Check boolean not quoted") - assert.Contains(t, condString, " lt ", "Check less than present") + condString := idql.Policies[3].Condition.Rule + assert.Contains(t, condString, " true", "Check boolean not quoted") + assert.Contains(t, condString, " lt ", "Check less than present") } func TestGcpMapped(t *testing.T) { - file := getTestFile("../test/testGcpIdql.json") - policies, err := hexapolicysupport.ParsePolicyFile(file) - assert.NoError(t, err) + file := getTestFile("../test/testGcpIdql.json") + policies, err := hexapolicysupport.ParsePolicyFile(file) + assert.NoError(t, err) - cedarPols, err := cedarMapper.MapPoliciesToCedar(policies) - assert.NoError(t, err) - assert.Equal(t, 7, len(cedarPols.Policies)) + cedarPols, err := cedarMapper.MapPoliciesToCedar(policies) + assert.NoError(t, err) + assert.Equal(t, 7, len(cedarPols.Policies)) } func TestMultiCond(t *testing.T) { - file := getTestFile("../test/cedarMultiCond.txt") - idql, err := cedarMapper.ParseFile(file) - if err != nil { - assert.NoError(t, err, "error parsing and mapping of cedar bytes") + file := getTestFile("../test/cedarMultiCond.txt") + idql, err := cedarMapper.ParseFile(file) + if err != nil { + assert.NoError(t, err, "error parsing and mapping of cedar bytes") - } + } - condString := idql.Policies[0].Condition.Rule + condString := idql.Policies[0].Condition.Rule - assert.Equal(t, "(not(resource.tag eq \"private\")) && (resource.type eq \"file\")", condString) - fmt.Println(condString) + assert.Equal(t, "(not(resource.tag eq private)) && (resource.type eq file)", condString) + fmt.Println(condString) } diff --git a/models/formats/cedar/cedar_test.go b/models/formats/cedar/cedar_test.go new file mode 100644 index 0000000..82b4238 --- /dev/null +++ b/models/formats/cedar/cedar_test.go @@ -0,0 +1,252 @@ +package cedar + +import ( + "fmt" + "reflect" + "testing" +) + +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) { + 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": { + "sourceData": { + "annotations": { + "comment": "this is an annotation", + "description": "Makes everything permitted" + } + } + }, + "subject": { + "members": [ + "any" + ] + }, + "actions": null, + "object": { + "resource_id": "" + } +}`, + err: false, + }, + {"AlicePhoto", `permit ( + principal == User::"alice", + action == Action::"viewPhoto", + resource == Photo::"VacationPhoto.jpg" +);`, `{ + "meta": {}, + "subject": { + "members": [ + "User:alice" + ] + }, + "actions": [ + { + "actionUri": "viewPhoto" + } + ], + "object": { + "resource_id": "Photo::\"VacationPhoto.jpg\"" + } +}`, false}, + {"Multi-Action", `permit ( + principal is User in Group::"AVTeam", + action in [PhotoOp::"view", PhotoOp::"edit", PhotoOp::"delete"], + resource == Photo::"VacationPhoto.jpg" +);`, `{ + "meta": {}, + "subject": { + "members": [ + "Group:\"AVTeam\".(User)" + ] + }, + "actions": [ + { + "actionUri": "PhotoOp::\"view\"" + }, + { + "actionUri": "PhotoOp::\"edit\"" + }, + { + "actionUri": "PhotoOp::\"delete\"" + } + ], + "object": { + "resource_id": "Photo::\"VacationPhoto.jpg\"" + } +}`, false}, + {"Conditions", `permit ( + principal in UserGroup::"AVTeam", + action == Action::"viewPhoto", + resource is Photo +) +when { resource in PhotoApp::Account::"stacey" } +unless { principal has parents };`, + `{ + "meta": {}, + "subject": { + "members": [ + "Group:UserGroup::\"AVTeam\"" + ] + }, + "actions": [ + { + "actionUri": "viewPhoto" + } + ], + "object": { + "resource_id": "Type:Photo" + }, + "Condition": { + "Rule": "resource in PhotoApp::Account::\"stacey\" and not (principal.parents pr)", + "Action": "allow" + } +}`, false}, + {"action equals", `permit ( + principal is User, + action == Action::"viewPhoto", + resource +) +when { resource in PhotoShop::"Photo" };`, `{ + "meta": {}, + "subject": { + "members": [ + "Type:User" + ] + }, + "actions": [ + { + "actionUri": "viewPhoto" + } + ], + "object": { + "resource_id": "" + }, + "Condition": { + "Rule": "resource in PhotoShop::\"Photo\"", + "Action": "allow" + } +}`, false}, + } + + 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)) + testutilEquals(t, tt.err, err != nil) + idqlOut := result.Policies[0].String() + fmt.Println("Mapped:\n" + idqlOut) + testutilEquals(t, idqlOut, tt.idql) + + }) + } + +} + +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 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) +} + +func testutilOK(t testing.TB, err error) { + t.Helper() + if err == nil { + return + } + 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/cedar/entity.go b/models/formats/cedar/entity.go new file mode 100644 index 0000000..4df470b --- /dev/null +++ b/models/formats/cedar/entity.go @@ -0,0 +1,210 @@ +package cedar + +// This is from https://github.com/iann0036/polai/entitystore.go +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "reflect" + + "golang.org/x/exp/maps" +) + +type rawEntity struct { + Uid string `json:"uid"` + LowerParents []string `json:"parents"` + Attrs map[string]interface{} `json:"attrs"` + EntityId *complexEntityName `json:"EntityId"` + Identifier *complexEntityName `json:"Identifier"` + Parents []complexEntityName `json:"Parents"` + Attributes map[string]complexAttribute `json:"Attributes"` +} + +type complexEntityName struct { + EntityID string `json:"EntityId"` + EntityType string `json:"EntityType"` +} + +type complexAttribute struct { + String *string `json:"String"` + Long *int64 `json:"Long"` + Boolean *bool `json:"Boolean"` + Record *map[string]interface{} `json:"Record"` + Set *[]interface{} `json:"Set"` +} + +type Entity struct { + Identifier string + Parents []string + Attributes []Attribute +} + +type Attribute struct { + Name string + StringValue *string + LongValue *int64 + BooleanValue *bool + RecordValue *map[string]interface{} + SetValue *[]interface{} +} + +// EntityStore represents the complete set of known entities within the system. +type EntityStore struct { + r *bufio.Reader + entities *[]Entity +} + +// NewEntityStore returns a new instance of EntityStore. +func NewEntityStore(r io.Reader) *EntityStore { + return &EntityStore{r: bufio.NewReader(r)} +} + +// SetEntities overrides all entities. +func (e *EntityStore) SetEntities(r io.Reader) { + e.r = bufio.NewReader(r) + e.entities = nil +} + +// GetEntities retrieves all entities. +func (e *EntityStore) GetEntities() ([]Entity, error) { + if e.entities == nil { + b, err := ioutil.ReadAll(e.r) + if err != nil && err != io.EOF { + return nil, err + } + + var rawEntities []rawEntity + if err := json.Unmarshal(b, &rawEntities); err != nil { + return nil, fmt.Errorf("error parsing entity store json, %s", err.Error()) + } + + var entities []Entity + for _, rawEntity := range rawEntities { + if rawEntity.EntityId != nil { + rawEntity.Identifier = rawEntity.EntityId + } + + if rawEntity.Uid != "" { + var attributes []Attribute + for attrName, attrVal := range rawEntity.Attrs { + attribute := Attribute{ + Name: attrName, + } + + switch attrVal.(type) { + case int: + val := int64(attrVal.(int)) + attribute.LongValue = &val + case int64: + val := attrVal.(int64) + attribute.LongValue = &val + case float64: + val := int64(attrVal.(float64)) + attribute.LongValue = &val + case string: + val := attrVal.(string) + attribute.StringValue = &val + case bool: + val := attrVal.(bool) + attribute.BooleanValue = &val + case map[string]interface{}: + val := attrVal.(map[string]interface{}) + attribute.RecordValue = &val + case []interface{}: + val := attrVal.([]interface{}) + attribute.SetValue = &val + default: + return nil, fmt.Errorf("unknown type in attribute block: %v (%s)", attrVal, reflect.TypeOf(attrVal).String()) + } + + attributes = append(attributes, attribute) + } + + entities = append(entities, Entity{ + Identifier: rawEntity.Uid, + Parents: rawEntity.LowerParents, + Attributes: attributes, + }) + } else if rawEntity.Identifier != nil { + b, _ := json.Marshal(rawEntity.Identifier.EntityID) + entity := Entity{ + Identifier: fmt.Sprintf("%s::%s", rawEntity.Identifier.EntityType, string(b)), + } + + for _, parent := range rawEntity.Parents { + b, _ := json.Marshal(parent.EntityID) + entity.Parents = append(entity.Parents, fmt.Sprintf("%s::%s", parent.EntityType, string(b))) + } + + for attrName, attrVal := range rawEntity.Attributes { + // TODO: validate only one field set + entity.Attributes = append(entity.Attributes, Attribute{ + Name: attrName, + BooleanValue: attrVal.Boolean, + StringValue: attrVal.String, + LongValue: attrVal.Long, + RecordValue: attrVal.Record, + SetValue: attrVal.Set, + }) + } + + entities = append(entities, entity) + } else { + return nil, fmt.Errorf("no entity identifier found in entity list item") + } + } + + e.entities = &entities + } + + return *e.entities, nil +} + +// GetEntityDescendents retrieves all entities that match or are descendents of those passed in. +func (e *EntityStore) GetEntityDescendents(parents []string) ([]Entity, error) { + baseEntities, err := e.GetEntities() + if err != nil { + return nil, err + } + + foundEntities := map[string]Entity{} // using map[string] for dedup purposes + i := 0 + for i < len(parents) { + parent := parents[i] + for _, baseEntity := range baseEntities { + for _, baseEntityParent := range baseEntity.Parents { + if baseEntityParent == parent && !contains(parents, baseEntity.Identifier) { + parents = append(parents, baseEntity.Identifier) + } + } + if baseEntity.Identifier == parent { + foundEntities[baseEntity.Identifier] = baseEntity + } + } + i++ + } + + return maps.Values(foundEntities), nil +} + +func contains(s []string, str string) bool { + for _, v := range s { + if v == str { + return true + } + } + + return false +} + +func containsEntity(list []Entity, id string) bool { + for _, v := range list { + if v.Identifier == id { + return true + } + } + + return false +} diff --git a/models/formats/cedar/parse.go b/models/formats/cedar/parse.go new file mode 100644 index 0000000..6b911a4 --- /dev/null +++ b/models/formats/cedar/parse.go @@ -0,0 +1,365 @@ +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{} + 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 = hexapolicy.SubjectInfo{Members: []string{hexapolicy.SAnyUser}} + 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 = hexapolicy.SubjectInfo{Members: []string{member}} + case cedarParser.MatchIs: + // This is "principal is User" + // subj = hexapolicy.SubjectInfo{Members: []string{hexapolicy.SAnyAuth}} + subj = hexapolicy.SubjectInfo{Members: []string{fmt.Sprintf("Type:%s", principal.Path.String())}} + case cedarParser.MatchIn: + subj = hexapolicy.SubjectInfo{Members: []string{fmt.Sprintf("Group:%s", principal.Entity.String())}} + case cedarParser.MatchIsIn: + isType := principal.Path.String() + inEntity := strings.Replace(principal.Entity.String(), "::", ":", 1) + subj = hexapolicy.SubjectInfo{Members: []string{fmt.Sprintf("%s.(%s)", inEntity, isType)}} + } + + pp.HexaPolicy.Subject = subj +} + +func (pp *PolicyPair) HasMultiSubjects() bool { + if pp.HexaPolicy != nil && pp.HexaPolicy.Subject.Members != nil { + return len(pp.HexaPolicy.Subject.Members) > 0 + } + return false +} + +func (pp *PolicyPair) mapHexaSubjects() []string { + if pp.HexaPolicy == nil || pp.HexaPolicy.Subject.Members != nil { + return nil + } + members := pp.HexaPolicy.Subject.Members + if len(members) == 0 || strings.EqualFold(members[0], hexapolicy.SAnyUser) { + 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{ActionUri: mapCedarEntityName(action.Entities[0])}) + case cedarParser.MatchIn: + aInfo = append(aInfo, hexapolicy.ActionInfo{ActionUri: "Role:" + mapCedarEntityName(action.Entities[0])}) + case cedarParser.MatchInList: + for _, entity := range action.Entities { + aInfo = append(aInfo, hexapolicy.ActionInfo{ActionUri: mapCedarEntityName(entity)}) + } + + } + + 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 := actions[0].ActionUri + 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(e.ActionUri) + } + 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{ResourceID: resource.Entity.String()} + case cedarParser.MatchAny: + pp.HexaPolicy.Object = hexapolicy.ObjectInfo{} + case cedarParser.MatchIs: + pp.HexaPolicy.Object = hexapolicy.ObjectInfo{ResourceID: fmt.Sprintf("Type:%s", resource.Path.String())} + case cedarParser.MatchIsIn: + pp.HexaPolicy.Object = hexapolicy.ObjectInfo{ResourceID: fmt.Sprintf("[%s].(%s)", resource.Entity.String(), resource.Path.String())} + case cedarParser.MatchIn: + pp.HexaPolicy.Object = hexapolicy.ObjectInfo{ResourceID: fmt.Sprintf("[%s]", resource.Entity.String())} + } +} + +func (pp *PolicyPair) mapHexaResource() string { + resource := pp.HexaPolicy.Object.ResourceID + 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/models/formats/cedar/test/cedarPhotoPolicy.txt b/models/formats/cedar/test/cedarPhotoPolicy.txt new file mode 100644 index 0000000..20620ee --- /dev/null +++ b/models/formats/cedar/test/cedarPhotoPolicy.txt @@ -0,0 +1,12 @@ +permit ( + principal == PhotoApp::User::"alice", + action == PhotoApp::Action::"viewPhoto", + resource == PhotoApp::Photo::"vacationPhoto.jpg" +); + +permit ( + principal == PhotoApp::User::"stacey", + action == PhotoApp::Action::"viewPhoto", + resource +) +when { resource in PhotoApp::Account::"stacey" }; \ No newline at end of file diff --git a/models/formats/cedar/test/healthEntities.json b/models/formats/cedar/test/healthEntities.json new file mode 100644 index 0000000..be3b14d --- /dev/null +++ b/models/formats/cedar/test/healthEntities.json @@ -0,0 +1,67 @@ +[ + { + "uid": { + "type": "HealthCareApp::User", + "id": "Victor" + }, + "attrs": {}, + "parents": [ + { + "type": "HealthCareApp::Role", + "id": "admin" + } + ] + }, + {"a": "b"}, + { + "uid": { + "type": "Unknown::User", + "id": "Gerry" + }, + "attrs": { + "first_name": "Gerry" + } + }, + { + "uid": { + "type": "HealthCareApp::Info", + "id": "apointment003" + }, + "attrs": { + "provider": { + "__entity": { + "type": "HealthCareApp::User", + "id": "DrSeuz" + } + }, + "patient": { + "__entity": { + "type": "HealthCareApp::User", + "id": "Victor" + } + } + }, + "parents": [ + { + "type": "HealthCareApp::InfoType", + "id": "appointment" + } + ] + }, + { + "uid": { + "type": "HealthCareApp::InfoType", + "id": "appointment" + }, + "attrs": {}, + "parents": [] + }, + { + "uid": { + "type": "HealthCareApp::Role", + "id": "admin" + }, + "attrs": {}, + "parents": [] + } +] \ No newline at end of file diff --git a/models/formats/cedar/test/healthSchema.json b/models/formats/cedar/test/healthSchema.json new file mode 100644 index 0000000..fabc4f0 --- /dev/null +++ b/models/formats/cedar/test/healthSchema.json @@ -0,0 +1,110 @@ +{ + "HealthCareApp": { + "entityTypes": { + "User": { + "shape": { + "type": "Record", + "attributes": {} + }, + "memberOfTypes": [ + "Role" + ] + }, + "Role": { + "shape": { + "type": "Record", + "attributes": {} + }, + "memberOfTypes": [] + }, + "Info": { + "shape": { + "type": "Record", + "attributes": { + "patient": { + "type": "Entity", + "name": "User" + }, + "subject": { + "type": "Entity", + "name": "User" + } + } + }, + "memberOfTypes": [ + "InfoType" + ] + }, + "InfoType": { + "shape": { + "type": "Record", + "attributes": {} + }, + "memberOfTypes": [] + } + }, + "actions": { + "createAppointment": { + "appliesTo": { + "principalTypes": [ + "User" + ], + "resourceTypes": [ + "Info" + ], + "context": { + "type": "Record", + "attributes": { + "referrer": { + "type": "Entity", + "name": "User" + } + } + } + } + }, + "updateAppointment": { + "appliesTo": { + "principalTypes": [ + "User" + ], + "resourceTypes": [ + "Info" + ], + "context": { + "type": "Record", + "attributes": {} + } + } + }, + "deleteAppointment": { + "appliesTo": { + "principalTypes": [ + "User" + ], + "resourceTypes": [ + "Info" + ], + "context": { + "type": "Record", + "attributes": {} + } + } + }, + "listAppointments": { + "appliesTo": { + "principalTypes": [ + "User" + ], + "resourceTypes": [ + "Info" + ], + "context": { + "type": "Record", + "attributes": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/models/formats/cedar/test/photoEntities.json b/models/formats/cedar/test/photoEntities.json new file mode 100644 index 0000000..a6bd4b1 --- /dev/null +++ b/models/formats/cedar/test/photoEntities.json @@ -0,0 +1,57 @@ +[ + { + "uid": { + "type": "PhotoApp::User", + "id": "alice" + }, + "attrs": { + "userId": "897345789237492878", + "personInformation": { + "age": 25, + "name": "alice" + } + }, + "parents": [ + { + "type": "PhotoApp::UserGroup", + "id": "alice_friends" + }, + { + "type": "PhotoApp::UserGroup", + "id": "AVTeam" + } + ] + }, + { + "uid": { + "type": "PhotoApp::Photo", + "id": "vacationPhoto.jpg" + }, + "attrs": { + "private": false, + "account": { + "__entity": { + "type": "PhotoApp::Account", + "id": "ahmad" + } + } + }, + "parents": [] + }, + { + "uid": { + "type": "PhotoApp::UserGroup", + "id": "alice_friends" + }, + "attrs": {}, + "parents": [] + }, + { + "uid": { + "type": "PhotoApp::UserGroup", + "id": "AVTeam" + }, + "attrs": {}, + "parents": [] + } +] \ No newline at end of file diff --git a/models/formats/cedar/test/photoSchema.json b/models/formats/cedar/test/photoSchema.json new file mode 100644 index 0000000..d04c0fc --- /dev/null +++ b/models/formats/cedar/test/photoSchema.json @@ -0,0 +1,131 @@ +{ + "PhotoApp": { + "commonTypes": { + "PersonType": { + "type": "Record", + "attributes": { + "age": { + "type": "Long" + }, + "name": { + "type": "String" + } + } + }, + "ContextType": { + "type": "Record", + "attributes": { + "ip": { + "type": "Extension", + "name": "ipaddr", + "required": false + }, + "authenticated": { + "type": "Boolean", + "required": true + } + } + } + }, + "entityTypes": { + "User": { + "shape": { + "type": "Record", + "attributes": { + "userId": { + "type": "String" + }, + "personInformation": { + "type": "PersonType" + } + } + }, + "memberOfTypes": [ + "UserGroup" + ] + }, + "UserGroup": { + "shape": { + "type": "Record", + "attributes": {} + } + }, + "Photo": { + "shape": { + "type": "Record", + "attributes": { + "account": { + "type": "Entity", + "name": "Account", + "required": true + }, + "private": { + "type": "Boolean", + "required": true + } + } + }, + "memberOfTypes": [ + "Album", + "Account" + ] + }, + "Album": { + "shape": { + "type": "Record", + "attributes": {} + } + }, + "Account": { + "shape": { + "type": "Record", + "attributes": {} + } + } + }, + "actions": { + "viewPhoto": { + "appliesTo": { + "principalTypes": [ + "User", + "UserGroup" + ], + "resourceTypes": [ + "Photo" + ], + "context": { + "type": "ContextType" + } + } + }, + "createPhoto": { + "appliesTo": { + "principalTypes": [ + "User", + "UserGroup" + ], + "resourceTypes": [ + "Photo" + ], + "context": { + "type": "ContextType" + } + } + }, + "listPhotos": { + "appliesTo": { + "principalTypes": [ + "User", + "UserGroup" + ], + "resourceTypes": [ + "Photo" + ], + "context": { + "type": "ContextType" + } + } + } + } + } +} \ No newline at end of file diff --git a/pkg/hexapolicy/conditions/parser/parser_test.go b/pkg/hexapolicy/conditions/parser/parser_test.go index b062b97..44dd8ec 100644 --- a/pkg/hexapolicy/conditions/parser/parser_test.go +++ b/pkg/hexapolicy/conditions/parser/parser_test.go @@ -1,166 +1,167 @@ package parser import ( - "fmt" - "testing" + "fmt" + "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestParseFilter(t *testing.T) { - examples := [][2]string{ - {"title pr"}, - {"name pr and userName pr and title pr"}, - {"name.familyName co \"O'Malley\""}, - {"(userName eq \"bjensen\")"}, - {"userName eq \"bjensen\"", "userName eq \"bjensen\""}, - {"level gt 12"}, - {"level gt 12.3"}, - {"level eq 123.45e-5"}, - {"emails.type eq \"w o(rk)\""}, - {"userName Eq \"bjensen\"", "userName eq \"bjensen\""}, - {"((userName eq A) or (username eq \"B\")) or username eq C", "((userName eq \"A\") or (username eq \"B\")) or username eq \"C\""}, - {"((userName eq A or username eq \"B\") or (username eq C))", "((userName eq \"A\" or username eq \"B\") or (username eq \"C\"))"}, - {"userName sw \"J\""}, - {"urn:ietf:params:scim:schemas:core:2.0:User:userName sw \"J\""}, - - {"meta.lastModified gt \"2011-05-13T04:42:34Z\""}, - {"meta.lastModified ge \"2011-05-13T04:42:34Z\""}, - {"meta.lastModified lt \"2011-05-13T04:42:34Z\""}, - {"meta.lastModified le \"2011-05-13T04:42:34Z\""}, - {"title pr and userType eq \"Employee\""}, - {"title pr or userType eq \"Intern\""}, - {"schemas eq \"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User\""}, - - {"userType eq \"Employee\" and (emails.type eq \"work\")"}, - {"userType eq \"Employee\" and emails[type eq \"work\" and value co \"@example.com\"]"}, - {"userType eq \"Employee\" and (emails co \"example.com\" or emails.value co \"example.org\")"}, - {"userType ne \"Employee\" and not (emails co \"example.com\" or emails.value co \"example.org\")"}, - {"emails[type eq \"work\" and value co \"@example.com\"] or ims[type eq \"xmpp\" and value co \"@foo.com\"]"}, - - {"name pr and not (first eq \"test\") and another ne \"test\""}, - {"NAME PR AND NOT (FIRST EQ \"t[es]t\") AND ANOTHER NE \"test\"", "NAME pr and not (FIRST eq \"t[es]t\") and ANOTHER ne \"test\""}, - {"name pr or userName pr or title pr"}, - {"emails[type eq work and value ew \"h[exa].org\"]", "emails[type eq \"work\" and value ew \"h[exa].org\"]"}, - } - for _, example := range examples { - t.Run(example[0], func(t *testing.T) { - fmt.Println(fmt.Sprintf("Input:\t%s", example[0])) - ast, err := ParseFilter(example[0]) - assert.NoError(t, err, "Example not parsed: "+example[0]) - element := *ast - out := element.String() - fmt.Println(fmt.Sprintf("Parsed:\t%s", out)) - match := example[1] - if match == "" { - match = example[0] - } - assert.Equal(t, match, out, "Check expected result matches: %s", match) - }) - } + examples := [][2]string{ + {"title pr"}, + {"name pr and userName pr and title pr"}, + {"name.familyName co O'Malley"}, + {"(userName eq bjensen)"}, + {"userName eq bjensen", "userName eq bjensen"}, + {"level gt 12"}, + {"level gt 12.3"}, + {"level eq 123.45e-5"}, + {"emails.type eq \"w o(rk)\""}, + {"userName Eq bjensen", "userName eq bjensen"}, + {"((userName eq A) or (username eq B)) or username eq C", "((userName eq A) or (username eq B)) or username eq C"}, + {"((userName eq A or username eq \"B\") or (username eq C))", "((userName eq A or username eq B) or (username eq C))"}, + {"userName sw J"}, + {"urn:ietf:params:scim:schemas:core:2.0:User:userName sw J"}, + + {"meta.lastModified gt 2011-05-13T04:42:34Z"}, + {"meta.lastModified ge 2011-05-13T04:42:34Z"}, + {"meta.lastModified lt 2011-05-13T04:42:34Z"}, + {"meta.lastModified le 2011-05-13T04:42:34Z"}, + {"title pr and userType eq Employee"}, + {"title pr or userType eq Intern"}, + {"schemas eq urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"}, + + {"userType eq Employee and (emails.type eq work)"}, + {"userType eq Employee and emails[type eq work and value co @example.com]"}, + {"userType eq Employee and (emails co example.com or emails.value co example.org)"}, + {"userType ne Employee and not (emails co example.com or emails.value co example.org)"}, + {"emails[type eq work and value co @example.com] or ims[type eq xmpp and value co @foo.com]"}, + + {"name pr and not (first eq test) and another ne test"}, + {"NAME PR AND NOT (FIRST EQ t[es]t) AND ANOTHER NE test", "NAME pr and not (FIRST eq t[es]t) and ANOTHER ne test"}, + {"name pr or userName pr or title pr"}, + {"emails[type eq work and value ew \"h[exa].org\"]", "emails[type eq work and value ew h[exa].org]"}, + } + for _, example := range examples { + t.Run(example[0], func(t *testing.T) { + var err error + fmt.Println(fmt.Sprintf("Input:\t%s", example[0])) + ast, err := ParseFilter(example[0]) + assert.NoError(t, err, "Example not parsed: "+example[0]) + element := *ast + out := element.String() + fmt.Println(fmt.Sprintf("Parsed:\t%s", out)) + match := example[1] + if match == "" { + match = example[0] + } + assert.Equal(t, match, out, "Check expected result matches: %s", match) + }) + } } func TestNegParseTests(t *testing.T) { - ast, err := ParseFilter("username == blah") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: ==") - } - - assert.Nil(t, ast, "No idqlCondition should be parsed") - - ast, err = ParseFilter("((username pr or quota eq 0) and black eq white") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing close ')' bracket") - } - - assert.Nil(t, ast, "No idqlCondition should be parsed") - - ast, err = ParseFilter("username pr or quota eq \"none\") and black eq white") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing open '(' bracket") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - ast, err = ParseFilter("username eq \"none\")") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing open '(' bracket") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - ast, err = ParseFilter("username eq \"none\" and") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Incomplete expression") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - ast, err = ParseFilter("username eq \"none\" or abc") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Incomplete expression") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - // The following test is poorly formed. Expression should be emails[type eq work and value ew "hexa.org"] - ast, err = ParseFilter("emails[type eq work] ew \"hexa.org\"") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing and/or clause") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - ast, err = ParseFilter("emails[type eq work and value ew \"hexa.org\"") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing close ']' bracket") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - ast, err = ParseFilter("emails[type[sub eq val] eq work and value ew \"hexa.org\"") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: A second '[' was detected while looking for a ']' in a value path idqlCondition") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - // This checks if a sufFilter expression is invalid - ast, err = ParseFilter("(username == \"malformed\")") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: ==") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - // .value is not currently supported - ast, err = ParseFilter("emails[type eq val].value eq work") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: expecting space after ']' in value path expression") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - ast, err = ParseFilter("emails.type] eq work") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing open '[' bracket") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - ast, err = ParseFilter("emails.type) eq work and a eq b") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing open '(' bracket") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") - - ast, err = ParseFilter("emails[type == work] and a eq b") - if err != nil { - fmt.Println(err.Error()) - assert.EqualError(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: ==") - } - assert.Nil(t, ast, "No idqlCondition should be parsed") + ast, err := ParseFilter("username == blah") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: ==") + } + + assert.Nil(t, ast, "No idqlCondition should be parsed") + + ast, err = ParseFilter("((username pr or quota eq 0) and black eq white") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing close ')' bracket") + } + + assert.Nil(t, ast, "No idqlCondition should be parsed") + + ast, err = ParseFilter("username pr or quota eq \"none\") and black eq white") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing open '(' bracket") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + ast, err = ParseFilter("username eq \"none\")") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing open '(' bracket") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + ast, err = ParseFilter("username eq \"none\" and") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Incomplete expression") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + ast, err = ParseFilter("username eq \"none\" or abc") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Incomplete expression") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + // The following test is poorly formed. Expression should be emails[type eq work and value ew "hexa.org"] + ast, err = ParseFilter("emails[type eq work] ew \"hexa.org\"") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing and/or clause") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + ast, err = ParseFilter("emails[type eq work and value ew \"hexa.org\"") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing close ']' bracket") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + ast, err = ParseFilter("emails[type[sub eq val] eq work and value ew \"hexa.org\"") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: A second '[' was detected while looking for a ']' in a value path idqlCondition") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + // This checks if a sufFilter expression is invalid + ast, err = ParseFilter("(username == \"malformed\")") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: ==") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + // .value is not currently supported + ast, err = ParseFilter("emails[type eq val].value eq work") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: expecting space after ']' in value path expression") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + ast, err = ParseFilter("emails.type] eq work") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing open '[' bracket") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + ast, err = ParseFilter("emails.type) eq work and a eq b") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Missing open '(' bracket") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") + + ast, err = ParseFilter("emails[type == work] and a eq b") + if err != nil { + fmt.Println(err.Error()) + assert.EqualError(t, err, "invalid IDQL idqlCondition: Unsupported comparison operator: ==") + } + assert.Nil(t, ast, "No idqlCondition should be parsed") } diff --git a/pkg/hexapolicy/conditions/parser/types.go b/pkg/hexapolicy/conditions/parser/types.go index 7d6df9b..df55444 100644 --- a/pkg/hexapolicy/conditions/parser/types.go +++ b/pkg/hexapolicy/conditions/parser/types.go @@ -1,39 +1,42 @@ package parser import ( - "fmt" - "regexp" - "strings" + "fmt" + "regexp" + "strings" ) const ( - // PR is an abbreviation for 'present'. - PR CompareOperator = "pr" - // EQ is an abbreviation for 'equals'. - EQ CompareOperator = "eq" - // NE is an abbreviation for 'not equals'. - NE CompareOperator = "ne" - // CO is an abbreviation for 'contains'. - CO CompareOperator = "co" - // IN is an abbreviation for 'in'. - IN CompareOperator = "in" - // SW is an abbreviation for 'starts with'. - SW CompareOperator = "sw" - // EW an abbreviation for 'ends with'. - EW CompareOperator = "ew" - // GT is an abbreviation for 'greater than'. - GT CompareOperator = "gt" - // LT is an abbreviation for 'less than'. - LT CompareOperator = "lt" - // GE is an abbreviation for 'greater or equal than'. - GE CompareOperator = "ge" - // LE is an abbreviation for 'less or equal than'. - LE CompareOperator = "le" - - // AND is the logical operation and (&&). - AND LogicalOperator = "and" - // OR is the logical operation or (||). - OR LogicalOperator = "or" + // PR is an abbreviation for 'present'. + PR CompareOperator = "pr" + // EQ is an abbreviation for 'equals'. + EQ CompareOperator = "eq" + // NE is an abbreviation for 'not equals'. + NE CompareOperator = "ne" + // CO is an abbreviation for 'contains'. + CO CompareOperator = "co" + // IN is an abbreviation for 'in'. + IN CompareOperator = "in" + // SW is an abbreviation for 'starts with'. + SW CompareOperator = "sw" + // EW an abbreviation for 'ends with'. + EW CompareOperator = "ew" + // GT is an abbreviation for 'greater than'. + GT CompareOperator = "gt" + // LT is an abbreviation for 'less than'. + LT CompareOperator = "lt" + // GE is an abbreviation for 'greater or equal than'. + GE CompareOperator = "ge" + // LE is an abbreviation for 'less or equal than'. + LE CompareOperator = "le" + + // IS allows comparison of Object/Resource Types - added for Cedar compat + IS CompareOperator = "is" + + // AND is the logical operation and (&&). + AND LogicalOperator = "and" + // OR is the logical operation or (||). + OR LogicalOperator = "or" ) type CompareOperator string @@ -41,75 +44,81 @@ type CompareOperator string type LogicalOperator string type Expression interface { - exprNode() - String() string + exprNode() + String() string } type LogicalExpression struct { - Operator LogicalOperator - Left, Right Expression + Operator LogicalOperator + Left, Right Expression } func (LogicalExpression) exprNode() {} func (e LogicalExpression) String() string { - return fmt.Sprintf("%s %s %s", e.Left.String(), e.Operator, e.Right.String()) + return fmt.Sprintf("%s %s %s", e.Left.String(), e.Operator, e.Right.String()) } type NotExpression struct { - Expression Expression + Expression Expression } func (e NotExpression) String() string { - return fmt.Sprintf("not (%s)", e.Expression.String()) + return fmt.Sprintf("not (%s)", e.Expression.String()) } func (NotExpression) exprNode() {} type PrecedenceExpression struct { - Expression Expression + Expression Expression } func (PrecedenceExpression) exprNode() {} func (e PrecedenceExpression) String() string { - return fmt.Sprintf("(%s)", e.Expression.String()) + return fmt.Sprintf("(%s)", e.Expression.String()) } type AttributeExpression struct { - AttributePath string - Operator CompareOperator - CompareValue string + AttributePath string + Operator CompareOperator + CompareValue string } func (AttributeExpression) exprNode() {} func (e AttributeExpression) String() string { - if e.Operator == "pr" { - return fmt.Sprintf("%s pr", e.AttributePath) - } - - isNumber, _ := regexp.MatchString("^[-+]?[0-9]+[.]?[0-9]*([eE][-+]?[0-9]+)?$", e.CompareValue) - if isNumber { - // Numbers are not quoted - return fmt.Sprintf("%s %s %v", e.AttributePath, e.Operator, e.CompareValue) - } - - // Check boolean - lVal := strings.ToLower(e.CompareValue) - if lVal == "true" || lVal == "false" { - return fmt.Sprintf("%s %s %v", e.AttributePath, e.Operator, lVal) - } - - // treat as string - return fmt.Sprintf("%s %s \"%s\"", e.AttributePath, e.Operator, e.CompareValue) + if e.Operator == "pr" { + return fmt.Sprintf("%s pr", e.AttributePath) + } + + isNumber, _ := regexp.MatchString("^[-+]?[0-9]+[.]?[0-9]*([eE][-+]?[0-9]+)?$", e.CompareValue) + if isNumber { + // Numbers are not quoted + return fmt.Sprintf("%s %s %v", e.AttributePath, e.Operator, e.CompareValue) + } + + // Check boolean + lVal := strings.ToLower(e.CompareValue) + if lVal == "true" || lVal == "false" { + return fmt.Sprintf("%s %s %v", e.AttributePath, e.Operator, lVal) + } + + // treat as string + comp := e.CompareValue + + // Do not quote if value is already quoted or doesn't have a space + if strings.Contains(comp, "\"") || !strings.Contains(comp, " ") { + return fmt.Sprintf("%s %s %s", e.AttributePath, e.Operator, e.CompareValue) + } + return fmt.Sprintf("%s %s \"%s\"", e.AttributePath, e.Operator, e.CompareValue) } type ValuePathExpression struct { - Attribute string - VPathFilter Expression + Attribute string + VPathFilter Expression } func (ValuePathExpression) exprNode() {} func (e ValuePathExpression) String() string { - return fmt.Sprintf("%s[%s]", e.Attribute, e.VPathFilter.String()) + return fmt.Sprintf("%s[%s]", e.Attribute, e.VPathFilter.String()) } diff --git a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego index 5c15e83..3b2adc9 100644 --- a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego +++ b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego @@ -5,7 +5,7 @@ import rego.v1 import data.bundle.policies -hexa_rego_version := "0.6.10" +hexa_rego_version := "0.6.15" policies_evaluated := count(policies) From 710b217576ad490ae57da3f86b4166320e7354c6 Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Thu, 5 Sep 2024 18:14:03 -0700 Subject: [PATCH 04/13] Issue #59, Revised and Refactored SubjectInfo to drop use of redundant Members element Signed-off-by: Phil Hunt --- cmd/hexa/hexa_test.go | 2 +- cmd/hexa/test/example_idql.json | 75 +- go.mod | 78 +- go.sum | 156 ++-- models/formats/awsCedar/amazon_cedar.go | 14 +- models/formats/awsCedar/test/data.json | 79 +- models/formats/awsCedar/test/testGcpIdql.json | 46 +- models/formats/cedar/cedar_test.go | 48 +- models/formats/cedar/parse.go | 34 +- models/formats/gcpBind/google_bind_policy.go | 12 +- models/formats/gcpBind/test/data.json | 56 +- models/rar/policy_transformer.go | 26 +- models/rar/policy_transformer_test.go | 551 ++++++------- .../resource_action_role_policy_support.go | 84 +- .../policy_checker_support.go | 119 ++- .../policytestsupport/policy_data_support.go | 123 +-- .../schema/resources}/README.md | 0 .../schema/resources}/idql.jschema | 55 +- models/schema/schema_test.go | 37 + models/schema/test/cedarPhotoPolicy.txt | 12 + models/schema/test/healthEntities.json | 67 ++ models/schema/test/photoEntities.json | 57 ++ pkg/hexapolicy/hexa_policy.go | 48 +- pkg/hexapolicy/hexa_policy_test.go | 135 ++-- .../aws/avpProvider/avpClient/avp_client.go | 15 + providers/aws/avpProvider/avp_provider.go | 21 +- .../aws/avpProvider/avp_provider_test.go | 725 +++++++++-------- .../aws_apigw_provider_service.go | 92 +-- .../aws_apigw_provider_service_test.go | 442 +++++----- .../aws/cognitoProvider/cognito_provider.go | 162 ++-- .../cognitoProvider/cognito_provider_test.go | 758 +++++++++--------- .../azureProvider/azure_policy_mapper.go | 138 ++-- .../azureProvider/azure_policy_mapper_test.go | 109 +-- .../azure/azureProvider/azure_provider.go | 232 +++--- .../azureProvider/azure_provider_test.go | 691 ++++++++-------- .../iapProvider/google_client_test.go | 268 +++---- .../iapProvider/google_cloud_provider_test.go | 176 ++-- .../decisionsupport/opa_provider.go | 65 -- .../decisionsupport/opa_provider_test.go | 92 --- .../http_bundle_client_test.go | 75 +- providers/openpolicyagent/opa_policy_test.go | 114 --- .../openpolicyagent/opa_provider_test.go | 6 +- .../resources/bundles/bundle/data.json | 75 +- .../resources/bundles/bundle/hexaPolicy.rego | 12 +- sdk/providerTools_test.go | 288 +++---- 45 files changed, 3292 insertions(+), 3178 deletions(-) rename {pkg/hexapolicy/schema => models/schema/resources}/README.md (100%) rename {pkg/hexapolicy/schema => models/schema/resources}/idql.jschema (65%) create mode 100644 models/schema/test/cedarPhotoPolicy.txt create mode 100644 models/schema/test/healthEntities.json create mode 100644 models/schema/test/photoEntities.json delete mode 100644 providers/openpolicyagent/decisionsupport/opa_provider.go delete mode 100644 providers/openpolicyagent/decisionsupport/opa_provider_test.go delete mode 100644 providers/openpolicyagent/opa_policy_test.go diff --git a/cmd/hexa/hexa_test.go b/cmd/hexa/hexa_test.go index 1235b47..5468b15 100644 --- a/cmd/hexa/hexa_test.go +++ b/cmd/hexa/hexa_test.go @@ -330,7 +330,7 @@ func (suite *testSuite) Test05_SetPolicies() { testPolicyMods[0].Actions = append(policy.Actions, newAction) // 3rd policy has a different subject - testPolicyMods[1].Subject.Members = testPolicyMods[1].Subject.Members[1:] + testPolicyMods[1].Subjects = testPolicyMods[1].Subjects[1:] // 4th policy unchanged. diff --git a/cmd/hexa/test/example_idql.json b/cmd/hexa/test/example_idql.json index 5159b27..1f645ce 100644 --- a/cmd/hexa/test/example_idql.json +++ b/cmd/hexa/test/example_idql.json @@ -1,13 +1,18 @@ { "policies": [ { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/"}], - "subject": { - "members": [ - "allusers", "allauthenticated" - ] + "meta": { + "version": "0.6" }, + "actions": [ + { + "actionUri": "http:GET:/" + } + ], + "subjects": [ + "allusers", + "allauthenticated" + ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" @@ -17,27 +22,41 @@ } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/sales"}, {"actionUri": "http:GET:/marketing"}], - "subject": { - "members": [ - "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" - ] + "meta": { + "version": "0.6" }, + "actions": [ + { + "actionUri": "http:GET:/sales" + }, + { + "actionUri": "http:GET:/marketing" + } + ], + "subjects": [ + "allauthenticated", + "sales@hexaindustries.io", + "marketing@hexaindustries.io" + ], "object": { "resource_id": "aResourceId2" } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/accounting"}, {"actionUri": "http:POST:/accounting"}], - "subject": { - "members": [ - "accounting@hexaindustries.io" - ] + "meta": { + "version": "0.6" }, + "actions": [ + { + "actionUri": "http:GET:/accounting" + }, + { + "actionUri": "http:POST:/accounting" + } + ], + "subjects": [ + "accounting@hexaindustries.io" + ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" @@ -47,13 +66,17 @@ } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/humanresources"}], - "subject": { - "members": [ - "humanresources@hexaindustries.io" - ] + "meta": { + "version": "0.6" }, + "actions": [ + { + "actionUri": "http:GET:/humanresources" + } + ], + "subjects": [ + "humanresources@hexaindustries.io" + ], "object": { "resource_id": "aResourceId1" } diff --git a/go.mod b/go.mod index 42a3132..9050186 100644 --- a/go.mod +++ b/go.mod @@ -13,14 +13,14 @@ require ( github.com/alecthomas/kong v0.9.0 github.com/alecthomas/participle/v2 v2.1.1 github.com/alexedwards/scs/v2 v2.8.0 - github.com/aws/aws-sdk-go-v2 v1.30.4 - github.com/aws/aws-sdk-go-v2/config v1.27.29 - github.com/aws/aws-sdk-go-v2/credentials v1.17.29 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.11 - github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.2 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5 - github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1 - github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.4 + github.com/aws/aws-sdk-go-v2 v1.30.5 + github.com/aws/aws-sdk-go-v2/config v1.27.33 + github.com/aws/aws-sdk-go-v2/credentials v1.17.32 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.2 + github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.4 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.8 + github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 + github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.6 github.com/cedar-policy/cedar-go v0.1.0 github.com/chzyer/readline v1.5.1 github.com/coreos/go-oidc/v3 v3.11.0 @@ -33,17 +33,17 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/hhsnopek/etag v0.0.0-20171206181245-aea95f647346 - github.com/prometheus/client_golang v1.20.1 + github.com/prometheus/client_golang v1.20.3 github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 github.com/stretchr/testify v1.9.0 - golang.org/x/exp v0.0.0-20240822175202-778ce7bba035 - golang.org/x/oauth2 v0.22.0 - google.golang.org/api v0.194.0 - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e + golang.org/x/oauth2 v0.23.0 + google.golang.org/api v0.196.0 + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 ) require ( - cloud.google.com/go/auth v0.9.1 // indirect + cloud.google.com/go/auth v0.9.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/compute/metadata v0.5.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect @@ -51,24 +51,24 @@ require ( github.com/alecthomas/repr v0.4.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect github.com/aws/smithy-go v1.20.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59 // indirect + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.5 // indirect @@ -79,7 +79,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/s2a-go v0.1.8 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.3 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -90,23 +90,23 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.59.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/tj/assert v0.0.3 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.6.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.65.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.66.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e5f744d..3e4ba95 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/auth v0.9.1 h1:+pMtLEV2k0AXKvs/tGZojuj6QaioxfUjOpMsG5Gtx+w= -cloud.google.com/go/auth v0.9.1/go.mod h1:Sw8ocT5mhhXxFklyhT12Eiy0ed6tTrPMCJjSI8KhYLk= +cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= +cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= @@ -30,52 +30,52 @@ github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZx github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= -github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= -github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= +github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw= -github.com/aws/aws-sdk-go-v2/config v1.27.29 h1:+ZPKb3u9Up4KZWLGTtpTmC5T3XmRD1ZQ8XQjRCHUvJw= -github.com/aws/aws-sdk-go-v2/config v1.27.29/go.mod h1:yxqvuubha9Vw8stEgNiStO+yZpP68Wm9hLmcm+R/Qk4= -github.com/aws/aws-sdk-go-v2/credentials v1.17.29 h1:CwGsupsXIlAFYuDVHv1nnK0wnxO0wZ/g1L8DSK/xiIw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.29/go.mod h1:BPJ/yXV92ZVq6G8uYvbU0gSl8q94UB63nMT5ctNO38g= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.11 h1:KUHQows9JhDp+RJRs9KLN+ljsK5D+oLV13Wr/TwlSr4= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.11/go.mod h1:4kdmcGnKW4R9l2ddj6hNgKnJoxztjvJNCoI9eikMgvI= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs= +github.com/aws/aws-sdk-go-v2/config v1.27.33 h1:Nof9o/MsmH4oa0s2q9a0k7tMz5x/Yj5k06lDODWz3BU= +github.com/aws/aws-sdk-go-v2/config v1.27.33/go.mod h1:kEqdYzRb8dd8Sy2pOdEbExTTF5v7ozEXX0McgPE7xks= +github.com/aws/aws-sdk-go-v2/credentials v1.17.32 h1:7Cxhp/BnT2RcGy4VisJ9miUPecY+lyE9I8JvcZofn9I= +github.com/aws/aws-sdk-go-v2/credentials v1.17.32/go.mod h1:P5/QMF3/DCHbXGEGkdbilXHsyTBX5D3HSwcrSc9p20I= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.2 h1:ss2pLhKcLRqzzWR08Z3arJN1R/9gcjDbzlYHyYNZ/F0= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.2/go.mod h1:luXuuIR1T/EQo8PO3rkxKajO0hMRa7NYUhComrBpgW0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 h1:mimdLQkIX1zr8GIPY1ZtALdBQGxcASiBd2MOp8m/dMc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16/go.mod h1:YHk6owoSwrIsok+cAH9PENCOGoH5PU2EllX4vLtSrsY= -github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.2 h1:DolLrk9um5/oj6k8p0sKc5A9eiW+DhFmc/Ip64LNktU= -github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.2/go.mod h1:PUxIbGvs00Dw/BBqPPxqDpE5k2DvFHPVlNMXgChv0Co= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5 h1:Cm77yt+/CV7A6DglkENsWA3H1hq8+4ItJnFKrhxHkvg= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5/go.mod h1:s2fYaueBuCnwv1XQn6T8TfShxJWusv5tWPMcL+GY6+g= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.5 h1:sM/SaWUKPtsCcXE0bHZPUG4jjCbFbxakyptXQbYLrdU= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.5/go.mod h1:3YxVsEoCNYOLIbdA+cCXSp1fom9hrhyB1DsCiYryCaQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.4 h1:C8uf+nwieFWZtdPTCYOM8u/UyaIsDPfr95TJrfYekwQ= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.4/go.mod h1:hsciKQ2xFfOPEuebyKmFo7wOSVNoLuzmCi6Qtol4UDc= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.8 h1:XTz8pSCsPiM9FpT+gTPIL6ryiu/T4Z3dpR/FBtPaBXA= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.8/go.mod h1:N3YdUYxyxhiuAelUgCpSVBuBI1klobJxZrDtL+olu10= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 h1:VTBHXWkSeFgT3sfYB4U92qMgzHl0nz9H1tYNHHutLg0= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7/go.mod h1:F/ybU7YfgFcktSp+biKgiHjyscGhlZxOz4QFFQqHXGw= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 h1:GckUnpm4EJOAio1c8o25a+b3lVfwVzC9gnSBqiiNmZM= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18/go.mod h1:Br6+bxfG33Dk3ynmkhsW2Z/t9D4+lRqdLDNCKi85w0U= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 h1:HDJGz1jlV7RokVgTPfx1UHBHANC0N5Uk++xgyYgz5E0= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17/go.mod h1:5szDu6TWdRDytfDxUQVv2OYfpTQMKApVFyqpm+TcA98= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 h1:jg16PhLPUiHIj8zYIW6bqzeQSuHVEiWnGA0Brz5Xv2I= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16/go.mod h1:Uyk1zE1VVdsHSU7096h/rwnXDzOzYQVl+FNPhPw7ShY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1 h1:mx2ucgtv+MWzJesJY9Ig/8AFHgoE5FwLXwUVgW/FGdI= -github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wINh+4UK+k/0Yo/q8= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= -github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.4 h1:vqIZR0Mo6u0Lx/Ep5ea4kaxalsKY1+Um1tJ6UvoDArs= -github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.4/go.mod h1:lmvSNrXkQPdl9SaIi+yvK9UQ3USZC8N3iImoCu1ADo0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 h1:FLMkfEiRjhgeDTCjjLoc3URo/TBkgeQbocA78lfkzSI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19/go.mod h1:Vx+GucNSsdhaxs3aZIKfSUjKVGsxN25nX2SRcdhuw08= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18 h1:GACdEPdpBE59I7pbfvu0/Mw1wzstlP3QtPHklUxybFE= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18/go.mod h1:K+xV06+Wni4TSaOOJ1Y35e5tYOCUBYbebLKmJQQa8yY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.7/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 h1:/Cfdu0XV3mONYKaOt1Gr0k1KvQzkzPyiKUdlWJqy+J4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 h1:NKTa1eqZYw8tiHSRGpP0VtTdub/8KNk8sDkNPFaOKDE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o= +github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.6 h1:OALTvlqxlJysbfpPN02yEaQbq+i0mupm14m28IadjXs= +github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.6/go.mod h1:/il6CcYy1TceX8GhBT8qbEUiqIGP/R+OvlztiT8OMEw= github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -93,8 +93,8 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59 h1:fLZ97KE86ELjEYJCEUVzmbhfzDxHHGwBrDVMd4XL6Bs= -github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -163,8 +163,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.3.3 h1:QRje2j5GZimBzlbhGA2V2QlGNgL8G6e+wGo/+/2bWI0= +github.com/googleapis/enterprise-certificate-proxy v0.3.3/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -196,13 +196,13 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.1 h1:IMJXHOD6eARkQpxo8KkhgEVFlBNm+nkrFUyGlIu7Na8= -github.com/prometheus/client_golang v1.20.1/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= +github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -226,21 +226,21 @@ github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240822175202-778ce7bba035 h1:VkSUcpKXdGwUpn/JsiWXwSNnIJVXRfMA4ThL5vwljWg= -golang.org/x/exp v0.0.0-20240822175202-778ce7bba035/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -250,11 +250,11 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -266,12 +266,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -280,24 +280,24 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.194.0 h1:dztZKG9HgtIpbI35FhfuSNR/zmaMVdxNlntHj1sIS4s= -google.golang.org/api v0.194.0/go.mod h1:AgvUFdojGANh3vI+P7EVnxj3AISHllxGCJSFmggmnd0= +google.golang.org/api v0.196.0 h1:k/RafYqebaIJBO3+SMnfEGtFVlvp5vSgqTUF54UN/zg= +google.golang.org/api v0.196.0/go.mod h1:g9IL21uGkYgvQ5BZg6BAtoGJQIm8r6EgaAbpNey5wBE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/models/formats/awsCedar/amazon_cedar.go b/models/formats/awsCedar/amazon_cedar.go index 2ce3861..61b548a 100644 --- a/models/formats/awsCedar/amazon_cedar.go +++ b/models/formats/awsCedar/amazon_cedar.go @@ -38,7 +38,7 @@ IDQL supports multiple subjects where Cedar is limited to 1 Principal and 1 Reso func (c *CedarPolicyMapper) MapPolicyToCedar(idqlPol hexapolicy.PolicyInfo) ([]*CedarPolicy, error) { cpolicies := make([]*CedarPolicy, 0) - if len(idqlPol.Subject.Members) == 0 { + if len(idqlPol.Subjects) == 0 { cpolicy, err := c.mapSimplePolicyToCedar("", idqlPol) if err != nil { return nil, err @@ -48,7 +48,7 @@ func (c *CedarPolicyMapper) MapPolicyToCedar(idqlPol hexapolicy.PolicyInfo) ([]* return cpolicies, nil } - for _, v := range idqlPol.Subject.Members { + for _, v := range idqlPol.Subjects { cpolicy, err := c.mapSimplePolicyToCedar(v, idqlPol) if err != nil { return nil, err @@ -222,10 +222,10 @@ func (c *CedarPolicyMapper) mapSimplePolicyToCedar(member string, policy hexapol var principal *PrincipalExpression switch member { - case hexapolicy.SAnyUser, "": + case hexapolicy.SubjectAnyUser, "": principal = nil - case hexapolicy.SAnyAuth, hexapolicy.SJwtAuth, hexapolicy.SSamlAuth, hexapolicy.SBasicAuth: + case hexapolicy.SubjectAnyAuth, hexapolicy.SubjectJwtAuth, hexapolicy.SubjectSamlAuth, hexapolicy.SubjectBasicAuth: principal = nil cond := ConditionType("context.authenticated == true") conds = append(conds, &ConditionalClause{ @@ -335,9 +335,9 @@ func (c *CedarPolicyMapper) MapCedarPolicyToIdql(policy *CedarPolicy) (*hexapoli var subj hexapolicy.SubjectInfo if policy.Head.Principal == nil { - subj = hexapolicy.SubjectInfo{Members: []string{hexapolicy.SAnyUser}} + subj = []string{hexapolicy.SubjectAnyUser} } else { - subj = hexapolicy.SubjectInfo{Members: []string{mapPrincipalToMember(policy.Head.Principal.Entity)}} + subj = []string{mapPrincipalToMember(policy.Head.Principal.Entity)} } actions := make([]hexapolicy.ActionInfo, 0) @@ -416,7 +416,7 @@ func (c *CedarPolicyMapper) MapCedarPolicyToIdql(policy *CedarPolicy) (*hexapoli ret := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: actions, - Subject: subj, + Subjects: subj, Object: obj, Condition: condInfo, } diff --git a/models/formats/awsCedar/test/data.json b/models/formats/awsCedar/test/data.json index fdc6b92..17e1038 100644 --- a/models/formats/awsCedar/test/data.json +++ b/models/formats/awsCedar/test/data.json @@ -2,18 +2,16 @@ "policies": [ { "Meta": { - "Version": "0.6" + "Version": "0.7" }, "Actions": [ { "ActionUri": "cedar:Action::view" } ], - "Subject": { - "Members": [ - "User:stacey" - ] - }, + "Subjects": [ + "User:stacey" + ], "Object": { "resource_id": "" }, @@ -23,13 +21,20 @@ } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "cedar:Action::ReadFile"},{"actionuri": "cedar:Action::ListFiles"}], - "subject": { - "members": [ - "any" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "cedar:Action::ReadFile" + }, + { + "actionuri": "cedar:Action::ListFiles" + } + ], + "subjects": [ + "any" + ], "condition": { "rule": "action.isReadOperation eq true", "action": "allow" @@ -39,27 +44,38 @@ } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "cedar:Action::writeFile"}], - "subject": { - "members": [ - "anyAuthenticated", - "User:sales@hexaindustries.io", - "Group:marketing@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "cedar:Action::writeFile" + } + ], + "subjects": [ + "anyAuthenticated", + "User:sales@hexaindustries.io", + "Group:marketing@hexaindustries.io" + ], "object": { "resource_id": "File::ec37b3b17a1e4ae08a641dcd9d915535" } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "cedar:Action::ReadFile"},{"actionUri": "cedar:Action::ListFiles"}], - "subject": { - "members": [ - "User:accounting@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "cedar:Action::ReadFile" + }, + { + "actionUri": "cedar:Action::ListFiles" + } + ], + "subjects": [ + "User:accounting@hexaindustries.io" + ], "condition": { "rule": "context.sourceIp eq \"192.158.1.38\" and context.http.method eq GET", "action": "allow" @@ -70,22 +86,19 @@ }, { "Meta": { - "Version": "0.6" + "Version": "0.7" }, "Actions": [ { "ActionUri": "cedar:Action::view" } ], - "Subject": { - "Members": [ - "User:alice" - ] - }, + "subjects": [ + "User:alice" + ], "Object": { "resource_id": "cedar:Photo::VacationPhoto94.jpg" } } - ] } \ No newline at end of file diff --git a/models/formats/awsCedar/test/testGcpIdql.json b/models/formats/awsCedar/test/testGcpIdql.json index acf7412..c896f3a 100644 --- a/models/formats/awsCedar/test/testGcpIdql.json +++ b/models/formats/awsCedar/test/testGcpIdql.json @@ -1,15 +1,13 @@ [ { "Meta": { - "Version": "0.6" + "Version": "0.7" }, "Actions": null, - "Subject": { - "Members": [ - "allusers", - "allauthenticated" - ] - }, + "Subjects": [ + "allusers", + "allauthenticated" + ], "Object": { "resource_id": "aResourceId1" }, @@ -20,44 +18,38 @@ }, { "Meta": { - "Version": "0.6" + "Version": "0.7" }, "Actions": null, - "Subject": { - "Members": [ - "humanresources@hexaindustries.io" - ] - }, + "Subjects": [ + "humanresources@hexaindustries.io" + ], "Object": { "resource_id": "aResourceId1" } }, { "Meta": { - "Version": "0.6" + "Version": "0.7" }, "Actions": null, - "Subject": { - "Members": [ - "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" - ] - }, + "Subjects": [ + "allauthenticated", + "sales@hexaindustries.io", + "marketing@hexaindustries.io" + ], "Object": { "resource_id": "aResourceId2" } }, { "Meta": { - "Version": "0.6" + "Version": "0.7" }, "Actions": null, - "Subject": { - "Members": [ - "accounting@hexaindustries.io" - ] - }, + "Subjects": [ + "accounting@hexaindustries.io" + ], "Object": { "resource_id": "aResourceId3" }, diff --git a/models/formats/cedar/cedar_test.go b/models/formats/cedar/cedar_test.go index 82b4238..4ca9814 100644 --- a/models/formats/cedar/cedar_test.go +++ b/models/formats/cedar/cedar_test.go @@ -1,9 +1,13 @@ package cedar import ( + "encoding/json" "fmt" "reflect" "testing" + + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/stretchr/testify/assert" ) const policyCedar = ` @@ -90,12 +94,14 @@ permit ( } } }, - "subject": { - "members": [ - "any" - ] - }, - "actions": null, + "subjects": [ + "any" + ], + "actions": [ + { + "actionUri": "action" + } + ], "object": { "resource_id": "" } @@ -108,11 +114,9 @@ permit ( resource == Photo::"VacationPhoto.jpg" );`, `{ "meta": {}, - "subject": { - "members": [ + "subjects": [ "User:alice" - ] - }, + ], "actions": [ { "actionUri": "viewPhoto" @@ -128,11 +132,9 @@ permit ( resource == Photo::"VacationPhoto.jpg" );`, `{ "meta": {}, - "subject": { - "members": [ + "subjects": [ "Group:\"AVTeam\".(User)" - ] - }, + ], "actions": [ { "actionUri": "PhotoOp::\"view\"" @@ -157,11 +159,9 @@ when { resource in PhotoApp::Account::"stacey" } unless { principal has parents };`, `{ "meta": {}, - "subject": { - "members": [ + "subjects": [ "Group:UserGroup::\"AVTeam\"" - ] - }, + ], "actions": [ { "actionUri": "viewPhoto" @@ -182,11 +182,9 @@ unless { principal has parents };`, ) when { resource in PhotoShop::"Photo" };`, `{ "meta": {}, - "subject": { - "members": [ + "subjects": [ "Type:User" - ] - }, + ], "actions": [ { "actionUri": "viewPhoto" @@ -211,7 +209,11 @@ when { resource in PhotoShop::"Photo" };`, `{ testutilEquals(t, tt.err, err != nil) idqlOut := result.Policies[0].String() fmt.Println("Mapped:\n" + idqlOut) - testutilEquals(t, idqlOut, 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) }) } diff --git a/models/formats/cedar/parse.go b/models/formats/cedar/parse.go index 6b911a4..b6e0f5a 100644 --- a/models/formats/cedar/parse.go +++ b/models/formats/cedar/parse.go @@ -168,7 +168,7 @@ func (pp *PolicyPair) mapCedarSubject() { var subj hexapolicy.SubjectInfo switch principal.Type { case cedarParser.MatchAny: - subj = hexapolicy.SubjectInfo{Members: []string{hexapolicy.SAnyUser}} + subj = []string{hexapolicy.SubjectAnyUser} case cedarParser.MatchEquals: member := principal.Entity.String() // This is principal == xxx @@ -176,35 +176,38 @@ func (pp *PolicyPair) mapCedarSubject() { member = fmt.Sprintf("%s:%s", paths[0], strings.Join(paths[1:], "::")) } - subj = hexapolicy.SubjectInfo{Members: []string{member}} + subj = []string{member} case cedarParser.MatchIs: // This is "principal is User" - // subj = hexapolicy.SubjectInfo{Members: []string{hexapolicy.SAnyAuth}} - subj = hexapolicy.SubjectInfo{Members: []string{fmt.Sprintf("Type:%s", principal.Path.String())}} + // subj = []string{hexapolicy.SubjectAnyAuth} + subj = []string{fmt.Sprintf("Type:%s", principal.Path.String())} case cedarParser.MatchIn: - subj = hexapolicy.SubjectInfo{Members: []string{fmt.Sprintf("Group:%s", principal.Entity.String())}} + subj = []string{fmt.Sprintf("Group:%s", principal.Entity.String())} case cedarParser.MatchIsIn: isType := principal.Path.String() inEntity := strings.Replace(principal.Entity.String(), "::", ":", 1) - subj = hexapolicy.SubjectInfo{Members: []string{fmt.Sprintf("%s.(%s)", inEntity, isType)}} + 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.Subject = subj + pp.HexaPolicy.Subjects = subj } func (pp *PolicyPair) HasMultiSubjects() bool { - if pp.HexaPolicy != nil && pp.HexaPolicy.Subject.Members != nil { - return len(pp.HexaPolicy.Subject.Members) > 0 + 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.Subject.Members != nil { + if pp.HexaPolicy == nil || pp.HexaPolicy.Subjects != nil { return nil } - members := pp.HexaPolicy.Subject.Members - if len(members) == 0 || strings.EqualFold(members[0], hexapolicy.SAnyUser) { + members := pp.HexaPolicy.Subjects + if len(members) == 0 || strings.EqualFold(members[0], hexapolicy.SubjectAnyUser) { return []string{"principal,"} } res := make([]string, 0) @@ -264,7 +267,9 @@ func (pp *PolicyPair) mapCedarAction() { for _, entity := range action.Entities { aInfo = append(aInfo, hexapolicy.ActionInfo{ActionUri: mapCedarEntityName(entity)}) } - + default: + fmt.Println(fmt.Sprintf("Unexpected action type: %T, value: %s", action, action.String())) + aInfo = append(aInfo, hexapolicy.ActionInfo{ActionUri: action.String()}) } pp.HexaPolicy.Actions = aInfo @@ -331,6 +336,9 @@ func (pp *PolicyPair) mapCedarResource() { pp.HexaPolicy.Object = hexapolicy.ObjectInfo{ResourceID: fmt.Sprintf("[%s].(%s)", resource.Entity.String(), resource.Path.String())} case cedarParser.MatchIn: pp.HexaPolicy.Object = hexapolicy.ObjectInfo{ResourceID: 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{ResourceID: fmt.Sprintf("[%s]", resource.String())} } } diff --git a/models/formats/gcpBind/google_bind_policy.go b/models/formats/gcpBind/google_bind_policy.go index 1ef47a1..404aa44 100644 --- a/models/formats/gcpBind/google_bind_policy.go +++ b/models/formats/gcpBind/google_bind_policy.go @@ -71,17 +71,17 @@ func (m *GooglePolicyMapper) MapBindingToPolicy(objectId string, binding iam.Bin policy := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: convertRoleToAction(binding.Role), - Subject: hexapolicy.SubjectInfo{Members: binding.Members}, + Subjects: binding.Members, Object: hexapolicy.ObjectInfo{ResourceID: objectId}, Condition: &condition, } return policy, nil } policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, - Actions: convertRoleToAction(binding.Role), - Subject: hexapolicy.SubjectInfo{Members: binding.Members}, - Object: hexapolicy.ObjectInfo{ResourceID: objectId}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, + Actions: convertRoleToAction(binding.Role), + Subjects: binding.Members, + Object: hexapolicy.ObjectInfo{ResourceID: objectId}, } return policy, nil @@ -102,7 +102,7 @@ func (m *GooglePolicyMapper) MapPolicyToBinding(policy hexapolicy.PolicyInfo) (* } return &iam.Binding{ Condition: condExpr, - Members: policy.Subject.Members, + Members: policy.Subjects, Role: convertActionToRole(policy), }, nil } diff --git a/models/formats/gcpBind/test/data.json b/models/formats/gcpBind/test/data.json index a246198..fc419b0 100644 --- a/models/formats/gcpBind/test/data.json +++ b/models/formats/gcpBind/test/data.json @@ -2,19 +2,17 @@ "policies": [ { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ { "actionUri": "http:GET:/" } ], - "subject": { - "members": [ - "allusers", - "allauthenticated" - ] - }, + "subjects": [ + "allusers", + "allauthenticated" + ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" @@ -25,7 +23,7 @@ }, { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ { @@ -35,20 +33,18 @@ "actionUri": "http:GET:/marketing" } ], - "subject": { - "members": [ - "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" - ] - }, + "subjects": [ + "allauthenticated", + "sales@hexaindustries.io", + "marketing@hexaindustries.io" + ], "object": { "resource_id": "bResourceId" } }, { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ { @@ -58,11 +54,9 @@ "actionUri": "http:POST:/accounting" } ], - "subject": { - "members": [ - "accounting@hexaindustries.io" - ] - }, + "subjects": [ + "accounting@hexaindustries.io" + ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" @@ -73,31 +67,27 @@ }, { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ { "actionUri": "http:GET:/humanresources" } ], - "subject": { - "members": [ - "humanresources@hexaindustries.io" - ] - }, + "subjects": [ + "humanresources@hexaindustries.io" + ], "object": { "resource_id": "aResourceId" } }, { "meta": { - "version": "0.6" - }, - "subject": { - "members": [ - "user:gerry@strata.io" - ] + "version": "0.7" }, + "subjects": [ + "user:gerry@strata.io" + ], "actions": [ { "actionUri": "gcp:roles/iap.httpsResourceAccessor" diff --git a/models/rar/policy_transformer.go b/models/rar/policy_transformer.go index b88f1d9..69b36fc 100644 --- a/models/rar/policy_transformer.go +++ b/models/rar/policy_transformer.go @@ -20,10 +20,10 @@ func BuildPolicies(resourceActionRolesList []ResourceActionRoles) []hexapolicy.P roles := one.Roles slices.Sort(roles) policies = append(policies, hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion, ProviderType: "RARmodel"}, - Actions: []hexapolicy.ActionInfo{{ActionUriPrefix + httpMethod}}, - Subject: hexapolicy.SubjectInfo{Members: roles}, - Object: hexapolicy.ObjectInfo{ResourceID: one.Resource}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion, ProviderType: "RARmodel"}, + Actions: []hexapolicy.ActionInfo{{ActionUriPrefix + httpMethod}}, + Subjects: roles, + Object: hexapolicy.ObjectInfo{ResourceID: one.Resource}, }) } @@ -49,14 +49,14 @@ func FlattenPolicy(origPolicies []hexapolicy.PolicyInfo) []hexapolicy.PolicyInfo matchingPolicy, found := resActionPolicyMap[lookupKey] var existingMembers []string if found { - existingMembers = matchingPolicy.Subject.Members + existingMembers = matchingPolicy.Subjects } - newMembers := CompactMembers(existingMembers, pol.Subject.Members) + newMembers := CompactMembers(existingMembers, pol.Subjects) newPol := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, - Actions: []hexapolicy.ActionInfo{{ActionUri: act.ActionUri}}, - Subject: hexapolicy.SubjectInfo{Members: newMembers}, - Object: hexapolicy.ObjectInfo{ResourceID: resource}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, + Actions: []hexapolicy.ActionInfo{{ActionUri: act.ActionUri}}, + Subjects: newMembers, + Object: hexapolicy.ObjectInfo{ResourceID: resource}, } resActionPolicyMap[lookupKey] = newPol @@ -126,16 +126,16 @@ func ResourcePolicyMap(origPolicies []hexapolicy.PolicyInfo) map[string]hexapoli var existingMembers []string if existing, exists := resPolicyMap[resource]; exists { existingActions = existing.Actions - existingMembers = existing.Subject.Members + existingMembers = existing.Subjects } mergedActions := CompactActions(existingActions, pol.Actions) - newMembers := CompactMembers(existingMembers, pol.Subject.Members) + newMembers := CompactMembers(existingMembers, pol.Subjects) newPol := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: mergedActions, - Subject: hexapolicy.SubjectInfo{Members: newMembers}, + Subjects: hexapolicy.SubjectInfo{Members: newMembers}, Object: hexapolicy.ObjectInfo{ResourceID: resource}, } diff --git a/models/rar/policy_transformer_test.go b/models/rar/policy_transformer_test.go index e9c9d8a..158af65 100644 --- a/models/rar/policy_transformer_test.go +++ b/models/rar/policy_transformer_test.go @@ -1,338 +1,341 @@ package rar_test import ( - rar "github.com/hexa-org/policy-mapper/models/rar" - "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + rar "github.com/hexa-org/policy-mapper/models/rar" + "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "testing" + "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestBuildPolicies_EmptyNil(t *testing.T) { - policies := rar.BuildPolicies(nil) - assert.NotNil(t, policies) - assert.Empty(t, policies) + policies := rar.BuildPolicies(nil) + assert.NotNil(t, policies) + assert.Empty(t, policies) - policies = rar.BuildPolicies([]rar.ResourceActionRoles{}) - assert.NotNil(t, policies) - assert.Empty(t, policies) + policies = rar.BuildPolicies([]rar.ResourceActionRoles{}) + assert.NotNil(t, policies) + assert.Empty(t, policies) } func TestBuildPolicies(t *testing.T) { - existingActionRoles := map[string][]string{ - policytestsupport.ActionGetProfile: {"2-some-profile-role", "1-some-profile-role"}, - policytestsupport.ActionGetHrUs: {"2-some-hr-role", "1-some-hr-role"}, - } - resourceRoles := policytestsupport.MakeRarList(existingActionRoles) - policies := rar.BuildPolicies(resourceRoles) - assert.NotNil(t, policies) - assert.Len(t, policies, 2) - - actPol := policies[0] - assert.Equal(t, policytestsupport.ResourceHrUs, actPol.Object.ResourceID) - assert.Equal(t, "http:GET", actPol.Actions[0].ActionUri) - assert.Equal(t, []string{"1-some-hr-role", "2-some-hr-role"}, actPol.Subject.Members) - - actPol = policies[1] - assert.Equal(t, policytestsupport.ResourceProfile, actPol.Object.ResourceID) - assert.Equal(t, "http:GET", actPol.Actions[0].ActionUri) - assert.Equal(t, []string{"1-some-profile-role", "2-some-profile-role"}, actPol.Subject.Members) + existingActionRoles := map[string][]string{ + policytestsupport.ActionGetProfile: {"2-some-profile-role", "1-some-profile-role"}, + policytestsupport.ActionGetHrUs: {"2-some-hr-role", "1-some-hr-role"}, + } + resourceRoles := policytestsupport.MakeRarList(existingActionRoles) + policies := rar.BuildPolicies(resourceRoles) + assert.NotNil(t, policies) + assert.Len(t, policies, 2) + + actPol := policies[0] + assert.Equal(t, policytestsupport.ResourceHrUs, actPol.Object.ResourceID) + assert.Equal(t, "http:GET", actPol.Actions[0].ActionUri) + var res []string + res = actPol.Subjects + assert.Equal(t, []string{"1-some-hr-role", "2-some-hr-role"}, res) + + actPol = policies[1] + assert.Equal(t, policytestsupport.ResourceProfile, actPol.Object.ResourceID) + assert.Equal(t, "http:GET", actPol.Actions[0].ActionUri) + res = actPol.Subjects + assert.Equal(t, []string{"1-some-profile-role", "2-some-profile-role"}, res) } func TestCompactActions_NilEmpty(t *testing.T) { - tests := []struct { - name string - existing []hexapolicy.ActionInfo - newOnes []hexapolicy.ActionInfo - }{ - {name: "nils", existing: nil, newOnes: nil}, - {name: "empties", existing: []hexapolicy.ActionInfo{}, newOnes: []hexapolicy.ActionInfo{}}, - {name: "existing nil", existing: nil, newOnes: []hexapolicy.ActionInfo{}}, - {name: "newOnes nil", existing: []hexapolicy.ActionInfo{}, newOnes: nil}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - compacted := rar.CompactActions(tt.existing, tt.newOnes) - assert.NotNil(t, compacted) - assert.Empty(t, compacted) - }) - } + tests := []struct { + name string + existing []hexapolicy.ActionInfo + newOnes []hexapolicy.ActionInfo + }{ + {name: "nils", existing: nil, newOnes: nil}, + {name: "empties", existing: []hexapolicy.ActionInfo{}, newOnes: []hexapolicy.ActionInfo{}}, + {name: "existing nil", existing: nil, newOnes: []hexapolicy.ActionInfo{}}, + {name: "newOnes nil", existing: []hexapolicy.ActionInfo{}, newOnes: nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + compacted := rar.CompactActions(tt.existing, tt.newOnes) + assert.NotNil(t, compacted) + assert.Empty(t, compacted) + }) + } } func TestCompactActions_AllWhitespace(t *testing.T) { - arr1 := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: " "}, {ActionUri: " "}, - } - compacted := rar.CompactActions(arr1, arr1) - assert.NotNil(t, compacted) - assert.Empty(t, compacted) + arr1 := []hexapolicy.ActionInfo{ + {ActionUri: ""}, {ActionUri: " "}, {ActionUri: " "}, + } + compacted := rar.CompactActions(arr1, arr1) + assert.NotNil(t, compacted) + assert.Empty(t, compacted) } func TestCompactActions_DuplicatesAndWhitespace(t *testing.T) { - arr1 := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, - } - arr2 := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, - } - - compacted := rar.CompactActions(arr1, arr2) - assert.NotNil(t, compacted) - assert.Equal(t, []hexapolicy.ActionInfo{ - {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, - }, compacted) + arr1 := []hexapolicy.ActionInfo{ + {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, + } + arr2 := []hexapolicy.ActionInfo{ + {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, + } + + compacted := rar.CompactActions(arr1, arr2) + assert.NotNil(t, compacted) + assert.Equal(t, []hexapolicy.ActionInfo{ + {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, + }, compacted) } func TestCompactActions_UniqueAndWhitespace(t *testing.T) { - arr1 := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, - } - arr2 := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "4four"}, {ActionUri: " "}, {ActionUri: "5five"}, - } - - compacted := rar.CompactActions(arr1, arr2) - assert.NotNil(t, compacted) - assert.Equal(t, []hexapolicy.ActionInfo{ - {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, {ActionUri: "4four"}, {ActionUri: "5five"}, - }, compacted) + arr1 := []hexapolicy.ActionInfo{ + {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, + } + arr2 := []hexapolicy.ActionInfo{ + {ActionUri: ""}, {ActionUri: "4four"}, {ActionUri: " "}, {ActionUri: "5five"}, + } + + compacted := rar.CompactActions(arr1, arr2) + assert.NotNil(t, compacted) + assert.Equal(t, []hexapolicy.ActionInfo{ + {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, {ActionUri: "4four"}, {ActionUri: "5five"}, + }, compacted) } func TestCompactActions_OneEmptyNil(t *testing.T) { - arr := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, - } - - compacted := rar.CompactActions(arr, nil) - assert.NotNil(t, compacted) - assert.Equal(t, []hexapolicy.ActionInfo{ - {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, - }, compacted) - - compacted = rar.CompactActions(nil, arr) - assert.NotNil(t, compacted) - assert.Equal(t, []hexapolicy.ActionInfo{ - {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, - }, compacted) + arr := []hexapolicy.ActionInfo{ + {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, + } + + compacted := rar.CompactActions(arr, nil) + assert.NotNil(t, compacted) + assert.Equal(t, []hexapolicy.ActionInfo{ + {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, + }, compacted) + + compacted = rar.CompactActions(nil, arr) + assert.NotNil(t, compacted) + assert.Equal(t, []hexapolicy.ActionInfo{ + {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, + }, compacted) } func TestCompactMembers_Nil(t *testing.T) { - tests := []struct { - name string - existing []string - newOnes []string - }{ - {name: "nils", existing: nil, newOnes: nil}, - {name: "empties", existing: []string{}, newOnes: []string{}}, - {name: "existing nil, newOnes empty", existing: nil, newOnes: []string{}}, - {name: "existing empty, newOnes nil", existing: []string{}, newOnes: nil}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - compacted := rar.CompactMembers(tt.existing, tt.newOnes) - assert.NotNil(t, compacted) - assert.Empty(t, compacted) - }) - } + tests := []struct { + name string + existing []string + newOnes []string + }{ + {name: "nils", existing: nil, newOnes: nil}, + {name: "empties", existing: []string{}, newOnes: []string{}}, + {name: "existing nil, newOnes empty", existing: nil, newOnes: []string{}}, + {name: "existing empty, newOnes nil", existing: []string{}, newOnes: nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + compacted := rar.CompactMembers(tt.existing, tt.newOnes) + assert.NotNil(t, compacted) + assert.Empty(t, compacted) + }) + } } func TestCompactMembers_AllWhitespace(t *testing.T) { - compacted := rar.CompactMembers([]string{"", "", " ", " ", "", " "}, []string{"", "", " ", " ", "", " "}) - assert.NotNil(t, compacted) - assert.Empty(t, compacted) + compacted := rar.CompactMembers([]string{"", "", " ", " ", "", " "}, []string{"", "", " ", " ", "", " "}) + assert.NotNil(t, compacted) + assert.Empty(t, compacted) } func TestCompactMembers_DuplicatesAndWhitespace(t *testing.T) { - arr := []string{"hello", "", "how", "are", " ", "you", "hello", " ", "hello", "", "how", "are", "you", " "} - compacted := rar.CompactMembers(arr, arr) - assert.Equal(t, []string{"are", "hello", "how", "you"}, compacted) + arr := []string{"hello", "", "how", "are", " ", "you", "hello", " ", "hello", "", "how", "are", "you", " "} + compacted := rar.CompactMembers(arr, arr) + assert.Equal(t, []string{"are", "hello", "how", "you"}, compacted) } func TestCompactMembers_UniqueWhitespace(t *testing.T) { - arr1 := []string{"hello", "", "how", "are", " ", "you"} - arr2 := []string{"i", "", "am", "find", " ", "thank", "you"} - compacted := rar.CompactMembers(arr1, arr2) - assert.Equal(t, []string{"am", "are", "find", "hello", "how", "i", "thank", "you"}, compacted) + arr1 := []string{"hello", "", "how", "are", " ", "you"} + arr2 := []string{"i", "", "am", "find", " ", "thank", "you"} + compacted := rar.CompactMembers(arr1, arr2) + assert.Equal(t, []string{"am", "are", "find", "hello", "how", "i", "thank", "you"}, compacted) } func TestCompactMembers_OneNil(t *testing.T) { - arr := []string{"1one", "", "2two", "3three", " ", "4four"} - compacted := rar.CompactMembers(arr, nil) - assert.Equal(t, []string{"1one", "2two", "3three", "4four"}, compacted) + arr := []string{"1one", "", "2two", "3three", " ", "4four"} + compacted := rar.CompactMembers(arr, nil) + assert.Equal(t, []string{"1one", "2two", "3three", "4four"}, compacted) - compacted = rar.CompactMembers(nil, arr) - assert.Equal(t, []string{"1one", "2two", "3three", "4four"}, compacted) + compacted = rar.CompactMembers(nil, arr) + assert.Equal(t, []string{"1one", "2two", "3three", "4four"}, compacted) } func TestFlattenPolicy_ReturnsEmpty(t *testing.T) { - actPolicies := rar.FlattenPolicy([]hexapolicy.PolicyInfo{}) - assert.NotNil(t, actPolicies) - assert.Empty(t, actPolicies) + actPolicies := rar.FlattenPolicy([]hexapolicy.PolicyInfo{}) + assert.NotNil(t, actPolicies) + assert.Empty(t, actPolicies) - actPolicies = rar.FlattenPolicy(nil) - assert.NotNil(t, actPolicies) - assert.Empty(t, actPolicies) + actPolicies = rar.FlattenPolicy(nil) + assert.NotNil(t, actPolicies) + assert.Empty(t, actPolicies) } func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { - pol1 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "1act"}, {ActionUri: " "}, {ActionUri: "2act"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"1mem", "", "2mem"}}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, - } - - pol2 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "3act"}, {ActionUri: " "}, {ActionUri: "4act"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"1mem", "", "2mem"}}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, - } - - orig := []hexapolicy.PolicyInfo{pol1, pol2} - actPolicies := rar.FlattenPolicy(orig) - assert.NotNil(t, actPolicies) - assert.Equal(t, 4, len(actPolicies)) - - expResource := "resource1" - expActions := []string{"1act", "2act", "3act", "4act"} - expMembers := []string{"1mem", "2mem"} - - for i, actPol := range actPolicies { - assert.Equal(t, expResource, actPol.Object.ResourceID) - assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) - assert.Equal(t, expMembers, actPol.Subject.Members) - } + pol1 := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{ + {ActionUri: ""}, {ActionUri: "1act"}, {ActionUri: " "}, {ActionUri: "2act"}}, + Subjects: []string{"1mem", "", "2mem"}, + Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + } + + pol2 := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{ + {ActionUri: ""}, {ActionUri: "3act"}, {ActionUri: " "}, {ActionUri: "4act"}}, + Subjects: []string{"1mem", "", "2mem"}, + Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + } + + orig := []hexapolicy.PolicyInfo{pol1, pol2} + actPolicies := rar.FlattenPolicy(orig) + assert.NotNil(t, actPolicies) + assert.Equal(t, 4, len(actPolicies)) + + expResource := "resource1" + expActions := []string{"1act", "2act", "3act", "4act"} + expMembers := []string{"1mem", "2mem"} + + for i, actPol := range actPolicies { + assert.Equal(t, expResource, actPol.Object.ResourceID) + assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) + assert.Equal(t, expMembers, actPol.Subjects.String()) + } } func TestFlattenPolicy_NoResource(t *testing.T) { - pol1 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{ActionUri: "1act"}, {ActionUri: "2act"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"1mem", "", "2mem"}}, - } - pol2 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{ActionUri: "1act"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"1mem", "2mem"}}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, - } - - tests := []struct { - name string - inputPolicies []hexapolicy.PolicyInfo - expLen int - }{ - { - name: "Single policy without resource", - inputPolicies: []hexapolicy.PolicyInfo{pol1}, - }, - { - name: "Two policies one with, one without resource", - inputPolicies: []hexapolicy.PolicyInfo{pol1, pol2}, - expLen: 1, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - orig := tt.inputPolicies - actPolicies := rar.FlattenPolicy(orig) - assert.NotNil(t, actPolicies) - assert.Len(t, actPolicies, tt.expLen) - }) - } + pol1 := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{{ActionUri: "1act"}, {ActionUri: "2act"}}, + Subjects: []string{"1mem", "", "2mem"}, + } + pol2 := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{{ActionUri: "1act"}}, + Subjects: []string{"1mem", "2mem"}, + Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + } + + tests := []struct { + name string + inputPolicies []hexapolicy.PolicyInfo + expLen int + }{ + { + name: "Single policy without resource", + inputPolicies: []hexapolicy.PolicyInfo{pol1}, + }, + { + name: "Two policies one with, one without resource", + inputPolicies: []hexapolicy.PolicyInfo{pol1, pol2}, + expLen: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + orig := tt.inputPolicies + actPolicies := rar.FlattenPolicy(orig) + assert.NotNil(t, actPolicies) + assert.Len(t, actPolicies, tt.expLen) + }) + } } func TestFlattenPolicy_NoActions(t *testing.T) { - pol1 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Subject: hexapolicy.SubjectInfo{Members: []string{"1mem", "", "2mem"}}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, - } - orig := []hexapolicy.PolicyInfo{pol1} - actPolicies := rar.FlattenPolicy(orig) - assert.NotNil(t, actPolicies) - assert.Equal(t, []hexapolicy.PolicyInfo{}, actPolicies) + pol1 := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Subjects: []string{"1mem", "", "2mem"}, + Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + } + orig := []hexapolicy.PolicyInfo{pol1} + actPolicies := rar.FlattenPolicy(orig) + assert.NotNil(t, actPolicies) + assert.Equal(t, []hexapolicy.PolicyInfo{}, actPolicies) } func TestFlattenPolicy_NoMembers(t *testing.T) { - pol1 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{ActionUri: "1act"}, {ActionUri: "2act"}}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, - } - orig := []hexapolicy.PolicyInfo{pol1} - actPolicies := rar.FlattenPolicy(orig) - assert.NotNil(t, actPolicies) - assert.Equal(t, 2, len(actPolicies)) - - expActions := []string{"1act", "2act"} - for i, actPol := range actPolicies { - assert.Equal(t, "resource1", actPol.Object.ResourceID) - assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) - assert.NotNil(t, actPol.Subject.Members) - assert.Equal(t, []string{}, actPol.Subject.Members) - } + pol1 := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{{ActionUri: "1act"}, {ActionUri: "2act"}}, + Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + } + orig := []hexapolicy.PolicyInfo{pol1} + actPolicies := rar.FlattenPolicy(orig) + assert.NotNil(t, actPolicies) + assert.Equal(t, 2, len(actPolicies)) + + expActions := []string{"1act", "2act"} + for i, actPol := range actPolicies { + assert.Equal(t, "resource1", actPol.Object.ResourceID) + assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) + assert.NotNil(t, actPol.Subjects) + assert.Equal(t, []string{}, actPol.Subjects.String()) + } } func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { - pol1a := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{ - {ActionUri: "1act"}, {ActionUri: "2act"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"1mem", "2mem"}}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, - } - - pol1b := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{ - {ActionUri: "1act"}, {ActionUri: "2act"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"3mem", "4mem"}}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, - } - - pol2 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{ - {ActionUri: "3act"}, {ActionUri: "4act"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"1mem", "2mem"}}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource2"}, - } - - orig := []hexapolicy.PolicyInfo{pol1a, pol2, pol1b} - actPolicies := rar.FlattenPolicy(orig) - - assert.NotNil(t, actPolicies) - assert.Equal(t, 4, len(actPolicies)) - - expResource := "resource1" - expMembers := []string{"1mem", "2mem", "3mem", "4mem"} - expActions := []string{"1act", "2act"} - for i := 0; i < len(expActions); i++ { - actPol := actPolicies[i] - assert.NotNil(t, actPol) - assert.Equal(t, expResource, actPol.Object.ResourceID) - assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) - assert.Equal(t, expMembers, actPol.Subject.Members) - } - - expResource = "resource2" - expMembers = []string{"1mem", "2mem"} - expActions = []string{"3act", "4act"} - for i := 0; i < len(expActions); i++ { - actPol := actPolicies[i+2] - assert.NotNil(t, actPol) - assert.Equal(t, expResource, actPol.Object.ResourceID) - assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) - assert.Equal(t, expMembers, actPol.Subject.Members) - } + pol1a := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{ + {ActionUri: "1act"}, {ActionUri: "2act"}}, + Subjects: []string{"1mem", "2mem"}, + Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + } + + pol1b := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{ + {ActionUri: "1act"}, {ActionUri: "2act"}}, + Subjects: []string{"3mem", "4mem"}, + Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + } + + pol2 := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{ + {ActionUri: "3act"}, {ActionUri: "4act"}}, + Subjects: []string{"1mem", "2mem"}, + Object: hexapolicy.ObjectInfo{ResourceID: "resource2"}, + } + + orig := []hexapolicy.PolicyInfo{pol1a, pol2, pol1b} + actPolicies := rar.FlattenPolicy(orig) + + assert.NotNil(t, actPolicies) + assert.Equal(t, 4, len(actPolicies)) + + expResource := "resource1" + expMembers := []string{"1mem", "2mem", "3mem", "4mem"} + expActions := []string{"1act", "2act"} + for i := 0; i < len(expActions); i++ { + actPol := actPolicies[i] + assert.NotNil(t, actPol) + assert.Equal(t, expResource, actPol.Object.ResourceID) + assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) + assert.Equal(t, expMembers, actPol.Subjects.String()) + } + + expResource = "resource2" + expMembers = []string{"1mem", "2mem"} + expActions = []string{"3act", "4act"} + for i := 0; i < len(expActions); i++ { + actPol := actPolicies[i+2] + assert.NotNil(t, actPol) + assert.Equal(t, expResource, actPol.Object.ResourceID) + assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) + assert.Equal(t, expMembers, actPol.Subjects.String()) + } } diff --git a/models/rar/resource_action_role_policy_support.go b/models/rar/resource_action_role_policy_support.go index 2e6d1c4..47c747f 100644 --- a/models/rar/resource_action_role_policy_support.go +++ b/models/rar/resource_action_role_policy_support.go @@ -1,10 +1,10 @@ package rar import ( - "github.com/hexa-org/policy-mapper/models/rar/functionalsupport" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/models/rar/functionalsupport" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - log "golang.org/x/exp/slog" + log "golang.org/x/exp/slog" ) // CalcResourceActionRolesForUpdate @@ -13,37 +13,37 @@ import ( // If policyInfos is empty, returns empty slice func CalcResourceActionRolesForUpdate(existing []ResourceActionRoles, policyInfos []hexapolicy.PolicyInfo) []ResourceActionRoles { - existingRarMap := mapResourceActionRoles(existing) - newPolicies := FlattenPolicy(policyInfos) + existingRarMap := mapResourceActionRoles(existing) + newPolicies := FlattenPolicy(policyInfos) - if len(existingRarMap) == 0 || len(newPolicies) == 0 { - return []ResourceActionRoles{} - } + if len(existingRarMap) == 0 || len(newPolicies) == 0 { + return []ResourceActionRoles{} + } - rarUpdateList := make([]ResourceActionRoles, 0) + rarUpdateList := make([]ResourceActionRoles, 0) - for _, pol := range newPolicies { - polResource := pol.Object.ResourceID - polAction := pol.Actions[0].ActionUri - polRoles := pol.Subject.Members + for _, pol := range newPolicies { + polResource := pol.Object.ResourceID + polAction := pol.Actions[0].ActionUri + polRoles := pol.Subjects - newRarKey := MakeRarKeyForPolicy(polAction, polResource) - existingRar, found := existingRarMap[newRarKey] - if !found { - log.Warn("Ignoring policy as no existing resource action matches", "resource", polResource, "action", polAction) - continue - } + newRarKey := MakeRarKeyForPolicy(polAction, polResource) + existingRar, found := existingRarMap[newRarKey] + if !found { + log.Warn("Ignoring policy as no existing resource action matches", "resource", polResource, "action", polAction) + continue + } - hasChanges, rolesToUpdate := findRolesToUpdate(existingRar.Roles, polRoles) - if !hasChanges { - continue - } + hasChanges, rolesToUpdate := findRolesToUpdate(existingRar.Roles, polRoles) + if !hasChanges { + continue + } - updatedRar := NewResourceActionUriRoles(polResource, polAction, rolesToUpdate) - rarUpdateList = append(rarUpdateList, updatedRar) - } + updatedRar := NewResourceActionUriRoles(polResource, polAction, rolesToUpdate) + rarUpdateList = append(rarUpdateList, updatedRar) + } - return rarUpdateList + return rarUpdateList } // rolesToKeep - removes from existingRoles, those that are not present in newRoles @@ -52,23 +52,23 @@ func CalcResourceActionRolesForUpdate(existing []ResourceActionRoles, policyInfo // slice - list of changes (i.e. new - existing) // OR nil/empty if all existing need to be removed. func findRolesToUpdate(existingRoles, newRoles []string) (hasChanges bool, changes []string) { - existingOnly, matches, newOnly := functionalsupport.DiffUnique(existingRoles, newRoles) - if len(existingOnly) == 0 && len(newOnly) == 0 { - // no changes - return - } + existingOnly, matches, newOnly := functionalsupport.DiffUnique(existingRoles, newRoles) + if len(existingOnly) == 0 && len(newOnly) == 0 { + // no changes + return + } - hasChanges = true - changes = append(changes, matches...) // keep matching - changes = append(changes, newOnly...) // keep new ones - changes = functionalsupport.SortCompact(changes) // keep sorted and compact - return + hasChanges = true + changes = append(changes, matches...) // keep matching + changes = append(changes, newOnly...) // keep new ones + changes = functionalsupport.SortCompact(changes) // keep sorted and compact + return } func mapResourceActionRoles(rarList []ResourceActionRoles) map[string]ResourceActionRoles { - rarMap := make(map[string]ResourceActionRoles) - for _, rar := range rarList { - rarMap[rar.Name()] = rar - } - return rarMap + rarMap := make(map[string]ResourceActionRoles) + for _, rar := range rarList { + rarMap[rar.Name()] = rar + } + return rarMap } diff --git a/models/rar/testsupport/policytestsupport/policy_checker_support.go b/models/rar/testsupport/policytestsupport/policy_checker_support.go index 93b4f6e..86fa634 100644 --- a/models/rar/testsupport/policytestsupport/policy_checker_support.go +++ b/models/rar/testsupport/policytestsupport/policy_checker_support.go @@ -1,89 +1,88 @@ package policytestsupport import ( - "fmt" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "github.com/stretchr/testify/assert" - "reflect" - "sort" - "testing" + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/stretchr/testify/assert" ) func ContainsPolicies(t *testing.T, expPolicies []hexapolicy.PolicyInfo, actPolicies []hexapolicy.PolicyInfo) bool { - for _, act := range actPolicies { - if HasPolicy(expPolicies, act) { - return true - } - } + for _, act := range actPolicies { + if HasPolicy(expPolicies, act) { + return true + } + } - return assert.Fail(t, fmt.Sprintf("Policies do not match expected: \n expected: %v\n actual: %v", expPolicies, actPolicies)) + return assert.Fail(t, fmt.Sprintf("Policies do not match expected: \n expected: %v\n actual: %v", expPolicies, actPolicies)) } func HasPolicy(expPolicies []hexapolicy.PolicyInfo, act hexapolicy.PolicyInfo) bool { - for _, exp := range expPolicies { - if MatchPolicy(exp, act) { - return true - } - } - return false + for _, exp := range expPolicies { + if MatchPolicy(exp, act) { + return true + } + } + return false } func MatchPolicy(exp hexapolicy.PolicyInfo, act hexapolicy.PolicyInfo) bool { - if exp.Object.ResourceID != act.Object.ResourceID { - return false - } + if exp.Object.ResourceID != act.Object.ResourceID { + return false + } - expActions := sortAction(exp.Actions) - actActions := sortAction(act.Actions) - if !reflect.DeepEqual(expActions, actActions) { - return false - } + expActions := sortAction(exp.Actions) + actActions := sortAction(act.Actions) + if !reflect.DeepEqual(expActions, actActions) { + return false + } - expMembers := sortMembers(exp.Subject) - actMembers := sortMembers(act.Subject) - return reflect.DeepEqual(expMembers, actMembers) + expMembers := sortMembers(exp.Subjects) + actMembers := sortMembers(act.Subjects) + return reflect.DeepEqual(expMembers, actMembers) } func MakePolicies(actionMembers map[string][]string, resourceId string) []hexapolicy.PolicyInfo { - policies := make([]hexapolicy.PolicyInfo, 0) + policies := make([]hexapolicy.PolicyInfo, 0) - for action, membersNoPrefix := range actionMembers { - members := make([]string, 0) - for _, mem := range membersNoPrefix { - members = append(members, "user:"+mem) - } + for action, membersNoPrefix := range actionMembers { + members := make([]string, 0) + for _, mem := range membersNoPrefix { + members = append(members, "user:"+mem) + } - pol := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{action}}, - Subject: hexapolicy.SubjectInfo{Members: members}, - Object: hexapolicy.ObjectInfo{ - ResourceID: resourceId, - }, - } + pol := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{{action}}, + Subjects: members, + Object: hexapolicy.ObjectInfo{ + ResourceID: resourceId, + }, + } - policies = append(policies, pol) - } + policies = append(policies, pol) + } - return policies + return policies } func sortAction(orig []hexapolicy.ActionInfo) []hexapolicy.ActionInfo { - sorted := make([]hexapolicy.ActionInfo, 0) - sorted = append(sorted, orig...) - sort.Slice(sorted, func(i, j int) bool { - return sorted[i].ActionUri <= sorted[j].ActionUri - }) - return sorted + sorted := make([]hexapolicy.ActionInfo, 0) + sorted = append(sorted, orig...) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].ActionUri <= sorted[j].ActionUri + }) + return sorted } func sortMembers(subInfo hexapolicy.SubjectInfo) hexapolicy.SubjectInfo { - sorted := make([]string, 0) - for _, one := range subInfo.Members { - sorted = append(sorted, one) - } - sort.Strings(sorted) - return hexapolicy.SubjectInfo{ - Members: sorted, - } + sorted := make([]string, 0) + for _, one := range subInfo { + sorted = append(sorted, one) + } + sort.Strings(sorted) + return sorted } diff --git a/models/rar/testsupport/policytestsupport/policy_data_support.go b/models/rar/testsupport/policytestsupport/policy_data_support.go index 1434768..5a42b58 100644 --- a/models/rar/testsupport/policytestsupport/policy_data_support.go +++ b/models/rar/testsupport/policytestsupport/policy_data_support.go @@ -1,9 +1,10 @@ package policytestsupport import ( - "fmt" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "strings" + "fmt" + "strings" + + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" ) const PolicyObjectResourceId = "some-resource-id" @@ -23,90 +24,90 @@ var UserEmailGetProfile = MakeEmail(UserIdGetProfile) var UserEmailGetHrUsAndProfile = MakeEmail(UserIdGetHrUsAndProfile) func MakeEmail(principalId string) string { - emailPrefix, found := strings.CutSuffix(principalId, "-id") - if !found { - emailPrefix = "user-not-found" - } - emailPrefix = strings.ReplaceAll(emailPrefix, "-", "") - return emailPrefix + "@stratatest.io" + emailPrefix, found := strings.CutSuffix(principalId, "-id") + if !found { + emailPrefix = "user-not-found" + } + emailPrefix = strings.ReplaceAll(emailPrefix, "-", "") + return emailPrefix + "@stratatest.io" } func MakePrincipalEmailMap() map[string]string { - return map[string]string{ - UserIdGetHrUs: MakeEmail(UserIdGetHrUs), - UserIdGetProfile: MakeEmail(UserIdGetProfile), - UserIdGetHrUsAndProfile: MakeEmail(UserIdGetHrUsAndProfile), - } + return map[string]string{ + UserIdGetHrUs: MakeEmail(UserIdGetHrUs), + UserIdGetProfile: MakeEmail(UserIdGetProfile), + UserIdGetHrUsAndProfile: MakeEmail(UserIdGetHrUsAndProfile), + } } type ActionMembers struct { - MemberIds []string - Emails []string + MemberIds []string + Emails []string } func MakeActionMembers() map[string]ActionMembers { - return map[string]ActionMembers{ - ActionGetHrUs: { - MemberIds: []string{UserIdGetHrUs, UserIdGetHrUsAndProfile}, - Emails: []string{UserEmailGetHrUs, UserEmailGetHrUsAndProfile}, - }, - ActionGetProfile: { - MemberIds: []string{UserIdGetProfile, UserIdGetHrUsAndProfile}, - Emails: []string{UserEmailGetProfile, UserEmailGetHrUsAndProfile}, - }, - } + return map[string]ActionMembers{ + ActionGetHrUs: { + MemberIds: []string{UserIdGetHrUs, UserIdGetHrUsAndProfile}, + Emails: []string{UserEmailGetHrUs, UserEmailGetHrUsAndProfile}, + }, + ActionGetProfile: { + MemberIds: []string{UserIdGetProfile, UserIdGetHrUsAndProfile}, + Emails: []string{UserEmailGetProfile, UserEmailGetHrUsAndProfile}, + }, + } } func MakeTestPolicies(actionMembers map[string]ActionMembers) []hexapolicy.PolicyInfo { - policies := make([]hexapolicy.PolicyInfo, 0) - for action, members := range actionMembers { - policies = append(policies, MakeTestPolicy(PolicyObjectResourceId, action, members)) - } - return policies + policies := make([]hexapolicy.PolicyInfo, 0) + for action, members := range actionMembers { + policies = append(policies, MakeTestPolicy(PolicyObjectResourceId, action, members)) + } + return policies } // MakeRoleSubjectTestPolicies - makes policies with passed in param // actionMembers = { "GET/humanresources/us": ["role1", "role2"] } func MakeRoleSubjectTestPolicies(actionMembers map[string][]string) []hexapolicy.PolicyInfo { - policies := make([]hexapolicy.PolicyInfo, 0) - for action, members := range actionMembers { - parts := strings.Split(action, "/") - actionUri := "http:" + parts[0] - resId := "/" + strings.Join(parts[1:], "/") - policies = append(policies, MakeRoleSubjectTestPolicy(resId, actionUri, members)) - } - return policies + policies := make([]hexapolicy.PolicyInfo, 0) + for action, members := range actionMembers { + parts := strings.Split(action, "/") + actionUri := "http:" + parts[0] + resId := "/" + strings.Join(parts[1:], "/") + policies = append(policies, MakeRoleSubjectTestPolicy(resId, actionUri, members)) + } + return policies } func MakeRoleSubjectTestPolicy(resourceId string, action string, roles []string) hexapolicy.PolicyInfo { - return hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{action}}, - Subject: hexapolicy.SubjectInfo{Members: roles}, - Object: hexapolicy.ObjectInfo{ - ResourceID: resourceId, - }, - } + return hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{{action}}, + Subjects: roles, + Object: hexapolicy.ObjectInfo{ + ResourceID: resourceId, + }, + } } func MakeTestPolicy(resourceId string, action string, actionMembers ActionMembers) hexapolicy.PolicyInfo { - return hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{action}}, - Subject: hexapolicy.SubjectInfo{Members: MakePolicyTestUsers(actionMembers)}, - Object: hexapolicy.ObjectInfo{ - ResourceID: resourceId, - }, - } + return hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{{action}}, + Subjects: MakePolicyTestUsers(actionMembers), + Object: hexapolicy.ObjectInfo{ + ResourceID: resourceId, + }, + } } func MakePolicyTestUsers(actionMember ActionMembers) []string { - policyUsers := make([]string, 0) - for _, email := range actionMember.Emails { - policyUsers = append(policyUsers, MakePolicyTestUser(email)) - } - return policyUsers + policyUsers := make([]string, 0) + for _, email := range actionMember.Emails { + policyUsers = append(policyUsers, MakePolicyTestUser(email)) + } + return policyUsers } func MakePolicyTestUser(emailNoPrefix string) string { - return fmt.Sprintf("user:%s", emailNoPrefix) + return fmt.Sprintf("user:%s", emailNoPrefix) } diff --git a/pkg/hexapolicy/schema/README.md b/models/schema/resources/README.md similarity index 100% rename from pkg/hexapolicy/schema/README.md rename to models/schema/resources/README.md diff --git a/pkg/hexapolicy/schema/idql.jschema b/models/schema/resources/idql.jschema similarity index 65% rename from pkg/hexapolicy/schema/idql.jschema rename to models/schema/resources/idql.jschema index ccc826a..1e106df 100644 --- a/pkg/hexapolicy/schema/idql.jschema +++ b/models/schema/resources/idql.jschema @@ -25,13 +25,15 @@ "type": "string", "title": "Creation date", "description": "Timestamp formatted in RFC3339 with nanosecond precision.", - "examples": ["1985-04-12T23:20:50.52Z"] + "examples": ["1985-04-12T23:20:50.52Z"], + "format": "date-time" }, "modified": { "type": "string", "title": "Last modified date", "description": "Timestamp formatted in RFC3339 with nanosecond precision.", - "examples": ["1985-04-12T23:20:50.52Z"] + "examples": ["1985-04-12T23:20:50.52Z"], + "format": "date-time" }, "etag": { "type": "string", @@ -62,7 +64,54 @@ "members": { "type": "array", "title": "Member subjects", - "items": {"type": "string"}, + "items": { + "type": "string", + "format": "", + "anyOf": [ + { + "type": "string", + "title": "User Account", + "examples": ["user:gerry@hexaindustries.io"], + "pattern": "^(user|User):.*" + }, + { + "type": "string", + "title": "Role", + "description": "A role held by a subject", + "examples": ["role:customerAccountHolder"], + "pattern": "^(role|Role):.*" + }, + { + "type": "string", + "title": "Domain", + "description": "Domain associated with Subject", + "examples": [ + "hexaindustries.io", + "example.com" + ], + "pattern": "^(domain|Domain):.*" + }, + { + "type": "string", + "title": "Network", + "description": "A CIDR network mask", + "examples": ["net:127.0.0.1/24"], + "pattern": "^(net|Net):.*" + }, + { + "type": "string", + "const": "anyauthenticated", + "title": "AnyAuthenticated", + "description": "Any authenticated subject" + }, + { + "type": "string", + "const": "any", + "title": "Any", + "description": "Allow any subject" + } + ] + }, "description": "One or more members values to be matched to the policy", "examples": ["user:gerry@example.com"] } diff --git a/models/schema/schema_test.go b/models/schema/schema_test.go index 7fe034c..0d46785 100644 --- a/models/schema/schema_test.go +++ b/models/schema/schema_test.go @@ -4,8 +4,10 @@ import ( "os" "path/filepath" "runtime" + "strings" "testing" + "github.com/santhosh-tekuri/jsonschema/v6" "github.com/stretchr/testify/assert" ) @@ -69,3 +71,38 @@ func TestParseHealthSchema(t *testing.T) { assert.Equal(t, "Record", context.Type) assert.Equal(t, "User", context.Attributes["referrer"].Name) } + +func TestJsonSchemaIdql(t *testing.T) { + _, file, _, _ := runtime.Caller(0) + idqlSchemaBytes, err := os.ReadFile(filepath.Join(file, "..", "resources", "idql.jschema")) + assert.NoError(t, err) + assert.NotNil(t, idqlSchemaBytes) + schema, err := jsonschema.UnmarshalJSON(strings.NewReader(string(idqlSchemaBytes))) + + assert.NoError(t, err) + assert.NotNil(t, schema) + + healthPath := filepath.Join(file, "..", "test", "healthSchema.json") + healthSchemaBytes, err := os.ReadFile(healthPath) + assert.NoError(t, err) + assert.NotNil(t, healthSchemaBytes) + + healthSchema, err := jsonschema.UnmarshalJSON(strings.NewReader(string(healthSchemaBytes))) + assert.NoError(t, err) + assert.NotNil(t, healthSchema) + + c := jsonschema.NewCompiler() + err = c.AddResource(healthPath, healthSchema) + assert.NoError(t, err) + + sch, err := c.Compile(healthPath) + assert.NoError(t, err) + assert.NotNil(t, sch) + + healthEntitiesPath := filepath.Join(file, "..", "test", "healthEntities.json") + healthDataBytes, err := os.ReadFile(healthEntitiesPath) + assert.NoError(t, err) + healthInst, err := jsonschema.UnmarshalJSON(strings.NewReader(string(healthDataBytes))) + err = sch.Validate(healthInst) + assert.NoError(t, err) +} diff --git a/models/schema/test/cedarPhotoPolicy.txt b/models/schema/test/cedarPhotoPolicy.txt new file mode 100644 index 0000000..20620ee --- /dev/null +++ b/models/schema/test/cedarPhotoPolicy.txt @@ -0,0 +1,12 @@ +permit ( + principal == PhotoApp::User::"alice", + action == PhotoApp::Action::"viewPhoto", + resource == PhotoApp::Photo::"vacationPhoto.jpg" +); + +permit ( + principal == PhotoApp::User::"stacey", + action == PhotoApp::Action::"viewPhoto", + resource +) +when { resource in PhotoApp::Account::"stacey" }; \ No newline at end of file diff --git a/models/schema/test/healthEntities.json b/models/schema/test/healthEntities.json new file mode 100644 index 0000000..be3b14d --- /dev/null +++ b/models/schema/test/healthEntities.json @@ -0,0 +1,67 @@ +[ + { + "uid": { + "type": "HealthCareApp::User", + "id": "Victor" + }, + "attrs": {}, + "parents": [ + { + "type": "HealthCareApp::Role", + "id": "admin" + } + ] + }, + {"a": "b"}, + { + "uid": { + "type": "Unknown::User", + "id": "Gerry" + }, + "attrs": { + "first_name": "Gerry" + } + }, + { + "uid": { + "type": "HealthCareApp::Info", + "id": "apointment003" + }, + "attrs": { + "provider": { + "__entity": { + "type": "HealthCareApp::User", + "id": "DrSeuz" + } + }, + "patient": { + "__entity": { + "type": "HealthCareApp::User", + "id": "Victor" + } + } + }, + "parents": [ + { + "type": "HealthCareApp::InfoType", + "id": "appointment" + } + ] + }, + { + "uid": { + "type": "HealthCareApp::InfoType", + "id": "appointment" + }, + "attrs": {}, + "parents": [] + }, + { + "uid": { + "type": "HealthCareApp::Role", + "id": "admin" + }, + "attrs": {}, + "parents": [] + } +] \ No newline at end of file diff --git a/models/schema/test/photoEntities.json b/models/schema/test/photoEntities.json new file mode 100644 index 0000000..a6bd4b1 --- /dev/null +++ b/models/schema/test/photoEntities.json @@ -0,0 +1,57 @@ +[ + { + "uid": { + "type": "PhotoApp::User", + "id": "alice" + }, + "attrs": { + "userId": "897345789237492878", + "personInformation": { + "age": 25, + "name": "alice" + } + }, + "parents": [ + { + "type": "PhotoApp::UserGroup", + "id": "alice_friends" + }, + { + "type": "PhotoApp::UserGroup", + "id": "AVTeam" + } + ] + }, + { + "uid": { + "type": "PhotoApp::Photo", + "id": "vacationPhoto.jpg" + }, + "attrs": { + "private": false, + "account": { + "__entity": { + "type": "PhotoApp::Account", + "id": "ahmad" + } + } + }, + "parents": [] + }, + { + "uid": { + "type": "PhotoApp::UserGroup", + "id": "alice_friends" + }, + "attrs": {}, + "parents": [] + }, + { + "uid": { + "type": "PhotoApp::UserGroup", + "id": "AVTeam" + }, + "attrs": {}, + "parents": [] + } +] \ No newline at end of file diff --git a/pkg/hexapolicy/hexa_policy.go b/pkg/hexapolicy/hexa_policy.go index 0491da6..afb1c4c 100644 --- a/pkg/hexapolicy/hexa_policy.go +++ b/pkg/hexapolicy/hexa_policy.go @@ -12,14 +12,14 @@ import ( ) const ( - SAnyUser string = "any" - SAnyAuth string = "anyAuthenticated" - SBasicAuth string = "basic" - SJwtAuth string = "jwt" - SSamlAuth string = "saml" + SubjectAnyUser string = "any" + SubjectAnyAuth string = "anyAuthenticated" + SubjectBasicAuth string = "basic" + SubjectJwtAuth string = "jwt" + SubjectSamlAuth string = "saml" // SCidr string = "net" - IdqlVersion string = "0.6" + IdqlVersion string = "0.7" ) type Policies struct { @@ -48,12 +48,12 @@ func (p *Policies) AddPolicies(policies Policies) { // PolicyInfo holds a single IDQL Policy Statement type PolicyInfo struct { - Meta MetaInfo `json:"meta" validate:"required"` // Meta holds additional information about the policy including policy management data - Subject SubjectInfo `json:"subject" validate:"required"` // Subject holds the subject clause of an IDQL policy - Actions []ActionInfo `json:"actions" validate:"required"` // Actions holds one or moe action uris - Object ObjectInfo `json:"object" validate:"required"` // Object the resource, application, or system to which a policy applies - Condition *conditions.ConditionInfo `json:",omitempty"` // Condition is optional // Condition is an IDQL filter condition (e.g. ABAC rule) which must also be met - Scope *ScopeInfo `json:"scope,omitempty"` // Scope represents obligations returned to a PEP (e.g. attributes, where clause) + Meta MetaInfo `json:"meta" validate:"required"` // Meta holds additional information about the policy including policy management data + Subjects SubjectInfo `json:"subjects" validate:"required"` // Subjects holds the subject clause of an IDQL policy + Actions []ActionInfo `json:"actions" validate:"required"` // Actions holds one or moe action uris + Object ObjectInfo `json:"object" validate:"required"` // Object the resource, application, or system to which a policy applies + Condition *conditions.ConditionInfo `json:",omitempty"` // Condition is optional // Condition is an IDQL filter condition (e.g. ABAC rule) which must also be met + Scope *ScopeInfo `json:"scope,omitempty"` // Scope represents obligations returned to a PEP (e.g. attributes, where clause) } func (p *PolicyInfo) String() string { @@ -62,11 +62,11 @@ func (p *PolicyInfo) String() string { } /* -CalculateEtag calculates an ETAG hash value for the policy which includes the Subject, Actions, Object, and Conditions objects only +CalculateEtag calculates an ETAG hash value for the policy which includes the Subjects, Actions, Object, and Conditions objects only */ func (p *PolicyInfo) CalculateEtag() string { pderef := *p // this was causing a pointer interaction - so deref - subjectBytes, _ := json.Marshal(pderef.Subject) + subjectBytes, _ := json.Marshal(pderef.Subjects) actionBytes, _ := json.Marshal(pderef.Actions) objectBytes, _ := json.Marshal(pderef.Object) conditionBytes := make([]byte, 0) @@ -101,7 +101,7 @@ func (p *PolicyInfo) Equals(hexaPolicy PolicyInfo) bool { } // check for semantic equivalence. - if !(p.Subject.equals(&hexaPolicy.Subject) && p.actionEquals(hexaPolicy.Actions) && p.Object.equals(&hexaPolicy.Object)) { + if !(p.Subjects.equals(hexaPolicy.Subjects) && p.actionEquals(hexaPolicy.Actions) && p.Object.equals(&hexaPolicy.Object)) { return false } @@ -149,7 +149,7 @@ func (p *PolicyInfo) Compare(hexaPolicy PolicyInfo) []string { var difs = make([]string, 0) // Now do a semantic compare (e.g. things can be different order but the same) - if !p.Subject.equals(&hexaPolicy.Subject) { + if !p.Subjects.equals(hexaPolicy.Subjects) { difs = append(difs, CompareDifSubject) } @@ -211,18 +211,20 @@ type ActionInfo struct { ActionUri string `json:"actionUri" validate:"required"` } -type SubjectInfo struct { - Members []string `json:"members" validate:"required"` +type SubjectInfo []string + +func (s SubjectInfo) String() []string { + return s } -func (s *SubjectInfo) equals(subject *SubjectInfo) bool { - if len(s.Members) != len(subject.Members) { +func (s SubjectInfo) equals(subjects SubjectInfo) bool { + if len(s) != len(subjects) { return false } - for _, member := range s.Members { + for _, member := range s { isMatch := false - for _, cmember := range subject.Members { - if strings.EqualFold(member, cmember) { + for _, compareMember := range subjects { + if strings.EqualFold(member, compareMember) { isMatch = true break } diff --git a/pkg/hexapolicy/hexa_policy_test.go b/pkg/hexapolicy/hexa_policy_test.go index 85b5b14..bee9404 100644 --- a/pkg/hexapolicy/hexa_policy_test.go +++ b/pkg/hexapolicy/hexa_policy_test.go @@ -22,11 +22,9 @@ var testPolicy1 = ` "actionUri": "http:POST:/accounting" } ], - "subject": { - "members": [ + "subjects": [ "accounting@hexaindustries.io" - ] - }, + ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" @@ -50,20 +48,21 @@ var testPolicy2 = ` "actionUri": "http:GET:/humanresources" } ], - "subject": { - "members": [ + "subjects": [ "humanresources@hexaindustries.io" - ] - }, + ], "object": { "resource_id": "aResourceId" } }` -func getPolicies() Policies { +func getPolicies(t *testing.T) Policies { + t.Helper() var policy1, policy2 PolicyInfo - _ = json.Unmarshal([]byte(testPolicy1), &policy1) - _ = json.Unmarshal([]byte(testPolicy2), &policy2) + err := json.Unmarshal([]byte(testPolicy1), &policy1) + assert.NoError(t, err) + err = json.Unmarshal([]byte(testPolicy2), &policy2) + assert.NoError(t, err) pols := &Policies{Policies: []PolicyInfo{policy1, policy2}} pols.CalculateEtags() return *pols @@ -73,7 +72,8 @@ func TestReadPolicy(t *testing.T) { var policy1, policy2, policy3 PolicyInfo err := json.Unmarshal([]byte(testPolicy1), &policy1) assert.NoError(t, err, "Check no policy parse error #1") - + assert.NotNil(t, policy1.Subjects, "Subjects should not be nil") + assert.Equal(t, 1, len(policy1.Subjects), "Should be one subject") err = json.Unmarshal([]byte(testPolicy2), &policy2) assert.NoError(t, err, "Check no policy parse error #2") @@ -88,18 +88,19 @@ func TestReadPolicy(t *testing.T) { } func TestSubjectInfo_equals(t *testing.T) { - policies := getPolicies() + policies := getPolicies(t) p1 := policies.Policies[0] p2 := policies.Policies[1] - assert.False(t, p1.Subject.equals(&p2.Subject)) + assert.NotNil(t, p1.Subjects, "Subjects should not be nil") + assert.False(t, p1.Subjects.equals(p2.Subjects)) p3 := p1 // check case sensitivity - p3.Subject = SubjectInfo{Members: []string{"Accounting@Hexaindustries.io"}} - assert.True(t, p1.Subject.equals(&p3.Subject)) + p3.Subjects = []string{"Accounting@Hexaindustries.io"} + assert.True(t, p1.Subjects.equals(p3.Subjects)) } func TestPolicyInfo_actionEquals(t *testing.T) { - policies := getPolicies() + policies := getPolicies(t) p1 := policies.Policies[0] p2 := policies.Policies[1] @@ -123,7 +124,7 @@ func TestPolicyInfo_actionEquals(t *testing.T) { } func TestObjectInfo_equals(t *testing.T) { - policies := getPolicies() + policies := getPolicies(t) p1 := policies.Policies[0] p2 := policies.Policies[1] assert.True(t, p1.Object.equals(&p2.Object)) @@ -133,7 +134,7 @@ func TestObjectInfo_equals(t *testing.T) { } func TestConditionInfo_Equals(t *testing.T) { - policies := getPolicies() + policies := getPolicies(t) p1 := policies.Policies[0] p2 := policies.Policies[1] assert.False(t, p1.Condition.Equals(p2.Condition)) @@ -144,7 +145,7 @@ func TestConditionInfo_Equals(t *testing.T) { } func TestScope_equals(t *testing.T) { - policies := getPolicies() + policies := getPolicies(t) scope1 := policies.Policies[0].Scope /* @@ -216,7 +217,7 @@ func TestPolicies_AddPolicies(t *testing.T) { } func TestPolicyInfo_CalculateEtag(t *testing.T) { - policies := getPolicies() + policies := getPolicies(t) p1 := policies.Policies[0] etag := p1.CalculateEtag() @@ -231,11 +232,11 @@ func TestPolicyInfo_CalculateEtag(t *testing.T) { } func TestPolicyInfo_Equals(t *testing.T) { - policies := getPolicies() + policies := getPolicies(t) p3 := policies.Policies[0] // This will be used to make sure subject is case insensitive - p3.Subject = SubjectInfo{Members: []string{"Accounting@Hexaindustries.io"}} + p3.Subjects = []string{"Accounting@Hexaindustries.io"} type fields struct { testPolicy PolicyInfo @@ -262,7 +263,7 @@ func TestPolicyInfo_Equals(t *testing.T) { want: false, }, { - name: "Subject case test", + name: "Subjects case test", fields: fields{testPolicy: policies.Policies[0]}, args: args{hexaPolicy: p3}, want: true, @@ -277,11 +278,11 @@ func TestPolicyInfo_Equals(t *testing.T) { } func TestPolicyInfo_Compare(t *testing.T) { - policies := getPolicies() + policies := getPolicies(t) p3 := policies.Policies[0] // This will be used to make sure subject is case insensitive - p3.Subject = SubjectInfo{Members: []string{"Accounting@Hexaindustries.io"}} + p3.Subjects = []string{"Accounting@Hexaindustries.io"} type fields struct { hexaPolicy PolicyInfo @@ -308,7 +309,7 @@ func TestPolicyInfo_Compare(t *testing.T) { want: []string{CompareDifSubject, CompareDifAction, CompareDifCondition}, }, { - name: "Subject case test", + name: "Subjects case test", fields: fields{hexaPolicy: policies.Policies[0]}, args: args{hexaPolicy: p3}, want: []string{CompareEqual}, @@ -324,35 +325,38 @@ func TestPolicyInfo_Compare(t *testing.T) { func TestPolicyDif_Report(t *testing.T) { pid := "abc" - policyString := ` -{ - "meta": { - "etag": "20-bf24e8e84dfa3c07c776a4e2ac31d1ca642502a9", - "policyId": "abc" - }, - "subject": { - "members": [ - "user1" - ] - }, - "actions": [ - { - "actionUri": "actionUri" - } - ], - "object": { - "resource_id": "aresource" - } -}` + /* + policyString := `{ + "meta": { + "etag": "20-bf24e8e84dfa3c07c776a4e2ac31d1ca642502a9", + "policyId": "abc" + }, + "subjects": [ + "user1" + ], + "actions": [ + { + "actionUri": "actionUri" + } + ], + "object": { + "resource_id": "aresource" + } + }` + + */ + testPolicy := PolicyInfo{ Meta: MetaInfo{PolicyId: &pid}, - Subject: SubjectInfo{Members: []string{"user1"}}, + Subjects: []string{"user1"}, Actions: []ActionInfo{{ActionUri: "actionUri"}}, Object: ObjectInfo{ResourceID: "aresource"}, Condition: nil, } testPolicy.CalculateEtag() + policyString := "\n" + testPolicy.String() + type fields struct { Type string PolicyId string @@ -448,6 +452,7 @@ func TestPolicyDif_Report(t *testing.T) { PolicyCompare: tt.fields.PolicyCompare, } fmt.Println(d.Report()) + assert.Equalf(t, tt.want, d.Report(), "Report()") }) } @@ -457,14 +462,12 @@ func TestPolicyInfo_String(t *testing.T) { pid := "abc" policyString := `{ "meta": { - "etag": "20-bf24e8e84dfa3c07c776a4e2ac31d1ca642502a9", + "etag": "20-03ce8ba19fe5a7a2ea9daf6e4c9645716d1dff39", "policyId": "abc" }, - "subject": { - "members": [ - "user1" - ] - }, + "subjects": [ + "user1" + ], "actions": [ { "actionUri": "actionUri" @@ -476,30 +479,30 @@ func TestPolicyInfo_String(t *testing.T) { }` testPolicy := PolicyInfo{ Meta: MetaInfo{PolicyId: &pid}, - Subject: SubjectInfo{Members: []string{"user1"}}, + Subjects: []string{"user1"}, Actions: []ActionInfo{{ActionUri: "actionUri"}}, Object: ObjectInfo{ResourceID: "aresource"}, Condition: nil, } testPolicy.CalculateEtag() - - assert.Equal(t, policyString, testPolicy.String(), "String check") + result := testPolicy.String() + assert.Equal(t, policyString, result, "String check") } func TestReconcilePolicies(t *testing.T) { - policies := getPolicies() + policies := getPolicies(t) - matchingPolicies := getPolicies() + matchingPolicies := getPolicies(t) assert.Equal(t, policies.Policies[0].Meta.Etag, matchingPolicies.Policies[0].Meta.Etag) assert.Equal(t, policies.Policies[1].Meta.Etag, matchingPolicies.Policies[1].Meta.Etag) - policiesWithIds := getPolicies() - policiesIdSame := getPolicies() + policiesWithIds := getPolicies(t) + policiesIdSame := getPolicies(t) - policiesWithChangesIds := getPolicies() + policiesWithChangesIds := getPolicies(t) - policiesWithChangesHash := getPolicies() + policiesWithChangesHash := getPolicies(t) pid := "abc" pid2 := "def" @@ -534,9 +537,9 @@ func TestReconcilePolicies(t *testing.T) { Version: IdqlVersion, PolicyId: &npid, }, - Subject: SubjectInfo{Members: []string{"phil.hunt@independentid.com"}}, - Actions: []ActionInfo{{ActionUri: "http:GET:/admin"}, {ActionUri: "http:POST:/admin"}}, - Object: ObjectInfo{ResourceID: "hexaindustries"}, + Subjects: []string{"phil.hunt@independentid.com"}, + Actions: []ActionInfo{{ActionUri: "http:GET:/admin"}, {ActionUri: "http:POST:/admin"}}, + Object: ObjectInfo{ResourceID: "hexaindustries"}, } newPolicy.CalculateEtag() diff --git a/providers/aws/avpProvider/avpClient/avp_client.go b/providers/aws/avpProvider/avpClient/avp_client.go index a1a006b..9c2003a 100644 --- a/providers/aws/avpProvider/avpClient/avp_client.go +++ b/providers/aws/avpProvider/avpClient/avp_client.go @@ -18,6 +18,8 @@ type AvpClient interface { CreatePolicy(createPolicyInput *verifiedpermissions.CreatePolicyInput) (*verifiedpermissions.CreatePolicyOutput, error) UpdatePolicy(updatePolicy *verifiedpermissions.UpdatePolicyInput) (*verifiedpermissions.UpdatePolicyOutput, error) DeletePolicy(deletePolicyInput *verifiedpermissions.DeletePolicyInput) (*verifiedpermissions.DeletePolicyOutput, error) + GetSchema(app policyprovider.ApplicationInfo) (*verifiedpermissions.GetSchemaOutput, error) + PutSchema(app policyprovider.ApplicationInfo, schema types.SchemaDefinition) (*verifiedpermissions.PutSchemaOutput, error) } type avpClient struct { @@ -117,3 +119,16 @@ func (c *avpClient) UpdatePolicy(updatePolicy *verifiedpermissions.UpdatePolicyI func (c *avpClient) DeletePolicy(deletePolicyInput *verifiedpermissions.DeletePolicyInput) (*verifiedpermissions.DeletePolicyOutput, error) { return c.client.DeletePolicy(context.TODO(), deletePolicyInput) } + +func (c *avpClient) GetSchema(app policyprovider.ApplicationInfo) (*verifiedpermissions.GetSchemaOutput, error) { + return c.client.GetSchema(context.TODO(), &verifiedpermissions.GetSchemaInput{ + PolicyStoreId: &app.ObjectID, + }) +} + +func (c *avpClient) PutSchema(app policyprovider.ApplicationInfo, schema types.SchemaDefinition) (*verifiedpermissions.PutSchemaOutput, error) { + return c.client.PutSchema(context.TODO(), &verifiedpermissions.PutSchemaInput{ + PolicyStoreId: &app.ObjectID, + Definition: schema, + }) +} diff --git a/providers/aws/avpProvider/avp_provider.go b/providers/aws/avpProvider/avp_provider.go index 9418e1a..afe1559 100644 --- a/providers/aws/avpProvider/avp_provider.go +++ b/providers/aws/avpProvider/avp_provider.go @@ -10,6 +10,7 @@ import ( "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/schema" "github.com/hexa-org/policy-mapper/pkg/hexapolicy" "github.com/hexa-org/policy-mapper/providers/aws/avpProvider/avpClient" "github.com/hexa-org/policy-mapper/providers/aws/awscommon" @@ -446,7 +447,7 @@ func (a AmazonAvpProvider) prepareDelete(avpMeta hexapolicy.MetaInfo) *verifiedp func isTemplate(hexaPolicy hexapolicy.PolicyInfo) bool { if hexaPolicy.Meta.SourceData == nil { // in the case where a policy is new, but contains template we need to test for ?resource and ?principle - if slices.Contains(hexaPolicy.Subject.Members, "?principal") { + if slices.Contains(hexaPolicy.Subjects.String(), "?principal") { return true } if strings.Contains(hexaPolicy.Object.ResourceID, "?resource") { @@ -458,3 +459,21 @@ func isTemplate(hexaPolicy hexapolicy.PolicyInfo) bool { policyType, exists := hexaPolicy.Meta.SourceData[ParamPolicyType] return exists && policyType == string(types.PolicyTypeTemplateLinked) } + +func (a AmazonAvpProvider) GetSchema(info policyprovider.IntegrationInfo, applicationInfo policyprovider.ApplicationInfo) (*schema.Namespaces, error) { + client, err := a.getAvpClient(info) + if err != nil { + return nil, err + } + + schemaResponse, err := client.GetSchema(applicationInfo) + if err != nil { + return nil, err + } + namespaceNames := schemaResponse.Namespaces + fmt.Println(fmt.Sprintf("Found namespaces: %v", namespaceNames)) + cedarSchemaString := schemaResponse.Schema + + return schema.ParseSchemaFile([]byte(*cedarSchemaString)) + +} diff --git a/providers/aws/avpProvider/avp_provider_test.go b/providers/aws/avpProvider/avp_provider_test.go index ced0344..eff4281 100644 --- a/providers/aws/avpProvider/avp_provider_test.go +++ b/providers/aws/avpProvider/avp_provider_test.go @@ -1,56 +1,57 @@ package avpProvider_test import ( - "context" - "fmt" - "net/http" - "os" - "slices" - "testing" - "time" - - "github.com/aws/aws-sdk-go-v2/config" - "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/pkg/hexapolicy" - "github.com/hexa-org/policy-mapper/providers/aws/avpProvider" - "github.com/hexa-org/policy-mapper/providers/aws/avpProvider/avpClient" - "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" + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "slices" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/config" + "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/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/providers/aws/avpProvider" + "github.com/hexa-org/policy-mapper/providers/aws/avpProvider/avpClient" + "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" ) type TestInfo struct { - Apps []policyprovider.ApplicationInfo - Provider avpProvider.AmazonAvpProvider - Info policyprovider.IntegrationInfo + Apps []policyprovider.ApplicationInfo + Provider avpProvider.AmazonAvpProvider + Info policyprovider.IntegrationInfo } var initialized = false var testData TestInfo func isLiveTest() bool { - _, ok := os.LookupEnv("AWS_LIVE") - return ok + _, ok := os.LookupEnv("AWS_LIVE") + return ok } func initializeOnlineTests() error { - if initialized { - return nil - } - cfg, err := config.LoadDefaultConfig(context.TODO()) - if err != nil { - return err - } - cred, err := cfg.Credentials.Retrieve(context.TODO()) - if err != nil { - return err - } - - str := fmt.Sprintf(` + if initialized { + return nil + } + cfg, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + return err + } + cred, err := cfg.Credentials.Retrieve(context.TODO()) + if err != nil { + return err + } + + str := fmt.Sprintf(` { "accessKeyID": "%s", "secretAccessKey": "%s", @@ -58,323 +59,369 @@ func initializeOnlineTests() error { } `, cred.AccessKeyID, cred.SecretAccessKey, cfg.Region) - info := policyprovider.IntegrationInfo{Name: "avp", Key: []byte(str)} - provider := avpProvider.AmazonAvpProvider{AwsClientOpts: awscommon.AWSClientOptions{DisableRetry: true}} + info := policyprovider.IntegrationInfo{Name: "avp", Key: []byte(str)} + provider := avpProvider.AmazonAvpProvider{AwsClientOpts: awscommon.AWSClientOptions{DisableRetry: true}, + CedarMapper: awsCedar.New(map[string]string{})} - testData = TestInfo{ - Provider: provider, - Info: info, - } + testData = TestInfo{ + Provider: provider, + Info: info, + } - initialized = true - return nil + initialized = true + return nil } func Test_SetPolicy_live(t *testing.T) { - if isLiveTest() { - var err error - _ = initializeOnlineTests() - apps, err := testData.Provider.DiscoverApplications(testData.Info) - assert.NoError(t, err) - - policies, err := testData.Provider.GetPolicyInfo(testData.Info, apps[0]) - assert.NoError(t, err) - assert.NotNil(t, policies) - - if len(policies) < 2 { - client, err := avpClient.NewAvpClient(testData.Info.Key, testData.Provider.AwsClientOpts) // NewFromConfig(info.Key, a.AwsClientOpts) - assert.NoError(t, err) - // Create the template policies - createPolicyDefinition := types.StaticPolicyDefinition{ - Statement: &avpTestSupport.TestCedarStaticPolicy, - Description: &avpTestSupport.TestCedarStaticPolicyDescription, - } - createStatic := types.PolicyDefinitionMemberStatic{ - Value: createPolicyDefinition, - } - createPolicyInput := verifiedpermissions.CreatePolicyInput{ - Definition: &createStatic, - PolicyStoreId: &apps[0].ObjectID, - } - _, err = client.CreatePolicy(&createPolicyInput) - assert.NoError(t, err) - policies, err = testData.Provider.GetPolicyInfo(testData.Info, apps[0]) - assert.NoError(t, err) - assert.NotNil(t, policies) - } - - // Each time this is run we add or remove the action - policy := policies[0] - actions := policy.Actions - present := false - for i, action := range actions { - if action.ActionUri == "cedar:hexa_avp::Action::\"UpdateAccount\"" { - actions = append(actions[:i], actions[i+1:]...) - present = true - fmt.Println("This test run will remove UpdateAccount action") - } - } - if !present { - fmt.Println("This run will add UpdateAccount action") - actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::\"UpdateAccount\""}) - } - policies[0].Actions = actions - - status, err := testData.Provider.SetPolicyInfo(testData.Info, apps[0], policies) - assert.NoError(t, err) - assert.Equal(t, 200, status, "Should be status 200") - - // this should cause a replacement (delete and add) to occur - policies[0].Subject.Members = []string{"hexa_avp::User::\"gerry@strata.io\""} - - status, err = testData.Provider.SetPolicyInfo(testData.Info, apps[0], policies) - assert.NoError(t, err) - assert.Equal(t, 200, status, "Should be status 200") - - policies2, err := testData.Provider.GetPolicyInfo(testData.Info, apps[0]) - assert.NoError(t, err) - assert.NotNil(t, policies2) - assert.Len(t, policies2, 2) - - // now do the implied delete - policies2 = policies2[1:] - status, err = testData.Provider.SetPolicyInfo(testData.Info, apps[0], policies2) - assert.NoError(t, err) - assert.Equal(t, 200, status, "Should be status 200") - - policies3, err := testData.Provider.GetPolicyInfo(testData.Info, apps[0]) - assert.NoError(t, err) - assert.NotNil(t, policies3) - assert.Len(t, policies3, 1) - - } + if isLiveTest() { + var err error + _ = initializeOnlineTests() + apps, err := testData.Provider.DiscoverApplications(testData.Info) + assert.NoError(t, err) + + policies, err := testData.Provider.GetPolicyInfo(testData.Info, apps[0]) + assert.NoError(t, err) + assert.NotNil(t, policies) + + if len(policies) < 2 { + client, err := avpClient.NewAvpClient(testData.Info.Key, testData.Provider.AwsClientOpts) // NewFromConfig(info.Key, a.AwsClientOpts) + assert.NoError(t, err) + // Create the template policies + createPolicyDefinition := types.StaticPolicyDefinition{ + Statement: &avpTestSupport.TestCedarStaticPolicy, + Description: &avpTestSupport.TestCedarStaticPolicyDescription, + } + createStatic := types.PolicyDefinitionMemberStatic{ + Value: createPolicyDefinition, + } + createPolicyInput := verifiedpermissions.CreatePolicyInput{ + Definition: &createStatic, + PolicyStoreId: &apps[0].ObjectID, + } + _, err = client.CreatePolicy(&createPolicyInput) + assert.NoError(t, err) + policies, err = testData.Provider.GetPolicyInfo(testData.Info, apps[0]) + assert.NoError(t, err) + assert.NotNil(t, policies) + } + + // Each time this is run we add or remove the action + policy := policies[0] + actions := policy.Actions + present := false + for i, action := range actions { + if action.ActionUri == "cedar:hexa_avp::Action::\"UpdateAccount\"" { + actions = append(actions[:i], actions[i+1:]...) + present = true + fmt.Println("This test run will remove UpdateAccount action") + } + } + if !present { + fmt.Println("This run will add UpdateAccount action") + actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::\"UpdateAccount\""}) + } + policies[0].Actions = actions + + status, err := testData.Provider.SetPolicyInfo(testData.Info, apps[0], policies) + assert.NoError(t, err) + assert.Equal(t, 200, status, "Should be status 200") + + // this should cause a replacement (delete and add) to occur + policies[0].Subjects = []string{"hexa_avp::User::\"gerry@strata.io\""} + + status, err = testData.Provider.SetPolicyInfo(testData.Info, apps[0], policies) + assert.NoError(t, err) + assert.Equal(t, 200, status, "Should be status 200") + + policies2, err := testData.Provider.GetPolicyInfo(testData.Info, apps[0]) + assert.NoError(t, err) + assert.NotNil(t, policies2) + assert.Len(t, policies2, 2) + + // now do the implied delete + policies2 = policies2[1:] + status, err = testData.Provider.SetPolicyInfo(testData.Info, apps[0], policies2) + assert.NoError(t, err) + assert.Equal(t, 200, status, "Should be status 200") + + policies3, err := testData.Provider.GetPolicyInfo(testData.Info, apps[0]) + assert.NoError(t, err) + assert.NotNil(t, policies3) + assert.Len(t, policies3, 1) + + } } func TestAvp_1_ListStores(t *testing.T) { - mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() - mockClient.MockListStores() - - p := avpProvider.AmazonAvpProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true, - }, - CedarMapper: awsCedar.New(map[string]string{})} - - info := avpTestSupport.IntegrationInfo() - apps, err := p.DiscoverApplications(info) - assert.NoError(t, err) - assert.Len(t, apps, 1) - assert.Equal(t, avpTestSupport.TestPolicyStoreDescription, apps[0].Description) - assert.True(t, mockClient.VerifyCalled()) - - mockClient.MockListStoresWithHttpStatus(http.StatusUnauthorized) - apps2, err := p.DiscoverApplications(info) - assert.Error(t, err) - assert.Nil(t, apps2) - // assert.Equal(t, avpTestSupport.TestPolicyStoreDescription, apps[0].Description) - assert.True(t, mockClient.VerifyCalled()) + mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() + mockClient.MockListStores() + + p := avpProvider.AmazonAvpProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true, + }, + CedarMapper: awsCedar.New(map[string]string{})} + + info := avpTestSupport.IntegrationInfo() + apps, err := p.DiscoverApplications(info) + assert.NoError(t, err) + assert.Len(t, apps, 1) + assert.Equal(t, avpTestSupport.TestPolicyStoreDescription, apps[0].Description) + assert.True(t, mockClient.VerifyCalled()) + + mockClient.MockListStoresWithHttpStatus(http.StatusUnauthorized) + apps2, err := p.DiscoverApplications(info) + assert.Error(t, err) + assert.Nil(t, apps2) + // assert.Equal(t, avpTestSupport.TestPolicyStoreDescription, apps[0].Description) + assert.True(t, mockClient.VerifyCalled()) } func TestAvp_2_GetPolicies(t *testing.T) { - mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() - - p := avpProvider.AmazonAvpProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true, - }, - CedarMapper: awsCedar.New(map[string]string{})} - - mockClient.MockListStores() - info := avpTestSupport.IntegrationInfo() - apps, err := p.DiscoverApplications(info) - assert.NoError(t, err) - assert.True(t, mockClient.VerifyCalled()) - - mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - policies, err := p.GetPolicyInfo(info, apps[0]) - assert.NoError(t, err) - assert.NotNil(t, policies) - assert.Len(t, policies, 2, "Should be 2 policies") - assert.True(t, mockClient.VerifyCalled()) + mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() + + p := avpProvider.AmazonAvpProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true, + }, + CedarMapper: awsCedar.New(map[string]string{})} + + mockClient.MockListStores() + info := avpTestSupport.IntegrationInfo() + apps, err := p.DiscoverApplications(info) + assert.NoError(t, err) + assert.True(t, mockClient.VerifyCalled()) + + mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + policies, err := p.GetPolicyInfo(info, apps[0]) + assert.NoError(t, err) + assert.NotNil(t, policies) + assert.Len(t, policies, 2, "Should be 2 policies") + assert.True(t, mockClient.VerifyCalled()) } func TestAvp_3_Reconcile(t *testing.T) { - mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() - - p := avpProvider.AmazonAvpProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true, - }, - CedarMapper: awsCedar.New(map[string]string{})} - - mockClient.MockListStores() - info := avpTestSupport.IntegrationInfo() - apps, err := p.DiscoverApplications(info) - assert.NoError(t, err) - assert.True(t, mockClient.VerifyCalled()) - - mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 10, 1, nil) - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"1") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"2") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"3") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"4") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"5") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"6") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"7") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"8") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"9") - mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - policies, err := p.GetPolicyInfo(info, apps[0]) - assert.True(t, mockClient.VerifyCalled()) - - // origPolicies := policies[0:] - // This section will cause an update since only action changed - policy := policies[0] - actions := policy.Actions - - actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::\"UpdateAccount\""}) - - policies[0].Actions = actions - - avpMeta := policies[1].Meta - var avpType string - avpType, exist := avpMeta.SourceData[avpProvider.ParamPolicyType].(string) - assert.True(t, exist, "Check policy type exists") - assert.Equal(t, "TEMPLATE_LINKED", avpType, "Second [1] policy should be template") - - // this should cause a replacement (delete and add) to occur (subject change) - policies[2].Subject.Members = []string{"hexa_avp::User::\"gerry@strata.io\""} - - // this should cause an implied delete by removing policy 5 - policies = append(policies[0:5], policies[6:]...) - - now := time.Now() - // now append a policy by copying and modifying the first - newPolicy := policies[0] - newPolicy.Meta = hexapolicy.MetaInfo{ - Version: "0.5", - Description: "Test New Policy", - Created: &now, - Modified: &now, - } - newPolicy.Subject.Members = []string{"hexa_avp::User::\"nobody@strata.io\""} - - policies = append(policies, newPolicy) - - mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 10, 1, nil) - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"1") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"2") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"3") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"4") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"5") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"6") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"7") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"8") - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"9") - mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - difs, err := p.Reconcile(info, apps[0], policies, true) - assert.NoError(t, err) - assert.True(t, mockClient.VerifyCalled()) - assert.Len(t, difs, 5) - assert.Equal(t, hexapolicy.ChangeTypeUpdate, difs[0].Type) - assert.True(t, slices.Equal([]string{"ACTION"}, difs[0].DifTypes)) - assert.Equal(t, hexapolicy.ChangeTypeIgnore, difs[1].Type) - assert.Equal(t, hexapolicy.ChangeTypeUpdate, difs[2].Type) - assert.True(t, slices.Equal([]string{"SUBJECT"}, difs[2].DifTypes)) - assert.Equal(t, hexapolicy.ChangeTypeNew, difs[3].Type) - assert.Equal(t, hexapolicy.ChangeTypeDelete, difs[4].Type) - for _, dif := range difs { - fmt.Println(dif.Report()) - } + mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() + + p := avpProvider.AmazonAvpProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true, + }, + CedarMapper: awsCedar.New(map[string]string{})} + + mockClient.MockListStores() + info := avpTestSupport.IntegrationInfo() + apps, err := p.DiscoverApplications(info) + assert.NoError(t, err) + assert.True(t, mockClient.VerifyCalled()) + + mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 10, 1, nil) + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"1") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"2") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"3") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"4") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"5") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"6") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"7") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"8") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"9") + mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + policies, err := p.GetPolicyInfo(info, apps[0]) + assert.True(t, mockClient.VerifyCalled()) + + // origPolicies := policies[0:] + // This section will cause an update since only action changed + policy := policies[0] + actions := policy.Actions + + actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::\"UpdateAccount\""}) + + policies[0].Actions = actions + + avpMeta := policies[1].Meta + var avpType string + avpType, exist := avpMeta.SourceData[avpProvider.ParamPolicyType].(string) + assert.True(t, exist, "Check policy type exists") + assert.Equal(t, "TEMPLATE_LINKED", avpType, "Second [1] policy should be template") + + // this should cause a replacement (delete and add) to occur (subject change) + policies[2].Subjects = []string{"hexa_avp::User::\"gerry@strata.io\""} + + // this should cause an implied delete by removing policy 5 + policies = append(policies[0:5], policies[6:]...) + + now := time.Now() + // now append a policy by copying and modifying the first + newPolicy := policies[0] + newPolicy.Meta = hexapolicy.MetaInfo{ + Version: "0.5", + Description: "Test New Policy", + Created: &now, + Modified: &now, + } + newPolicy.Subjects = []string{"hexa_avp::User::\"nobody@strata.io\""} + + policies = append(policies, newPolicy) + + mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 10, 1, nil) + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"1") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"2") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"3") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"4") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"5") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"6") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"7") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"8") + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"9") + mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + difs, err := p.Reconcile(info, apps[0], policies, true) + assert.NoError(t, err) + assert.True(t, mockClient.VerifyCalled()) + assert.Len(t, difs, 5) + assert.Equal(t, hexapolicy.ChangeTypeUpdate, difs[0].Type) + assert.True(t, slices.Equal([]string{"ACTION"}, difs[0].DifTypes)) + assert.Equal(t, hexapolicy.ChangeTypeIgnore, difs[1].Type) + assert.Equal(t, hexapolicy.ChangeTypeUpdate, difs[2].Type) + assert.True(t, slices.Equal([]string{"SUBJECT"}, difs[2].DifTypes)) + assert.Equal(t, hexapolicy.ChangeTypeNew, difs[3].Type) + assert.Equal(t, hexapolicy.ChangeTypeDelete, difs[4].Type) + for _, dif := range difs { + fmt.Println(dif.Report()) + } } func TestAvp_4_SetPolicies(t *testing.T) { - mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() - - p := avpProvider.AmazonAvpProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true, - }, - CedarMapper: awsCedar.New(map[string]string{})} - - mockClient.MockListStores() - info := avpTestSupport.IntegrationInfo() - apps, err := p.DiscoverApplications(info) - assert.NoError(t, err) - assert.True(t, mockClient.VerifyCalled()) - - mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - policies, err := p.GetPolicyInfo(info, apps[0]) - assert.True(t, mockClient.VerifyCalled()) - - // This section will cause an update since only action changed - policy := policies[0] - actions := policy.Actions - present := false - for i, action := range actions { - if action.ActionUri == "cedar:hexa_avp::Action::\"UpdateAccount\"" { - actions = append(actions[:i], actions[i+1:]...) - present = true - fmt.Println("This test run will remove UpdateAccount action") - } - } - if !present { - fmt.Println("This run will add UpdateAccount action") - actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::\"UpdateAccount\""}) - } - policies[0].Actions = actions - - mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - mockClient.MockUpdatePolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - status, err := p.SetPolicyInfo(info, apps[0], policies) - assert.NoError(t, err) - assert.True(t, mockClient.VerifyCalled()) - - // this should cause a replacement (delete and add) to occur (subject change) - policies[0].Subject.Members = []string{"hexa_avp::User::\"gerry@strata.io\""} - - mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - mockClient.MockDeletePolicyWithHttpStatus(http.StatusOK) - mockClient.MockCreatePolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - status, err = p.SetPolicyInfo(info, apps[0], policies) - assert.NoError(t, err) - assert.Equal(t, 200, status, "Should be status 200") - assert.True(t, mockClient.VerifyCalled()) - - // now do the implied delete - policies2 := policies[1:] - mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) - mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - mockClient.MockDeletePolicyWithHttpStatus(http.StatusOK) - status, err = p.SetPolicyInfo(info, apps[0], policies2) - assert.NoError(t, err) - assert.Equal(t, 200, status, "Should be status 200") - assert.True(t, mockClient.VerifyCalled()) - - // now do an add policy - mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 0, 1, nil) - mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - mockClient.MockCreatePolicyWithHttpStatus(http.StatusOK, "id10") - - // Note policies has both a static and template. Initial list was mocked with only 1 template - to cause an add - status, err = p.SetPolicyInfo(info, apps[0], policies) - assert.NoError(t, err) - assert.Equal(t, 200, status, "Should be status 200") - assert.True(t, mockClient.VerifyCalled()) + mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() + + p := avpProvider.AmazonAvpProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true, + }, + CedarMapper: awsCedar.New(map[string]string{})} + + mockClient.MockListStores() + info := avpTestSupport.IntegrationInfo() + apps, err := p.DiscoverApplications(info) + assert.NoError(t, err) + assert.True(t, mockClient.VerifyCalled()) + + mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + policies, err := p.GetPolicyInfo(info, apps[0]) + assert.True(t, mockClient.VerifyCalled()) + + // This section will cause an update since only action changed + policy := policies[0] + actions := policy.Actions + present := false + for i, action := range actions { + if action.ActionUri == "cedar:hexa_avp::Action::\"UpdateAccount\"" { + actions = append(actions[:i], actions[i+1:]...) + present = true + fmt.Println("This test run will remove UpdateAccount action") + } + } + if !present { + fmt.Println("This run will add UpdateAccount action") + actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::\"UpdateAccount\""}) + } + policies[0].Actions = actions + + mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + mockClient.MockUpdatePolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + status, err := p.SetPolicyInfo(info, apps[0], policies) + assert.NoError(t, err) + assert.True(t, mockClient.VerifyCalled()) + + // this should cause a replacement (delete and add) to occur (subject change) + policies[0].Subjects = []string{"hexa_avp::User::\"gerry@strata.io\""} + + mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + mockClient.MockDeletePolicyWithHttpStatus(http.StatusOK) + mockClient.MockCreatePolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + status, err = p.SetPolicyInfo(info, apps[0], policies) + assert.NoError(t, err) + assert.Equal(t, 200, status, "Should be status 200") + assert.True(t, mockClient.VerifyCalled()) + + // now do the implied delete + policies2 := policies[1:] + mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) + mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + mockClient.MockDeletePolicyWithHttpStatus(http.StatusOK) + status, err = p.SetPolicyInfo(info, apps[0], policies2) + assert.NoError(t, err) + assert.Equal(t, 200, status, "Should be status 200") + assert.True(t, mockClient.VerifyCalled()) + + // now do an add policy + mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 0, 1, nil) + mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + mockClient.MockCreatePolicyWithHttpStatus(http.StatusOK, "id10") + + // Note policies has both a static and template. Initial list was mocked with only 1 template - to cause an add + status, err = p.SetPolicyInfo(info, apps[0], policies) + assert.NoError(t, err) + assert.Equal(t, 200, status, "Should be status 200") + assert.True(t, mockClient.VerifyCalled()) +} + +func TestAvp_5_GetSchemaLive(t *testing.T) { + if isLiveTest() { + var err error + _ = initializeOnlineTests() + apps, err := testData.Provider.DiscoverApplications(testData.Info) + assert.NoError(t, err) + + schemaNamespaces, err := testData.Provider.GetSchema(testData.Info, apps[0]) + + assert.NoError(t, err) + assert.NotNil(t, schemaNamespaces) + for k, schema := range *schemaNamespaces { + assert.NotNil(t, schema.EntityTypes) + assert.NotEmpty(t, schema.EntityTypes) + actions := schema.Actions + readAccountAction, ok := actions["ReadAccount"] + ptypes := *readAccountAction.AppliesTo.PrincipalTypes + + assert.True(t, ok, "ReadAccount action not ok") + assert.Equal(t, "User", ptypes[0]) + fmt.Println(fmt.Sprintf("Namespace: %s\n%v", k, schema)) + } + + } + +} + +func TestAvp_6_GetPoliciesLive(t *testing.T) { + if isLiveTest() { + + var err error + _ = initializeOnlineTests() + apps, err := testData.Provider.DiscoverApplications(testData.Info) + assert.NoError(t, err) + + policies, err := testData.Provider.GetPolicyInfo(testData.Info, apps[0]) + assert.NoError(t, err) + assert.NotNil(t, policies) + + output, err := json.MarshalIndent(policies, "", " ") + assert.NoError(t, err) + fmt.Println(string(output)) + } } diff --git a/providers/aws/awsapigwProvider/aws_apigw_provider_service.go b/providers/aws/awsapigwProvider/aws_apigw_provider_service.go index 31180de..43a1dcc 100644 --- a/providers/aws/awsapigwProvider/aws_apigw_provider_service.go +++ b/providers/aws/awsapigwProvider/aws_apigw_provider_service.go @@ -1,73 +1,73 @@ package awsapigwProvider import ( - "net/http" + "net/http" - "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/models/rar" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "github.com/hexa-org/policy-mapper/providers/aws/awsapigwProvider/dynamodbpolicy" - "github.com/hexa-org/policy-mapper/providers/aws/awscognito" + "github.com/hexa-org/policy-mapper/api/policyprovider" + "github.com/hexa-org/policy-mapper/models/rar" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/providers/aws/awsapigwProvider/dynamodbpolicy" + "github.com/hexa-org/policy-mapper/providers/aws/awscognito" - log "golang.org/x/exp/slog" + log "golang.org/x/exp/slog" ) type AwsApiGatewayProviderService struct { - cognitoClient awscognito.CognitoClient - policySvc dynamodbpolicy.PolicyStoreSvc + cognitoClient awscognito.CognitoClient + policySvc dynamodbpolicy.PolicyStoreSvc } func NewAwsApiGatewayProviderService(cognitoClient awscognito.CognitoClient, policySvc dynamodbpolicy.PolicyStoreSvc) *AwsApiGatewayProviderService { - return &AwsApiGatewayProviderService{cognitoClient: cognitoClient, policySvc: policySvc} + return &AwsApiGatewayProviderService{cognitoClient: cognitoClient, policySvc: policySvc} } func (s *AwsApiGatewayProviderService) DiscoverApplications(_ policyprovider.IntegrationInfo) ([]policyprovider.ApplicationInfo, error) { - return s.cognitoClient.ListUserPools() + return s.cognitoClient.ListUserPools() } func (s *AwsApiGatewayProviderService) GetPolicyInfo(appInfo policyprovider.ApplicationInfo) ([]hexapolicy.PolicyInfo, error) { - rarList, err := s.policySvc.GetResourceRoles() - if err != nil { - log.Error("AwsApiGatewayProviderService.GetPolicyInfo", "error calling GetResourceRoles App.Name", appInfo.Name, "identifierUrl[0]", appInfo.Service, "err=", err) - return []hexapolicy.PolicyInfo{}, err - } - return rar.BuildPolicies(rarList), nil + rarList, err := s.policySvc.GetResourceRoles() + if err != nil { + log.Error("AwsApiGatewayProviderService.GetPolicyInfo", "error calling GetResourceRoles App.Name", appInfo.Name, "identifierUrl[0]", appInfo.Service, "err=", err) + return []hexapolicy.PolicyInfo{}, err + } + return rar.BuildPolicies(rarList), nil } func (s *AwsApiGatewayProviderService) SetPolicyInfo(appInfo policyprovider.ApplicationInfo, policyInfos []hexapolicy.PolicyInfo) (int, error) { - rarList, err := s.policySvc.GetResourceRoles() - if err != nil { - log.Error("AwsApiGatewayProviderService.SetPolicyInfo", "error calling GetResourceRoles App.Name", appInfo.Name, "identifierUrl[0]", appInfo.Service, "err=", err) - return http.StatusBadGateway, err - } + rarList, err := s.policySvc.GetResourceRoles() + if err != nil { + log.Error("AwsApiGatewayProviderService.SetPolicyInfo", "error calling GetResourceRoles App.Name", appInfo.Name, "identifierUrl[0]", appInfo.Service, "err=", err) + return http.StatusBadGateway, err + } - rarUpdateList := rar.CalcResourceActionRolesForUpdate(rarList, policyInfos) - for _, rar := range rarUpdateList { - err = s.policySvc.UpdateResourceRole(rar) - if err != nil { - return http.StatusBadGateway, err - } - } - return http.StatusCreated, nil + rarUpdateList := rar.CalcResourceActionRolesForUpdate(rarList, policyInfos) + for _, rarItem := range rarUpdateList { + err = s.policySvc.UpdateResourceRole(rarItem) + if err != nil { + return http.StatusBadGateway, err + } + } + return http.StatusCreated, nil } func (s *AwsApiGatewayProviderService) setPolicyInfoOld(appInfo policyprovider.ApplicationInfo, policyInfos []hexapolicy.PolicyInfo) (int, error) { - allGroups, err := s.cognitoClient.GetGroups(appInfo.ObjectID) - if err != nil { - return http.StatusInternalServerError, err - } - for _, pol := range policyInfos { - groupName := pol.Actions[0].ActionUri - _, exists := allGroups[groupName] - if !exists { - continue - } + allGroups, err := s.cognitoClient.GetGroups(appInfo.ObjectID) + if err != nil { + return http.StatusInternalServerError, err + } + for _, pol := range policyInfos { + groupName := pol.Actions[0].ActionUri + _, exists := allGroups[groupName] + if !exists { + continue + } - err = s.cognitoClient.SetGroupsAssignedTo(groupName, pol.Subject.Members, appInfo) - if err != nil { - return http.StatusInternalServerError, err - } - } + err = s.cognitoClient.SetGroupsAssignedTo(groupName, pol.Subjects, appInfo) + if err != nil { + return http.StatusInternalServerError, err + } + } - return http.StatusCreated, nil + return http.StatusCreated, nil } diff --git a/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go b/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go index 7923cf6..299583a 100644 --- a/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go +++ b/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go @@ -1,298 +1,298 @@ package awsapigwProvider_test import ( - "errors" - "net/http" - "testing" - - "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/models/rar" - "github.com/hexa-org/policy-mapper/models/rar/testsupport/awstestsupport" - "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "github.com/hexa-org/policy-mapper/providers/aws/awsapigwProvider" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" + "errors" + "net/http" + "testing" + + "github.com/hexa-org/policy-mapper/api/policyprovider" + "github.com/hexa-org/policy-mapper/models/rar" + "github.com/hexa-org/policy-mapper/models/rar/testsupport/awstestsupport" + "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/providers/aws/awsapigwProvider" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) type mockCognitoClient struct { - mock.Mock + mock.Mock } func (m *mockCognitoClient) ListUserPools() (apps []policyprovider.ApplicationInfo, err error) { - args := m.Called() - return args.Get(0).([]policyprovider.ApplicationInfo), args.Error(1) + args := m.Called() + return args.Get(0).([]policyprovider.ApplicationInfo), args.Error(1) } func (m *mockCognitoClient) GetGroups(_ string) (map[string]string, error) { - panic("GetGroups not implemented") + panic("GetGroups not implemented") } func (m *mockCognitoClient) GetMembersAssignedTo(_ policyprovider.ApplicationInfo, _ string) ([]string, error) { - panic("GetMembersAssignedTo not implemented") + panic("GetMembersAssignedTo not implemented") } func (m *mockCognitoClient) SetGroupsAssignedTo(_ string, _ []string, _ policyprovider.ApplicationInfo) error { - panic("SetGroupsAssignedTo not implemented") + panic("SetGroupsAssignedTo not implemented") } func (m *mockCognitoClient) expectListUserPools(apps []policyprovider.ApplicationInfo, err error) { - m.On("ListUserPools").Return(apps, err) + m.On("ListUserPools").Return(apps, err) } func TestAwsApiGatewayProviderService_DiscoverApplications_Error(t *testing.T) { - cognitoClient := &mockCognitoClient{} - cognitoClient.expectListUserPools(nil, errors.New("some error")) - service := awsapigwProvider.NewAwsApiGatewayProviderService(cognitoClient, nil) - apps, err := service.DiscoverApplications(awstestsupport.IntegrationInfo("amazon")) - assert.Error(t, err) - assert.Len(t, apps, 0) + cognitoClient := &mockCognitoClient{} + cognitoClient.expectListUserPools(nil, errors.New("some error")) + service := awsapigwProvider.NewAwsApiGatewayProviderService(cognitoClient, nil) + apps, err := service.DiscoverApplications(awstestsupport.IntegrationInfo("amazon")) + assert.Error(t, err) + assert.Len(t, apps, 0) } func TestAwsApiGatewayProviderService_DiscoverApplications(t *testing.T) { - cognitoClient := &mockCognitoClient{} - expApps := []policyprovider.ApplicationInfo{awstestsupport.AppInfo()} - cognitoClient.expectListUserPools(expApps, nil) - service := awsapigwProvider.NewAwsApiGatewayProviderService(cognitoClient, nil) - apps, err := service.DiscoverApplications(awstestsupport.IntegrationInfo("amazon")) - assert.NoError(t, err) - assert.Len(t, apps, len(expApps)) + cognitoClient := &mockCognitoClient{} + expApps := []policyprovider.ApplicationInfo{awstestsupport.AppInfo()} + cognitoClient.expectListUserPools(expApps, nil) + service := awsapigwProvider.NewAwsApiGatewayProviderService(cognitoClient, nil) + apps, err := service.DiscoverApplications(awstestsupport.IntegrationInfo("amazon")) + assert.NoError(t, err) + assert.Len(t, apps, len(expApps)) } func TestGetPolicyInfo_Error(t *testing.T) { - policyStoreSvc := &mockPolicyStoreSvc{} + policyStoreSvc := &mockPolicyStoreSvc{} - policyStoreSvc.expectGetResourceRoles(nil, errors.New("some-error")) - service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) + policyStoreSvc.expectGetResourceRoles(nil, errors.New("some-error")) + service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) - appInfo := policyprovider.ApplicationInfo{} - actPolicies, err := service.GetPolicyInfo(appInfo) - assert.ErrorContains(t, err, "some-error") - assert.NotNil(t, actPolicies) - assert.Empty(t, actPolicies) + appInfo := policyprovider.ApplicationInfo{} + actPolicies, err := service.GetPolicyInfo(appInfo) + assert.ErrorContains(t, err, "some-error") + assert.NotNil(t, actPolicies) + assert.Empty(t, actPolicies) } func TestGetPolicyInfo_NoResourceRoles(t *testing.T) { - policyStoreSvc := &mockPolicyStoreSvc{} + policyStoreSvc := &mockPolicyStoreSvc{} - policyStoreSvc.expectGetResourceRoles(nil, nil) - service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) + policyStoreSvc.expectGetResourceRoles(nil, nil) + service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) - appInfo := policyprovider.ApplicationInfo{} - actPolicies, err := service.GetPolicyInfo(appInfo) - assert.NoError(t, err) - assert.NotNil(t, actPolicies) - assert.Empty(t, actPolicies) + appInfo := policyprovider.ApplicationInfo{} + actPolicies, err := service.GetPolicyInfo(appInfo) + assert.NoError(t, err) + assert.NotNil(t, actPolicies) + assert.Empty(t, actPolicies) } func TestGetPolicyInfo(t *testing.T) { - policyStoreSvc := &mockPolicyStoreSvc{} - existingActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {"some-hr-role"}, - policytestsupport.ActionGetProfile: {"some-profile-role"}, - } - expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) - - policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) - service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) - - appInfo := policyprovider.ApplicationInfo{} - actPolicies, err := service.GetPolicyInfo(appInfo) - assert.NoError(t, err) - assert.NotNil(t, actPolicies) - assert.Equal(t, len(existingActionRoles), len(actPolicies)) - - for _, actPol := range actPolicies { - actMembers := actPol.Subject.Members - actPolRar := rar.NewResourceActionUriRoles(actPol.Object.ResourceID, actPol.Actions[0].ActionUri, actMembers) - actLookupKey := actPolRar.Action + actPolRar.Resource - expMembers, found := existingActionRoles[actLookupKey] - assert.True(t, found) - assert.Equal(t, expMembers, actMembers) - } + policyStoreSvc := &mockPolicyStoreSvc{} + existingActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {"some-hr-role"}, + policytestsupport.ActionGetProfile: {"some-profile-role"}, + } + expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) + + policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) + service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) + + appInfo := policyprovider.ApplicationInfo{} + actPolicies, err := service.GetPolicyInfo(appInfo) + assert.NoError(t, err) + assert.NotNil(t, actPolicies) + assert.Equal(t, len(existingActionRoles), len(actPolicies)) + + for _, actPol := range actPolicies { + actMembers := actPol.Subjects.String() + actPolRar := rar.NewResourceActionUriRoles(actPol.Object.ResourceID, actPol.Actions[0].ActionUri, actMembers) + actLookupKey := actPolRar.Action + actPolRar.Resource + expMembers, found := existingActionRoles[actLookupKey] + assert.True(t, found) + assert.Equal(t, expMembers, actMembers) + } } func TestSetPolicyInfo_GetResourcesError(t *testing.T) { - policyStoreSvc := &mockPolicyStoreSvc{} - policyStoreSvc.expectGetResourceRoles(nil, errors.New("some-error")) - service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) - appInfo := policyprovider.ApplicationInfo{} - status, err := service.SetPolicyInfo(appInfo, []hexapolicy.PolicyInfo{}) - assert.ErrorContains(t, err, "some-error") - assert.Equal(t, http.StatusBadGateway, status) + policyStoreSvc := &mockPolicyStoreSvc{} + policyStoreSvc.expectGetResourceRoles(nil, errors.New("some-error")) + service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) + appInfo := policyprovider.ApplicationInfo{} + status, err := service.SetPolicyInfo(appInfo, []hexapolicy.PolicyInfo{}) + assert.ErrorContains(t, err, "some-error") + assert.Equal(t, http.StatusBadGateway, status) } func TestSetPolicyInfo_UpdateError(t *testing.T) { - policyStoreSvc := &mockPolicyStoreSvc{} - existingActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {"some-hr-role"}, - policytestsupport.ActionGetProfile: {"some-profile-role"}, - } - expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) - policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) - - newActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {"some-hr-role-new"}, - policytestsupport.ActionGetProfile: {"some-profile-role-new"}, - } - newResourceRoles := policytestsupport.MakeRarList(newActionRoles) - policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[0], errors.New("some-error")) - - policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) - - service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) - appInfo := policyprovider.ApplicationInfo{} - status, err := service.SetPolicyInfo(appInfo, policies) - assert.ErrorContains(t, err, "some-error") - assert.Equal(t, http.StatusBadGateway, status) + policyStoreSvc := &mockPolicyStoreSvc{} + existingActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {"some-hr-role"}, + policytestsupport.ActionGetProfile: {"some-profile-role"}, + } + expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) + policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) + + newActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {"some-hr-role-new"}, + policytestsupport.ActionGetProfile: {"some-profile-role-new"}, + } + newResourceRoles := policytestsupport.MakeRarList(newActionRoles) + policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[0], errors.New("some-error")) + + policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) + + service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) + appInfo := policyprovider.ApplicationInfo{} + status, err := service.SetPolicyInfo(appInfo, policies) + assert.ErrorContains(t, err, "some-error") + assert.Equal(t, http.StatusBadGateway, status) } func TestSetPolicyInfo_MultiplePolicies(t *testing.T) { - policyStoreSvc := &mockPolicyStoreSvc{} - existingActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {"some-hr-role"}, - policytestsupport.ActionGetProfile: {"some-profile-role"}, - } - expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) - policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) - - newActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {"some-hr-role-new"}, - policytestsupport.ActionGetProfile: {"some-profile-role-new"}, - } - newResourceRoles := policytestsupport.MakeRarList(newActionRoles) - policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[0], nil) - policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[1], nil) - - policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) - - service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) - appInfo := policyprovider.ApplicationInfo{} - status, err := service.SetPolicyInfo(appInfo, policies) - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) + policyStoreSvc := &mockPolicyStoreSvc{} + existingActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {"some-hr-role"}, + policytestsupport.ActionGetProfile: {"some-profile-role"}, + } + expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) + policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) + + newActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {"some-hr-role-new"}, + policytestsupport.ActionGetProfile: {"some-profile-role-new"}, + } + newResourceRoles := policytestsupport.MakeRarList(newActionRoles) + policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[0], nil) + policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[1], nil) + + policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) + + service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) + appInfo := policyprovider.ApplicationInfo{} + status, err := service.SetPolicyInfo(appInfo, policies) + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) } func TestSetPolicyInfo_UpdateInputPolicyOnly(t *testing.T) { - policyStoreSvc := &mockPolicyStoreSvc{} - existingActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {"some-hr-role"}, - policytestsupport.ActionGetProfile: {"some-profile-role"}, - } - expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) - policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) - - newActionRoles := map[string][]string{ - policytestsupport.ActionGetProfile: {"some-profile-role-new"}, - } - newResourceRoles := policytestsupport.MakeRarList(newActionRoles) - policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[0], nil) - - policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) - - service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) - appInfo := policyprovider.ApplicationInfo{} - status, err := service.SetPolicyInfo(appInfo, policies) - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) + policyStoreSvc := &mockPolicyStoreSvc{} + existingActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {"some-hr-role"}, + policytestsupport.ActionGetProfile: {"some-profile-role"}, + } + expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) + policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) + + newActionRoles := map[string][]string{ + policytestsupport.ActionGetProfile: {"some-profile-role-new"}, + } + newResourceRoles := policytestsupport.MakeRarList(newActionRoles) + policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[0], nil) + + policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) + + service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) + appInfo := policyprovider.ApplicationInfo{} + status, err := service.SetPolicyInfo(appInfo, policies) + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) } func TestSetPolicyInfo_RemoveAllMembers(t *testing.T) { - policyStoreSvc := &mockPolicyStoreSvc{} - existingActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {"some-hr-role"}, - policytestsupport.ActionGetProfile: {"some-profile-role"}, - } - expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) - policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) - - newActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {}, - policytestsupport.ActionGetProfile: {}, - } - newResourceRoles := policytestsupport.MakeRarList(newActionRoles) - policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[0], nil) - policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[1], nil) - - policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) - - service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) - appInfo := policyprovider.ApplicationInfo{} - status, err := service.SetPolicyInfo(appInfo, policies) - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) + policyStoreSvc := &mockPolicyStoreSvc{} + existingActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {"some-hr-role"}, + policytestsupport.ActionGetProfile: {"some-profile-role"}, + } + expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) + policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) + + newActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {}, + policytestsupport.ActionGetProfile: {}, + } + newResourceRoles := policytestsupport.MakeRarList(newActionRoles) + policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[0], nil) + policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[1], nil) + + policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) + + service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) + appInfo := policyprovider.ApplicationInfo{} + status, err := service.SetPolicyInfo(appInfo, policies) + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) } func TestSetPolicyInfo_NoChange(t *testing.T) { - policyStoreSvc := &mockPolicyStoreSvc{} - existingActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {"some-hr-role"}, - policytestsupport.ActionGetProfile: {"some-profile-role"}, - } - expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) - policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) - - newActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {"some-hr-role"}, - policytestsupport.ActionGetProfile: {"some-profile-role"}, - } - policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) - - service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) - appInfo := policyprovider.ApplicationInfo{} - status, err := service.SetPolicyInfo(appInfo, policies) - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) + policyStoreSvc := &mockPolicyStoreSvc{} + existingActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {"some-hr-role"}, + policytestsupport.ActionGetProfile: {"some-profile-role"}, + } + expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) + policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) + + newActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {"some-hr-role"}, + policytestsupport.ActionGetProfile: {"some-profile-role"}, + } + policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) + + service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) + appInfo := policyprovider.ApplicationInfo{} + status, err := service.SetPolicyInfo(appInfo, policies) + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) } func TestSetPolicyInfo_AddsNewMembersAll(t *testing.T) { - policyStoreSvc := &mockPolicyStoreSvc{} - existingActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {}, - policytestsupport.ActionGetProfile: {}, - } - expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) - policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) - - newActionRoles := map[string][]string{ - policytestsupport.ActionGetHrUs: {"some-hr-role"}, - policytestsupport.ActionGetProfile: {"some-profile-role"}, - } - newResourceRoles := policytestsupport.MakeRarList(newActionRoles) - policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[0], nil) - policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[1], nil) - policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) - - service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) - appInfo := policyprovider.ApplicationInfo{} - status, err := service.SetPolicyInfo(appInfo, policies) - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) + policyStoreSvc := &mockPolicyStoreSvc{} + existingActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {}, + policytestsupport.ActionGetProfile: {}, + } + expReturnResourceRoles := policytestsupport.MakeRarList(existingActionRoles) + policyStoreSvc.expectGetResourceRoles(expReturnResourceRoles, nil) + + newActionRoles := map[string][]string{ + policytestsupport.ActionGetHrUs: {"some-hr-role"}, + policytestsupport.ActionGetProfile: {"some-profile-role"}, + } + newResourceRoles := policytestsupport.MakeRarList(newActionRoles) + policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[0], nil) + policyStoreSvc.expectUpdateResourceRoles(newResourceRoles[1], nil) + policies := policytestsupport.MakeRoleSubjectTestPolicies(newActionRoles) + + service := awsapigwProvider.NewAwsApiGatewayProviderService(nil, policyStoreSvc) + appInfo := policyprovider.ApplicationInfo{} + status, err := service.SetPolicyInfo(appInfo, policies) + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) } type mockPolicyStoreSvc struct { - mock.Mock + mock.Mock } func (m *mockPolicyStoreSvc) GetResourceRoles() ([]rar.ResourceActionRoles, error) { - args := m.Called() - rars := args.Get(0) - if rars == nil { - return nil, args.Error(1) - } - return args.Get(0).([]rar.ResourceActionRoles), args.Error(1) + args := m.Called() + rars := args.Get(0) + if rars == nil { + return nil, args.Error(1) + } + return args.Get(0).([]rar.ResourceActionRoles), args.Error(1) } func (m *mockPolicyStoreSvc) UpdateResourceRole(rar rar.ResourceActionRoles) error { - args := m.Called(rar) - return args.Error(0) + args := m.Called(rar) + return args.Error(0) } func (m *mockPolicyStoreSvc) expectGetResourceRoles(andReturn []rar.ResourceActionRoles, orError error) { - m.On("GetResourceRoles").Return(andReturn, orError) + m.On("GetResourceRoles").Return(andReturn, orError) } func (m *mockPolicyStoreSvc) expectUpdateResourceRoles(withResRole rar.ResourceActionRoles, orError error) { - m.On("UpdateResourceRole", withResRole).Return(orError) + m.On("UpdateResourceRole", withResRole).Return(orError) } diff --git a/providers/aws/cognitoProvider/cognito_provider.go b/providers/aws/cognitoProvider/cognito_provider.go index 5346eb8..9699951 100644 --- a/providers/aws/cognitoProvider/cognito_provider.go +++ b/providers/aws/cognitoProvider/cognito_provider.go @@ -1,104 +1,104 @@ package cognitoProvider import ( - "net/http" - "strings" + "net/http" + "strings" - "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "github.com/hexa-org/policy-mapper/providers/aws/awscognito" - "github.com/hexa-org/policy-mapper/providers/aws/awscommon" + "github.com/hexa-org/policy-mapper/api/policyprovider" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/providers/aws/awscognito" + "github.com/hexa-org/policy-mapper/providers/aws/awscommon" - "github.com/go-playground/validator/v10" + "github.com/go-playground/validator/v10" ) const ProviderTypeAwsCognito string = "cognito" type CognitoProvider struct { - AwsClientOpts awscommon.AWSClientOptions + AwsClientOpts awscommon.AWSClientOptions } func (a *CognitoProvider) Name() string { - return ProviderTypeAwsCognito + return ProviderTypeAwsCognito } func (a *CognitoProvider) DiscoverApplications(info policyprovider.IntegrationInfo) ([]policyprovider.ApplicationInfo, error) { - if !strings.EqualFold(info.Name, a.Name()) { - return []policyprovider.ApplicationInfo{}, nil - } - - client, err := awscognito.NewCognitoClient(info.Key, a.AwsClientOpts) - if err != nil { - return nil, err - } - return client.ListUserPools() + if !strings.EqualFold(info.Name, a.Name()) { + return []policyprovider.ApplicationInfo{}, nil + } + + client, err := awscognito.NewCognitoClient(info.Key, a.AwsClientOpts) + if err != nil { + return nil, err + } + return client.ListUserPools() } func (a *CognitoProvider) GetPolicyInfo(info policyprovider.IntegrationInfo, applicationInfo policyprovider.ApplicationInfo) ([]hexapolicy.PolicyInfo, error) { - client, err := awscognito.NewCognitoClient(info.Key, a.AwsClientOpts) - if err != nil { - return nil, err - } - - groups, err := client.GetGroups(applicationInfo.ObjectID) - if err != nil { - return nil, err - } - - var policies []hexapolicy.PolicyInfo - for groupName := range groups { - members, err := client.GetMembersAssignedTo(applicationInfo, groupName) - if err != nil { - return nil, err - } - policies = append(policies, hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{ - Version: hexapolicy.IdqlVersion, - ProviderType: ProviderTypeAwsCognito, - }, - Actions: []hexapolicy.ActionInfo{{groupName}}, - Subject: hexapolicy.SubjectInfo{Members: members}, - Object: hexapolicy.ObjectInfo{ - ResourceID: applicationInfo.Name, - }, - }) - } - - return policies, nil + client, err := awscognito.NewCognitoClient(info.Key, a.AwsClientOpts) + if err != nil { + return nil, err + } + + groups, err := client.GetGroups(applicationInfo.ObjectID) + if err != nil { + return nil, err + } + + var policies []hexapolicy.PolicyInfo + for groupName := range groups { + members, err := client.GetMembersAssignedTo(applicationInfo, groupName) + if err != nil { + return nil, err + } + policies = append(policies, hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{ + Version: hexapolicy.IdqlVersion, + ProviderType: ProviderTypeAwsCognito, + }, + Actions: []hexapolicy.ActionInfo{{groupName}}, + Subjects: members, + Object: hexapolicy.ObjectInfo{ + ResourceID: applicationInfo.Name, + }, + }) + } + + return policies, nil } func (a *CognitoProvider) SetPolicyInfo(info policyprovider.IntegrationInfo, applicationInfo policyprovider.ApplicationInfo, policyInfos []hexapolicy.PolicyInfo) (int, error) { - validate := validator.New() // todo - move this up? - err := validate.Struct(applicationInfo) - if err != nil { - return http.StatusInternalServerError, err - } - err = validate.Var(policyInfos, "omitempty,dive") - if err != nil { - return http.StatusInternalServerError, err - } - - client, err := awscognito.NewCognitoClient(info.Key, a.AwsClientOpts) - if err != nil { - return http.StatusInternalServerError, err - } - - allGroups, err := client.GetGroups(applicationInfo.ObjectID) - if err != nil { - return http.StatusInternalServerError, err - } - for _, pol := range policyInfos { - groupName := pol.Actions[0].ActionUri - _, exists := allGroups[groupName] - if !exists { - continue - } - - err = client.SetGroupsAssignedTo(groupName, pol.Subject.Members, applicationInfo) - if err != nil { - return http.StatusInternalServerError, err - } - } - - return http.StatusCreated, nil + validate := validator.New() // todo - move this up? + err := validate.Struct(applicationInfo) + if err != nil { + return http.StatusInternalServerError, err + } + err = validate.Var(policyInfos, "omitempty,dive") + if err != nil { + return http.StatusInternalServerError, err + } + + client, err := awscognito.NewCognitoClient(info.Key, a.AwsClientOpts) + if err != nil { + return http.StatusInternalServerError, err + } + + allGroups, err := client.GetGroups(applicationInfo.ObjectID) + if err != nil { + return http.StatusInternalServerError, err + } + for _, pol := range policyInfos { + groupName := pol.Actions[0].ActionUri + _, exists := allGroups[groupName] + if !exists { + continue + } + + err = client.SetGroupsAssignedTo(groupName, pol.Subjects, applicationInfo) + if err != nil { + return http.StatusInternalServerError, err + } + } + + return http.StatusCreated, nil } diff --git a/providers/aws/cognitoProvider/cognito_provider_test.go b/providers/aws/cognitoProvider/cognito_provider_test.go index 2ed64d3..835fac8 100644 --- a/providers/aws/cognitoProvider/cognito_provider_test.go +++ b/providers/aws/cognitoProvider/cognito_provider_test.go @@ -1,440 +1,440 @@ package cognitoProvider_test import ( - "net/http" - "testing" - - "github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider" - "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/models/rar/testsupport/awstestsupport" - "github.com/hexa-org/policy-mapper/models/rar/testsupport/cognitotestsupport" - "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "github.com/hexa-org/policy-mapper/providers/aws/awscommon" - "github.com/hexa-org/policy-mapper/providers/aws/cognitoProvider" - - "github.com/stretchr/testify/assert" + "net/http" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider" + "github.com/hexa-org/policy-mapper/api/policyprovider" + "github.com/hexa-org/policy-mapper/models/rar/testsupport/awstestsupport" + "github.com/hexa-org/policy-mapper/models/rar/testsupport/cognitotestsupport" + "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/providers/aws/awscommon" + "github.com/hexa-org/policy-mapper/providers/aws/cognitoProvider" + + "github.com/stretchr/testify/assert" ) func TestAmazonProvider_DiscoverApplications(t *testing.T) { - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - p := cognitoProvider.CognitoProvider{AwsClientOpts: awscommon.AWSClientOptions{DisableRetry: true}} - _, err := p.DiscoverApplications(info) - assert.Error(t, err, "operation error Cognito Identity Provider: ListUserPools, expected endpoint resolver to not be nil") + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + p := cognitoProvider.CognitoProvider{AwsClientOpts: awscommon.AWSClientOptions{DisableRetry: true}} + _, err := p.DiscoverApplications(info) + assert.Error(t, err, "operation error Cognito Identity Provider: ListUserPools, expected endpoint resolver to not be nil") } func TestAmazonProvider_DiscoverApplications_withOtherProvider(t *testing.T) { - p := &cognitoProvider.CognitoProvider{} - info := policyprovider.IntegrationInfo{Name: "not_amazon", Key: []byte("aKey")} - apps, err := p.DiscoverApplications(info) - assert.NoError(t, err) - assert.Empty(t, apps) + p := &cognitoProvider.CognitoProvider{} + info := policyprovider.IntegrationInfo{Name: "not_amazon", Key: []byte("aKey")} + apps, err := p.DiscoverApplications(info) + assert.NoError(t, err) + assert.Empty(t, apps) } func TestAmazonProvider_ListUserPools_ErrorCallingListUserPoolsApi(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - mockClient.MockListUserPoolsWithHttpStatus(http.StatusBadRequest) - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - apps, err := p.DiscoverApplications(info) - assert.Error(t, err) - assert.ErrorContains(t, err, "error StatusCode: 400") - assert.ErrorContains(t, err, "ListUserPools") - assert.Empty(t, apps) - assert.True(t, mockClient.VerifyCalled()) + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + mockClient.MockListUserPoolsWithHttpStatus(http.StatusBadRequest) + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + apps, err := p.DiscoverApplications(info) + assert.Error(t, err) + assert.ErrorContains(t, err, "error StatusCode: 400") + assert.ErrorContains(t, err, "ListUserPools") + assert.Empty(t, apps) + assert.True(t, mockClient.VerifyCalled()) } func TestAmazonProvider_ListUserPools_ErrorCallingListResourceServicesApi(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - mockClient.MockListUserPools() - mockClient.MockListResourceServersWithHttpStatus(http.StatusBadRequest, cognitoidentityprovider.ListResourceServersOutput{}) - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - apps, err := p.DiscoverApplications(info) - assert.Error(t, err) - assert.ErrorContains(t, err, "error StatusCode: 400") - assert.ErrorContains(t, err, "ListResourceServers") - assert.Empty(t, apps) - assert.True(t, mockClient.VerifyCalled()) + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + mockClient.MockListUserPools() + mockClient.MockListResourceServersWithHttpStatus(http.StatusBadRequest, cognitoidentityprovider.ListResourceServersOutput{}) + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + apps, err := p.DiscoverApplications(info) + assert.Error(t, err) + assert.ErrorContains(t, err, "error StatusCode: 400") + assert.ErrorContains(t, err, "ListResourceServers") + assert.Empty(t, apps) + assert.True(t, mockClient.VerifyCalled()) } func TestAmazonProvider_ListUserPools_Success(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - mockClient.MockListUserPools() - mockClient.MockListResourceServers(cognitotestsupport.WithResourceServer()) - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - apps, err := p.DiscoverApplications(info) - assert.NoError(t, err) - assert.Len(t, apps, 1) - assert.Equal(t, awstestsupport.TestUserPoolId, apps[0].ObjectID) - assert.Equal(t, awstestsupport.TestResourceServerName, apps[0].Name) - assert.Equal(t, awstestsupport.TestResourceServerIdentifier, apps[0].Service) - assert.Equal(t, "Resource: some-resource-server-name, UserPool: some-user-pool-name", apps[0].Description) - assert.True(t, mockClient.VerifyCalled()) + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + mockClient.MockListUserPools() + mockClient.MockListResourceServers(cognitotestsupport.WithResourceServer()) + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + apps, err := p.DiscoverApplications(info) + assert.NoError(t, err) + assert.Len(t, apps, 1) + assert.Equal(t, awstestsupport.TestUserPoolId, apps[0].ObjectID) + assert.Equal(t, awstestsupport.TestResourceServerName, apps[0].Name) + assert.Equal(t, awstestsupport.TestResourceServerIdentifier, apps[0].Service) + assert.Equal(t, "Resource: some-resource-server-name, UserPool: some-user-pool-name", apps[0].Description) + assert.True(t, mockClient.VerifyCalled()) } func TestAmazonProvider_GetPolicyInfo_CognitoClientError(t *testing.T) { - p := cognitoProvider.CognitoProvider{} - info := policyprovider.IntegrationInfo{Name: cognitoProvider.ProviderTypeAwsCognito, Key: []byte("!!!")} - appInfo := awstestsupport.AppInfo() - policyInfo, err := p.GetPolicyInfo(info, appInfo) - assert.Error(t, err) - assert.ErrorContains(t, err, "invalid character") - assert.Nil(t, policyInfo) + p := cognitoProvider.CognitoProvider{} + info := policyprovider.IntegrationInfo{Name: cognitoProvider.ProviderTypeAwsCognito, Key: []byte("!!!")} + appInfo := awstestsupport.AppInfo() + policyInfo, err := p.GetPolicyInfo(info, appInfo) + assert.Error(t, err) + assert.ErrorContains(t, err, "invalid character") + assert.Nil(t, policyInfo) } func TestAmazonProvider_GetPolicyInfo(t *testing.T) { - // TODO - investigate why this test is flaky. Something to do with the ProcessAsync call - t.Skip("Skip flaky test TestAmazonProvider_GetPolicyInfo") - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - mockClient.MockListGroups(policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs) - mockClient.MockListUsersInGroup(policytestsupport.UserIdGetProfile) - mockClient.MockAdminGetUser(policytestsupport.UserIdGetProfile, policytestsupport.UserEmailGetProfile) - mockClient.MockListUsersInGroup(policytestsupport.UserIdGetHrUs) - mockClient.MockAdminGetUser(policytestsupport.UserIdGetHrUs, policytestsupport.UserEmailGetHrUs) - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - actualPolicies, err := p.GetPolicyInfo(info, appInfo) - assert.NoError(t, err) - assert.NotEmpty(t, actualPolicies) - expActionMembers := map[string][]string{ - policytestsupport.ActionGetProfile: {policytestsupport.UserEmailGetProfile}, - policytestsupport.ActionGetHrUs: {policytestsupport.UserEmailGetHrUs}, - } - - expPolicies := policytestsupport.MakePolicies(expActionMembers, awstestsupport.TestResourceServerName) - assert.Equal(t, len(expPolicies), len(actualPolicies)) - assert.True(t, policytestsupport.ContainsPolicies(t, expPolicies, actualPolicies)) - assert.True(t, mockClient.VerifyCalled()) + // TODO - investigate why this test is flaky. Something to do with the ProcessAsync call + t.Skip("Skip flaky test TestAmazonProvider_GetPolicyInfo") + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + mockClient.MockListGroups(policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs) + mockClient.MockListUsersInGroup(policytestsupport.UserIdGetProfile) + mockClient.MockAdminGetUser(policytestsupport.UserIdGetProfile, policytestsupport.UserEmailGetProfile) + mockClient.MockListUsersInGroup(policytestsupport.UserIdGetHrUs) + mockClient.MockAdminGetUser(policytestsupport.UserIdGetHrUs, policytestsupport.UserEmailGetHrUs) + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + actualPolicies, err := p.GetPolicyInfo(info, appInfo) + assert.NoError(t, err) + assert.NotEmpty(t, actualPolicies) + expActionMembers := map[string][]string{ + policytestsupport.ActionGetProfile: {policytestsupport.UserEmailGetProfile}, + policytestsupport.ActionGetHrUs: {policytestsupport.UserEmailGetHrUs}, + } + + expPolicies := policytestsupport.MakePolicies(expActionMembers, awstestsupport.TestResourceServerName) + assert.Equal(t, len(expPolicies), len(actualPolicies)) + assert.True(t, policytestsupport.ContainsPolicies(t, expPolicies, actualPolicies)) + assert.True(t, mockClient.VerifyCalled()) } func TestAmazonProvider_GetPolicyInfo_withListGroupsError(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - - mockClient.MockListGroupsWithHttpStatus(http.StatusBadRequest, policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs) - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - _, err := p.GetPolicyInfo(info, appInfo) - assert.Error(t, err) - assert.ErrorContains(t, err, "error StatusCode: 400") + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + + mockClient.MockListGroupsWithHttpStatus(http.StatusBadRequest, policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs) + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + _, err := p.GetPolicyInfo(info, appInfo) + assert.Error(t, err) + assert.ErrorContains(t, err, "error StatusCode: 400") } func TestSetPolicy_withInvalidArguments(t *testing.T) { - key := []byte("key") - p := cognitoProvider.CognitoProvider{} - - status, err := p.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{Name: "anAppName", Description: "anAppId"}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, - }}) - - assert.Equal(t, http.StatusInternalServerError, status) - assert.EqualError(t, err, "Key: 'ApplicationInfo.ObjectID' Error:Field validation for 'ObjectID' failed on the 'required' tag") - - status, err = p.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: "aDescription"}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}}, - Object: hexapolicy.ObjectInfo{}, - }}) - - assert.Equal(t, http.StatusInternalServerError, status) - assert.EqualError(t, err, "Key: '[0].Object.ResourceID' Error:Field validation for 'ResourceID' failed on the 'required' tag") + key := []byte("key") + p := cognitoProvider.CognitoProvider{} + + status, err := p.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{Name: "anAppName", Description: "anAppId"}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, + Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, + Object: hexapolicy.ObjectInfo{ + ResourceID: "anObjectId", + }, + }}) + + assert.Equal(t, http.StatusInternalServerError, status) + assert.EqualError(t, err, "Key: 'ApplicationInfo.ObjectID' Error:Field validation for 'ObjectID' failed on the 'required' tag") + + status, err = p.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: "aDescription"}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, + Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, + Object: hexapolicy.ObjectInfo{}, + }}) + + assert.Equal(t, http.StatusInternalServerError, status) + assert.EqualError(t, err, "Key: '[0].Object.ResourceID' Error:Field validation for 'ResourceID' failed on the 'required' tag") } func TestSetPolicyInfo_CognitoClientError(t *testing.T) { - p := cognitoProvider.CognitoProvider{} - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - info.Key = []byte("!!!!") - appInfo := awstestsupport.AppInfo() - _, err := p.SetPolicyInfo(info, appInfo, []hexapolicy.PolicyInfo{}) - assert.Error(t, err) - assert.ErrorContains(t, err, "invalid character '!'") + p := cognitoProvider.CognitoProvider{} + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + info.Key = []byte("!!!!") + appInfo := awstestsupport.AppInfo() + _, err := p.SetPolicyInfo(info, appInfo, []hexapolicy.PolicyInfo{}) + assert.Error(t, err) + assert.ErrorContains(t, err, "invalid character '!'") } func TestSetPolicyInfo_ListGroupsError(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - mockClient.MockListGroupsWithHttpStatus(http.StatusBadRequest) - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - _, err := p.SetPolicyInfo(info, appInfo, []hexapolicy.PolicyInfo{}) - assert.Error(t, err) - assert.ErrorContains(t, err, "ListGroups") - assert.ErrorContains(t, err, "error StatusCode: 400") + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + mockClient.MockListGroupsWithHttpStatus(http.StatusBadRequest) + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + _, err := p.SetPolicyInfo(info, appInfo, []hexapolicy.PolicyInfo{}) + assert.Error(t, err) + assert.ErrorContains(t, err, "ListGroups") + assert.ErrorContains(t, err, "error StatusCode: 400") } func TestSetPolicyInfo_NoPoliciesInput(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} - mockClient.MockListGroups(expGroups...) - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - status, err := p.SetPolicyInfo(info, appInfo, []hexapolicy.PolicyInfo{}) - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} + mockClient.MockListGroups(expGroups...) + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + status, err := p.SetPolicyInfo(info, appInfo, []hexapolicy.PolicyInfo{}) + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) } func TestSetPolicyInfo_ListUsersInGroupError(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} - mockClient.MockListGroups(expGroups...) - mockClient.MockListUsersInGroupWithHttpStatus(http.StatusBadRequest) - - actionMemberMap := policytestsupport.MakeActionMembers() - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - - expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) - _, err := p.SetPolicyInfo(info, appInfo, expPolicies) - assert.Error(t, err) - assert.ErrorContains(t, err, "ListUsersInGroup") - assert.ErrorContains(t, err, "error StatusCode: 400") + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} + mockClient.MockListGroups(expGroups...) + mockClient.MockListUsersInGroupWithHttpStatus(http.StatusBadRequest) + + actionMemberMap := policytestsupport.MakeActionMembers() + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + + expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) + _, err := p.SetPolicyInfo(info, appInfo, expPolicies) + assert.Error(t, err) + assert.ErrorContains(t, err, "ListUsersInGroup") + assert.ErrorContains(t, err, "error StatusCode: 400") } func TestSetPolicyInfo_IgnoresNotFoundPrincipal(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - expGroups := []string{policytestsupport.ActionGetHrUs} - mockClient.MockListGroups(expGroups...) - - actionMemberMap := map[string]policytestsupport.ActionMembers{ - policytestsupport.ActionGetHrUs: { - MemberIds: []string{policytestsupport.UserIdGetHrUs, ""}, - Emails: []string{policytestsupport.UserEmailGetHrUs, policytestsupport.UserEmailGetHrUsAndProfile}, - }, - } - for range actionMemberMap { - mockClient.MockListUsersInGroup() - } - - for _, actionMem := range actionMemberMap { - for _, principalId := range actionMem.MemberIds { - mockClient.MockListUsers(principalId) - if principalId != "" { - mockClient.MockAdminAddUserToGroup() - } - } - } - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) - _, err := p.SetPolicyInfo(info, appInfo, expPolicies) - assert.NoError(t, err) - assert.True(t, mockClient.VerifyCalled()) + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + expGroups := []string{policytestsupport.ActionGetHrUs} + mockClient.MockListGroups(expGroups...) + + actionMemberMap := map[string]policytestsupport.ActionMembers{ + policytestsupport.ActionGetHrUs: { + MemberIds: []string{policytestsupport.UserIdGetHrUs, ""}, + Emails: []string{policytestsupport.UserEmailGetHrUs, policytestsupport.UserEmailGetHrUsAndProfile}, + }, + } + for range actionMemberMap { + mockClient.MockListUsersInGroup() + } + + for _, actionMem := range actionMemberMap { + for _, principalId := range actionMem.MemberIds { + mockClient.MockListUsers(principalId) + if principalId != "" { + mockClient.MockAdminAddUserToGroup() + } + } + } + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) + _, err := p.SetPolicyInfo(info, appInfo, expPolicies) + assert.NoError(t, err) + assert.True(t, mockClient.VerifyCalled()) } func TestSetPolicyInfo_AddUserToGroupError(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} - mockClient.MockListGroups(expGroups...) - - actionMemberMap := policytestsupport.MakeActionMembers() - mockClient.MockListUsersInGroup() - for _, principalId := range actionMemberMap[expGroups[0]].MemberIds { - mockClient.MockListUsers(principalId) - } - - mockClient.MockAdminAddUserToGroupWithHttpStatus(http.StatusBadRequest) - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) - stat, err := p.SetPolicyInfo(info, appInfo, expPolicies) - assert.Equal(t, http.StatusInternalServerError, stat) - assert.Error(t, err) - assert.ErrorContains(t, err, "AdminAddUserToGroup") - assert.ErrorContains(t, err, "error StatusCode: 400") - assert.True(t, mockClient.VerifyCalled()) + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} + mockClient.MockListGroups(expGroups...) + + actionMemberMap := policytestsupport.MakeActionMembers() + mockClient.MockListUsersInGroup() + for _, principalId := range actionMemberMap[expGroups[0]].MemberIds { + mockClient.MockListUsers(principalId) + } + + mockClient.MockAdminAddUserToGroupWithHttpStatus(http.StatusBadRequest) + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) + stat, err := p.SetPolicyInfo(info, appInfo, expPolicies) + assert.Equal(t, http.StatusInternalServerError, stat) + assert.Error(t, err) + assert.ErrorContains(t, err, "AdminAddUserToGroup") + assert.ErrorContains(t, err, "error StatusCode: 400") + assert.True(t, mockClient.VerifyCalled()) } func TestSetPolicyInfo_RemoveUserFromGroupError(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} - mockClient.MockListGroups(expGroups...) - - actionMemberMap := map[string]policytestsupport.ActionMembers{ - policytestsupport.ActionGetProfile: { - MemberIds: []string{policytestsupport.UserIdGetProfile, policytestsupport.UserIdGetHrUsAndProfile}, - }, - policytestsupport.ActionGetHrUs: { - MemberIds: []string{policytestsupport.UserIdGetHrUs, policytestsupport.UserIdGetHrUsAndProfile}, - }, - } - - mockClient.MockListUsersInGroup(actionMemberMap[expGroups[0]].MemberIds...) - for _, principalId := range actionMemberMap[expGroups[0]].MemberIds { - mockClient.MockAdminGetUser(principalId, "random@email.io") - } - - mockClient.MockAdminRemoveUserFromGroupWithHttpStatus(http.StatusBadRequest) - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) - stat, err := p.SetPolicyInfo(info, appInfo, expPolicies) - assert.Equal(t, http.StatusInternalServerError, stat) - assert.Error(t, err) - assert.ErrorContains(t, err, "AdminRemoveUserFromGroup") - assert.ErrorContains(t, err, "error StatusCode: 400") - assert.True(t, mockClient.VerifyCalled()) + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} + mockClient.MockListGroups(expGroups...) + + actionMemberMap := map[string]policytestsupport.ActionMembers{ + policytestsupport.ActionGetProfile: { + MemberIds: []string{policytestsupport.UserIdGetProfile, policytestsupport.UserIdGetHrUsAndProfile}, + }, + policytestsupport.ActionGetHrUs: { + MemberIds: []string{policytestsupport.UserIdGetHrUs, policytestsupport.UserIdGetHrUsAndProfile}, + }, + } + + mockClient.MockListUsersInGroup(actionMemberMap[expGroups[0]].MemberIds...) + for _, principalId := range actionMemberMap[expGroups[0]].MemberIds { + mockClient.MockAdminGetUser(principalId, "random@email.io") + } + + mockClient.MockAdminRemoveUserFromGroupWithHttpStatus(http.StatusBadRequest) + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) + stat, err := p.SetPolicyInfo(info, appInfo, expPolicies) + assert.Equal(t, http.StatusInternalServerError, stat) + assert.Error(t, err) + assert.ErrorContains(t, err, "AdminRemoveUserFromGroup") + assert.ErrorContains(t, err, "error StatusCode: 400") + assert.True(t, mockClient.VerifyCalled()) } func TestSetPolicyInfo_RemoveAllExistingAssignments(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} - mockClient.MockListGroups(expGroups...) - - actionMemberMap := map[string]policytestsupport.ActionMembers{ - policytestsupport.ActionGetProfile: { - MemberIds: []string{policytestsupport.UserIdGetProfile, policytestsupport.UserIdGetHrUsAndProfile}, - }, - policytestsupport.ActionGetHrUs: { - MemberIds: []string{policytestsupport.UserIdGetHrUs, policytestsupport.UserIdGetHrUsAndProfile}, - }, - } - - for _, actionMem := range actionMemberMap { - mockClient.MockListUsersInGroup(actionMem.MemberIds...) - for _, principalId := range actionMem.MemberIds { - mockClient.MockAdminGetUser(principalId, "random@email.io") - mockClient.MockAdminRemoveUserFromGroup() - } - } - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) - _, err := p.SetPolicyInfo(info, appInfo, expPolicies) - assert.NoError(t, err) - assert.True(t, mockClient.VerifyCalled()) + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} + mockClient.MockListGroups(expGroups...) + + actionMemberMap := map[string]policytestsupport.ActionMembers{ + policytestsupport.ActionGetProfile: { + MemberIds: []string{policytestsupport.UserIdGetProfile, policytestsupport.UserIdGetHrUsAndProfile}, + }, + policytestsupport.ActionGetHrUs: { + MemberIds: []string{policytestsupport.UserIdGetHrUs, policytestsupport.UserIdGetHrUsAndProfile}, + }, + } + + for _, actionMem := range actionMemberMap { + mockClient.MockListUsersInGroup(actionMem.MemberIds...) + for _, principalId := range actionMem.MemberIds { + mockClient.MockAdminGetUser(principalId, "random@email.io") + mockClient.MockAdminRemoveUserFromGroup() + } + } + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) + _, err := p.SetPolicyInfo(info, appInfo, expPolicies) + assert.NoError(t, err) + assert.True(t, mockClient.VerifyCalled()) } func TestSetPolicyInfo_NoExistingAssignments_AddAll(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} - mockClient.MockListGroups(expGroups...) - - actionMemberMap := policytestsupport.MakeActionMembers() - for range actionMemberMap { - mockClient.MockListUsersInGroup() - } - - for _, actionMem := range actionMemberMap { - for _, principalId := range actionMem.MemberIds { - mockClient.MockListUsers(principalId) - mockClient.MockAdminAddUserToGroup() - } - } - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) - _, err := p.SetPolicyInfo(info, appInfo, expPolicies) - assert.NoError(t, err) - assert.True(t, mockClient.VerifyCalled()) + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} + mockClient.MockListGroups(expGroups...) + + actionMemberMap := policytestsupport.MakeActionMembers() + for range actionMemberMap { + mockClient.MockListUsersInGroup() + } + + for _, actionMem := range actionMemberMap { + for _, principalId := range actionMem.MemberIds { + mockClient.MockListUsers(principalId) + mockClient.MockAdminAddUserToGroup() + } + } + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) + _, err := p.SetPolicyInfo(info, appInfo, expPolicies) + assert.NoError(t, err) + assert.True(t, mockClient.VerifyCalled()) } func TestSetPolicyInfo_NoneAddedOrRemoved(t *testing.T) { - mockClient := cognitotestsupport.NewMockCognitoHTTPClient() - expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} - mockClient.MockListGroups(expGroups...) - - actionMemberMap := policytestsupport.MakeActionMembers() - for _, actionMem := range actionMemberMap { - mockClient.MockListUsersInGroup(actionMem.MemberIds...) - for p, principalId := range actionMem.MemberIds { - mockClient.MockAdminGetUser(principalId, actionMem.Emails[p]) - mockClient.MockListUsers(principalId) - } - } - - p := cognitoProvider.CognitoProvider{ - AwsClientOpts: awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true}} - - info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) - appInfo := awstestsupport.AppInfo() - expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) - _, err := p.SetPolicyInfo(info, appInfo, expPolicies) - assert.NoError(t, err) - assert.True(t, mockClient.VerifyCalled()) + mockClient := cognitotestsupport.NewMockCognitoHTTPClient() + expGroups := []string{policytestsupport.ActionGetProfile, policytestsupport.ActionGetHrUs} + mockClient.MockListGroups(expGroups...) + + actionMemberMap := policytestsupport.MakeActionMembers() + for _, actionMem := range actionMemberMap { + mockClient.MockListUsersInGroup(actionMem.MemberIds...) + for p, principalId := range actionMem.MemberIds { + mockClient.MockAdminGetUser(principalId, actionMem.Emails[p]) + mockClient.MockListUsers(principalId) + } + } + + p := cognitoProvider.CognitoProvider{ + AwsClientOpts: awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true}} + + info := awstestsupport.IntegrationInfo(cognitoProvider.ProviderTypeAwsCognito) + appInfo := awstestsupport.AppInfo() + expPolicies := policytestsupport.MakeTestPolicies(actionMemberMap) + _, err := p.SetPolicyInfo(info, appInfo, expPolicies) + assert.NoError(t, err) + assert.True(t, mockClient.VerifyCalled()) } diff --git a/providers/azure/azureProvider/azure_policy_mapper.go b/providers/azure/azureProvider/azure_policy_mapper.go index 87e1eff..7b5f620 100644 --- a/providers/azure/azureProvider/azure_policy_mapper.go +++ b/providers/azure/azureProvider/azure_policy_mapper.go @@ -1,94 +1,94 @@ package azureProvider import ( - "fmt" + "fmt" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "github.com/hexa-org/policy-mapper/providers/azure/azad" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/providers/azure/azad" ) type AzurePolicyMapper struct { - objectId string - roleIdToAppRole map[string]azad.AzureAppRole - existingRoleIdToAras map[string][]azad.AzureAppRoleAssignment - azureUserEmail map[string]string + objectId string + roleIdToAppRole map[string]azad.AzureAppRole + existingRoleIdToAras map[string][]azad.AzureAppRoleAssignment + azureUserEmail map[string]string } func NewAzurePolicyMapper(sps azad.AzureServicePrincipals, existingAssignments []azad.AzureAppRoleAssignment, azureUserEmail map[string]string) *AzurePolicyMapper { - if len(sps.List) == 0 { - return &AzurePolicyMapper{} - } - - return &AzurePolicyMapper{ - objectId: sps.List[0].Name, - roleIdToAppRole: mapAppRoles(sps.List[0].AppRoles), - existingRoleIdToAras: mapAppRoleAssignments(existingAssignments), - azureUserEmail: azureUserEmail} + if len(sps.List) == 0 { + return &AzurePolicyMapper{} + } + + return &AzurePolicyMapper{ + objectId: sps.List[0].Name, + roleIdToAppRole: mapAppRoles(sps.List[0].AppRoles), + existingRoleIdToAras: mapAppRoleAssignments(existingAssignments), + azureUserEmail: azureUserEmail} } func (azm *AzurePolicyMapper) ToIDQL() []hexapolicy.PolicyInfo { - policies := make([]hexapolicy.PolicyInfo, 0) - for appRoleId, appRole := range azm.roleIdToAppRole { - pol := azm.appRoleAssignmentToIDQL(azm.existingRoleIdToAras[appRoleId], appRole) - policies = append(policies, pol) - } - return policies + policies := make([]hexapolicy.PolicyInfo, 0) + for appRoleId, appRole := range azm.roleIdToAppRole { + pol := azm.appRoleAssignmentToIDQL(azm.existingRoleIdToAras[appRoleId], appRole) + policies = append(policies, pol) + } + return policies } func (azm *AzurePolicyMapper) appRoleAssignmentToIDQL(assignments []azad.AzureAppRoleAssignment, role azad.AzureAppRole) hexapolicy.PolicyInfo { - members := make([]string, 0) - for _, oneAssignment := range assignments { - email := azm.azureUserEmail[oneAssignment.PrincipalId] - if email != "" { - members = append(members, fmt.Sprintf("user:%s", email)) - } - - } - - sourceData := make(map[string]interface{}, 2) - if role.IsEnabled { - sourceData["enabled"] = "true" - } else { - sourceData["enabled"] = "false" - } - - sourceData["membertypes"] = role.AllowedMemberTypes - - return hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{ - Version: hexapolicy.IdqlVersion, - PolicyId: &role.ID, - Description: role.Description, - PapId: &azm.objectId, - ProviderType: ProviderTypeAzure, - SourceData: sourceData, - }, - Actions: []hexapolicy.ActionInfo{{role.Value}}, - Subject: hexapolicy.SubjectInfo{Members: members}, - Object: hexapolicy.ObjectInfo{ResourceID: azm.objectId}, - } + members := make([]string, 0) + for _, oneAssignment := range assignments { + email := azm.azureUserEmail[oneAssignment.PrincipalId] + if email != "" { + members = append(members, fmt.Sprintf("user:%s", email)) + } + + } + + sourceData := make(map[string]interface{}, 2) + if role.IsEnabled { + sourceData["enabled"] = "true" + } else { + sourceData["enabled"] = "false" + } + + sourceData["membertypes"] = role.AllowedMemberTypes + + return hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{ + Version: hexapolicy.IdqlVersion, + PolicyId: &role.ID, + Description: role.Description, + PapId: &azm.objectId, + ProviderType: ProviderTypeAzure, + SourceData: sourceData, + }, + Actions: []hexapolicy.ActionInfo{{role.Value}}, + Subjects: members, + Object: hexapolicy.ObjectInfo{ResourceID: azm.objectId}, + } } func mapAppRoles(appRoles []azad.AzureAppRole) map[string]azad.AzureAppRole { - appRolesMap := make(map[string]azad.AzureAppRole) - for _, role := range appRoles { - appRolesMap[role.ID] = role - } - return appRolesMap + appRolesMap := make(map[string]azad.AzureAppRole) + for _, role := range appRoles { + appRolesMap[role.ID] = role + } + return appRolesMap } func mapAppRoleAssignments(appRoleAssignments []azad.AzureAppRoleAssignment) map[string][]azad.AzureAppRoleAssignment { - roleAssignmentMap := make(map[string][]azad.AzureAppRoleAssignment) - for _, roleAssignment := range appRoleAssignments { - roleId := roleAssignment.AppRoleId - raArray, found := roleAssignmentMap[roleId] - if !found { - raArray = make([]azad.AzureAppRoleAssignment, 0) - } - - roleAssignmentMap[roleId] = append(raArray, roleAssignment) - } - return roleAssignmentMap + roleAssignmentMap := make(map[string][]azad.AzureAppRoleAssignment) + for _, roleAssignment := range appRoleAssignments { + roleId := roleAssignment.AppRoleId + raArray, found := roleAssignmentMap[roleId] + if !found { + raArray = make([]azad.AzureAppRoleAssignment, 0) + } + + roleAssignmentMap[roleId] = append(raArray, roleAssignment) + } + return roleAssignmentMap } diff --git a/providers/azure/azureProvider/azure_policy_mapper_test.go b/providers/azure/azureProvider/azure_policy_mapper_test.go index 1fc5a84..0165214 100644 --- a/providers/azure/azureProvider/azure_policy_mapper_test.go +++ b/providers/azure/azureProvider/azure_policy_mapper_test.go @@ -1,72 +1,73 @@ package azureProvider_test import ( - "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" - "github.com/hexa-org/policy-mapper/providers/azure/azad" - "github.com/hexa-org/policy-mapper/providers/azure/azureProvider" - "github.com/hexa-org/policy-mapper/providers/azure/azuretestsupport" - "github.com/stretchr/testify/assert" - "log" - "testing" + "log" + "testing" + + "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" + "github.com/hexa-org/policy-mapper/providers/azure/azad" + "github.com/hexa-org/policy-mapper/providers/azure/azureProvider" + "github.com/hexa-org/policy-mapper/providers/azure/azuretestsupport" + "github.com/stretchr/testify/assert" ) func TestAzurePolicyMapper_ToIDQL(t *testing.T) { - principalEmails := policytestsupport.MakePrincipalEmailMap() - roleAssignments := azuretestsupport.AppRoleAssignments - sps := azuretestsupport.AzureServicePrincipals() - mapper := azureProvider.NewAzurePolicyMapper(sps, roleAssignments, principalEmails) - actPolicies := mapper.ToIDQL() - assert.NotNil(t, actPolicies) - assert.Equal(t, len(sps.List[0].AppRoles), len(actPolicies)) + principalEmails := policytestsupport.MakePrincipalEmailMap() + roleAssignments := azuretestsupport.AppRoleAssignments + sps := azuretestsupport.AzureServicePrincipals() + mapper := azureProvider.NewAzurePolicyMapper(sps, roleAssignments, principalEmails) + actPolicies := mapper.ToIDQL() + assert.NotNil(t, actPolicies) + assert.Equal(t, len(sps.List[0].AppRoles), len(actPolicies)) - actActionMembersMap := make(map[string][]string) - for _, pol := range actPolicies { - assert.Equal(t, 1, len(pol.Actions)) - assert.Equal(t, sps.List[0].Name, pol.Object.ResourceID) - actActionMembersMap[pol.Actions[0].ActionUri] = pol.Subject.Members - } + actActionMembersMap := make(map[string][]string) + for _, pol := range actPolicies { + assert.Equal(t, 1, len(pol.Actions)) + assert.Equal(t, sps.List[0].Name, pol.Object.ResourceID) + actActionMembersMap[pol.Actions[0].ActionUri] = pol.Subjects + } - for _, expAction := range []string{policytestsupport.ActionGetHrUs, policytestsupport.ActionGetProfile} { + for _, expAction := range []string{policytestsupport.ActionGetHrUs, policytestsupport.ActionGetProfile} { - assert.NotNil(t, actActionMembersMap[expAction]) - var mainEmail string - switch expAction { - case policytestsupport.ActionGetHrUs: - mainEmail = policytestsupport.UserEmailGetHrUs - break - case policytestsupport.ActionGetProfile: - mainEmail = policytestsupport.UserEmailGetProfile - } - assert.Contains(t, actActionMembersMap[expAction], "user:"+mainEmail) - assert.Contains(t, actActionMembersMap[expAction], "user:"+policytestsupport.UserEmailGetHrUsAndProfile) - } + assert.NotNil(t, actActionMembersMap[expAction]) + var mainEmail string + switch expAction { + case policytestsupport.ActionGetHrUs: + mainEmail = policytestsupport.UserEmailGetHrUs + break + case policytestsupport.ActionGetProfile: + mainEmail = policytestsupport.UserEmailGetProfile + } + assert.Contains(t, actActionMembersMap[expAction], "user:"+mainEmail) + assert.Contains(t, actActionMembersMap[expAction], "user:"+policytestsupport.UserEmailGetHrUsAndProfile) + } } func TestAzurePolicyMapper_ToIDQL_NoRoleAssignments(t *testing.T) { - sps := azuretestsupport.AzureServicePrincipals() - mapper := azureProvider.NewAzurePolicyMapper(sps, nil, nil) - actPolicies := mapper.ToIDQL() - assert.NotNil(t, actPolicies) - assert.Equal(t, len(sps.List[0].AppRoles), len(actPolicies)) + sps := azuretestsupport.AzureServicePrincipals() + mapper := azureProvider.NewAzurePolicyMapper(sps, nil, nil) + actPolicies := mapper.ToIDQL() + assert.NotNil(t, actPolicies) + assert.Equal(t, len(sps.List[0].AppRoles), len(actPolicies)) - actPolicyActionMap := make(map[string]bool) - log.Println(actPolicies) - for _, pol := range actPolicies { - assert.Equal(t, 1, len(pol.Actions)) - assert.Equal(t, sps.List[0].Name, pol.Object.ResourceID) - assert.NotNil(t, pol.Subject.Members) - assert.Empty(t, pol.Subject.Members) - actPolicyActionMap[pol.Actions[0].ActionUri] = true - } + actPolicyActionMap := make(map[string]bool) + log.Println(actPolicies) + for _, pol := range actPolicies { + assert.Equal(t, 1, len(pol.Actions)) + assert.Equal(t, sps.List[0].Name, pol.Object.ResourceID) + assert.NotNil(t, pol.Subjects) + assert.Empty(t, pol.Subjects) + actPolicyActionMap[pol.Actions[0].ActionUri] = true + } - for _, expAction := range []string{policytestsupport.ActionGetHrUs, policytestsupport.ActionGetProfile} { - assert.True(t, actPolicyActionMap[expAction]) - } + for _, expAction := range []string{policytestsupport.ActionGetHrUs, policytestsupport.ActionGetProfile} { + assert.True(t, actPolicyActionMap[expAction]) + } } func TestAzurePolicyMapper_ToIDQL_NoAppRoles(t *testing.T) { - mapper := azureProvider.NewAzurePolicyMapper(azad.AzureServicePrincipals{}, nil, nil) - actPolicies := mapper.ToIDQL() - assert.NotNil(t, actPolicies) - assert.Equal(t, 0, len(actPolicies)) + mapper := azureProvider.NewAzurePolicyMapper(azad.AzureServicePrincipals{}, nil, nil) + actPolicies := mapper.ToIDQL() + assert.NotNil(t, actPolicies) + assert.Equal(t, 0, len(actPolicies)) } diff --git a/providers/azure/azureProvider/azure_provider.go b/providers/azure/azureProvider/azure_provider.go index 7011d29..5320ff1 100644 --- a/providers/azure/azureProvider/azure_provider.go +++ b/providers/azure/azureProvider/azure_provider.go @@ -1,145 +1,145 @@ package azureProvider import ( - "errors" - "log" - "net/http" - "strings" - - "github.com/go-playground/validator/v10" - "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "github.com/hexa-org/policy-mapper/pkg/workflowsupport" - "github.com/hexa-org/policy-mapper/providers/azure/azad" + "errors" + "log" + "net/http" + "strings" + + "github.com/go-playground/validator/v10" + "github.com/hexa-org/policy-mapper/api/policyprovider" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/pkg/workflowsupport" + "github.com/hexa-org/policy-mapper/providers/azure/azad" ) const ProviderTypeAzure string = "azure" type AzureProvider struct { - client azad.AzureClient + client azad.AzureClient } type ProviderOpt func(provider *AzureProvider) func WithAzureClient(clientOverride azad.AzureClient) func(provider *AzureProvider) { - return func(provider *AzureProvider) { - provider.client = clientOverride - } + return func(provider *AzureProvider) { + provider.client = clientOverride + } } func NewAzureProvider(opts ...ProviderOpt) *AzureProvider { - provider := &AzureProvider{client: azad.NewAzureClient(&http.Client{})} - if opts != nil { - for _, opt := range opts { - if opt != nil { - opt(provider) - } - } - } - return provider + provider := &AzureProvider{client: azad.NewAzureClient(&http.Client{})} + if opts != nil { + for _, opt := range opts { + if opt != nil { + opt(provider) + } + } + } + return provider } func (a *AzureProvider) Name() string { - return ProviderTypeAzure + return ProviderTypeAzure } func (a *AzureProvider) DiscoverApplications(info policyprovider.IntegrationInfo) (apps []policyprovider.ApplicationInfo, err error) { - if !strings.EqualFold(info.Name, a.Name()) { - return apps, err - } - - key := info.Key - found, _ := a.client.GetWebApplications(key) - apps = append(apps, found...) - return apps, err + if !strings.EqualFold(info.Name, a.Name()) { + return apps, err + } + + key := info.Key + found, _ := a.client.GetWebApplications(key) + apps = append(apps, found...) + return apps, err } func (a *AzureProvider) GetPolicyInfo(integrationInfo policyprovider.IntegrationInfo, applicationInfo policyprovider.ApplicationInfo) ([]hexapolicy.PolicyInfo, error) { - key := integrationInfo.Key - servicePrincipals, _ := a.client.GetServicePrincipals(key, applicationInfo.Description) // todo - description is named poorly - if len(servicePrincipals.List) == 0 { - return []hexapolicy.PolicyInfo{}, nil - } - assignments, _ := a.client.GetAppRoleAssignedTo(key, servicePrincipals.List[0].ID) - - userEmailList := workflowsupport.ProcessAsync[azad.AzureUser, azad.AzureAppRoleAssignment](assignments.List, func(ara azad.AzureAppRoleAssignment) (azad.AzureUser, error) { - user, _ := a.client.GetUserInfoFromPrincipalId(key, ara.PrincipalId) - - if user.Email == "" { - return azad.AzureUser{}, errors.New("no email found for principalId " + ara.PrincipalId) - } - return azad.AzureUser{PrincipalId: ara.PrincipalId, Email: user.Email}, nil - }) - - userEmailMap := make(map[string]string) - for _, ue := range userEmailList { - if ue.PrincipalId != "" && ue.Email != "" { - userEmailMap[ue.PrincipalId] = ue.Email - } - } - - policyMapper := NewAzurePolicyMapper(servicePrincipals, assignments.List, userEmailMap) - return policyMapper.ToIDQL(), nil + key := integrationInfo.Key + servicePrincipals, _ := a.client.GetServicePrincipals(key, applicationInfo.Description) // todo - description is named poorly + if len(servicePrincipals.List) == 0 { + return []hexapolicy.PolicyInfo{}, nil + } + assignments, _ := a.client.GetAppRoleAssignedTo(key, servicePrincipals.List[0].ID) + + userEmailList := workflowsupport.ProcessAsync[azad.AzureUser, azad.AzureAppRoleAssignment](assignments.List, func(ara azad.AzureAppRoleAssignment) (azad.AzureUser, error) { + user, _ := a.client.GetUserInfoFromPrincipalId(key, ara.PrincipalId) + + if user.Email == "" { + return azad.AzureUser{}, errors.New("no email found for principalId " + ara.PrincipalId) + } + return azad.AzureUser{PrincipalId: ara.PrincipalId, Email: user.Email}, nil + }) + + userEmailMap := make(map[string]string) + for _, ue := range userEmailList { + if ue.PrincipalId != "" && ue.Email != "" { + userEmailMap[ue.PrincipalId] = ue.Email + } + } + + policyMapper := NewAzurePolicyMapper(servicePrincipals, assignments.List, userEmailMap) + return policyMapper.ToIDQL(), nil } func (a *AzureProvider) SetPolicyInfo(integrationInfo policyprovider.IntegrationInfo, applicationInfo policyprovider.ApplicationInfo, policyInfos []hexapolicy.PolicyInfo) (int, error) { - validate := validator.New() // todo - move this up? - errApp := validate.Struct(applicationInfo) - if errApp != nil { - return http.StatusInternalServerError, errApp - } - errPolicies := validate.Var(policyInfos, "omitempty,dive") - if errPolicies != nil { - return http.StatusInternalServerError, errPolicies - } - - key := integrationInfo.Key - - sps, _ := a.client.GetServicePrincipals(key, applicationInfo.Description) // todo - description is named poorly - - appRoleValueToId := make(map[string]string) - for _, ara := range sps.List[0].AppRoles { - appRoleValueToId[ara.Value] = ara.ID - } - - for _, policyInfo := range policyInfos { - var assignments []azad.AzureAppRoleAssignment - - actionUri := strings.TrimPrefix(policyInfo.Actions[0].ActionUri, "azure:") - appRoleId, found := appRoleValueToId[actionUri] - if !found { - log.Println("No Azure AppRoleAssignment found for policy action", actionUri) - continue - } - - if len(policyInfo.Subject.Members) == 0 { - assignments = append(assignments, azad.AzureAppRoleAssignment{ - AppRoleId: appRoleId, - ResourceId: sps.List[0].ID, - }) - } - - for _, user := range policyInfo.Subject.Members { - principalId, _ := a.client.GetPrincipalIdFromEmail(key, strings.Split(user, ":")[1]) - if principalId == "" { - continue - } - assignments = append(assignments, azad.AzureAppRoleAssignment{ - AppRoleId: appRoleId, - PrincipalId: principalId, - ResourceId: sps.List[0].ID, - // ResourceId: strings.Split(policyInfo.Object.ResourceID, ":")[0], - }) - } - - if len(assignments) == 0 { - continue - } - - err := a.client.SetAppRoleAssignedTo(key, sps.List[0].ID, assignments) - if err != nil { - return http.StatusInternalServerError, err - } - } - return http.StatusCreated, nil + validate := validator.New() // todo - move this up? + errApp := validate.Struct(applicationInfo) + if errApp != nil { + return http.StatusInternalServerError, errApp + } + errPolicies := validate.Var(policyInfos, "omitempty,dive") + if errPolicies != nil { + return http.StatusInternalServerError, errPolicies + } + + key := integrationInfo.Key + + sps, _ := a.client.GetServicePrincipals(key, applicationInfo.Description) // todo - description is named poorly + + appRoleValueToId := make(map[string]string) + for _, ara := range sps.List[0].AppRoles { + appRoleValueToId[ara.Value] = ara.ID + } + + for _, policyInfo := range policyInfos { + var assignments []azad.AzureAppRoleAssignment + + actionUri := strings.TrimPrefix(policyInfo.Actions[0].ActionUri, "azure:") + appRoleId, found := appRoleValueToId[actionUri] + if !found { + log.Println("No Azure AppRoleAssignment found for policy action", actionUri) + continue + } + + if len(policyInfo.Subjects) == 0 { + assignments = append(assignments, azad.AzureAppRoleAssignment{ + AppRoleId: appRoleId, + ResourceId: sps.List[0].ID, + }) + } + + for _, user := range policyInfo.Subjects { + principalId, _ := a.client.GetPrincipalIdFromEmail(key, strings.Split(user, ":")[1]) + if principalId == "" { + continue + } + assignments = append(assignments, azad.AzureAppRoleAssignment{ + AppRoleId: appRoleId, + PrincipalId: principalId, + ResourceId: sps.List[0].ID, + // ResourceId: strings.Split(policyInfo.Object.ResourceID, ":")[0], + }) + } + + if len(assignments) == 0 { + continue + } + + err := a.client.SetAppRoleAssignedTo(key, sps.List[0].ID, assignments) + if err != nil { + return http.StatusInternalServerError, err + } + } + return http.StatusCreated, nil } diff --git a/providers/azure/azureProvider/azure_provider_test.go b/providers/azure/azureProvider/azure_provider_test.go index 4e6d8e5..c51f0f9 100644 --- a/providers/azure/azureProvider/azure_provider_test.go +++ b/providers/azure/azureProvider/azure_provider_test.go @@ -1,404 +1,403 @@ package azureProvider_test import ( - "log" - "net/http" - "testing" - - "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "github.com/hexa-org/policy-mapper/providers/azure/azad" - "github.com/hexa-org/policy-mapper/providers/azure/azureProvider" - "github.com/hexa-org/policy-mapper/providers/azure/azuretestsupport" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" + "log" + "net/http" + "testing" + + "github.com/hexa-org/policy-mapper/api/policyprovider" + "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/providers/azure/azad" + "github.com/hexa-org/policy-mapper/providers/azure/azureProvider" + "github.com/hexa-org/policy-mapper/providers/azure/azuretestsupport" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) func TestDiscoverApplications(t *testing.T) { - key := azuretestsupport.AzureKeyBytes() - mockAzClient := azuretestsupport.NewMockAzureClient() - expApps := []policyprovider.ApplicationInfo{ - { - ObjectID: "anId", - Name: "aName", - Description: "aDescription", - Service: "App Service", - }, - } - mockAzClient.On("GetWebApplications", key).Return(expApps, nil) - - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - - info := policyprovider.IntegrationInfo{Name: "azure", Key: key} - applications, _ := p.DiscoverApplications(info) - log.Println(applications[0]) - - assert.Len(t, applications, 1) - assert.Equal(t, "azure", p.Name()) - assert.Equal(t, "App Service", applications[0].Service) - mockAzClient.AssertExpectations(t) + key := azuretestsupport.AzureKeyBytes() + mockAzClient := azuretestsupport.NewMockAzureClient() + expApps := []policyprovider.ApplicationInfo{ + { + ObjectID: "anId", + Name: "aName", + Description: "aDescription", + Service: "App Service", + }, + } + mockAzClient.On("GetWebApplications", key).Return(expApps, nil) + + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + + info := policyprovider.IntegrationInfo{Name: "azure", Key: key} + applications, _ := p.DiscoverApplications(info) + log.Println(applications[0]) + + assert.Len(t, applications, 1) + assert.Equal(t, "azure", p.Name()) + assert.Equal(t, "App Service", applications[0].Service) + mockAzClient.AssertExpectations(t) } func TestGetPolicy_WithoutUserEmail(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - mockAzClient.ExpectAppRoleAssignedTo(azuretestsupport.AppRoleAssignmentGetProfile) - mockAzClient.On("GetUserInfoFromPrincipalId", mock.Anything, mock.Anything). - Return(azad.AzureUser{ - PrincipalId: policytestsupport.UserIdGetProfile, - }, nil) - - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - - info := policyprovider.IntegrationInfo{Name: "azure", Key: key} - appInfo := policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId} - - actualPolicies, err := p.GetPolicyInfo(info, appInfo) - assert.NoError(t, err) - assert.NotNil(t, actualPolicies) - assert.Equal(t, len(azuretestsupport.AzureServicePrincipals().List[0].AppRoles), len(actualPolicies)) - - for _, pol := range actualPolicies { - assert.True(t, len(pol.Actions) > 0) - assert.NotEmpty(t, pol.Actions[0].ActionUri) - assert.Equal(t, 0, len(pol.Subject.Members)) - assert.Equal(t, policytestsupport.PolicyObjectResourceId, pol.Object.ResourceID) - } - mockAzClient.AssertExpectations(t) + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + mockAzClient.ExpectAppRoleAssignedTo(azuretestsupport.AppRoleAssignmentGetProfile) + mockAzClient.On("GetUserInfoFromPrincipalId", mock.Anything, mock.Anything). + Return(azad.AzureUser{ + PrincipalId: policytestsupport.UserIdGetProfile, + }, nil) + + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + + info := policyprovider.IntegrationInfo{Name: "azure", Key: key} + appInfo := policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId} + + actualPolicies, err := p.GetPolicyInfo(info, appInfo) + assert.NoError(t, err) + assert.NotNil(t, actualPolicies) + assert.Equal(t, len(azuretestsupport.AzureServicePrincipals().List[0].AppRoles), len(actualPolicies)) + + for _, pol := range actualPolicies { + assert.True(t, len(pol.Actions) > 0) + assert.NotEmpty(t, pol.Actions[0].ActionUri) + assert.Equal(t, 0, len(pol.Subjects)) + assert.Equal(t, policytestsupport.PolicyObjectResourceId, pol.Object.ResourceID) + } + mockAzClient.AssertExpectations(t) } func TestGetPolicy_WithRoleAssignment(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - expAssignments := azuretestsupport.AppRoleAssignmentGetHrUs + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + expAssignments := azuretestsupport.AppRoleAssignmentGetHrUs - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - mockAzClient.ExpectAppRoleAssignedTo(expAssignments) - mockAzClient.ExpectGetUserInfoFromPrincipalId(policytestsupport.UserIdGetHrUs) + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + mockAzClient.ExpectAppRoleAssignedTo(expAssignments) + mockAzClient.ExpectGetUserInfoFromPrincipalId(policytestsupport.UserIdGetHrUs) - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - info := policyprovider.IntegrationInfo{Name: "azure", Key: key} - appInfo := policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId} + info := policyprovider.IntegrationInfo{Name: "azure", Key: key} + appInfo := policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId} - actualPolicies, err := p.GetPolicyInfo(info, appInfo) - assert.NoError(t, err) - assert.NotNil(t, actualPolicies) - assert.Equal(t, len(azuretestsupport.AzureServicePrincipals().List[0].AppRoles), len(actualPolicies)) + actualPolicies, err := p.GetPolicyInfo(info, appInfo) + assert.NoError(t, err) + assert.NotNil(t, actualPolicies) + assert.Equal(t, len(azuretestsupport.AzureServicePrincipals().List[0].AppRoles), len(actualPolicies)) - expPolicies := azuretestsupport.MakePolicies(expAssignments) - assert.Equal(t, len(expPolicies), len(actualPolicies)) - assert.True(t, policytestsupport.ContainsPolicies(t, expPolicies, actualPolicies)) - mockAzClient.AssertExpectations(t) + expPolicies := azuretestsupport.MakePolicies(expAssignments) + assert.Equal(t, len(expPolicies), len(actualPolicies)) + assert.True(t, policytestsupport.ContainsPolicies(t, expPolicies, actualPolicies)) + mockAzClient.AssertExpectations(t) } func TestGetPolicy_MultiplePolicies(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - expAssignments := azuretestsupport.AppRoleAssignmentGetHrUsAndProfile + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + expAssignments := azuretestsupport.AppRoleAssignmentGetHrUsAndProfile - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - mockAzClient.ExpectAppRoleAssignedTo(expAssignments) - mockAzClient.ExpectGetUserInfoFromPrincipalId(policytestsupport.UserIdGetHrUsAndProfile) + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + mockAzClient.ExpectAppRoleAssignedTo(expAssignments) + mockAzClient.ExpectGetUserInfoFromPrincipalId(policytestsupport.UserIdGetHrUsAndProfile) - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - info := policyprovider.IntegrationInfo{Name: "azure", Key: key} - appInfo := policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId} + info := policyprovider.IntegrationInfo{Name: "azure", Key: key} + appInfo := policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId} - actualPolicies, err := p.GetPolicyInfo(info, appInfo) - assert.NoError(t, err) - assert.NotNil(t, actualPolicies) - assert.Equal(t, len(azuretestsupport.AzureServicePrincipals().List[0].AppRoles), len(actualPolicies)) + actualPolicies, err := p.GetPolicyInfo(info, appInfo) + assert.NoError(t, err) + assert.NotNil(t, actualPolicies) + assert.Equal(t, len(azuretestsupport.AzureServicePrincipals().List[0].AppRoles), len(actualPolicies)) - expPolicies := azuretestsupport.MakePolicies(expAssignments) - assert.Equal(t, len(expPolicies), len(actualPolicies)) - assert.True(t, policytestsupport.ContainsPolicies(t, expPolicies, actualPolicies)) - mockAzClient.AssertExpectations(t) + expPolicies := azuretestsupport.MakePolicies(expAssignments) + assert.Equal(t, len(expPolicies), len(actualPolicies)) + assert.True(t, policytestsupport.ContainsPolicies(t, expPolicies, actualPolicies)) + mockAzClient.AssertExpectations(t) } func TestGetPolicy_MultipleMembersInOnePolicy(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - expAssignments := azuretestsupport.AppRoleAssignmentMultipleMembers + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + expAssignments := azuretestsupport.AppRoleAssignmentMultipleMembers - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - mockAzClient.ExpectAppRoleAssignedTo(expAssignments) - mockAzClient.ExpectGetUserInfoFromPrincipalId(policytestsupport.UserIdGetHrUs, policytestsupport.UserIdGetHrUsAndProfile) + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + mockAzClient.ExpectAppRoleAssignedTo(expAssignments) + mockAzClient.ExpectGetUserInfoFromPrincipalId(policytestsupport.UserIdGetHrUs, policytestsupport.UserIdGetHrUsAndProfile) - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - info := policyprovider.IntegrationInfo{Name: "azure", Key: key} - appInfo := policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId} + info := policyprovider.IntegrationInfo{Name: "azure", Key: key} + appInfo := policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId} - actualPolicies, err := p.GetPolicyInfo(info, appInfo) - assert.NoError(t, err) - assert.NotNil(t, actualPolicies) - assert.Equal(t, len(azuretestsupport.AzureServicePrincipals().List[0].AppRoles), len(actualPolicies)) + actualPolicies, err := p.GetPolicyInfo(info, appInfo) + assert.NoError(t, err) + assert.NotNil(t, actualPolicies) + assert.Equal(t, len(azuretestsupport.AzureServicePrincipals().List[0].AppRoles), len(actualPolicies)) - expPolicies := azuretestsupport.MakePolicies(expAssignments) - assert.Equal(t, len(expPolicies), len(actualPolicies)) - assert.True(t, policytestsupport.ContainsPolicies(t, expPolicies, actualPolicies)) - mockAzClient.AssertExpectations(t) + expPolicies := azuretestsupport.MakePolicies(expAssignments) + assert.Equal(t, len(expPolicies), len(actualPolicies)) + assert.True(t, policytestsupport.ContainsPolicies(t, expPolicies, actualPolicies)) + mockAzClient.AssertExpectations(t) } func TestSetPolicy_withInvalidArguments(t *testing.T) { - azureProvider := azureProvider.NewAzureProvider() - key := []byte("key") - - status, err := azureProvider.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{Name: "anAppName", Description: "anAppId"}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, - }}) - - assert.Equal(t, http.StatusInternalServerError, status) - assert.EqualError(t, err, "Key: 'ApplicationInfo.ObjectID' Error:Field validation for 'ObjectID' failed on the 'required' tag") - - status, err = azureProvider.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: "aDescription"}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}}, - Object: hexapolicy.ObjectInfo{}, - }}) - - assert.Equal(t, http.StatusInternalServerError, status) - assert.EqualError(t, err, "Key: '[0].Object.ResourceID' Error:Field validation for 'ResourceID' failed on the 'required' tag") + azureProvider := azureProvider.NewAzureProvider() + key := []byte("key") + + status, err := azureProvider.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{Name: "anAppName", Description: "anAppId"}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, + Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, + Object: hexapolicy.ObjectInfo{ + ResourceID: "anObjectId", + }, + }}) + + assert.Equal(t, http.StatusInternalServerError, status) + assert.EqualError(t, err, "Key: 'ApplicationInfo.ObjectID' Error:Field validation for 'ObjectID' failed on the 'required' tag") + + status, err = azureProvider.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: "aDescription"}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, + Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, + Object: hexapolicy.ObjectInfo{}, + }}) + + assert.Equal(t, http.StatusInternalServerError, status) + assert.EqualError(t, err, "Key: '[0].Object.ResourceID' Error:Field validation for 'ResourceID' failed on the 'required' tag") } func TestSetPolicy_IgnoresAllPrincipalIdsNotFound(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - - mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetHrUs, "") - mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetProfile, "") - - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - status, err := p.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"user:" + policytestsupport.UserEmailGetHrUs, - "user:" + policytestsupport.UserEmailGetProfile}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, - }}) - - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) - mockAzClient.AssertExpectations(t) + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + + mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetHrUs, "") + mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetProfile, "") + + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + status, err := p.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs, + "user:" + policytestsupport.UserEmailGetProfile}, + Object: hexapolicy.ObjectInfo{ + ResourceID: policytestsupport.PolicyObjectResourceId, + }, + }}) + + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) + mockAzClient.AssertExpectations(t) } func TestSetPolicy_IgnoresAnyNotFoundPrincipalId(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - - mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetHrUs, policytestsupport.UserIdGetHrUs) - mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetProfile, "") - mockAzClient.ExpectSetAppRoleAssignedTo(azuretestsupport.AppRoleAssignmentGetHrUs) - - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - status, err := p.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"user:" + policytestsupport.UserEmailGetHrUs, - "user:" + policytestsupport.UserEmailGetProfile}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, - }}) - - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) - mockAzClient.AssertExpectations(t) + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + + mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetHrUs, policytestsupport.UserIdGetHrUs) + mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetProfile, "") + mockAzClient.ExpectSetAppRoleAssignedTo(azuretestsupport.AppRoleAssignmentGetHrUs) + + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + status, err := p.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs, + "user:" + policytestsupport.UserEmailGetProfile}, + Object: hexapolicy.ObjectInfo{ + ResourceID: policytestsupport.PolicyObjectResourceId, + }, + }}) + + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) + mockAzClient.AssertExpectations(t) } func TestSetPolicy_AddAssignment_IgnoresInvalidAction(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - status, err := p.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:GET/not_defined"}}, - Subject: hexapolicy.SubjectInfo{ - Members: []string{ - "user:" + policytestsupport.UserEmailGetHrUs, - "user:" + policytestsupport.UserEmailGetProfile}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, - }}) - - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) - mockAzClient.AssertExpectations(t) + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + status, err := p.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:GET/not_defined"}}, + Subjects: []string{ + "user:" + policytestsupport.UserEmailGetHrUs, + "user:" + policytestsupport.UserEmailGetProfile}, + Object: hexapolicy.ObjectInfo{ + ResourceID: policytestsupport.PolicyObjectResourceId, + }, + }}) + + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) + mockAzClient.AssertExpectations(t) } func TestSetPolicy(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetHrUs, policytestsupport.UserIdGetHrUs) - mockAzClient.ExpectSetAppRoleAssignedTo(azuretestsupport.AppRoleAssignmentGetHrUs) - - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - status, err := p.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"user:" + policytestsupport.UserEmailGetHrUs}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, - }}) - - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) - mockAzClient.AssertExpectations(t) + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetHrUs, policytestsupport.UserIdGetHrUs) + mockAzClient.ExpectSetAppRoleAssignedTo(azuretestsupport.AppRoleAssignmentGetHrUs) + + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + status, err := p.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs}, + Object: hexapolicy.ObjectInfo{ + ResourceID: policytestsupport.PolicyObjectResourceId, + }, + }}) + + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) + mockAzClient.AssertExpectations(t) } func TestSetPolicy_RemovedAllMembers_FromOnePolicy(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - mockAzClient.ExpectSetAppRoleAssignedTo( - azuretestsupport.AssignmentsForDelete(azuretestsupport.AppRoleAssignmentGetHrUs)) - - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - status, err := p.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, - Subject: hexapolicy.SubjectInfo{Members: []string{}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, - }}) - - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) - mockAzClient.AssertExpectations(t) + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + mockAzClient.ExpectSetAppRoleAssignedTo( + azuretestsupport.AssignmentsForDelete(azuretestsupport.AppRoleAssignmentGetHrUs)) + + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + status, err := p.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Subjects: []string{}, + Object: hexapolicy.ObjectInfo{ + ResourceID: policytestsupport.PolicyObjectResourceId, + }, + }}) + + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) + mockAzClient.AssertExpectations(t) } func TestSetPolicy_RemovedAllMembers_FromAllPolicies(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - mockAzClient.ExpectSetAppRoleAssignedTo( - azuretestsupport.AssignmentsForDelete(azuretestsupport.AppRoleAssignmentGetHrUs)) - mockAzClient.ExpectSetAppRoleAssignedTo( - azuretestsupport.AssignmentsForDelete(azuretestsupport.AppRoleAssignmentGetProfile)) - - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - status, err := p.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, - []hexapolicy.PolicyInfo{ - { - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, - Subject: hexapolicy.SubjectInfo{Members: []string{}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, - }, - { - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetProfile}}, - Subject: hexapolicy.SubjectInfo{Members: []string{}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, - }, - }) - - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) - mockAzClient.AssertExpectations(t) + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + mockAzClient.ExpectSetAppRoleAssignedTo( + azuretestsupport.AssignmentsForDelete(azuretestsupport.AppRoleAssignmentGetHrUs)) + mockAzClient.ExpectSetAppRoleAssignedTo( + azuretestsupport.AssignmentsForDelete(azuretestsupport.AppRoleAssignmentGetProfile)) + + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + status, err := p.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, + []hexapolicy.PolicyInfo{ + { + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Subjects: []string{}, + Object: hexapolicy.ObjectInfo{ + ResourceID: policytestsupport.PolicyObjectResourceId, + }, + }, + { + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetProfile}}, + Subjects: []string{}, + Object: hexapolicy.ObjectInfo{ + ResourceID: policytestsupport.PolicyObjectResourceId, + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) + mockAzClient.AssertExpectations(t) } func TestSetPolicy_MultipleAppRolePolicies(t *testing.T) { - appId := azuretestsupport.AzureAppId - key := azuretestsupport.AzureKeyBytes() - - mockAzClient := azuretestsupport.NewMockAzureClient() - mockAzClient.ExpectGetServicePrincipals() - mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetHrUs, policytestsupport.UserIdGetHrUs) - mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetProfile, policytestsupport.UserIdGetProfile) - - mockAzClient.ExpectSetAppRoleAssignedTo(azuretestsupport.AppRoleAssignmentGetHrUs) - mockAzClient.ExpectSetAppRoleAssignedTo(azuretestsupport.AppRoleAssignmentGetProfile) - - p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) - status, err := p.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, - []hexapolicy.PolicyInfo{ - { - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"user:" + policytestsupport.UserEmailGetHrUs}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, - }, - { - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetProfile}}, - Subject: hexapolicy.SubjectInfo{Members: []string{"user:" + policytestsupport.UserEmailGetProfile}}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, - }, - }) - - assert.NoError(t, err) - assert.Equal(t, http.StatusCreated, status) - mockAzClient.AssertExpectations(t) + appId := azuretestsupport.AzureAppId + key := azuretestsupport.AzureKeyBytes() + + mockAzClient := azuretestsupport.NewMockAzureClient() + mockAzClient.ExpectGetServicePrincipals() + mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetHrUs, policytestsupport.UserIdGetHrUs) + mockAzClient.ExpectGetPrincipalIdFromEmail(policytestsupport.UserEmailGetProfile, policytestsupport.UserIdGetProfile) + + mockAzClient.ExpectSetAppRoleAssignedTo(azuretestsupport.AppRoleAssignmentGetHrUs) + mockAzClient.ExpectSetAppRoleAssignedTo(azuretestsupport.AppRoleAssignmentGetProfile) + + p := azureProvider.NewAzureProvider(azureProvider.WithAzureClient(mockAzClient)) + status, err := p.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, + []hexapolicy.PolicyInfo{ + { + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs}, + Object: hexapolicy.ObjectInfo{ + ResourceID: policytestsupport.PolicyObjectResourceId, + }, + }, + { + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetProfile}}, + Subjects: []string{"user:" + policytestsupport.UserEmailGetProfile}, + Object: hexapolicy.ObjectInfo{ + ResourceID: policytestsupport.PolicyObjectResourceId, + }, + }, + }) + + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, status) + mockAzClient.AssertExpectations(t) } diff --git a/providers/googlecloud/iapProvider/google_client_test.go b/providers/googlecloud/iapProvider/google_client_test.go index c783886..576862a 100644 --- a/providers/googlecloud/iapProvider/google_client_test.go +++ b/providers/googlecloud/iapProvider/google_client_test.go @@ -1,168 +1,168 @@ package iapProvider_test import ( - "errors" - "testing" + "errors" + "testing" - "github.com/hexa-org/policy-mapper/models/formats/gcpBind" - "github.com/hexa-org/policy-mapper/models/rar/testsupport" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/models/formats/gcpBind" + "github.com/hexa-org/policy-mapper/models/rar/testsupport" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "github.com/hexa-org/policy-mapper/providers/googlecloud/iapProvider" - "github.com/stretchr/testify/assert" + "github.com/hexa-org/policy-mapper/providers/googlecloud/iapProvider" + "github.com/stretchr/testify/assert" ) func TestGoogleClient_GetAppEngineApplications(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.ResponseBody["https://appengine.googleapis.com/v1/apps/projectID"] = appEngineAppsJSON - client := iapProvider.GoogleClient{ProjectId: "projectID", HttpClient: m} + m := testsupport.NewMockHTTPClient() + m.ResponseBody["https://appengine.googleapis.com/v1/apps/projectID"] = appEngineAppsJSON + client := iapProvider.GoogleClient{ProjectId: "projectID", HttpClient: m} - applications, _ := client.GetAppEngineApplications() + applications, _ := client.GetAppEngineApplications() - assert.Equal(t, 1, len(applications)) - assert.Equal(t, "hexa-demo", applications[0].ObjectID) - assert.Equal(t, "apps/hexa-demo", applications[0].Name) - assert.Equal(t, "hexa-demo.uc.r.appspot.com", applications[0].Description) - assert.Equal(t, "AppEngine", applications[0].Service) + assert.Equal(t, 1, len(applications)) + assert.Equal(t, "hexa-demo", applications[0].ObjectID) + assert.Equal(t, "apps/hexa-demo", applications[0].Name) + assert.Equal(t, "hexa-demo.uc.r.appspot.com", applications[0].Description) + assert.Equal(t, "AppEngine", applications[0].Service) } func TestGoogleClient_GetAppEngineApplications_when_404(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.StatusCode = 404 - client := iapProvider.GoogleClient{HttpClient: m} + m := testsupport.NewMockHTTPClient() + m.StatusCode = 404 + client := iapProvider.GoogleClient{HttpClient: m} - applications, _ := client.GetAppEngineApplications() + applications, _ := client.GetAppEngineApplications() - assert.Equal(t, 0, len(applications)) + assert.Equal(t, 0, len(applications)) } func TestClient_GetAppEngineApplications_withRequestError(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.Err = errors.New("oops") + m := testsupport.NewMockHTTPClient() + m.Err = errors.New("oops") - client := iapProvider.GoogleClient{HttpClient: m} + client := iapProvider.GoogleClient{HttpClient: m} - _, err := client.GetAppEngineApplications() - assert.Error(t, err) + _, err := client.GetAppEngineApplications() + assert.Error(t, err) } func TestClient_GetAppEngineApplications_withBadJson(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.ResponseBody["compute"] = []byte("-") - client := iapProvider.GoogleClient{HttpClient: m} + m := testsupport.NewMockHTTPClient() + m.ResponseBody["compute"] = []byte("-") + client := iapProvider.GoogleClient{HttpClient: m} - _, err := client.GetAppEngineApplications() + _, err := client.GetAppEngineApplications() - assert.Error(t, err) + assert.Error(t, err) } func TestClient_GetBackendApplications(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.ResponseBody["https://compute.googleapis.com/compute/v1/projects/projectID/global/backendServices"] = backendAppsJSON - client := iapProvider.GoogleClient{ProjectId: "projectID", HttpClient: m} - - applications, _ := client.GetBackendApplications() - - assert.Equal(t, 3, len(applications)) - assert.Equal(t, "k8s1-aName", applications[0].Name) - assert.Equal(t, "k8s1-anotherName", applications[1].Name) - assert.Equal(t, "cloud-run-app", applications[2].Name) - assert.Equal(t, "Kubernetes", applications[0].Service) - assert.Equal(t, "Kubernetes", applications[1].Service) - assert.Equal(t, "Cloud Run", applications[2].Service) + m := testsupport.NewMockHTTPClient() + m.ResponseBody["https://compute.googleapis.com/compute/v1/projects/projectID/global/backendServices"] = backendAppsJSON + client := iapProvider.GoogleClient{ProjectId: "projectID", HttpClient: m} + + applications, _ := client.GetBackendApplications() + + assert.Equal(t, 3, len(applications)) + assert.Equal(t, "k8s1-aName", applications[0].Name) + assert.Equal(t, "k8s1-anotherName", applications[1].Name) + assert.Equal(t, "cloud-run-app", applications[2].Name) + assert.Equal(t, "Kubernetes", applications[0].Service) + assert.Equal(t, "Kubernetes", applications[1].Service) + assert.Equal(t, "Cloud Run", applications[2].Service) } func TestClient_GetBackendApplications_withRequestError(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.Err = errors.New("oops") - client := iapProvider.GoogleClient{HttpClient: m} + m := testsupport.NewMockHTTPClient() + m.Err = errors.New("oops") + client := iapProvider.GoogleClient{HttpClient: m} - _, err := client.GetBackendApplications() + _, err := client.GetBackendApplications() - assert.Error(t, err) + assert.Error(t, err) } func TestClient_GetBackendApplications_withBadJson(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.ResponseBody["compute"] = []byte("-") - client := iapProvider.GoogleClient{HttpClient: m} + m := testsupport.NewMockHTTPClient() + m.ResponseBody["compute"] = []byte("-") + client := iapProvider.GoogleClient{HttpClient: m} - _, err := client.GetBackendApplications() + _, err := client.GetBackendApplications() - assert.Error(t, err) + assert.Error(t, err) } func TestGoogleClient_GetAppEnginePolicies(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.ResponseBody["https://iap.googleapis.com/v1/projects/appengineproject/iap_web/appengine-appEngineObjectId/services/default:getIamPolicy"] = policyJSON - client := iapProvider.GoogleClient{HttpClient: m, ProjectId: "appengineproject"} - expectedUsers := []string{ - "user:phil@example.com", - "group:admins@example.com", - "domain:google.com", - "serviceAccount:my-project-id@appspot.gserviceaccount.com", - } - - infos, err := client.GetBackendPolicy("apps/EngineName", "appEngineObjectId") - assert.NoError(t, err, "Check for Get App engine error") - assert.Equal(t, 2, len(infos)) - assert.Equal(t, expectedUsers, infos[0].Members) + m := testsupport.NewMockHTTPClient() + m.ResponseBody["https://iap.googleapis.com/v1/projects/appengineproject/iap_web/appengine-appEngineObjectId/services/default:getIamPolicy"] = policyJSON + client := iapProvider.GoogleClient{HttpClient: m, ProjectId: "appengineproject"} + expectedUsers := []string{ + "user:phil@example.com", + "group:admins@example.com", + "domain:google.com", + "serviceAccount:my-project-id@appspot.gserviceaccount.com", + } + + infos, err := client.GetBackendPolicy("apps/EngineName", "appEngineObjectId") + assert.NoError(t, err, "Check for Get App engine error") + assert.Equal(t, 2, len(infos)) + assert.Equal(t, expectedUsers, infos[0].Members) } func TestGoogleClient_GetBackendPolicies(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.ResponseBody["https://iap.googleapis.com/v1/projects/k8sproject/iap_web/compute/services/k8sObjectId:getIamPolicy"] = policyJSON - client := iapProvider.GoogleClient{HttpClient: m, ProjectId: "k8sproject"} - expectedUsers := []string{ - "user:phil@example.com", - "group:admins@example.com", - "domain:google.com", - "serviceAccount:my-project-id@appspot.gserviceaccount.com", - } - - infos, _ := client.GetBackendPolicy("k8sName", "k8sObjectId") - - assert.Equal(t, 2, len(infos)) - assert.Equal(t, expectedUsers, infos[0].Members) + m := testsupport.NewMockHTTPClient() + m.ResponseBody["https://iap.googleapis.com/v1/projects/k8sproject/iap_web/compute/services/k8sObjectId:getIamPolicy"] = policyJSON + client := iapProvider.GoogleClient{HttpClient: m, ProjectId: "k8sproject"} + expectedUsers := []string{ + "user:phil@example.com", + "group:admins@example.com", + "domain:google.com", + "serviceAccount:my-project-id@appspot.gserviceaccount.com", + } + + infos, _ := client.GetBackendPolicy("k8sName", "k8sObjectId") + + assert.Equal(t, 2, len(infos)) + assert.Equal(t, expectedUsers, infos[0].Members) } func TestGoogleClient_GetBackendPolicies_withRequestError(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.Err = errors.New("oops") - client := iapProvider.GoogleClient{HttpClient: m} + m := testsupport.NewMockHTTPClient() + m.Err = errors.New("oops") + client := iapProvider.GoogleClient{HttpClient: m} - _, err := client.GetBackendPolicy("k8sName", "anObjectId") + _, err := client.GetBackendPolicy("k8sName", "anObjectId") - assert.Error(t, err) + assert.Error(t, err) } func TestGoogleClient_GetBackendPolicies_withBadJson(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.ResponseBody["compute"] = []byte("-") - client := iapProvider.GoogleClient{HttpClient: m} + m := testsupport.NewMockHTTPClient() + m.ResponseBody["compute"] = []byte("-") + client := iapProvider.GoogleClient{HttpClient: m} - _, err := client.GetBackendPolicy("k8sName", "anObjectId") + _, err := client.GetBackendPolicy("k8sName", "anObjectId") - assert.Error(t, err) + assert.Error(t, err) } func TestGoogleClient_SetAppEnginePolicies(t *testing.T) { - policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"roles/iap.httpsResourceAccessor"}}, Subject: hexapolicy.SubjectInfo{Members: []string{"aUser"}}, Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, - } - mapper := gcpBind.New(map[string]string{}) - bindPolicy, err := mapper.MapPolicyToBinding(policy) - assert.NoError(t, err) - m := testsupport.NewMockHTTPClient() - client := iapProvider.GoogleClient{HttpClient: m, ProjectId: "appengineproject"} - - err = client.SetBackendPolicy("apps/EngineName", "anObjectId", bindPolicy) - - assert.NoError(t, err) - compare := string(m.GetRequestBody(m.Url)) - assert.JSONEq(t, `{ + policy := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"roles/iap.httpsResourceAccessor"}}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ + ResourceID: "anObjectId", + }, + } + mapper := gcpBind.New(map[string]string{}) + bindPolicy, err := mapper.MapPolicyToBinding(policy) + assert.NoError(t, err) + m := testsupport.NewMockHTTPClient() + client := iapProvider.GoogleClient{HttpClient: m, ProjectId: "appengineproject"} + + err = client.SetBackendPolicy("apps/EngineName", "anObjectId", bindPolicy) + + assert.NoError(t, err) + compare := string(m.GetRequestBody(m.Url)) + assert.JSONEq(t, `{ "policy": { "bindings": [ { @@ -174,22 +174,22 @@ func TestGoogleClient_SetAppEnginePolicies(t *testing.T) { ] } }`, compare) - assert.Equal(t, "https://iap.googleapis.com/v1/projects/appengineproject/iap_web/appengine-anObjectId/services/default:setIamPolicy", m.Url) + assert.Equal(t, "https://iap.googleapis.com/v1/projects/appengineproject/iap_web/appengine-anObjectId/services/default:setIamPolicy", m.Url) } func TestGoogleClient_SetBackendPolicies(t *testing.T) { - policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"gcp:roles/iap.httpsResourceAccessor"}}, Subject: hexapolicy.SubjectInfo{Members: []string{"aUser"}}, Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, - } - mapper := gcpBind.New(map[string]string{}) - bindPolicy, err := mapper.MapPolicyToBinding(policy) - m := testsupport.NewMockHTTPClient() - client := iapProvider.GoogleClient{HttpClient: m, ProjectId: "k8sproject"} - err = client.SetBackendPolicy("k8sName", "anObjectId", bindPolicy) - assert.NoError(t, err) - assert.JSONEq(t, `{ + policy := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"gcp:roles/iap.httpsResourceAccessor"}}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ + ResourceID: "anObjectId", + }, + } + mapper := gcpBind.New(map[string]string{}) + bindPolicy, err := mapper.MapPolicyToBinding(policy) + m := testsupport.NewMockHTTPClient() + client := iapProvider.GoogleClient{HttpClient: m, ProjectId: "k8sproject"} + err = client.SetBackendPolicy("k8sName", "anObjectId", bindPolicy) + assert.NoError(t, err) + assert.JSONEq(t, `{ "policy": { "bindings": [ { @@ -201,20 +201,20 @@ func TestGoogleClient_SetBackendPolicies(t *testing.T) { ] } }`, string(m.GetRequestBody(m.Url))) - assert.Equal(t, "https://iap.googleapis.com/v1/projects/k8sproject/iap_web/compute/services/anObjectId:setIamPolicy", m.Url) + assert.Equal(t, "https://iap.googleapis.com/v1/projects/k8sproject/iap_web/compute/services/anObjectId:setIamPolicy", m.Url) } func TestGoogleClient_SetBackendPolicies_withRequestError(t *testing.T) { - policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"gcp:roles/iap.httpsResourceAccessor"}}, Subject: hexapolicy.SubjectInfo{Members: []string{"aUser"}}, Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, - } - mapper := gcpBind.New(map[string]string{}) - bindPolicy, err := mapper.MapPolicyToBinding(policy) - m := testsupport.NewMockHTTPClient() - m.Err = errors.New("oops") - client := iapProvider.GoogleClient{HttpClient: m} - err = client.SetBackendPolicy("k8sName", "anObjectId", bindPolicy) - assert.Error(t, err) + policy := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"gcp:roles/iap.httpsResourceAccessor"}}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ + ResourceID: "anObjectId", + }, + } + mapper := gcpBind.New(map[string]string{}) + bindPolicy, err := mapper.MapPolicyToBinding(policy) + m := testsupport.NewMockHTTPClient() + m.Err = errors.New("oops") + client := iapProvider.GoogleClient{HttpClient: m} + err = client.SetBackendPolicy("k8sName", "anObjectId", bindPolicy) + assert.Error(t, err) } diff --git a/providers/googlecloud/iapProvider/google_cloud_provider_test.go b/providers/googlecloud/iapProvider/google_cloud_provider_test.go index 1163880..4b27e54 100644 --- a/providers/googlecloud/iapProvider/google_cloud_provider_test.go +++ b/providers/googlecloud/iapProvider/google_cloud_provider_test.go @@ -1,138 +1,138 @@ package iapProvider_test import ( - "testing" + "testing" - "github.com/hexa-org/policy-mapper/api/policyprovider" - "github.com/hexa-org/policy-mapper/models/rar/testsupport" - "github.com/hexa-org/policy-mapper/pkg/hexapolicy" + "github.com/hexa-org/policy-mapper/api/policyprovider" + "github.com/hexa-org/policy-mapper/models/rar/testsupport" + "github.com/hexa-org/policy-mapper/pkg/hexapolicy" - "github.com/hexa-org/policy-mapper/providers/googlecloud/iapProvider" - "github.com/stretchr/testify/assert" + "github.com/hexa-org/policy-mapper/providers/googlecloud/iapProvider" + "github.com/stretchr/testify/assert" ) func TestGoogleProvider_BadClientKey(t *testing.T) { - p := iapProvider.GoogleProvider{} - info := policyprovider.IntegrationInfo{Name: iapProvider.ProviderTypeGoogleCloudIAP, Key: []byte("aKey")} + p := iapProvider.GoogleProvider{} + info := policyprovider.IntegrationInfo{Name: iapProvider.ProviderTypeGoogleCloudIAP, Key: []byte("aKey")} - _, discoverErr := p.DiscoverApplications(info) - assert.Error(t, discoverErr) + _, discoverErr := p.DiscoverApplications(info) + assert.Error(t, discoverErr) - _, getErr := p.GetPolicyInfo(info, policyprovider.ApplicationInfo{ObjectID: "anObjectId"}) - assert.Error(t, getErr) + _, getErr := p.GetPolicyInfo(info, policyprovider.ApplicationInfo{ObjectID: "anObjectId"}) + assert.Error(t, getErr) - status, setErr := p.SetPolicyInfo(info, policyprovider.ApplicationInfo{ObjectID: "anObjectId"}, []hexapolicy.PolicyInfo{}) - assert.Equal(t, 500, status) - assert.Error(t, setErr) + status, setErr := p.SetPolicyInfo(info, policyprovider.ApplicationInfo{ObjectID: "anObjectId"}, []hexapolicy.PolicyInfo{}) + assert.Equal(t, 500, status) + assert.Error(t, setErr) } func TestGoogleProvider_DiscoverApplications(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.ResponseBody["https://compute.googleapis.com/compute/v1/projects/google-cloud-project-id/global/backendServices"] = backendAppsJSON - m.ResponseBody["https://appengine.googleapis.com/v1/apps/google-cloud-project-id"] = appEngineAppsJSON - p := &iapProvider.GoogleProvider{HttpClientOverride: m} - info := policyprovider.IntegrationInfo{Name: iapProvider.ProviderTypeGoogleCloudIAP, Key: projectJSON} - - applications, err := p.DiscoverApplications(info) - - assert.NoError(t, err) - assert.Equal(t, 4, len(applications)) - assert.Equal(t, "Kubernetes", applications[0].Service) - assert.Equal(t, "Kubernetes", applications[1].Service) - assert.Equal(t, "Cloud Run", applications[2].Service) - assert.Equal(t, "AppEngine", applications[3].Service) - assert.Equal(t, iapProvider.ProviderTypeGoogleCloudIAP, p.Name()) + m := testsupport.NewMockHTTPClient() + m.ResponseBody["https://compute.googleapis.com/compute/v1/projects/google-cloud-project-id/global/backendServices"] = backendAppsJSON + m.ResponseBody["https://appengine.googleapis.com/v1/apps/google-cloud-project-id"] = appEngineAppsJSON + p := &iapProvider.GoogleProvider{HttpClientOverride: m} + info := policyprovider.IntegrationInfo{Name: iapProvider.ProviderTypeGoogleCloudIAP, Key: projectJSON} + + applications, err := p.DiscoverApplications(info) + + assert.NoError(t, err) + assert.Equal(t, 4, len(applications)) + assert.Equal(t, "Kubernetes", applications[0].Service) + assert.Equal(t, "Kubernetes", applications[1].Service) + assert.Equal(t, "Cloud Run", applications[2].Service) + assert.Equal(t, "AppEngine", applications[3].Service) + assert.Equal(t, iapProvider.ProviderTypeGoogleCloudIAP, p.Name()) } func TestGoogleProvider_DiscoverApplications_ignoresProviderCase(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.ResponseBody["https://compute.googleapis.com/compute/v1/projects/google-cloud-project-id/global/backendServices"] = backendAppsJSON - m.ResponseBody["https://appengine.googleapis.com/v1/apps/google-cloud-project-id"] = appEngineAppsJSON - p := &iapProvider.GoogleProvider{HttpClientOverride: m} - info := policyprovider.IntegrationInfo{Name: iapProvider.ProviderTypeGoogleCloudIAP, Key: projectJSON} + m := testsupport.NewMockHTTPClient() + m.ResponseBody["https://compute.googleapis.com/compute/v1/projects/google-cloud-project-id/global/backendServices"] = backendAppsJSON + m.ResponseBody["https://appengine.googleapis.com/v1/apps/google-cloud-project-id"] = appEngineAppsJSON + p := &iapProvider.GoogleProvider{HttpClientOverride: m} + info := policyprovider.IntegrationInfo{Name: iapProvider.ProviderTypeGoogleCloudIAP, Key: projectJSON} - applications, err := p.DiscoverApplications(info) + applications, err := p.DiscoverApplications(info) - assert.NoError(t, err) - assert.Equal(t, 4, len(applications)) - assert.Equal(t, iapProvider.ProviderTypeGoogleCloudIAP, p.Name()) + assert.NoError(t, err) + assert.Equal(t, 4, len(applications)) + assert.Equal(t, iapProvider.ProviderTypeGoogleCloudIAP, p.Name()) } func TestGoogleProvider_DiscoverApplications_emptyResponse(t *testing.T) { - m := testsupport.NewMockHTTPClient() - p := &iapProvider.GoogleProvider{HttpClientOverride: m} - info := policyprovider.IntegrationInfo{Name: "not google_cloud", Key: []byte("aKey")} + m := testsupport.NewMockHTTPClient() + p := &iapProvider.GoogleProvider{HttpClientOverride: m} + info := policyprovider.IntegrationInfo{Name: "not google_cloud", Key: []byte("aKey")} - applications, _ := p.DiscoverApplications(info) + applications, _ := p.DiscoverApplications(info) - assert.Equal(t, 0, len(applications)) + assert.Equal(t, 0, len(applications)) } func TestGoogleProvider_Project(t *testing.T) { - p := iapProvider.GoogleProvider{} + p := iapProvider.GoogleProvider{} - assert.Equal(t, "google-cloud-project-id", p.Project(projectJSON)) + assert.Equal(t, "google-cloud-project-id", p.Project(projectJSON)) } func TestGoogleProvider_HttpClient(t *testing.T) { - p := iapProvider.GoogleProvider{} + p := iapProvider.GoogleProvider{} - client, err := p.NewHttpClient(projectJSON) + client, err := p.NewHttpClient(projectJSON) - assert.NotNil(t, client) - assert.NoError(t, err) + assert.NotNil(t, client) + assert.NoError(t, err) } func TestGoogleProvider_HttpClient_withBadKey(t *testing.T) { - p := iapProvider.GoogleProvider{} + p := iapProvider.GoogleProvider{} - _, err := p.NewHttpClient([]byte("")) + _, err := p.NewHttpClient([]byte("")) - assert.Error(t, err) + assert.Error(t, err) } func TestGoogleProvider_GetPolicy(t *testing.T) { - m := testsupport.NewMockHTTPClient() - m.ResponseBody["https://iap.googleapis.com/v1/projects/google-cloud-project-id/iap_web/compute/services/k8sObjectId:getIamPolicy"] = policyJSON - p := iapProvider.GoogleProvider{HttpClientOverride: m} - info := policyprovider.IntegrationInfo{Name: iapProvider.ProviderTypeGoogleCloudIAP, Key: projectJSON} + m := testsupport.NewMockHTTPClient() + m.ResponseBody["https://iap.googleapis.com/v1/projects/google-cloud-project-id/iap_web/compute/services/k8sObjectId:getIamPolicy"] = policyJSON + p := iapProvider.GoogleProvider{HttpClientOverride: m} + info := policyprovider.IntegrationInfo{Name: iapProvider.ProviderTypeGoogleCloudIAP, Key: projectJSON} - infos, _ := p.GetPolicyInfo(info, policyprovider.ApplicationInfo{ObjectID: "k8sObjectId", Name: "k8sName"}) + infos, _ := p.GetPolicyInfo(info, policyprovider.ApplicationInfo{ObjectID: "k8sObjectId", Name: "k8sName"}) - assert.Equal(t, 2, len(infos)) + assert.Equal(t, 2, len(infos)) } func TestGoogleProvider_SetPolicy(t *testing.T) { - policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"anAction"}}, Subject: hexapolicy.SubjectInfo{Members: []string{"aUser"}}, Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, - } - m := testsupport.NewMockHTTPClient() - - p := iapProvider.GoogleProvider{HttpClientOverride: m} - info := policyprovider.IntegrationInfo{Name: iapProvider.ProviderTypeGoogleCloudIAP, Key: []byte("aKey")} - status, err := p.SetPolicyInfo(info, policyprovider.ApplicationInfo{ObjectID: "anObjectId"}, []hexapolicy.PolicyInfo{policy}) - assert.Equal(t, 201, status) - assert.NoError(t, err) + policy := hexapolicy.PolicyInfo{ + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"anAction"}}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ + ResourceID: "anObjectId", + }, + } + m := testsupport.NewMockHTTPClient() + + p := iapProvider.GoogleProvider{HttpClientOverride: m} + info := policyprovider.IntegrationInfo{Name: iapProvider.ProviderTypeGoogleCloudIAP, Key: []byte("aKey")} + status, err := p.SetPolicyInfo(info, policyprovider.ApplicationInfo{ObjectID: "anObjectId"}, []hexapolicy.PolicyInfo{policy}) + assert.Equal(t, 201, status) + assert.NoError(t, err) } func TestGoogleProvider_SetPolicy_withInvalidArguments(t *testing.T) { - missingMeta := hexapolicy.PolicyInfo{ - Actions: []hexapolicy.ActionInfo{{"anAction"}}, Subject: hexapolicy.SubjectInfo{Members: []string{"aUser"}}, Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, - } - m := testsupport.NewMockHTTPClient() - - p := iapProvider.GoogleProvider{HttpClientOverride: m} - info := policyprovider.IntegrationInfo{Name: "not google_cloud", Key: []byte("aKey")} - - status, err := p.SetPolicyInfo(info, policyprovider.ApplicationInfo{}, []hexapolicy.PolicyInfo{missingMeta}) - assert.Equal(t, 500, status) - assert.Error(t, err) - - status, err = p.SetPolicyInfo(info, policyprovider.ApplicationInfo{ObjectID: "anObjectId"}, []hexapolicy.PolicyInfo{missingMeta}) - assert.Equal(t, 500, status) - assert.Error(t, err) + missingMeta := hexapolicy.PolicyInfo{ + Actions: []hexapolicy.ActionInfo{{"anAction"}}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ + ResourceID: "anObjectId", + }, + } + m := testsupport.NewMockHTTPClient() + + p := iapProvider.GoogleProvider{HttpClientOverride: m} + info := policyprovider.IntegrationInfo{Name: "not google_cloud", Key: []byte("aKey")} + + status, err := p.SetPolicyInfo(info, policyprovider.ApplicationInfo{}, []hexapolicy.PolicyInfo{missingMeta}) + assert.Equal(t, 500, status) + assert.Error(t, err) + + status, err = p.SetPolicyInfo(info, policyprovider.ApplicationInfo{ObjectID: "anObjectId"}, []hexapolicy.PolicyInfo{missingMeta}) + assert.Equal(t, 500, status) + assert.Error(t, err) } diff --git a/providers/openpolicyagent/decisionsupport/opa_provider.go b/providers/openpolicyagent/decisionsupport/opa_provider.go deleted file mode 100644 index 6a0bcc7..0000000 --- a/providers/openpolicyagent/decisionsupport/opa_provider.go +++ /dev/null @@ -1,65 +0,0 @@ -package decisionsupport - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" -) - -type OpaDecisionProvider struct { - Client HTTPClient - Url string - Principal string -} - -type OpaQuery struct { - Input map[string]interface{} `json:"input"` -} - -func (o OpaDecisionProvider) BuildInput(r *http.Request) (any interface{}, err error) { - scheme := "http" - if len(r.URL.Scheme) != 0 { - scheme = r.URL.Scheme - } - method := fmt.Sprintf( - "%s:%s:%s", - scheme, - r.Method, - strings.Split(r.RequestURI, "?")[0], - ) - return OpaQuery{map[string]interface{}{ - "method": method, - "principal": o.Principal, - }}, nil -} - -type HTTPClient interface { - Do(req *http.Request) (*http.Response, error) -} - -type OpaResponse struct { - Result bool -} - -func (o OpaDecisionProvider) Allow(any interface{}) (bool, error) { - marshal, _ := json.Marshal(any.(OpaQuery)) - request, _ := http.NewRequest("POST", o.Url, bytes.NewBuffer(marshal)) - request.Header.Set("Content-Type", "application/json; charset=UTF-8") - response, err := o.Client.Do(request) - if err != nil { - return false, err - } - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(response.Body) - - var jsonResponse OpaResponse - err = json.NewDecoder(response.Body).Decode(&jsonResponse) - if err != nil { - return false, err - } - return jsonResponse.Result, nil -} diff --git a/providers/openpolicyagent/decisionsupport/opa_provider_test.go b/providers/openpolicyagent/decisionsupport/opa_provider_test.go deleted file mode 100644 index f1397d6..0000000 --- a/providers/openpolicyagent/decisionsupport/opa_provider_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package decisionsupport_test - -import ( - "bytes" - "errors" - "io" - - "net/http" - "testing" - - decisionsupportproviders "github.com/hexa-org/policy-mapper/providers/openpolicyagent/decisionsupport" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestOpaDecisionProvider_BuildInput(t *testing.T) { - provider := decisionsupportproviders.OpaDecisionProvider{ - Principal: "sales@hexaindustries.io", - } - - req, _ := http.NewRequest("POST", "https://aDomain.com/noop", nil) - req.RequestURI = "/noop" - query, _ := provider.BuildInput(req) - casted := query.(decisionsupportproviders.OpaQuery).Input - assert.Equal(t, "https:POST:/noop", casted["method"]) - assert.Equal(t, "sales@hexaindustries.io", casted["principal"]) -} - -func TestOpaDecisionProvider_BuildInput_RemovesQueryParams(t *testing.T) { - mockClient := new(MockClient) - mockClient.response = []byte("{\"result\":true}") - provider := decisionsupportproviders.OpaDecisionProvider{Client: mockClient, Url: "aUrl"} - - req, _ := http.NewRequest("GET", "http://aDomain.com/noop/?param=aParam", nil) - req.RequestURI = "/noop" - query, _ := provider.BuildInput(req) - - assert.Equal(t, "http:GET:/noop", query.(decisionsupportproviders.OpaQuery).Input["method"]) -} - -type MockClient struct { - mock.Mock - response []byte - err error -} - -func (m *MockClient) Do(_ *http.Request) (*http.Response, error) { - r := io.NopCloser(bytes.NewReader(m.response)) - return &http.Response{StatusCode: 200, Body: r}, m.err -} - -func TestOpaDecisionProvider_Allow(t *testing.T) { - mockClient := new(MockClient) - mockClient.response = []byte("{\"result\":true}") - provider := decisionsupportproviders.OpaDecisionProvider{Client: mockClient, Url: "aUrl"} - - req, _ := http.NewRequest("GET", "http://aDomain.com/noop", nil) - req.RequestURI = "/noop" - query, _ := provider.BuildInput(req) - - allow, _ := provider.Allow(query) - assert.Equal(t, true, allow) -} - -func TestOpaDecisionProvider_AllowWithRequestErr(t *testing.T) { - mockClient := new(MockClient) - mockClient.response = []byte("{\"result\":true}") - mockClient.err = errors.New("oops") - provider := decisionsupportproviders.OpaDecisionProvider{Client: mockClient, Url: "aUrl"} - - req, _ := http.NewRequest("GET", "http://aDomain.com/noop", nil) - req.RequestURI = "/noop" - query, _ := provider.BuildInput(req) - - allow, err := provider.Allow(query) - assert.Equal(t, "oops", err.Error()) - assert.Equal(t, false, allow) -} - -func TestOpaDecisionProvider_AllowWithResponseErr(t *testing.T) { - mockClient := new(MockClient) - mockClient.response = []byte("__bad__ {\"result\":true}") - provider := decisionsupportproviders.OpaDecisionProvider{Client: mockClient, Url: "aUrl"} - - req, _ := http.NewRequest("GET", "http://aDomain.com/noop", nil) - req.RequestURI = "/noop" - query, _ := provider.BuildInput(req) - - allow, err := provider.Allow(query) - assert.Equal(t, "invalid character '_' looking for beginning of value", err.Error()) - assert.Equal(t, false, allow) -} diff --git a/providers/openpolicyagent/http_bundle_client_test.go b/providers/openpolicyagent/http_bundle_client_test.go index 0c4cb6b..01cedf4 100644 --- a/providers/openpolicyagent/http_bundle_client_test.go +++ b/providers/openpolicyagent/http_bundle_client_test.go @@ -22,51 +22,74 @@ const expectedBundleData = `{ "comment": "Policy data for testing only!", "policies": [ { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/"}], - "subject": { - "members": [ - "allusers", "allauthenticated" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "http:GET:/" + } + ], + "subjects:": [ + "any", + "anyauthenticated" + ], "object": { "resource_id": "aResourceId" } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/sales"}, {"actionUri": "http:GET:/marketing"}], - "subject": { - "members": [ - "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "http:GET:/sales" + }, + { + "actionUri": "http:GET:/marketing" + } + ], + "subjects:": [ + "anyauthenticated", + "sales@hexaindustries.io", + "marketing@hexaindustries.io" + ], "object": { "resource_id": "aResourceId" } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/accounting"}, {"actionUri": "http:POST:/accounting"}], - "subject": { - "members": [ - "accounting@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "http:GET:/accounting" + }, + { + "actionUri": "http:POST:/accounting" + } + ], + "subjects:": [ + "accounting@hexaindustries.io" + ], "object": { "resource_id": "aResourceId" } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/humanresources"}], - "subject": { - "members": [ - "humanresources@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "http:GET:/humanresources" + } + ], + "subjects:": [ + "humanresources@hexaindustries.io" + ], "object": { "resource_id": "aResourceId" } diff --git a/providers/openpolicyagent/opa_policy_test.go b/providers/openpolicyagent/opa_policy_test.go deleted file mode 100644 index 129229d..0000000 --- a/providers/openpolicyagent/opa_policy_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package openpolicyagent_test - -import ( - "bytes" - "log" - "net/http" - "os" - "os/exec" - "path/filepath" - "runtime" - "syscall" - "testing" - "time" - - "github.com/hexa-org/policy-mapper/providers/openpolicyagent/decisionsupport" - assert "github.com/stretchr/testify/require" -) - -func TestPolicy(t *testing.T) { - if testing.Short() { - t.Skip("Skipping test with opa run") - } - _, opaEnabled := os.LookupEnv("OPA_TEST") - if !opaEnabled { - t.Skip("Skipping test as OPA_TEST not set") - } - openPolicyAgent := exec.Command("opa", "run", "--server", "--addr", ":8887") - openPolicyAgent.Stdout = os.Stdout - openPolicyAgent.Stderr = os.Stderr - openPolicyAgent.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - startCmd(openPolicyAgent, 8887) - defer func() { - _ = syscall.Kill(-openPolicyAgent.Process.Pid, syscall.SIGKILL) - }() - - _, file, _, _ := runtime.Caller(0) - - data, _ := os.ReadFile(filepath.Join(file, "../resources/bundles/bundle/data.json")) - dataReq, _ := http.NewRequest(http.MethodPut, "http://localhost:8887/v1/data/bundle", bytes.NewBuffer(data)) - dataDo, err := http.DefaultClient.Do(dataReq) - assert.NoError(t, err) - assert.Equal(t, http.StatusNoContent, dataDo.StatusCode) - - rego, err := os.ReadFile(filepath.Join(file, "../resources/bundles/bundle/hexaPolicy.rego")) - assert.NoError(t, err) - regoReq, err := http.NewRequest(http.MethodPut, "http://localhost:8887/v1/policies/hexaPolicy", bytes.NewBuffer(rego)) - assert.NoError(t, err) - regoDo, err := http.DefaultClient.Do(regoReq) - assert.NoError(t, err) - assert.Equal(t, http.StatusOK, regoDo.StatusCode) - - provider := decisionsupport.OpaDecisionProvider{ - Client: http.DefaultClient, - Url: "http://localhost:8887/v1/data/hexaPolicy/allow", - } - - shouldAllow(t, provider, "http:GET:/", "") - shouldAllow(t, provider, "http:GET:/", "any@google.com") - shouldAllow(t, provider, "http:GET:/", "sales@hexaindustries.io") - - shouldNotAllow(t, provider, "http:GET:/sales", "") - shouldAllow(t, provider, "http:GET:/sales", "any@google.com") - shouldAllow(t, provider, "http:GET:/sales", "sales@hexaindustries.io") - shouldAllow(t, provider, "http:GET:/sales", "marketing@hexaindustries.io") - - shouldNotAllow(t, provider, "http:GET:/marketing", "") - shouldAllow(t, provider, "http:GET:/marketing", "any@google.com") - shouldAllow(t, provider, "http:GET:/marketing", "sales@hexaindustries.io") - shouldAllow(t, provider, "http:GET:/marketing", "marketing@hexaindustries.io") - - shouldNotAllow(t, provider, "http:GET:/accounting", "") - shouldNotAllow(t, provider, "http:GET:/accounting", "any@google.com") - shouldNotAllow(t, provider, "http:GET:/accounting", "sales@hexaindustries.io") - shouldAllow(t, provider, "http:GET:/accounting", "accounting@hexaindustries.io") - - shouldNotAllow(t, provider, "http:GET:/humanresources", "") - shouldNotAllow(t, provider, "http:GET:/humanresources", "any@google.com") - shouldNotAllow(t, provider, "http:GET:/humanresources", "sales@hexaindustries.io") - shouldAllow(t, provider, "http:GET:/humanresources", "humanresources@hexaindustries.io") -} - -func shouldAllow(t *testing.T, provider decisionsupport.OpaDecisionProvider, action string, principal string) { - assert.True(t, allows(provider, action, principal)) -} - -func shouldNotAllow(t *testing.T, provider decisionsupport.OpaDecisionProvider, action string, principal string) { - assert.False(t, allows(provider, action, principal)) -} - -func allows(provider decisionsupport.OpaDecisionProvider, action string, principal string) bool { - allow, _ := provider.Allow(decisionsupport.OpaQuery{Input: map[string]interface{}{"method": action, "principal": principal}}) - return allow -} - -func startCmd(cmd *exec.Cmd, _ int) { - log.Printf("Starting command: %v\n", cmd) - errCh := make(chan error) - - go func() { - err := cmd.Run() - if err != nil { - errCh <- err - } - }() - - select { - case <-time.After(time.Second): - log.Println("Command started") - case err := <-errCh: - if err != nil { - log.Fatalf("Unable to start cmd %v\n", err) - } - } -} diff --git a/providers/openpolicyagent/opa_provider_test.go b/providers/openpolicyagent/opa_provider_test.go index edd9ede..d41a80d 100644 --- a/providers/openpolicyagent/opa_provider_test.go +++ b/providers/openpolicyagent/opa_provider_test.go @@ -305,7 +305,7 @@ func TestSetPolicyInfo(t *testing.T) { policyprovider.IntegrationInfo{Name: openpolicyagent.ProviderTypeOpa, Key: key}, policyprovider.ApplicationInfo{ObjectID: "anotherResourceId"}, []hexapolicy.PolicyInfo{ - {Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{{"http:GET"}}, Subject: hexapolicy.SubjectInfo{Members: []string{"allusers"}}, Object: hexapolicy.ObjectInfo{ + {Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{{"http:GET"}}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ ResourceID: "aResourceId", }}, }, @@ -368,7 +368,7 @@ func TestSetPolicyInfo_withInvalidArguments(t *testing.T) { policyprovider.ApplicationInfo{ObjectID: "aResourceId"}, []hexapolicy.PolicyInfo{ { - Actions: []hexapolicy.ActionInfo{{"http:GET"}}, Subject: hexapolicy.SubjectInfo{Members: []string{"allusers"}}, Object: hexapolicy.ObjectInfo{ + Actions: []hexapolicy.ActionInfo{{"http:GET"}}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ ResourceID: "aResourceId", }}, }, @@ -422,7 +422,7 @@ func TestSetPolicyInfo_WithHTTPSBundleServer(t *testing.T) { policyprovider.IntegrationInfo{Name: openpolicyagent.ProviderTypeOpa, Key: key}, policyprovider.ApplicationInfo{ObjectID: "aResourceId"}, []hexapolicy.PolicyInfo{ - {Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{{"http:GET"}}, Subject: hexapolicy.SubjectInfo{Members: []string{"allusers"}}, Object: hexapolicy.ObjectInfo{ + {Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{{"http:GET"}}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ ResourceID: "aResourceId", }}, }, diff --git a/providers/openpolicyagent/resources/bundles/bundle/data.json b/providers/openpolicyagent/resources/bundles/bundle/data.json index 6e00c56..fa479b5 100644 --- a/providers/openpolicyagent/resources/bundles/bundle/data.json +++ b/providers/openpolicyagent/resources/bundles/bundle/data.json @@ -2,51 +2,74 @@ "comment": "Policy data for testing only!", "policies": [ { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/"}], - "subject": { - "members": [ - "allusers", "allauthenticated" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "http:GET:/" + } + ], + "subjects:": [ + "any", + "anyauthenticated" + ], "object": { "resource_id": "aResourceId" } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/sales"}, {"actionUri": "http:GET:/marketing"}], - "subject": { - "members": [ - "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "http:GET:/sales" + }, + { + "actionUri": "http:GET:/marketing" + } + ], + "subjects:": [ + "anyauthenticated", + "sales@hexaindustries.io", + "marketing@hexaindustries.io" + ], "object": { "resource_id": "aResourceId" } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/accounting"}, {"actionUri": "http:POST:/accounting"}], - "subject": { - "members": [ - "accounting@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "http:GET:/accounting" + }, + { + "actionUri": "http:POST:/accounting" + } + ], + "subjects:": [ + "accounting@hexaindustries.io" + ], "object": { "resource_id": "aResourceId" } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/humanresources"}], - "subject": { - "members": [ - "humanresources@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "http:GET:/humanresources" + } + ], + "subjects:": [ + "humanresources@hexaindustries.io" + ], "object": { "resource_id": "aResourceId" } diff --git a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego index 3b2adc9..3c587ff 100644 --- a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego +++ b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego @@ -1,11 +1,11 @@ package hexaPolicy -# Rego Hexa Policy Interpreter v0.6.9 +# Rego Hexa Policy Interpreter v0.7.0 import rego.v1 import data.bundle.policies -hexa_rego_version := "0.6.15" +hexa_rego_version := "0.7.0" policies_evaluated := count(policies) @@ -50,13 +50,13 @@ allow if { } subject_match(psubject, _, _) if { - # Match if no members value specified - not psubject.members + # Match if no value specified - treat as wildcard + not psubject } subject_match(psubject, insubject, req) if { - # Match if no members value specified - some member in psubject.members + # Match if a member matches + some member in psubject subject_member_match(member, insubject, req) } diff --git a/sdk/providerTools_test.go b/sdk/providerTools_test.go index a20c766..6bfd223 100644 --- a/sdk/providerTools_test.go +++ b/sdk/providerTools_test.go @@ -1,174 +1,174 @@ package sdk import ( - "fmt" - "log" - "net/http" - "os" - "slices" - "testing" - "time" - - "github.com/hexa-org/policy-mapper/api/policyprovider" - "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/avpTestSupport" - "github.com/hexa-org/policy-mapper/providers/aws/awscommon" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" + "fmt" + "log" + "net/http" + "os" + "slices" + "testing" + "time" + + "github.com/hexa-org/policy-mapper/api/policyprovider" + "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/avpTestSupport" + "github.com/hexa-org/policy-mapper/providers/aws/awscommon" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) var testLog = log.New(os.Stdout, "SDK-TEST: ", log.Ldate|log.Ltime) type testSuite struct { - suite.Suite - Integration *Integration - Info policyprovider.IntegrationInfo - papId string - mockClient *avpTestSupport.MockVerifiedPermissionsHTTPClient + suite.Suite + Integration *Integration + Info policyprovider.IntegrationInfo + papId string + mockClient *avpTestSupport.MockVerifiedPermissionsHTTPClient } func TestSdk(t *testing.T) { - mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() + mockClient := avpTestSupport.NewMockVerifiedPermissionsHTTPClient() - options := awscommon.AWSClientOptions{ - HTTPClient: mockClient, - DisableRetry: true, - } - info := avpTestSupport.IntegrationInfo() - integration, err := OpenIntegration(WithIntegrationInfo(info), WithProviderOptions(options)) - assert.NoError(t, err, "Check no error opening mock provider") - s := testSuite{ - Info: info, - Integration: integration, - mockClient: mockClient, - } + options := awscommon.AWSClientOptions{ + HTTPClient: mockClient, + DisableRetry: true, + } + info := avpTestSupport.IntegrationInfo() + integration, err := OpenIntegration(WithIntegrationInfo(info), WithProviderOptions(options)) + assert.NoError(t, err, "Check no error opening mock provider") + s := testSuite{ + Info: info, + Integration: integration, + mockClient: mockClient, + } - suite.Run(t, &s) + suite.Run(t, &s) - testLog.Println("** SDK Tests Complete **") + testLog.Println("** SDK Tests Complete **") } func (s *testSuite) Test1_GetPaps() { - s.mockClient.MockListStores() - - apps, err := s.Integration.GetPolicyApplicationPoints(nil) - assert.NoError(s.T(), err, "Check no error for get PAPs") - assert.Len(s.T(), apps, 1, "Should be 1 app") - assert.Len(s.T(), s.Integration.Apps, len(apps)) - s.papId = apps[0].ObjectID // save for the next test - s.mockClient.VerifyCalled() + s.mockClient.MockListStores() + + apps, err := s.Integration.GetPolicyApplicationPoints(nil) + assert.NoError(s.T(), err, "Check no error for get PAPs") + assert.Len(s.T(), apps, 1, "Should be 1 app") + assert.Len(s.T(), s.Integration.Apps, len(apps)) + s.papId = apps[0].ObjectID // save for the next test + s.mockClient.VerifyCalled() } func (s *testSuite) Test2_GetPolicies() { - s.mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - - policySet, err := s.Integration.GetPolicies(s.papId) - policies := policySet.Policies - assert.NoError(s.T(), err) - assert.NotNil(s.T(), policies) - assert.Len(s.T(), policies, 2, "Should be 2 policies") - assert.True(s.T(), s.mockClient.VerifyCalled()) + s.mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + + policySet, err := s.Integration.GetPolicies(s.papId) + policies := policySet.Policies + assert.NoError(s.T(), err) + assert.NotNil(s.T(), policies) + assert.Len(s.T(), policies, 2, "Should be 2 policies") + assert.True(s.T(), s.mockClient.VerifyCalled()) } func (s *testSuite) Test3_Reconcile() { - s.mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 5, 1, nil) - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"1") - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"2") - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"3") - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"4") - s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - - policySet, err := s.Integration.GetPolicies(s.papId) - - policies := policySet.Policies - - assert.True(s.T(), s.mockClient.VerifyCalled()) - - // origPolicies := policies[0:] - // This section will cause an update since only action changed - policy := policies[0] - actions := policy.Actions - - actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::UpdateAccount"}) - - policies[0].Actions = actions - - avpMeta := policies[1].Meta - var avpType string - avpType, exist := avpMeta.SourceData[avpProvider.ParamPolicyType].(string) - assert.True(s.T(), exist, "Check policy type exists") - assert.Equal(s.T(), "TEMPLATE_LINKED", avpType, "Second [1] policy should be template") - - // this should cause a replacement (delete and add) to occur (subject change) - policies[2].Subject.Members = []string{"hexa_avp::User::\"gerry@strata.io\""} - - // this should cause an implied delete by removing policy 5 - policies = append(policies[0:5], policies[6:]...) - - now := time.Now() - // now append a policy by copying and modifying the first - newPolicy := policies[0] - newPolicy.Meta = hexapolicy.MetaInfo{ - Version: "0.5", - Description: "Test New Policy", - Created: &now, - Modified: &now, - } - newPolicy.Subject.Members = []string{"hexa_avp::User::\"nobody@strata.io\""} - - policies = append(policies, newPolicy) - - s.mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 5, 1, nil) - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"1") - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"2") - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"3") - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"4") - s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - difs, err := s.Integration.ReconcilePolicy(s.papId, policies, true) - assert.NoError(s.T(), err) - assert.True(s.T(), s.mockClient.VerifyCalled()) - assert.Len(s.T(), difs, 5) - assert.Equal(s.T(), hexapolicy.ChangeTypeUpdate, difs[0].Type) - assert.True(s.T(), slices.Equal([]string{"ACTION"}, difs[0].DifTypes)) - assert.Equal(s.T(), hexapolicy.ChangeTypeIgnore, difs[1].Type) - assert.Equal(s.T(), hexapolicy.ChangeTypeUpdate, difs[2].Type) - assert.True(s.T(), slices.Equal([]string{"SUBJECT"}, difs[2].DifTypes)) - assert.Equal(s.T(), hexapolicy.ChangeTypeNew, difs[3].Type) - assert.Equal(s.T(), hexapolicy.ChangeTypeDelete, difs[4].Type) - for _, dif := range difs { - fmt.Println(dif.Report()) - } + s.mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 5, 1, nil) + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"1") + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"2") + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"3") + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"4") + s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + + policySet, err := s.Integration.GetPolicies(s.papId) + + policies := policySet.Policies + + assert.True(s.T(), s.mockClient.VerifyCalled()) + + // origPolicies := policies[0:] + // This section will cause an update since only action changed + policy := policies[0] + actions := policy.Actions + + actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::UpdateAccount"}) + + policies[0].Actions = actions + + avpMeta := policies[1].Meta + var avpType string + avpType, exist := avpMeta.SourceData[avpProvider.ParamPolicyType].(string) + assert.True(s.T(), exist, "Check policy type exists") + assert.Equal(s.T(), "TEMPLATE_LINKED", avpType, "Second [1] policy should be template") + + // this should cause a replacement (delete and add) to occur (subject change) + policies[2].Subjects = []string{"hexa_avp::User::\"gerry@strata.io\""} + + // this should cause an implied delete by removing policy 5 + policies = append(policies[0:5], policies[6:]...) + + now := time.Now() + // now append a policy by copying and modifying the first + newPolicy := policies[0] + newPolicy.Meta = hexapolicy.MetaInfo{ + Version: "0.5", + Description: "Test New Policy", + Created: &now, + Modified: &now, + } + newPolicy.Subjects = []string{"hexa_avp::User::\"nobody@strata.io\""} + + policies = append(policies, newPolicy) + + s.mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 5, 1, nil) + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"1") + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"2") + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"3") + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"4") + s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + difs, err := s.Integration.ReconcilePolicy(s.papId, policies, true) + assert.NoError(s.T(), err) + assert.True(s.T(), s.mockClient.VerifyCalled()) + assert.Len(s.T(), difs, 5) + assert.Equal(s.T(), hexapolicy.ChangeTypeUpdate, difs[0].Type) + assert.True(s.T(), slices.Equal([]string{"ACTION"}, difs[0].DifTypes)) + assert.Equal(s.T(), hexapolicy.ChangeTypeIgnore, difs[1].Type) + assert.Equal(s.T(), hexapolicy.ChangeTypeUpdate, difs[2].Type) + assert.True(s.T(), slices.Equal([]string{"SUBJECT"}, difs[2].DifTypes)) + assert.Equal(s.T(), hexapolicy.ChangeTypeNew, difs[3].Type) + assert.Equal(s.T(), hexapolicy.ChangeTypeDelete, difs[4].Type) + for _, dif := range difs { + fmt.Println(dif.Report()) + } } func (s *testSuite) Test4_SetPolicies() { - s.mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - policySet, err := s.Integration.GetPolicies(s.papId) - policies := policySet.Policies - assert.True(s.T(), s.mockClient.VerifyCalled()) - - // This section will cause an update since only action changed - policy := policies[0] - actions := policy.Actions - actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::UpdateAccount"}) - - policies[0].Actions = actions - - s.mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) - s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") - s.mockClient.MockUpdatePolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") - status, err := s.Integration.SetPolicyInfo(s.papId, policies) - assert.NoError(s.T(), err) - assert.True(s.T(), s.mockClient.VerifyCalled()) - assert.Equal(s.T(), 200, status, "Should be status 200") + s.mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + policySet, err := s.Integration.GetPolicies(s.papId) + policies := policySet.Policies + assert.True(s.T(), s.mockClient.VerifyCalled()) + + // This section will cause an update since only action changed + policy := policies[0] + actions := policy.Actions + actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::UpdateAccount"}) + + policies[0].Actions = actions + + s.mockClient.MockListPoliciesWithHttpStatus(http.StatusOK, 1, 1, nil) + s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") + s.mockClient.MockUpdatePolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") + status, err := s.Integration.SetPolicyInfo(s.papId, policies) + assert.NoError(s.T(), err) + assert.True(s.T(), s.mockClient.VerifyCalled()) + assert.Equal(s.T(), 200, status, "Should be status 200") } From f8e3c415cb96e4f9dea5003efe6edc1b848dec21 Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Fri, 6 Sep 2024 12:20:39 -0700 Subject: [PATCH 05/13] Issue #59, Simplified actions to array of strings Signed-off-by: Phil Hunt --- cmd/hexa/hexa_test.go | 2 +- cmd/hexa/test/example_idql.json | 24 +-- .../bundles/bundle/hexaPolicy.rego | 151 ++++++++++-------- examples/policyExamples/example_idql.json | 63 +++++--- examples/policyExamples/idqlAlice.json | 24 +-- models/formats/awsCedar/amazon_cedar.go | 8 +- models/formats/awsCedar/test/data.json | 30 +--- models/formats/cedar/cedar_test.go | 36 +---- models/formats/cedar/parse.go | 12 +- models/formats/gcpBind/google_bind_policy.go | 5 +- models/formats/gcpBind/test/data.json | 28 +--- models/rar/policy_transformer.go | 18 +-- models/rar/policy_transformer_test.go | 50 +++--- .../resource_action_role_policy_support.go | 2 +- .../policy_checker_support.go | 4 +- .../policytestsupport/policy_data_support.go | 4 +- pkg/hexapolicy/hexa_policy.go | 12 +- pkg/hexapolicy/hexa_policy_test.go | 44 +++-- pkg/hexapolicysupport/test/data.json | 69 +++----- .../aws/avpProvider/avp_provider_test.go | 10 +- .../aws_apigw_provider_service.go | 2 +- .../aws_apigw_provider_service_test.go | 2 +- .../aws/cognitoProvider/cognito_provider.go | 4 +- .../cognitoProvider/cognito_provider_test.go | 4 +- .../azureProvider/azure_policy_mapper.go | 2 +- .../azureProvider/azure_policy_mapper_test.go | 4 +- .../azure/azureProvider/azure_provider.go | 2 +- .../azureProvider/azure_provider_test.go | 30 ++-- .../iapProvider/google_client_test.go | 6 +- .../iapProvider/google_cloud_provider_test.go | 4 +- .../http_bundle_client_test.go | 24 +-- .../openpolicyagent/opa_provider_test.go | 14 +- .../resources/bundles/bundle/data.json | 24 +-- .../resources/bundles/bundle/hexaPolicy.rego | 8 +- sdk/providerTools_test.go | 4 +- 35 files changed, 321 insertions(+), 409 deletions(-) diff --git a/cmd/hexa/hexa_test.go b/cmd/hexa/hexa_test.go index 5468b15..dbae496 100644 --- a/cmd/hexa/hexa_test.go +++ b/cmd/hexa/hexa_test.go @@ -326,7 +326,7 @@ func (suite *testSuite) Test05_SetPolicies() { // 2nd policy has a different action policy := testPolicyMods[0] - newAction := hexapolicy.ActionInfo{ActionUri: "http:POST:/update"} + newAction := hexapolicy.ActionInfo("http:POST:/update") testPolicyMods[0].Actions = append(policy.Actions, newAction) // 3rd policy has a different subject diff --git a/cmd/hexa/test/example_idql.json b/cmd/hexa/test/example_idql.json index 1f645ce..91259d6 100644 --- a/cmd/hexa/test/example_idql.json +++ b/cmd/hexa/test/example_idql.json @@ -5,9 +5,7 @@ "version": "0.6" }, "actions": [ - { - "actionUri": "http:GET:/" - } + "http:GET:/" ], "subjects": [ "allusers", @@ -26,12 +24,8 @@ "version": "0.6" }, "actions": [ - { - "actionUri": "http:GET:/sales" - }, - { - "actionUri": "http:GET:/marketing" - } + "http:GET:/sales", + "http:GET:/marketing" ], "subjects": [ "allauthenticated", @@ -47,12 +41,8 @@ "version": "0.6" }, "actions": [ - { - "actionUri": "http:GET:/accounting" - }, - { - "actionUri": "http:POST:/accounting" - } + "http:GET:/accounting", + "http:POST:/accounting" ], "subjects": [ "accounting@hexaindustries.io" @@ -70,9 +60,7 @@ "version": "0.6" }, "actions": [ - { - "actionUri": "http:GET:/humanresources" - } + "http:GET:/humanresources" ], "subjects": [ "humanresources@hexaindustries.io" diff --git a/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego b/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego index 82ae00f..f60e1e2 100644 --- a/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego +++ b/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego @@ -1,183 +1,204 @@ package hexaPolicy -# Rego Policy Interpreter for IDQL V0.62.1b (IDQL) +# Rego Hexa Policy Interpreter v0.7.0 import rego.v1 -import data.policies +import data.bundle.policies -# Returns whether the current operation is allowed -allow if { - count(allowSet) > 0 +hexa_rego_version := "0.7.0" + +policies_evaluated := count(policies) + +# Returns the list of matching policy names based on current request +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.subject, input.subject, input.req) + + actions_match(policy.actions, input.req) + + object_match(policy.object, input.req) + + condition_match(policy, input) +} + +scopes contains scope if { + some policy in policies + policy.meta.policyId in allow_set + + scope := { + "policyId": policy.meta.policyId, + "scope": policy.scope + } } # Returns the list of possible actions allowed (e.g. for UI buttons) -actionRights contains name if { +action_rights contains name if { some policy in policies - policy.meta.policyId in allowSet + policy.meta.policyId in allow_set some action in policy.actions - name := sprintf("%s/%s", [policy.meta.policyId, action.actionUri]) + name := sprintf("%s:%s", [policy.meta.policyId, action.actionUri]) } -# Returns the list of matching policy names based on current request -allowSet contains name if { - some policy in policies - subjectMatch(policy.subject, input.subject, input.req) - actionsMatch(policy.actions, input.req) - objectMatch(policy.object, input.req) - conditionMatch(policy, input) - - name := policy.meta.policyId # this will be id of the policy +# Returns whether the current operation is allowed +allow if { + count(allow_set) > 0 } -subjectMatch(psubject, _, _) if { - # Match if no members value specified - not psubject.members +subject_match(psubject, _, _) if { + # Match if no value specified - treat as wildcard + not psubject } -subjectMatch(psubject, insubject, req) if { - # Match if no members value specified - some member in psubject.members - subjectMemberMatch(member, insubject, req) +subject_match(psubject, insubject, req) if { + # Match if a member matches + some member in psubject + subject_member_match(member, insubject, req) } -subjectMemberMatch(member, _, _) if { +subject_member_match(member, _, _) if { # If policy is any that we will skip processing of subject lower(member) == "any" } -subjectMemberMatch(member, insubj, _) if { +subject_member_match(member, insubj, _) if { # anyAutheticated - A match occurs if input.subject has a value other than anonymous and exists. insubj.sub # check sub exists lower(member) == "anyauthenticated" } # Check for match based on user: -subjectMemberMatch(member, insubj, _) if { +subject_member_match(member, insubj, _) if { startswith(lower(member), "user:") user := substring(member, 5, -1) lower(user) == lower(insubj.sub) } # Check for match if sub ends with domain -subjectMemberMatch(member, insubj, _) if { +subject_member_match(member, insubj, _) if { startswith(lower(member), "domain:") domain := lower(substring(member, 7, -1)) endswith(lower(insubj.sub), domain) } # Check for match based on role -subjectMemberMatch(member, insubj, _) if { +subject_member_match(member, insubj, _) if { startswith(lower(member), "role:") role := substring(member, 5, -1) role in insubj.roles } -subjectMemberMatch(member, _, req) if { +subject_member_match(member, _, req) if { startswith(lower(member), "net:") cidr := substring(member, 4, -1) - addr := split(req.ip, ":") # Split because IP is address:port + addr := split(req.ip, ":") # Split because IP is address:port net.cidr_contains(cidr, addr[0]) } -actionsMatch(actions, _) if { +actions_match(actions, _) if { # no actions is a match not actions } -actionsMatch(actions, req) if { +actions_match(actions, req) if { some action in actions - actionMatch(action, req) + action_match(action, req) } -actionMatch(action, req) if { +action_match(action, req) if { # Check for match based on ietf http - checkIetfMatch(action.actionUri, req) + check_http_match(action.actionUri, req) } -actionMatch(action, req) if { +action_match(action, req) if { action.actionUri # check for an action count(req.actionUris) > 0 # Check for a match based on req.ActionUris and actionUri - checkUrnMatch(action.actionUri, req.actionUris) + check_urn_match(action.actionUri, req.actionUris) } -checkUrnMatch(policyUri, actionUris) if { +check_urn_match(policyUri, actionUris) if { some action in actionUris lower(policyUri) == lower(action) } -checkIetfMatch(actionUri, req) if { +check_http_match(actionUri, req) if { # first match the rule against literals - components := split(lower(actionUri), ":") - count(components) > 2 - components[0] == "ietf" - startswith(components[1], "http") + comps := split(lower(actionUri), ":") + count(comps) > 1 + + startswith(lower(comps[0]), "http") + startswith(lower(req.protocol), "http") - startswith(lower(input.req.protocol), "http") - checkHttpMethod(components[2], req.method) + check_http_method(comps[1], req.method) - checkPath(components[3], req) + pathcomps := array.slice(comps, 2, count(comps)) + path := concat(":", pathcomps) + check_path(path, req) } -objectMatch(object, req) if { - not object +object_match(object, _) if { not object.resource_id } -objectMatch(object, req) if { +object_match(object, req) if { object.resource_id - some reqUri in req.resourceIds - lower(object.resource_id) == lower(reqUri) + some request_uri in req.resourceIds + lower(object.resource_id) == lower(request_uri) } -checkHttpMethod(allowMask, _) if { +check_http_method(allowMask, _) if { contains(allowMask, "*") } -checkHttpMethod(allowMask, reqMethod) if { +check_http_method(allowMask, reqMethod) if { startswith(allowMask, "!") not contains(allowMask, lower(reqMethod)) } -checkHttpMethod(allowMask, reqMethod) if { +check_http_method(allowMask, reqMethod) if { not startswith(allowMask, "!") contains(allowMask, lower(reqMethod)) } -checkPath(path, req) if { +check_path(path, req) if { path # if path specified it must match glob.match(path, ["*"], req.path) } -checkPath(path, _) if { +check_path(path, _) if { not path # if path not specified, it will not be matched } -conditionMatch(policy, _) if { +condition_match(policy, _) if { not policy.condition # Most policies won't have a condition } -conditionMatch(policy, inreq) if { +condition_match(policy, inreq) if { policy.condition - not policy.condition.action # Default is to allow + not policy.condition.action # Default is to allow hexaFilter(policy.condition.rule, inreq) # HexaFilter evaluations the rule for a match against input } -conditionMatch(policy, inreq) if { +condition_match(policy, inreq) if { policy.condition - action(policy.condition.action) # if defined, action must be "allow" + action_allow(policy.condition.action) # if defined, action must be "allow" hexaFilter(policy.condition.rule, inreq) # HexaFilter evaluations the rule for a match against input } -conditionMatch(policy, inreq) if { +condition_match(policy, inreq) if { # If action is deny, then hexaFilter must be false policy.condition - not action(policy.condition.action) + not action_allow(policy.condition.action) not hexaFilter(policy.condition.rule, inreq) # HexaFilter evaluations the rule for a match against input } -action(val) if lower(val) == "allow" +# Evaluate whether the condition is set to allow +action_allow(val) if lower(val) == "allow" diff --git a/examples/policyExamples/example_idql.json b/examples/policyExamples/example_idql.json index 5159b27..a95791b 100644 --- a/examples/policyExamples/example_idql.json +++ b/examples/policyExamples/example_idql.json @@ -1,13 +1,16 @@ { "policies": [ { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/"}], - "subject": { - "members": [ - "allusers", "allauthenticated" - ] + "meta": { + "version": "0.7" }, + "actions": [ + "http:GET:/" + ], + "subjects": [ + "allusers", + "allauthenticated" + ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" @@ -17,27 +20,33 @@ } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/sales"}, {"actionUri": "http:GET:/marketing"}], - "subject": { - "members": [ - "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + "http:GET:/sales", + "http:GET:/marketing" + ], + "subjects": [ + "allauthenticated", + "sales@hexaindustries.io", + "marketing@hexaindustries.io" + ], "object": { "resource_id": "aResourceId2" } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/accounting"}, {"actionUri": "http:POST:/accounting"}], - "subject": { - "members": [ - "accounting@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + "http:GET:/accounting", + "http:POST:/accounting" + ], + "subjects": [ + "accounting@hexaindustries.io" + ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" @@ -47,13 +56,15 @@ } }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/humanresources"}], - "subject": { - "members": [ - "humanresources@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + "http:GET:/humanresources" + ], + "subjects": [ + "humanresources@hexaindustries.io" + ], "object": { "resource_id": "aResourceId1" } diff --git a/examples/policyExamples/idqlAlice.json b/examples/policyExamples/idqlAlice.json index 53c99c9..760b32b 100644 --- a/examples/policyExamples/idqlAlice.json +++ b/examples/policyExamples/idqlAlice.json @@ -5,15 +5,11 @@ "Version": "0.6" }, "Actions": [ - { - "ActionUri": "cedar:Action::view" - } + "cedar:Action::view" + ], + "Subjects": [ + "User:\"alice\"" ], - "Subject": { - "Members": [ - "User:\"alice\"" - ] - }, "Object": { "resource_id": "cedar:Photo::\"VacationPhoto94.jpg\"" } @@ -23,15 +19,11 @@ "Version": "0.6" }, "Actions": [ - { - "ActionUri": "cedar:Action::\"view\"" - } + "cedar:Action::\"view\"" + ], + "Subjects": [ + "User:\"stacey\"" ], - "Subject": { - "Members": [ - "User:\"stacey\"" - ] - }, "Object": { "resource_id": "" }, diff --git a/models/formats/awsCedar/amazon_cedar.go b/models/formats/awsCedar/amazon_cedar.go index 61b548a..b5a4611 100644 --- a/models/formats/awsCedar/amazon_cedar.go +++ b/models/formats/awsCedar/amazon_cedar.go @@ -122,7 +122,7 @@ func (c *CedarPolicyMapper) mapActions(actions []hexapolicy.ActionInfo) *ActionE case 0: return nil case 1: - action := mapActionUri(actions[0].ActionUri) + action := mapActionUri(string(actions[0])) return &ActionExpression{ Operator: EQUALS, @@ -132,7 +132,7 @@ func (c *CedarPolicyMapper) mapActions(actions []hexapolicy.ActionInfo) *ActionE default: cActions := make([]ActionItem, len(actions)) for k, v := range actions { - actionIden := mapActionUri(v.ActionUri) + actionIden := mapActionUri(string(v)) cActions[k].Item = actionIden } return &ActionExpression{ @@ -343,10 +343,10 @@ func (c *CedarPolicyMapper) MapCedarPolicyToIdql(policy *CedarPolicy) (*hexapoli actions := make([]hexapolicy.ActionInfo, 0) if policy.Head.Actions != nil { if policy.Head.Actions.Action != "" { - actions = append(actions, hexapolicy.ActionInfo{ActionUri: mapActionItemToUri(policy.Head.Actions.Action)}) + actions = append(actions, hexapolicy.ActionInfo(mapActionItemToUri(policy.Head.Actions.Action))) } else { for _, v := range policy.Head.Actions.Actions { - actions = append(actions, hexapolicy.ActionInfo{ActionUri: mapActionItemToUri(v.Item)}) + actions = append(actions, hexapolicy.ActionInfo(mapActionItemToUri(v.Item))) } } } diff --git a/models/formats/awsCedar/test/data.json b/models/formats/awsCedar/test/data.json index 17e1038..1f813d5 100644 --- a/models/formats/awsCedar/test/data.json +++ b/models/formats/awsCedar/test/data.json @@ -5,9 +5,7 @@ "Version": "0.7" }, "Actions": [ - { - "ActionUri": "cedar:Action::view" - } + "cedar:Action::view" ], "Subjects": [ "User:stacey" @@ -25,12 +23,8 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "cedar:Action::ReadFile" - }, - { - "actionuri": "cedar:Action::ListFiles" - } + "cedar:Action::ReadFile", + "cedar:Action::ListFiles" ], "subjects": [ "any" @@ -48,9 +42,7 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "cedar:Action::writeFile" - } + "cedar:Action::writeFile" ], "subjects": [ "anyAuthenticated", @@ -66,12 +58,8 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "cedar:Action::ReadFile" - }, - { - "actionUri": "cedar:Action::ListFiles" - } + "cedar:Action::ReadFile", + "cedar:Action::ListFiles" ], "subjects": [ "User:accounting@hexaindustries.io" @@ -88,10 +76,8 @@ "Meta": { "Version": "0.7" }, - "Actions": [ - { - "ActionUri": "cedar:Action::view" - } + "actions": [ + "cedar:Action::view" ], "subjects": [ "User:alice" diff --git a/models/formats/cedar/cedar_test.go b/models/formats/cedar/cedar_test.go index 4ca9814..25113d8 100644 --- a/models/formats/cedar/cedar_test.go +++ b/models/formats/cedar/cedar_test.go @@ -97,11 +97,7 @@ permit ( "subjects": [ "any" ], - "actions": [ - { - "actionUri": "action" - } - ], + "actions": [ "action" ], "object": { "resource_id": "" } @@ -117,11 +113,7 @@ permit ( "subjects": [ "User:alice" ], - "actions": [ - { - "actionUri": "viewPhoto" - } - ], + "actions": [ "viewPhoto" ], "object": { "resource_id": "Photo::\"VacationPhoto.jpg\"" } @@ -136,15 +128,9 @@ permit ( "Group:\"AVTeam\".(User)" ], "actions": [ - { - "actionUri": "PhotoOp::\"view\"" - }, - { - "actionUri": "PhotoOp::\"edit\"" - }, - { - "actionUri": "PhotoOp::\"delete\"" - } + "PhotoOp::\"view\"", + "PhotoOp::\"edit\"", + "PhotoOp::\"delete\"" ], "object": { "resource_id": "Photo::\"VacationPhoto.jpg\"" @@ -162,11 +148,7 @@ unless { principal has parents };`, "subjects": [ "Group:UserGroup::\"AVTeam\"" ], - "actions": [ - { - "actionUri": "viewPhoto" - } - ], + "actions": [ "viewPhoto" ], "object": { "resource_id": "Type:Photo" }, @@ -185,11 +167,7 @@ when { resource in PhotoShop::"Photo" };`, `{ "subjects": [ "Type:User" ], - "actions": [ - { - "actionUri": "viewPhoto" - } - ], + "actions": [ "viewPhoto" ], "object": { "resource_id": "" }, diff --git a/models/formats/cedar/parse.go b/models/formats/cedar/parse.go index b6e0f5a..ae5a32a 100644 --- a/models/formats/cedar/parse.go +++ b/models/formats/cedar/parse.go @@ -260,16 +260,16 @@ func (pp *PolicyPair) mapCedarAction() { var aInfo []hexapolicy.ActionInfo switch action.Type { case cedarParser.MatchEquals: - aInfo = append(aInfo, hexapolicy.ActionInfo{ActionUri: mapCedarEntityName(action.Entities[0])}) + aInfo = append(aInfo, hexapolicy.ActionInfo(mapCedarEntityName(action.Entities[0]))) case cedarParser.MatchIn: - aInfo = append(aInfo, hexapolicy.ActionInfo{ActionUri: "Role:" + mapCedarEntityName(action.Entities[0])}) + aInfo = append(aInfo, hexapolicy.ActionInfo("Role:"+mapCedarEntityName(action.Entities[0]))) case cedarParser.MatchInList: for _, entity := range action.Entities { - aInfo = append(aInfo, hexapolicy.ActionInfo{ActionUri: mapCedarEntityName(entity)}) + 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{ActionUri: action.String()}) + aInfo = append(aInfo, hexapolicy.ActionInfo(action.String())) } pp.HexaPolicy.Actions = aInfo @@ -282,7 +282,7 @@ func (pp *PolicyPair) mapHexaAction() string { } if len(actions) == 1 { // if action has a prefix of "Role" then the value is action in xxx - value := actions[0].ActionUri + value := string(actions[0]) if strings.HasPrefix(strings.ToLower(value), "role:") { return fmt.Sprintf("action in %s,", value[5:]) } @@ -294,7 +294,7 @@ func (pp *PolicyPair) mapHexaAction() string { if i > 0 { sb.WriteString(",") } - sb.WriteString(e.ActionUri) + sb.WriteString(string(e)) } sb.WriteString("],") return sb.String() diff --git a/models/formats/gcpBind/google_bind_policy.go b/models/formats/gcpBind/google_bind_policy.go index 404aa44..7ed0122 100644 --- a/models/formats/gcpBind/google_bind_policy.go +++ b/models/formats/gcpBind/google_bind_policy.go @@ -137,7 +137,7 @@ func (m *GooglePolicyMapper) MapPoliciesToBindings(policies []hexapolicy.PolicyI func convertActionToRole(policy hexapolicy.PolicyInfo) string { for _, v := range policy.Actions { - action := v.ActionUri + action := string(v) if strings.HasPrefix(action, "gcp:") { return action[4:] } @@ -150,7 +150,8 @@ func convertRoleToAction(role string) []hexapolicy.ActionInfo { if role == "" { return nil } - return []hexapolicy.ActionInfo{{"gcp:" + role}} + var ret []hexapolicy.ActionInfo + return append(ret, hexapolicy.ActionInfo("gcp:"+role)) } func (m *GooglePolicyMapper) convertCelToCondition(expr *iam.Expr) (conditions.ConditionInfo, error) { diff --git a/models/formats/gcpBind/test/data.json b/models/formats/gcpBind/test/data.json index fc419b0..91a418e 100644 --- a/models/formats/gcpBind/test/data.json +++ b/models/formats/gcpBind/test/data.json @@ -5,9 +5,7 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/" - } + "http:GET:/" ], "subjects": [ "allusers", @@ -26,12 +24,8 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/sales" - }, - { - "actionUri": "http:GET:/marketing" - } + "http:GET:/sales", + "http:GET:/marketing" ], "subjects": [ "allauthenticated", @@ -47,12 +41,8 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/accounting" - }, - { - "actionUri": "http:POST:/accounting" - } + "http:GET:/accounting", + "http:POST:/accounting" ], "subjects": [ "accounting@hexaindustries.io" @@ -70,9 +60,7 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/humanresources" - } + "http:GET:/humanresources" ], "subjects": [ "humanresources@hexaindustries.io" @@ -89,9 +77,7 @@ "user:gerry@strata.io" ], "actions": [ - { - "actionUri": "gcp:roles/iap.httpsResourceAccessor" - } + "gcp:roles/iap.httpsResourceAccessor" ], "object": { "resource_id": "754290878449499554" diff --git a/models/rar/policy_transformer.go b/models/rar/policy_transformer.go index 69b36fc..ae278a3 100644 --- a/models/rar/policy_transformer.go +++ b/models/rar/policy_transformer.go @@ -21,7 +21,7 @@ func BuildPolicies(resourceActionRolesList []ResourceActionRoles) []hexapolicy.P slices.Sort(roles) policies = append(policies, hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion, ProviderType: "RARmodel"}, - Actions: []hexapolicy.ActionInfo{{ActionUriPrefix + httpMethod}}, + Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(ActionUriPrefix + httpMethod)}, Subjects: roles, Object: hexapolicy.ObjectInfo{ResourceID: one.Resource}, }) @@ -41,11 +41,11 @@ func FlattenPolicy(origPolicies []hexapolicy.PolicyInfo) []hexapolicy.PolicyInfo continue } for _, act := range pol.Actions { - if strings.TrimSpace(act.ActionUri) == "" { + if strings.TrimSpace(string(act)) == "" { log.Warn("FlattenPolicy Skipping policy without actionUri", "resource", resource) continue } - lookupKey := act.ActionUri + resource + lookupKey := string(act) + resource matchingPolicy, found := resActionPolicyMap[lookupKey] var existingMembers []string if found { @@ -54,7 +54,7 @@ func FlattenPolicy(origPolicies []hexapolicy.PolicyInfo) []hexapolicy.PolicyInfo newMembers := CompactMembers(existingMembers, pol.Subjects) newPol := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, - Actions: []hexapolicy.ActionInfo{{ActionUri: act.ActionUri}}, + Actions: []hexapolicy.ActionInfo{act}, Subjects: newMembers, Object: hexapolicy.ObjectInfo{ResourceID: resource}, } @@ -75,18 +75,16 @@ func FlattenPolicy(origPolicies []hexapolicy.PolicyInfo) []hexapolicy.PolicyInfo func CompactActions(existing, new []hexapolicy.ActionInfo) []hexapolicy.ActionInfo { actionUris := make([]string, 0) for _, act := range existing { - actionUris = append(actionUris, act.ActionUri) + actionUris = append(actionUris, string(act)) } for _, act := range new { - actionUris = append(actionUris, act.ActionUri) + actionUris = append(actionUris, string(act)) } actionUris = functionalsupport.SortCompact(actionUris) actionInfos := make([]hexapolicy.ActionInfo, 0) for _, uri := range actionUris { - actionInfos = append(actionInfos, hexapolicy.ActionInfo{ - ActionUri: uri, - }) + actionInfos = append(actionInfos, hexapolicy.ActionInfo(uri)) } return actionInfos } @@ -101,7 +99,7 @@ func CompactMembers(existing, new []string) []string { func sortPolicies(policies []hexapolicy.PolicyInfo) { sort.SliceStable(policies, func(i, j int) bool { resComp := strings.Compare(policies[i].Object.ResourceID, policies[j].Object.ResourceID) - actComp := strings.Compare(policies[i].Actions[0].ActionUri, policies[j].Actions[0].ActionUri) + actComp := strings.Compare(string(policies[i].Actions[0]), string(policies[j].Actions[0])) switch resComp { case 0: return actComp <= 0 diff --git a/models/rar/policy_transformer_test.go b/models/rar/policy_transformer_test.go index 158af65..ca1074a 100644 --- a/models/rar/policy_transformer_test.go +++ b/models/rar/policy_transformer_test.go @@ -1,7 +1,7 @@ package rar_test import ( - rar "github.com/hexa-org/policy-mapper/models/rar" + "github.com/hexa-org/policy-mapper/models/rar" "github.com/hexa-org/policy-mapper/models/rar/testsupport/policytestsupport" "github.com/hexa-org/policy-mapper/pkg/hexapolicy" @@ -32,14 +32,14 @@ func TestBuildPolicies(t *testing.T) { actPol := policies[0] assert.Equal(t, policytestsupport.ResourceHrUs, actPol.Object.ResourceID) - assert.Equal(t, "http:GET", actPol.Actions[0].ActionUri) + assert.Equal(t, "http:GET", actPol.Actions[0].String()) var res []string res = actPol.Subjects assert.Equal(t, []string{"1-some-hr-role", "2-some-hr-role"}, res) actPol = policies[1] assert.Equal(t, policytestsupport.ResourceProfile, actPol.Object.ResourceID) - assert.Equal(t, "http:GET", actPol.Actions[0].ActionUri) + assert.Equal(t, "http:GET", actPol.Actions[0].String()) res = actPol.Subjects assert.Equal(t, []string{"1-some-profile-role", "2-some-profile-role"}, res) } @@ -67,7 +67,7 @@ func TestCompactActions_NilEmpty(t *testing.T) { func TestCompactActions_AllWhitespace(t *testing.T) { arr1 := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: " "}, {ActionUri: " "}, + hexapolicy.ActionInfo(""), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo(" "), } compacted := rar.CompactActions(arr1, arr1) assert.NotNil(t, compacted) @@ -76,49 +76,49 @@ func TestCompactActions_AllWhitespace(t *testing.T) { func TestCompactActions_DuplicatesAndWhitespace(t *testing.T) { arr1 := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, + hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("1one"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("2two"), hexapolicy.ActionInfo("3three"), } arr2 := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, + hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("1one"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("2two"), hexapolicy.ActionInfo("3three"), } compacted := rar.CompactActions(arr1, arr2) assert.NotNil(t, compacted) assert.Equal(t, []hexapolicy.ActionInfo{ - {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, + "1one", "2two", "3three", }, compacted) } func TestCompactActions_UniqueAndWhitespace(t *testing.T) { arr1 := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, + hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("1one"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("2two"), hexapolicy.ActionInfo("3three"), } arr2 := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "4four"}, {ActionUri: " "}, {ActionUri: "5five"}, + hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("4four"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("5five"), } compacted := rar.CompactActions(arr1, arr2) assert.NotNil(t, compacted) assert.Equal(t, []hexapolicy.ActionInfo{ - {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, {ActionUri: "4four"}, {ActionUri: "5five"}, + "1one", "2two", "3three", "4four", "5five", }, compacted) } func TestCompactActions_OneEmptyNil(t *testing.T) { arr := []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "1one"}, {ActionUri: " "}, {ActionUri: "2two"}, {ActionUri: "3three"}, + hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("1one"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("2two"), hexapolicy.ActionInfo("3three"), } compacted := rar.CompactActions(arr, nil) assert.NotNil(t, compacted) assert.Equal(t, []hexapolicy.ActionInfo{ - {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, + "1one", "2two", "3three", }, compacted) compacted = rar.CompactActions(nil, arr) assert.NotNil(t, compacted) assert.Equal(t, []hexapolicy.ActionInfo{ - {ActionUri: "1one"}, {ActionUri: "2two"}, {ActionUri: "3three"}, + "1one", "2two", "3three", }, compacted) } @@ -185,7 +185,7 @@ func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { pol1 := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "1act"}, {ActionUri: " "}, {ActionUri: "2act"}}, + hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("2act")}, Subjects: []string{"1mem", "", "2mem"}, Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, } @@ -193,7 +193,7 @@ func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { pol2 := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{ - {ActionUri: ""}, {ActionUri: "3act"}, {ActionUri: " "}, {ActionUri: "4act"}}, + hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("3act"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("4act")}, Subjects: []string{"1mem", "", "2mem"}, Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, } @@ -209,7 +209,7 @@ func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { for i, actPol := range actPolicies { assert.Equal(t, expResource, actPol.Object.ResourceID) - assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) + assert.Equal(t, expActions[i], actPol.Actions[0].String()) assert.Equal(t, expMembers, actPol.Subjects.String()) } } @@ -217,12 +217,12 @@ func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { func TestFlattenPolicy_NoResource(t *testing.T) { pol1 := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{ActionUri: "1act"}, {ActionUri: "2act"}}, + Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, Subjects: []string{"1mem", "", "2mem"}, } pol2 := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{ActionUri: "1act"}}, + Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo("1act")}, Subjects: []string{"1mem", "2mem"}, Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, } @@ -269,7 +269,7 @@ func TestFlattenPolicy_NoActions(t *testing.T) { func TestFlattenPolicy_NoMembers(t *testing.T) { pol1 := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{ActionUri: "1act"}, {ActionUri: "2act"}}, + Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, } orig := []hexapolicy.PolicyInfo{pol1} @@ -280,7 +280,7 @@ func TestFlattenPolicy_NoMembers(t *testing.T) { expActions := []string{"1act", "2act"} for i, actPol := range actPolicies { assert.Equal(t, "resource1", actPol.Object.ResourceID) - assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) + assert.Equal(t, expActions[i], actPol.Actions[0].String()) assert.NotNil(t, actPol.Subjects) assert.Equal(t, []string{}, actPol.Subjects.String()) } @@ -290,7 +290,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { pol1a := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{ - {ActionUri: "1act"}, {ActionUri: "2act"}}, + hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, Subjects: []string{"1mem", "2mem"}, Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, } @@ -298,7 +298,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { pol1b := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{ - {ActionUri: "1act"}, {ActionUri: "2act"}}, + hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, Subjects: []string{"3mem", "4mem"}, Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, } @@ -306,7 +306,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { pol2 := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{ - {ActionUri: "3act"}, {ActionUri: "4act"}}, + hexapolicy.ActionInfo("3act"), hexapolicy.ActionInfo("4act")}, Subjects: []string{"1mem", "2mem"}, Object: hexapolicy.ObjectInfo{ResourceID: "resource2"}, } @@ -324,7 +324,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { actPol := actPolicies[i] assert.NotNil(t, actPol) assert.Equal(t, expResource, actPol.Object.ResourceID) - assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) + assert.Equal(t, expActions[i], actPol.Actions[0].String()) assert.Equal(t, expMembers, actPol.Subjects.String()) } @@ -335,7 +335,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { actPol := actPolicies[i+2] assert.NotNil(t, actPol) assert.Equal(t, expResource, actPol.Object.ResourceID) - assert.Equal(t, expActions[i], actPol.Actions[0].ActionUri) + assert.Equal(t, expActions[i], actPol.Actions[0].String()) assert.Equal(t, expMembers, actPol.Subjects.String()) } } diff --git a/models/rar/resource_action_role_policy_support.go b/models/rar/resource_action_role_policy_support.go index 47c747f..1d66e0f 100644 --- a/models/rar/resource_action_role_policy_support.go +++ b/models/rar/resource_action_role_policy_support.go @@ -24,7 +24,7 @@ func CalcResourceActionRolesForUpdate(existing []ResourceActionRoles, policyInfo for _, pol := range newPolicies { polResource := pol.Object.ResourceID - polAction := pol.Actions[0].ActionUri + polAction := string(pol.Actions[0]) polRoles := pol.Subjects newRarKey := MakeRarKeyForPolicy(polAction, polResource) diff --git a/models/rar/testsupport/policytestsupport/policy_checker_support.go b/models/rar/testsupport/policytestsupport/policy_checker_support.go index 86fa634..e8640fc 100644 --- a/models/rar/testsupport/policytestsupport/policy_checker_support.go +++ b/models/rar/testsupport/policytestsupport/policy_checker_support.go @@ -56,7 +56,7 @@ func MakePolicies(actionMembers map[string][]string, resourceId string) []hexapo pol := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{action}}, + Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(action)}, Subjects: members, Object: hexapolicy.ObjectInfo{ ResourceID: resourceId, @@ -73,7 +73,7 @@ func sortAction(orig []hexapolicy.ActionInfo) []hexapolicy.ActionInfo { sorted := make([]hexapolicy.ActionInfo, 0) sorted = append(sorted, orig...) sort.Slice(sorted, func(i, j int) bool { - return sorted[i].ActionUri <= sorted[j].ActionUri + return sorted[i] <= sorted[j] }) return sorted } diff --git a/models/rar/testsupport/policytestsupport/policy_data_support.go b/models/rar/testsupport/policytestsupport/policy_data_support.go index 5a42b58..4e75f56 100644 --- a/models/rar/testsupport/policytestsupport/policy_data_support.go +++ b/models/rar/testsupport/policytestsupport/policy_data_support.go @@ -82,7 +82,7 @@ func MakeRoleSubjectTestPolicies(actionMembers map[string][]string) []hexapolicy func MakeRoleSubjectTestPolicy(resourceId string, action string, roles []string) hexapolicy.PolicyInfo { return hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{action}}, + Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(action)}, Subjects: roles, Object: hexapolicy.ObjectInfo{ ResourceID: resourceId, @@ -93,7 +93,7 @@ func MakeRoleSubjectTestPolicy(resourceId string, action string, roles []string) func MakeTestPolicy(resourceId string, action string, actionMembers ActionMembers) hexapolicy.PolicyInfo { return hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, - Actions: []hexapolicy.ActionInfo{{action}}, + Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(action)}, Subjects: MakePolicyTestUsers(actionMembers), Object: hexapolicy.ObjectInfo{ ResourceID: resourceId, diff --git a/pkg/hexapolicy/hexa_policy.go b/pkg/hexapolicy/hexa_policy.go index afb1c4c..0418b75 100644 --- a/pkg/hexapolicy/hexa_policy.go +++ b/pkg/hexapolicy/hexa_policy.go @@ -183,7 +183,7 @@ func (p *PolicyInfo) actionEquals(actions []ActionInfo) bool { for _, action := range p.Actions { isMatch := false for _, caction := range actions { - if strings.EqualFold(action.ActionUri, caction.ActionUri) { + if action.Equals(caction) { isMatch = true break } @@ -207,8 +207,14 @@ type MetaInfo struct { ProviderType string `json:"providerType,omitempty"` // ProviderType is the SDK provider type indicating the source of the policy } -type ActionInfo struct { - ActionUri string `json:"actionUri" validate:"required"` +type ActionInfo string + +func (a ActionInfo) String() string { + return string(a) +} + +func (a ActionInfo) Equals(action ActionInfo) bool { + return strings.EqualFold(string(a), string(action)) } type SubjectInfo []string diff --git a/pkg/hexapolicy/hexa_policy_test.go b/pkg/hexapolicy/hexa_policy_test.go index bee9404..1925a91 100644 --- a/pkg/hexapolicy/hexa_policy_test.go +++ b/pkg/hexapolicy/hexa_policy_test.go @@ -12,19 +12,15 @@ import ( var testPolicy1 = ` { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/accounting" - }, - { - "actionUri": "http:POST:/accounting" - } + "http:GET:/accounting", + "http:POST:/accounting" ], "subjects": [ - "accounting@hexaindustries.io" - ], + "user:accounting@hexaindustries.io" + ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" @@ -41,15 +37,13 @@ var testPolicy1 = ` var testPolicy2 = ` { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/humanresources" - } + "http:GET:/humanresources" ], "subjects": [ - "humanresources@hexaindustries.io" + "user:humanresources@hexaindustries.io" ], "object": { "resource_id": "aResourceId" @@ -95,7 +89,7 @@ func TestSubjectInfo_equals(t *testing.T) { assert.False(t, p1.Subjects.equals(p2.Subjects)) p3 := p1 // check case sensitivity - p3.Subjects = []string{"Accounting@Hexaindustries.io"} + p3.Subjects = []string{"user:Accounting@Hexaindustries.io"} assert.True(t, p1.Subjects.equals(p3.Subjects)) } @@ -114,11 +108,11 @@ func TestPolicyInfo_actionEquals(t *testing.T) { assert.True(t, p1.actionEquals(p3.Actions)) // Check that equivalence works out of order - p3.Actions = []ActionInfo{{ActionUri: "http:POST:/accounting"}, {ActionUri: "http:GET:/accounting"}} + p3.Actions = []ActionInfo{"http:POST:/accounting", "http:GET:/accounting"} assert.True(t, p1.actionEquals(p3.Actions)) - p3.Actions = []ActionInfo{{ActionUri: "http:POST:/accounting"}} + p3.Actions = []ActionInfo{"http:POST:/accounting"} assert.False(t, p1.actionEquals(p3.Actions)) } @@ -236,7 +230,7 @@ func TestPolicyInfo_Equals(t *testing.T) { p3 := policies.Policies[0] // This will be used to make sure subject is case insensitive - p3.Subjects = []string{"Accounting@Hexaindustries.io"} + p3.Subjects = []string{"User:Accounting@Hexaindustries.io"} type fields struct { testPolicy PolicyInfo @@ -282,7 +276,7 @@ func TestPolicyInfo_Compare(t *testing.T) { p3 := policies.Policies[0] // This will be used to make sure subject is case insensitive - p3.Subjects = []string{"Accounting@Hexaindustries.io"} + p3.Subjects = []string{"User:Accounting@Hexaindustries.io"} type fields struct { hexaPolicy PolicyInfo @@ -349,7 +343,7 @@ func TestPolicyDif_Report(t *testing.T) { testPolicy := PolicyInfo{ Meta: MetaInfo{PolicyId: &pid}, Subjects: []string{"user1"}, - Actions: []ActionInfo{{ActionUri: "actionUri"}}, + Actions: []ActionInfo{"actionUri"}, Object: ObjectInfo{ResourceID: "aresource"}, Condition: nil, } @@ -462,16 +456,14 @@ func TestPolicyInfo_String(t *testing.T) { pid := "abc" policyString := `{ "meta": { - "etag": "20-03ce8ba19fe5a7a2ea9daf6e4c9645716d1dff39", + "etag": "20-e3f1663e6b8664347598121df047911cbacd961b", "policyId": "abc" }, "subjects": [ "user1" ], "actions": [ - { - "actionUri": "actionUri" - } + "actionUri" ], "object": { "resource_id": "aresource" @@ -480,7 +472,7 @@ func TestPolicyInfo_String(t *testing.T) { testPolicy := PolicyInfo{ Meta: MetaInfo{PolicyId: &pid}, Subjects: []string{"user1"}, - Actions: []ActionInfo{{ActionUri: "actionUri"}}, + Actions: []ActionInfo{"actionUri"}, Object: ObjectInfo{ResourceID: "aresource"}, Condition: nil, } @@ -538,7 +530,7 @@ func TestReconcilePolicies(t *testing.T) { PolicyId: &npid, }, Subjects: []string{"phil.hunt@independentid.com"}, - Actions: []ActionInfo{{ActionUri: "http:GET:/admin"}, {ActionUri: "http:POST:/admin"}}, + Actions: []ActionInfo{"http:GET:/admin", "http:POST:/admin"}, Object: ObjectInfo{ResourceID: "hexaindustries"}, } newPolicy.CalculateEtag() diff --git a/pkg/hexapolicysupport/test/data.json b/pkg/hexapolicysupport/test/data.json index 60e1e6e..95276b6 100644 --- a/pkg/hexapolicysupport/test/data.json +++ b/pkg/hexapolicysupport/test/data.json @@ -2,19 +2,14 @@ "policies": [ { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/" - } + "http:GET:/" + ], + "subjects": [ + "any" ], - "subject": { - "members": [ - "allusers", - "allauthenticated" - ] - }, "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" @@ -25,44 +20,32 @@ }, { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/sales" - }, - { - "actionUri": "http:GET:/marketing" - } + "http:GET:/sales", + "http:GET:/marketing" + ], + "subjects": [ + "anyauthenticated", + "user:sales@hexaindustries.io", + "user:marketing@hexaindustries.io" ], - "subject": { - "members": [ - "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" - ] - }, "object": { "resource_id": "aResourceId" } }, { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/accounting" - }, - { - "actionUri": "http:POST:/accounting" - } + "http:GET:/accounting", + "http:POST:/accounting" + ], + "subjects": [ + "user:accounting@hexaindustries.io" ], - "subject": { - "members": [ - "accounting@hexaindustries.io" - ] - }, "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" @@ -73,18 +56,14 @@ }, { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/humanresources" - } + "http:GET:/humanresources" + ], + "subjects": [ + "user:humanresources@hexaindustries.io" ], - "subject": { - "members": [ - "humanresources@hexaindustries.io" - ] - }, "object": { "resource_id": "aResourceId" } diff --git a/providers/aws/avpProvider/avp_provider_test.go b/providers/aws/avpProvider/avp_provider_test.go index eff4281..8dd4d63 100644 --- a/providers/aws/avpProvider/avp_provider_test.go +++ b/providers/aws/avpProvider/avp_provider_test.go @@ -110,7 +110,7 @@ func Test_SetPolicy_live(t *testing.T) { actions := policy.Actions present := false for i, action := range actions { - if action.ActionUri == "cedar:hexa_avp::Action::\"UpdateAccount\"" { + if action == "cedar:hexa_avp::Action::\"UpdateAccount\"" { actions = append(actions[:i], actions[i+1:]...) present = true fmt.Println("This test run will remove UpdateAccount action") @@ -118,7 +118,7 @@ func Test_SetPolicy_live(t *testing.T) { } if !present { fmt.Println("This run will add UpdateAccount action") - actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::\"UpdateAccount\""}) + actions = append(actions, "cedar:hexa_avp::Action::\"UpdateAccount\"") } policies[0].Actions = actions @@ -240,7 +240,7 @@ func TestAvp_3_Reconcile(t *testing.T) { policy := policies[0] actions := policy.Actions - actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::\"UpdateAccount\""}) + actions = append(actions, "cedar:hexa_avp::Action::\"UpdateAccount\"") policies[0].Actions = actions @@ -325,7 +325,7 @@ func TestAvp_4_SetPolicies(t *testing.T) { actions := policy.Actions present := false for i, action := range actions { - if action.ActionUri == "cedar:hexa_avp::Action::\"UpdateAccount\"" { + if action == "cedar:hexa_avp::Action::\"UpdateAccount\"" { actions = append(actions[:i], actions[i+1:]...) present = true fmt.Println("This test run will remove UpdateAccount action") @@ -333,7 +333,7 @@ func TestAvp_4_SetPolicies(t *testing.T) { } if !present { fmt.Println("This run will add UpdateAccount action") - actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::\"UpdateAccount\""}) + actions = append(actions, "cedar:hexa_avp::Action::\"UpdateAccount\"") } policies[0].Actions = actions diff --git a/providers/aws/awsapigwProvider/aws_apigw_provider_service.go b/providers/aws/awsapigwProvider/aws_apigw_provider_service.go index 43a1dcc..91a643d 100644 --- a/providers/aws/awsapigwProvider/aws_apigw_provider_service.go +++ b/providers/aws/awsapigwProvider/aws_apigw_provider_service.go @@ -57,7 +57,7 @@ func (s *AwsApiGatewayProviderService) setPolicyInfoOld(appInfo policyprovider.A return http.StatusInternalServerError, err } for _, pol := range policyInfos { - groupName := pol.Actions[0].ActionUri + groupName := string(pol.Actions[0]) _, exists := allGroups[groupName] if !exists { continue diff --git a/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go b/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go index 299583a..86f84ee 100644 --- a/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go +++ b/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go @@ -105,7 +105,7 @@ func TestGetPolicyInfo(t *testing.T) { for _, actPol := range actPolicies { actMembers := actPol.Subjects.String() - actPolRar := rar.NewResourceActionUriRoles(actPol.Object.ResourceID, actPol.Actions[0].ActionUri, actMembers) + actPolRar := rar.NewResourceActionUriRoles(actPol.Object.ResourceID, actPol.Actions[0].String(), actMembers) actLookupKey := actPolRar.Action + actPolRar.Resource expMembers, found := existingActionRoles[actLookupKey] assert.True(t, found) diff --git a/providers/aws/cognitoProvider/cognito_provider.go b/providers/aws/cognitoProvider/cognito_provider.go index 9699951..6dbf71a 100644 --- a/providers/aws/cognitoProvider/cognito_provider.go +++ b/providers/aws/cognitoProvider/cognito_provider.go @@ -56,7 +56,7 @@ func (a *CognitoProvider) GetPolicyInfo(info policyprovider.IntegrationInfo, app Version: hexapolicy.IdqlVersion, ProviderType: ProviderTypeAwsCognito, }, - Actions: []hexapolicy.ActionInfo{{groupName}}, + Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(groupName)}, Subjects: members, Object: hexapolicy.ObjectInfo{ ResourceID: applicationInfo.Name, @@ -88,7 +88,7 @@ func (a *CognitoProvider) SetPolicyInfo(info policyprovider.IntegrationInfo, app return http.StatusInternalServerError, err } for _, pol := range policyInfos { - groupName := pol.Actions[0].ActionUri + groupName := string(pol.Actions[0]) _, exists := allGroups[groupName] if !exists { continue diff --git a/providers/aws/cognitoProvider/cognito_provider_test.go b/providers/aws/cognitoProvider/cognito_provider_test.go index 835fac8..69793dc 100644 --- a/providers/aws/cognitoProvider/cognito_provider_test.go +++ b/providers/aws/cognitoProvider/cognito_provider_test.go @@ -154,7 +154,7 @@ func TestSetPolicy_withInvalidArguments(t *testing.T) { policyprovider.ApplicationInfo{Name: "anAppName", Description: "anAppId"}, []hexapolicy.PolicyInfo{{ Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, + Actions: []hexapolicy.ActionInfo{"azure:anAppRoleId"}, Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, Object: hexapolicy.ObjectInfo{ ResourceID: "anObjectId", @@ -169,7 +169,7 @@ func TestSetPolicy_withInvalidArguments(t *testing.T) { policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: "aDescription"}, []hexapolicy.PolicyInfo{{ Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, + Actions: []hexapolicy.ActionInfo{"azure:anAppRoleId"}, Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, Object: hexapolicy.ObjectInfo{}, }}) diff --git a/providers/azure/azureProvider/azure_policy_mapper.go b/providers/azure/azureProvider/azure_policy_mapper.go index 7b5f620..ab173a7 100644 --- a/providers/azure/azureProvider/azure_policy_mapper.go +++ b/providers/azure/azureProvider/azure_policy_mapper.go @@ -65,7 +65,7 @@ func (azm *AzurePolicyMapper) appRoleAssignmentToIDQL(assignments []azad.AzureAp ProviderType: ProviderTypeAzure, SourceData: sourceData, }, - Actions: []hexapolicy.ActionInfo{{role.Value}}, + Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(role.Value)}, Subjects: members, Object: hexapolicy.ObjectInfo{ResourceID: azm.objectId}, } diff --git a/providers/azure/azureProvider/azure_policy_mapper_test.go b/providers/azure/azureProvider/azure_policy_mapper_test.go index 0165214..e333820 100644 --- a/providers/azure/azureProvider/azure_policy_mapper_test.go +++ b/providers/azure/azureProvider/azure_policy_mapper_test.go @@ -24,7 +24,7 @@ func TestAzurePolicyMapper_ToIDQL(t *testing.T) { for _, pol := range actPolicies { assert.Equal(t, 1, len(pol.Actions)) assert.Equal(t, sps.List[0].Name, pol.Object.ResourceID) - actActionMembersMap[pol.Actions[0].ActionUri] = pol.Subjects + actActionMembersMap[pol.Actions[0].String()] = pol.Subjects } for _, expAction := range []string{policytestsupport.ActionGetHrUs, policytestsupport.ActionGetProfile} { @@ -57,7 +57,7 @@ func TestAzurePolicyMapper_ToIDQL_NoRoleAssignments(t *testing.T) { assert.Equal(t, sps.List[0].Name, pol.Object.ResourceID) assert.NotNil(t, pol.Subjects) assert.Empty(t, pol.Subjects) - actPolicyActionMap[pol.Actions[0].ActionUri] = true + actPolicyActionMap[pol.Actions[0].String()] = true } for _, expAction := range []string{policytestsupport.ActionGetHrUs, policytestsupport.ActionGetProfile} { diff --git a/providers/azure/azureProvider/azure_provider.go b/providers/azure/azureProvider/azure_provider.go index 5320ff1..2e2a691 100644 --- a/providers/azure/azureProvider/azure_provider.go +++ b/providers/azure/azureProvider/azure_provider.go @@ -105,7 +105,7 @@ func (a *AzureProvider) SetPolicyInfo(integrationInfo policyprovider.Integration for _, policyInfo := range policyInfos { var assignments []azad.AzureAppRoleAssignment - actionUri := strings.TrimPrefix(policyInfo.Actions[0].ActionUri, "azure:") + actionUri := strings.TrimPrefix(string(policyInfo.Actions[0]), "azure:") appRoleId, found := appRoleValueToId[actionUri] if !found { log.Println("No Azure AppRoleAssignment found for policy action", actionUri) diff --git a/providers/azure/azureProvider/azure_provider_test.go b/providers/azure/azureProvider/azure_provider_test.go index c51f0f9..5094668 100644 --- a/providers/azure/azureProvider/azure_provider_test.go +++ b/providers/azure/azureProvider/azure_provider_test.go @@ -65,7 +65,7 @@ func TestGetPolicy_WithoutUserEmail(t *testing.T) { for _, pol := range actualPolicies { assert.True(t, len(pol.Actions) > 0) - assert.NotEmpty(t, pol.Actions[0].ActionUri) + assert.NotEmpty(t, pol.Actions[0].String()) assert.Equal(t, 0, len(pol.Subjects)) assert.Equal(t, policytestsupport.PolicyObjectResourceId, pol.Object.ResourceID) } @@ -151,15 +151,15 @@ func TestGetPolicy_MultipleMembersInOnePolicy(t *testing.T) { } func TestSetPolicy_withInvalidArguments(t *testing.T) { - azureProvider := azureProvider.NewAzureProvider() + provider := azureProvider.NewAzureProvider() key := []byte("key") - status, err := azureProvider.SetPolicyInfo( + status, err := provider.SetPolicyInfo( policyprovider.IntegrationInfo{Name: "azure", Key: key}, policyprovider.ApplicationInfo{Name: "anAppName", Description: "anAppId"}, []hexapolicy.PolicyInfo{{ Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, + Actions: []hexapolicy.ActionInfo{"azure:anAppRoleId"}, Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, Object: hexapolicy.ObjectInfo{ ResourceID: "anObjectId", @@ -169,12 +169,12 @@ func TestSetPolicy_withInvalidArguments(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, status) assert.EqualError(t, err, "Key: 'ApplicationInfo.ObjectID' Error:Field validation for 'ObjectID' failed on the 'required' tag") - status, err = azureProvider.SetPolicyInfo( + status, err = provider.SetPolicyInfo( policyprovider.IntegrationInfo{Name: "azure", Key: key}, policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: "aDescription"}, []hexapolicy.PolicyInfo{{ Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:anAppRoleId"}}, + Actions: []hexapolicy.ActionInfo{"azure:anAppRoleId"}, Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, Object: hexapolicy.ObjectInfo{}, }}) @@ -199,7 +199,7 @@ func TestSetPolicy_IgnoresAllPrincipalIdsNotFound(t *testing.T) { policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, []hexapolicy.PolicyInfo{{ Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs, "user:" + policytestsupport.UserEmailGetProfile}, Object: hexapolicy.ObjectInfo{ @@ -229,7 +229,7 @@ func TestSetPolicy_IgnoresAnyNotFoundPrincipalId(t *testing.T) { policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, []hexapolicy.PolicyInfo{{ Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs, "user:" + policytestsupport.UserEmailGetProfile}, Object: hexapolicy.ObjectInfo{ @@ -255,7 +255,7 @@ func TestSetPolicy_AddAssignment_IgnoresInvalidAction(t *testing.T) { policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, []hexapolicy.PolicyInfo{{ Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:GET/not_defined"}}, + Actions: []hexapolicy.ActionInfo{"azure:GET/not_defined"}, Subjects: []string{ "user:" + policytestsupport.UserEmailGetHrUs, "user:" + policytestsupport.UserEmailGetProfile}, @@ -284,7 +284,7 @@ func TestSetPolicy(t *testing.T) { policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, []hexapolicy.PolicyInfo{{ Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs}, Object: hexapolicy.ObjectInfo{ ResourceID: policytestsupport.PolicyObjectResourceId, @@ -311,7 +311,7 @@ func TestSetPolicy_RemovedAllMembers_FromOnePolicy(t *testing.T) { policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: appId}, []hexapolicy.PolicyInfo{{ Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{}, Object: hexapolicy.ObjectInfo{ ResourceID: policytestsupport.PolicyObjectResourceId, @@ -341,7 +341,7 @@ func TestSetPolicy_RemovedAllMembers_FromAllPolicies(t *testing.T) { []hexapolicy.PolicyInfo{ { Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{}, Object: hexapolicy.ObjectInfo{ ResourceID: policytestsupport.PolicyObjectResourceId, @@ -349,7 +349,7 @@ func TestSetPolicy_RemovedAllMembers_FromAllPolicies(t *testing.T) { }, { Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetProfile}}, + Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetProfile}, Subjects: []string{}, Object: hexapolicy.ObjectInfo{ ResourceID: policytestsupport.PolicyObjectResourceId, @@ -381,7 +381,7 @@ func TestSetPolicy_MultipleAppRolePolicies(t *testing.T) { []hexapolicy.PolicyInfo{ { Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetHrUs}}, + Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs}, Object: hexapolicy.ObjectInfo{ ResourceID: policytestsupport.PolicyObjectResourceId, @@ -389,7 +389,7 @@ func TestSetPolicy_MultipleAppRolePolicies(t *testing.T) { }, { Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{{"azure:" + policytestsupport.ActionGetProfile}}, + Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetProfile}, Subjects: []string{"user:" + policytestsupport.UserEmailGetProfile}, Object: hexapolicy.ObjectInfo{ ResourceID: policytestsupport.PolicyObjectResourceId, diff --git a/providers/googlecloud/iapProvider/google_client_test.go b/providers/googlecloud/iapProvider/google_client_test.go index 576862a..cae7073 100644 --- a/providers/googlecloud/iapProvider/google_client_test.go +++ b/providers/googlecloud/iapProvider/google_client_test.go @@ -148,7 +148,7 @@ func TestGoogleClient_GetBackendPolicies_withBadJson(t *testing.T) { func TestGoogleClient_SetAppEnginePolicies(t *testing.T) { policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"roles/iap.httpsResourceAccessor"}}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"roles/iap.httpsResourceAccessor"}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ ResourceID: "anObjectId", }, } @@ -179,7 +179,7 @@ func TestGoogleClient_SetAppEnginePolicies(t *testing.T) { func TestGoogleClient_SetBackendPolicies(t *testing.T) { policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"gcp:roles/iap.httpsResourceAccessor"}}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"gcp:roles/iap.httpsResourceAccessor"}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ ResourceID: "anObjectId", }, } @@ -206,7 +206,7 @@ func TestGoogleClient_SetBackendPolicies(t *testing.T) { func TestGoogleClient_SetBackendPolicies_withRequestError(t *testing.T) { policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"gcp:roles/iap.httpsResourceAccessor"}}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"gcp:roles/iap.httpsResourceAccessor"}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ ResourceID: "anObjectId", }, } diff --git a/providers/googlecloud/iapProvider/google_cloud_provider_test.go b/providers/googlecloud/iapProvider/google_cloud_provider_test.go index 4b27e54..09308b5 100644 --- a/providers/googlecloud/iapProvider/google_cloud_provider_test.go +++ b/providers/googlecloud/iapProvider/google_cloud_provider_test.go @@ -104,7 +104,7 @@ func TestGoogleProvider_GetPolicy(t *testing.T) { func TestGoogleProvider_SetPolicy(t *testing.T) { policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{{"anAction"}}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"anAction"}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ ResourceID: "anObjectId", }, } @@ -119,7 +119,7 @@ func TestGoogleProvider_SetPolicy(t *testing.T) { func TestGoogleProvider_SetPolicy_withInvalidArguments(t *testing.T) { missingMeta := hexapolicy.PolicyInfo{ - Actions: []hexapolicy.ActionInfo{{"anAction"}}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ + Actions: []hexapolicy.ActionInfo{"anAction"}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ ResourceID: "anObjectId", }, } diff --git a/providers/openpolicyagent/http_bundle_client_test.go b/providers/openpolicyagent/http_bundle_client_test.go index 01cedf4..73ba82e 100644 --- a/providers/openpolicyagent/http_bundle_client_test.go +++ b/providers/openpolicyagent/http_bundle_client_test.go @@ -26,9 +26,7 @@ const expectedBundleData = `{ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/" - } + "http:GET:/" ], "subjects:": [ "any", @@ -43,12 +41,8 @@ const expectedBundleData = `{ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/sales" - }, - { - "actionUri": "http:GET:/marketing" - } + "http:GET:/sales", + "http:GET:/marketing" ], "subjects:": [ "anyauthenticated", @@ -64,12 +58,8 @@ const expectedBundleData = `{ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/accounting" - }, - { - "actionUri": "http:POST:/accounting" - } + "http:GET:/accounting", + "http:POST:/accounting" ], "subjects:": [ "accounting@hexaindustries.io" @@ -83,9 +73,7 @@ const expectedBundleData = `{ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/humanresources" - } + "http:GET:/humanresources" ], "subjects:": [ "humanresources@hexaindustries.io" diff --git a/providers/openpolicyagent/opa_provider_test.go b/providers/openpolicyagent/opa_provider_test.go index d41a80d..2466373 100644 --- a/providers/openpolicyagent/opa_provider_test.go +++ b/providers/openpolicyagent/opa_provider_test.go @@ -305,7 +305,7 @@ func TestSetPolicyInfo(t *testing.T) { policyprovider.IntegrationInfo{Name: openpolicyagent.ProviderTypeOpa, Key: key}, policyprovider.ApplicationInfo{ObjectID: "anotherResourceId"}, []hexapolicy.PolicyInfo{ - {Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{{"http:GET"}}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ + {Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{"http:GET"}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ ResourceID: "aResourceId", }}, }, @@ -336,7 +336,7 @@ func TestSetPolicyInfo(t *testing.T) { meta := policies[0].Meta modified := meta.Modified assert.True(t, now.Before(*modified)) - actionURi := policies[0].Actions[0].ActionUri + actionURi := policies[0].Actions[0].String() assert.Equal(t, "http:GET", actionURi) assert.Equal(t, "aResourceId", policies[0].Object.ResourceID) // assert.JSONEq(t, `{"policies":[{"meta":{"version":"0.5"},"actions":[{"actionUri":"http:GET"}],"subject":{"members":["allusers"]},"object":{"resource_id":"anotherResourceId"}}]}`, string(readFile)) @@ -368,7 +368,7 @@ func TestSetPolicyInfo_withInvalidArguments(t *testing.T) { policyprovider.ApplicationInfo{ObjectID: "aResourceId"}, []hexapolicy.PolicyInfo{ { - Actions: []hexapolicy.ActionInfo{{"http:GET"}}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ + Actions: []hexapolicy.ActionInfo{"http:GET"}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ ResourceID: "aResourceId", }}, }, @@ -422,7 +422,7 @@ func TestSetPolicyInfo_WithHTTPSBundleServer(t *testing.T) { policyprovider.IntegrationInfo{Name: openpolicyagent.ProviderTypeOpa, Key: key}, policyprovider.ApplicationInfo{ObjectID: "aResourceId"}, []hexapolicy.PolicyInfo{ - {Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{{"http:GET"}}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ + {Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{"http:GET"}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ ResourceID: "aResourceId", }}, }, @@ -439,9 +439,7 @@ func TestMakeDefaultBundle(t *testing.T) { "meta": { "version": "0.6" }, - "actions": [{ - "actionUri": "ietf:http:GET" - }], + "actions": [ "ietf:http:GET" ], "subject": { "members": [ "anyauthenticated" @@ -483,7 +481,7 @@ func TestMakeDefaultBundle(t *testing.T) { assert.Len(t, policies, 1, "Should be 1 policy") meta := policies[0].Meta - actionURi := policies[0].Actions[0].ActionUri + actionURi := policies[0].Actions[0].String() assert.Equal(t, "ietf:http:GET", actionURi) assert.Equal(t, "aResourceId", policies[0].Object.ResourceID) // assert.JSONEq(t, `{"policies":[{"meta":{"version":"0.5"},"actions":[{"actionUri":"http:GET"}],"subject":{"members":["allusers"]},"object":{"resource_id":"anotherResourceId"}}]}`, string(readFile)) diff --git a/providers/openpolicyagent/resources/bundles/bundle/data.json b/providers/openpolicyagent/resources/bundles/bundle/data.json index fa479b5..5e8d880 100644 --- a/providers/openpolicyagent/resources/bundles/bundle/data.json +++ b/providers/openpolicyagent/resources/bundles/bundle/data.json @@ -6,9 +6,7 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/" - } + "http:GET:/" ], "subjects:": [ "any", @@ -23,12 +21,8 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/sales" - }, - { - "actionUri": "http:GET:/marketing" - } + "http:GET:/sales", + "http:GET:/marketing" ], "subjects:": [ "anyauthenticated", @@ -44,12 +38,8 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/accounting" - }, - { - "actionUri": "http:POST:/accounting" - } + "http:GET:/accounting", + "http:POST:/accounting" ], "subjects:": [ "accounting@hexaindustries.io" @@ -63,9 +53,7 @@ "version": "0.7" }, "actions": [ - { - "actionUri": "http:GET:/humanresources" - } + "http:GET:/humanresources" ], "subjects:": [ "humanresources@hexaindustries.io" diff --git a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego index 3c587ff..5be5889 100644 --- a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego +++ b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego @@ -41,7 +41,7 @@ action_rights contains name if { policy.meta.policyId in allow_set some action in policy.actions - name := sprintf("%s:%s", [policy.meta.policyId, action.actionUri]) + name := sprintf("%s:%s", [policy.meta.policyId, action]) } # Returns whether the current operation is allowed @@ -111,15 +111,15 @@ actions_match(actions, req) if { action_match(action, req) if { # Check for match based on ietf http - check_http_match(action.actionUri, req) + check_http_match(action, req) } action_match(action, req) if { - action.actionUri # check for an action + action # check for an action count(req.actionUris) > 0 # Check for a match based on req.ActionUris and actionUri - check_urn_match(action.actionUri, req.actionUris) + check_urn_match(action, req.actionUris) } check_urn_match(policyUri, actionUris) if { diff --git a/sdk/providerTools_test.go b/sdk/providerTools_test.go index 6bfd223..dc41b44 100644 --- a/sdk/providerTools_test.go +++ b/sdk/providerTools_test.go @@ -95,7 +95,7 @@ func (s *testSuite) Test3_Reconcile() { policy := policies[0] actions := policy.Actions - actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::UpdateAccount"}) + actions = append(actions, "cedar:hexa_avp::Action::UpdateAccount") policies[0].Actions = actions @@ -158,7 +158,7 @@ func (s *testSuite) Test4_SetPolicies() { // This section will cause an update since only action changed policy := policies[0] actions := policy.Actions - actions = append(actions, hexapolicy.ActionInfo{ActionUri: "cedar:hexa_avp::Action::UpdateAccount"}) + actions = append(actions, "cedar:hexa_avp::Action::UpdateAccount") policies[0].Actions = actions From 69430b5d313f031178f1b3e851091c3c7ec6534a Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Sat, 7 Sep 2024 11:01:51 -0700 Subject: [PATCH 06/13] Issue #59, Changed ObjectInfo to simple string value Signed-off-by: Phil Hunt --- cmd/hexa/test/example_idql.json | 32 +++----- examples/policyExamples/example_idql.json | 16 +--- examples/policyExamples/idqlAlice.json | 8 +- models/formats/awsCedar/amazon_cedar.go | 9 +-- models/formats/awsCedar/test/data.json | 20 ++--- models/formats/awsCedar/test/testGcpIdql.json | 29 +++---- models/formats/cedar/cedar_test.go | 20 ++--- models/formats/cedar/parse.go | 14 ++-- models/formats/gcpBind/google_bind_policy.go | 6 +- models/formats/gcpBind/test/data.json | 33 +++----- models/rar/policy_transformer.go | 10 +-- models/rar/policy_transformer_test.go | 28 +++---- .../resource_action_role_policy_support.go | 2 +- .../policy_checker_support.go | 6 +- .../policytestsupport/policy_data_support.go | 8 +- pkg/hexapolicy/hexa_policy.go | 11 ++- pkg/hexapolicy/hexa_policy_test.go | 32 +++----- pkg/hexapolicysupport/test/data.json | 16 +--- providers/aws/avpProvider/avp_provider.go | 2 +- .../aws_apigw_provider_service_test.go | 2 +- .../aws/cognitoProvider/cognito_provider.go | 4 +- .../cognitoProvider/cognito_provider_test.go | 30 +++---- .../azureProvider/azure_policy_mapper.go | 2 +- .../azureProvider/azure_policy_mapper_test.go | 4 +- .../azureProvider/azure_provider_test.go | 67 ++++++---------- .../iapProvider/google_client_test.go | 13 +-- .../iapProvider/google_cloud_provider_test.go | 8 +- .../http_bundle_client_test.go | 16 +--- providers/openpolicyagent/opa_provider.go | 4 +- .../openpolicyagent/opa_provider_test.go | 32 ++++---- .../resources/bundles/bundle/data.json | 16 +--- .../resources/bundles/bundle/hexaPolicy.rego | 6 +- providers/openpolicyagent/test/mock_client.go | 20 ++--- providers/test/data/example_idql.json | 80 ++++++++++--------- 34 files changed, 247 insertions(+), 359 deletions(-) diff --git a/cmd/hexa/test/example_idql.json b/cmd/hexa/test/example_idql.json index 91259d6..a33037c 100644 --- a/cmd/hexa/test/example_idql.json +++ b/cmd/hexa/test/example_idql.json @@ -2,7 +2,7 @@ "policies": [ { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ "http:GET:/" @@ -15,13 +15,11 @@ "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "aResourceId1" - } + "object": "aResourceId1" }, { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ "http:GET:/sales", @@ -29,45 +27,39 @@ ], "subjects": [ "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" + "user:sales@hexaindustries.io", + "user:marketing@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId2" - } + "object": "aResourceId2" }, { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ "http:GET:/accounting", "http:POST:/accounting" ], "subjects": [ - "accounting@hexaindustries.io" + "user:accounting@hexaindustries.io" ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "aResourceId3" - } + "object": "aResourceId3" }, { "meta": { - "version": "0.6" + "version": "0.7" }, "actions": [ "http:GET:/humanresources" ], "subjects": [ - "humanresources@hexaindustries.io" + "user:humanresources@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId1" - } + "object": "aResourceId1" } ] } \ No newline at end of file diff --git a/examples/policyExamples/example_idql.json b/examples/policyExamples/example_idql.json index a95791b..0d93541 100644 --- a/examples/policyExamples/example_idql.json +++ b/examples/policyExamples/example_idql.json @@ -15,9 +15,7 @@ "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "aResourceId1" - } + "object": "aResourceId1" }, { "meta": { @@ -32,9 +30,7 @@ "sales@hexaindustries.io", "marketing@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId2" - } + "object": "aResourceId2" }, { "meta": { @@ -51,9 +47,7 @@ "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "aResourceId3" - } + "object": "aResourceId3" }, { "meta": { @@ -65,9 +59,7 @@ "subjects": [ "humanresources@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId1" - } + "object": "aResourceId1" } ] } \ No newline at end of file diff --git a/examples/policyExamples/idqlAlice.json b/examples/policyExamples/idqlAlice.json index 760b32b..770b1b1 100644 --- a/examples/policyExamples/idqlAlice.json +++ b/examples/policyExamples/idqlAlice.json @@ -10,9 +10,7 @@ "Subjects": [ "User:\"alice\"" ], - "Object": { - "resource_id": "cedar:Photo::\"VacationPhoto94.jpg\"" - } + "Object": "cedar:Photo::\"VacationPhoto94.jpg\"" }, { "Meta": { @@ -24,9 +22,7 @@ "Subjects": [ "User:\"stacey\"" ], - "Object": { - "resource_id": "" - }, + "Object": "", "Condition": { "Rule": "resource in \"Account:stacey\"", "Action": "permit" diff --git a/models/formats/awsCedar/amazon_cedar.go b/models/formats/awsCedar/amazon_cedar.go index b5a4611..7e86ed8 100644 --- a/models/formats/awsCedar/amazon_cedar.go +++ b/models/formats/awsCedar/amazon_cedar.go @@ -156,13 +156,11 @@ func isSingular(entityId string) bool { func mapResourceToObject(res *ResourceExpression) hexapolicy.ObjectInfo { mId := "cedar:" + removeLastQuotes(res.Entity) - return hexapolicy.ObjectInfo{ - ResourceID: mId, - } + return hexapolicy.ObjectInfo(mId) } func mapObjectToResource(object hexapolicy.ObjectInfo) *ResourceExpression { - id := object.ResourceID + id := object.String() if id == "" { return nil } @@ -409,7 +407,8 @@ func (c *CedarPolicyMapper) MapCedarPolicyToIdql(policy *CedarPolicy) (*hexapoli } } } - obj := hexapolicy.ObjectInfo{} + + var obj hexapolicy.ObjectInfo if policy.Head.Resource != nil { obj = mapResourceToObject(policy.Head.Resource) } diff --git a/models/formats/awsCedar/test/data.json b/models/formats/awsCedar/test/data.json index 1f813d5..b7ef409 100644 --- a/models/formats/awsCedar/test/data.json +++ b/models/formats/awsCedar/test/data.json @@ -10,9 +10,7 @@ "Subjects": [ "User:stacey" ], - "Object": { - "resource_id": "" - }, + "Object": "", "Condition": { "Rule": "resource in \"Account:stacey\"", "Action": "permit" @@ -33,9 +31,7 @@ "rule": "action.isReadOperation eq true", "action": "allow" }, - "object": { - "resource_id": "Folder::3b276b13858d46839d8cbfb45e5c6c2a" - } + "object": "Folder::3b276b13858d46839d8cbfb45e5c6c2a" }, { "meta": { @@ -49,9 +45,7 @@ "User:sales@hexaindustries.io", "Group:marketing@hexaindustries.io" ], - "object": { - "resource_id": "File::ec37b3b17a1e4ae08a641dcd9d915535" - } + "object": "File::ec37b3b17a1e4ae08a641dcd9d915535" }, { "meta": { @@ -68,9 +62,7 @@ "rule": "context.sourceIp eq \"192.158.1.38\" and context.http.method eq GET", "action": "allow" }, - "object": { - "resource_id": "Folder::900af98fc3ab47cbaa982d94da7c90e3" - } + "object": "Folder::900af98fc3ab47cbaa982d94da7c90e3" }, { "Meta": { @@ -82,9 +74,7 @@ "subjects": [ "User:alice" ], - "Object": { - "resource_id": "cedar:Photo::VacationPhoto94.jpg" - } + "Object": "cedar:Photo::VacationPhoto94.jpg" } ] } \ No newline at end of file diff --git a/models/formats/awsCedar/test/testGcpIdql.json b/models/formats/awsCedar/test/testGcpIdql.json index c896f3a..1c863e8 100644 --- a/models/formats/awsCedar/test/testGcpIdql.json +++ b/models/formats/awsCedar/test/testGcpIdql.json @@ -5,12 +5,9 @@ }, "Actions": null, "Subjects": [ - "allusers", - "allauthenticated" + "any","anyauthenticated" ], - "Object": { - "resource_id": "aResourceId1" - }, + "Object": "aResourceId1", "Condition": { "Rule": "req.ip sw 127 and req.method eq \"POST\"", "Action": "allow" @@ -22,11 +19,9 @@ }, "Actions": null, "Subjects": [ - "humanresources@hexaindustries.io" + "group:humanresources@hexaindustries.io" ], - "Object": { - "resource_id": "aResourceId1" - } + "Object": "aResourceId1" }, { "Meta": { @@ -34,13 +29,11 @@ }, "Actions": null, "Subjects": [ - "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" + "anyauthenticated", + "group:sales@hexaindustries.io", + "group:marketing@hexaindustries.io" ], - "Object": { - "resource_id": "aResourceId2" - } + "Object": "aResourceId2" }, { "Meta": { @@ -48,11 +41,9 @@ }, "Actions": null, "Subjects": [ - "accounting@hexaindustries.io" + "group:accounting@hexaindustries.io" ], - "Object": { - "resource_id": "aResourceId3" - }, + "Object": "aResourceId3", "Condition": { "Rule": "req.ip sw 127 and req.method eq \"POST\"", "Action": "allow" diff --git a/models/formats/cedar/cedar_test.go b/models/formats/cedar/cedar_test.go index 25113d8..529da7d 100644 --- a/models/formats/cedar/cedar_test.go +++ b/models/formats/cedar/cedar_test.go @@ -98,9 +98,7 @@ permit ( "any" ], "actions": [ "action" ], - "object": { - "resource_id": "" - } + "object": "" }`, err: false, }, @@ -114,9 +112,7 @@ permit ( "User:alice" ], "actions": [ "viewPhoto" ], - "object": { - "resource_id": "Photo::\"VacationPhoto.jpg\"" - } + "object": "Photo::\"VacationPhoto.jpg\"" }`, false}, {"Multi-Action", `permit ( principal is User in Group::"AVTeam", @@ -132,9 +128,7 @@ permit ( "PhotoOp::\"edit\"", "PhotoOp::\"delete\"" ], - "object": { - "resource_id": "Photo::\"VacationPhoto.jpg\"" - } + "object": "Photo::\"VacationPhoto.jpg\"" }`, false}, {"Conditions", `permit ( principal in UserGroup::"AVTeam", @@ -149,9 +143,7 @@ unless { principal has parents };`, "Group:UserGroup::\"AVTeam\"" ], "actions": [ "viewPhoto" ], - "object": { - "resource_id": "Type:Photo" - }, + "object": "Type:Photo", "Condition": { "Rule": "resource in PhotoApp::Account::\"stacey\" and not (principal.parents pr)", "Action": "allow" @@ -168,9 +160,7 @@ when { resource in PhotoShop::"Photo" };`, `{ "Type:User" ], "actions": [ "viewPhoto" ], - "object": { - "resource_id": "" - }, + "object": "", "Condition": { "Rule": "resource in PhotoShop::\"Photo\"", "Action": "allow" diff --git a/models/formats/cedar/parse.go b/models/formats/cedar/parse.go index ae5a32a..97bae37 100644 --- a/models/formats/cedar/parse.go +++ b/models/formats/cedar/parse.go @@ -327,23 +327,23 @@ func (pp *PolicyPair) mapCedarResource() { resource := pp.CedarPolicy.Resource switch resource.Type { case cedarParser.MatchEquals: - pp.HexaPolicy.Object = hexapolicy.ObjectInfo{ResourceID: resource.Entity.String()} + pp.HexaPolicy.Object = hexapolicy.ObjectInfo(resource.Entity.String()) case cedarParser.MatchAny: - pp.HexaPolicy.Object = hexapolicy.ObjectInfo{} + pp.HexaPolicy.Object = "" case cedarParser.MatchIs: - pp.HexaPolicy.Object = hexapolicy.ObjectInfo{ResourceID: fmt.Sprintf("Type:%s", resource.Path.String())} + pp.HexaPolicy.Object = hexapolicy.ObjectInfo(fmt.Sprintf("Type:%s", resource.Path.String())) case cedarParser.MatchIsIn: - pp.HexaPolicy.Object = hexapolicy.ObjectInfo{ResourceID: fmt.Sprintf("[%s].(%s)", resource.Entity.String(), resource.Path.String())} + pp.HexaPolicy.Object = hexapolicy.ObjectInfo(fmt.Sprintf("[%s].(%s)", resource.Entity.String(), resource.Path.String())) case cedarParser.MatchIn: - pp.HexaPolicy.Object = hexapolicy.ObjectInfo{ResourceID: fmt.Sprintf("[%s]", resource.Entity.String())} + 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{ResourceID: fmt.Sprintf("[%s]", resource.String())} + pp.HexaPolicy.Object = hexapolicy.ObjectInfo(fmt.Sprintf("[%s]", resource.String())) } } func (pp *PolicyPair) mapHexaResource() string { - resource := pp.HexaPolicy.Object.ResourceID + resource := pp.HexaPolicy.Object.String() if resource == "" { return "resource" } diff --git a/models/formats/gcpBind/google_bind_policy.go b/models/formats/gcpBind/google_bind_policy.go index 7ed0122..d5b997f 100644 --- a/models/formats/gcpBind/google_bind_policy.go +++ b/models/formats/gcpBind/google_bind_policy.go @@ -72,7 +72,7 @@ func (m *GooglePolicyMapper) MapBindingToPolicy(objectId string, binding iam.Bin Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: convertRoleToAction(binding.Role), Subjects: binding.Members, - Object: hexapolicy.ObjectInfo{ResourceID: objectId}, + Object: hexapolicy.ObjectInfo(objectId), Condition: &condition, } return policy, nil @@ -81,7 +81,7 @@ func (m *GooglePolicyMapper) MapBindingToPolicy(objectId string, binding iam.Bin Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: convertRoleToAction(binding.Role), Subjects: binding.Members, - Object: hexapolicy.ObjectInfo{ResourceID: objectId}, + Object: hexapolicy.ObjectInfo(objectId), } return policy, nil @@ -116,7 +116,7 @@ func (m *GooglePolicyMapper) MapPoliciesToBindings(policies []hexapolicy.PolicyI fmt.Println(err.Error()) continue } - key := policies[i].Object.ResourceID + key := policies[i].Object.String() existing := bindingMap[key] existing = append(existing, *binding) diff --git a/models/formats/gcpBind/test/data.json b/models/formats/gcpBind/test/data.json index 91a418e..57375a2 100644 --- a/models/formats/gcpBind/test/data.json +++ b/models/formats/gcpBind/test/data.json @@ -8,16 +8,13 @@ "http:GET:/" ], "subjects": [ - "allusers", - "allauthenticated" + "any" ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -28,13 +25,11 @@ "http:GET:/marketing" ], "subjects": [ - "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" + "anyauthenticated", + "user:sales@hexaindustries.io", + "group:marketing@hexaindustries.io" ], - "object": { - "resource_id": "bResourceId" - } + "object": "bResourceId" }, { "meta": { @@ -45,15 +40,13 @@ "http:POST:/accounting" ], "subjects": [ - "accounting@hexaindustries.io" + "group:accounting@hexaindustries.io" ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "cResourceId" - } + "object": "cResourceId" }, { "meta": { @@ -63,11 +56,9 @@ "http:GET:/humanresources" ], "subjects": [ - "humanresources@hexaindustries.io" + "group:humanresources@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -79,9 +70,7 @@ "actions": [ "gcp:roles/iap.httpsResourceAccessor" ], - "object": { - "resource_id": "754290878449499554" - } + "object": "754290878449499554" } ] } \ No newline at end of file diff --git a/models/rar/policy_transformer.go b/models/rar/policy_transformer.go index ae278a3..ef1c953 100644 --- a/models/rar/policy_transformer.go +++ b/models/rar/policy_transformer.go @@ -23,7 +23,7 @@ func BuildPolicies(resourceActionRolesList []ResourceActionRoles) []hexapolicy.P Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion, ProviderType: "RARmodel"}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(ActionUriPrefix + httpMethod)}, Subjects: roles, - Object: hexapolicy.ObjectInfo{ResourceID: one.Resource}, + Object: hexapolicy.ObjectInfo(one.Resource), }) } @@ -35,7 +35,7 @@ func FlattenPolicy(origPolicies []hexapolicy.PolicyInfo) []hexapolicy.PolicyInfo resActionPolicyMap := make(map[string]hexapolicy.PolicyInfo) for _, pol := range origPolicies { - resource := pol.Object.ResourceID + resource := pol.Object if resource == "" { log.Warn("FlattenPolicy Skipping policy without resource") continue @@ -45,7 +45,7 @@ func FlattenPolicy(origPolicies []hexapolicy.PolicyInfo) []hexapolicy.PolicyInfo log.Warn("FlattenPolicy Skipping policy without actionUri", "resource", resource) continue } - lookupKey := string(act) + resource + lookupKey := string(act) + resource.String() matchingPolicy, found := resActionPolicyMap[lookupKey] var existingMembers []string if found { @@ -56,7 +56,7 @@ func FlattenPolicy(origPolicies []hexapolicy.PolicyInfo) []hexapolicy.PolicyInfo Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{act}, Subjects: newMembers, - Object: hexapolicy.ObjectInfo{ResourceID: resource}, + Object: resource, } resActionPolicyMap[lookupKey] = newPol @@ -98,7 +98,7 @@ func CompactMembers(existing, new []string) []string { func sortPolicies(policies []hexapolicy.PolicyInfo) { sort.SliceStable(policies, func(i, j int) bool { - resComp := strings.Compare(policies[i].Object.ResourceID, policies[j].Object.ResourceID) + resComp := strings.Compare(policies[i].Object.String(), policies[j].Object.String()) actComp := strings.Compare(string(policies[i].Actions[0]), string(policies[j].Actions[0])) switch resComp { case 0: diff --git a/models/rar/policy_transformer_test.go b/models/rar/policy_transformer_test.go index ca1074a..72ee723 100644 --- a/models/rar/policy_transformer_test.go +++ b/models/rar/policy_transformer_test.go @@ -31,14 +31,14 @@ func TestBuildPolicies(t *testing.T) { assert.Len(t, policies, 2) actPol := policies[0] - assert.Equal(t, policytestsupport.ResourceHrUs, actPol.Object.ResourceID) + assert.Equal(t, policytestsupport.ResourceHrUs, actPol.Object.String()) assert.Equal(t, "http:GET", actPol.Actions[0].String()) var res []string res = actPol.Subjects assert.Equal(t, []string{"1-some-hr-role", "2-some-hr-role"}, res) actPol = policies[1] - assert.Equal(t, policytestsupport.ResourceProfile, actPol.Object.ResourceID) + assert.Equal(t, policytestsupport.ResourceProfile, actPol.Object.String()) assert.Equal(t, "http:GET", actPol.Actions[0].String()) res = actPol.Subjects assert.Equal(t, []string{"1-some-profile-role", "2-some-profile-role"}, res) @@ -187,7 +187,7 @@ func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { Actions: []hexapolicy.ActionInfo{ hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("2act")}, Subjects: []string{"1mem", "", "2mem"}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + Object: hexapolicy.ObjectInfo("resource1"), } pol2 := hexapolicy.PolicyInfo{ @@ -195,7 +195,7 @@ func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { Actions: []hexapolicy.ActionInfo{ hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("3act"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("4act")}, Subjects: []string{"1mem", "", "2mem"}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + Object: hexapolicy.ObjectInfo("resource1"), } orig := []hexapolicy.PolicyInfo{pol1, pol2} @@ -208,7 +208,7 @@ func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { expMembers := []string{"1mem", "2mem"} for i, actPol := range actPolicies { - assert.Equal(t, expResource, actPol.Object.ResourceID) + assert.Equal(t, expResource, actPol.Object.String()) assert.Equal(t, expActions[i], actPol.Actions[0].String()) assert.Equal(t, expMembers, actPol.Subjects.String()) } @@ -224,7 +224,7 @@ func TestFlattenPolicy_NoResource(t *testing.T) { Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo("1act")}, Subjects: []string{"1mem", "2mem"}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + Object: hexapolicy.ObjectInfo("resource1"), } tests := []struct { @@ -258,7 +258,7 @@ func TestFlattenPolicy_NoActions(t *testing.T) { pol1 := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, Subjects: []string{"1mem", "", "2mem"}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + Object: hexapolicy.ObjectInfo("resource1"), } orig := []hexapolicy.PolicyInfo{pol1} actPolicies := rar.FlattenPolicy(orig) @@ -270,7 +270,7 @@ func TestFlattenPolicy_NoMembers(t *testing.T) { pol1 := hexapolicy.PolicyInfo{ Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + Object: hexapolicy.ObjectInfo("resource1"), } orig := []hexapolicy.PolicyInfo{pol1} actPolicies := rar.FlattenPolicy(orig) @@ -279,7 +279,7 @@ func TestFlattenPolicy_NoMembers(t *testing.T) { expActions := []string{"1act", "2act"} for i, actPol := range actPolicies { - assert.Equal(t, "resource1", actPol.Object.ResourceID) + assert.Equal(t, "resource1", actPol.Object.String()) assert.Equal(t, expActions[i], actPol.Actions[0].String()) assert.NotNil(t, actPol.Subjects) assert.Equal(t, []string{}, actPol.Subjects.String()) @@ -292,7 +292,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { Actions: []hexapolicy.ActionInfo{ hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, Subjects: []string{"1mem", "2mem"}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + Object: hexapolicy.ObjectInfo("resource1"), } pol1b := hexapolicy.PolicyInfo{ @@ -300,7 +300,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { Actions: []hexapolicy.ActionInfo{ hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, Subjects: []string{"3mem", "4mem"}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource1"}, + Object: hexapolicy.ObjectInfo("resource1"), } pol2 := hexapolicy.PolicyInfo{ @@ -308,7 +308,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { Actions: []hexapolicy.ActionInfo{ hexapolicy.ActionInfo("3act"), hexapolicy.ActionInfo("4act")}, Subjects: []string{"1mem", "2mem"}, - Object: hexapolicy.ObjectInfo{ResourceID: "resource2"}, + Object: hexapolicy.ObjectInfo("resource2"), } orig := []hexapolicy.PolicyInfo{pol1a, pol2, pol1b} @@ -323,7 +323,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { for i := 0; i < len(expActions); i++ { actPol := actPolicies[i] assert.NotNil(t, actPol) - assert.Equal(t, expResource, actPol.Object.ResourceID) + assert.Equal(t, expResource, actPol.Object.String()) assert.Equal(t, expActions[i], actPol.Actions[0].String()) assert.Equal(t, expMembers, actPol.Subjects.String()) } @@ -334,7 +334,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { for i := 0; i < len(expActions); i++ { actPol := actPolicies[i+2] assert.NotNil(t, actPol) - assert.Equal(t, expResource, actPol.Object.ResourceID) + assert.Equal(t, expResource, actPol.Object.String()) assert.Equal(t, expActions[i], actPol.Actions[0].String()) assert.Equal(t, expMembers, actPol.Subjects.String()) } diff --git a/models/rar/resource_action_role_policy_support.go b/models/rar/resource_action_role_policy_support.go index 1d66e0f..5ed763f 100644 --- a/models/rar/resource_action_role_policy_support.go +++ b/models/rar/resource_action_role_policy_support.go @@ -23,7 +23,7 @@ func CalcResourceActionRolesForUpdate(existing []ResourceActionRoles, policyInfo rarUpdateList := make([]ResourceActionRoles, 0) for _, pol := range newPolicies { - polResource := pol.Object.ResourceID + polResource := pol.Object.String() polAction := string(pol.Actions[0]) polRoles := pol.Subjects diff --git a/models/rar/testsupport/policytestsupport/policy_checker_support.go b/models/rar/testsupport/policytestsupport/policy_checker_support.go index e8640fc..3050e2a 100644 --- a/models/rar/testsupport/policytestsupport/policy_checker_support.go +++ b/models/rar/testsupport/policytestsupport/policy_checker_support.go @@ -30,7 +30,7 @@ func HasPolicy(expPolicies []hexapolicy.PolicyInfo, act hexapolicy.PolicyInfo) b } func MatchPolicy(exp hexapolicy.PolicyInfo, act hexapolicy.PolicyInfo) bool { - if exp.Object.ResourceID != act.Object.ResourceID { + if exp.Object.String() != act.Object.String() { return false } @@ -58,9 +58,7 @@ func MakePolicies(actionMembers map[string][]string, resourceId string) []hexapo Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(action)}, Subjects: members, - Object: hexapolicy.ObjectInfo{ - ResourceID: resourceId, - }, + Object: hexapolicy.ObjectInfo(resourceId), } policies = append(policies, pol) diff --git a/models/rar/testsupport/policytestsupport/policy_data_support.go b/models/rar/testsupport/policytestsupport/policy_data_support.go index 4e75f56..f09814a 100644 --- a/models/rar/testsupport/policytestsupport/policy_data_support.go +++ b/models/rar/testsupport/policytestsupport/policy_data_support.go @@ -84,9 +84,7 @@ func MakeRoleSubjectTestPolicy(resourceId string, action string, roles []string) Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(action)}, Subjects: roles, - Object: hexapolicy.ObjectInfo{ - ResourceID: resourceId, - }, + Object: hexapolicy.ObjectInfo(resourceId), } } @@ -95,9 +93,7 @@ func MakeTestPolicy(resourceId string, action string, actionMembers ActionMember Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(action)}, Subjects: MakePolicyTestUsers(actionMembers), - Object: hexapolicy.ObjectInfo{ - ResourceID: resourceId, - }, + Object: hexapolicy.ObjectInfo(resourceId), } } diff --git a/pkg/hexapolicy/hexa_policy.go b/pkg/hexapolicy/hexa_policy.go index 0418b75..0320c45 100644 --- a/pkg/hexapolicy/hexa_policy.go +++ b/pkg/hexapolicy/hexa_policy.go @@ -242,12 +242,17 @@ func (s SubjectInfo) equals(subjects SubjectInfo) bool { return true } -type ObjectInfo struct { - ResourceID string `json:"resource_id" validate:"required"` +type ObjectInfo string + +func (o *ObjectInfo) String() string { + return string(*o) } func (o *ObjectInfo) equals(object *ObjectInfo) bool { - return o.ResourceID == object.ResourceID + if object == nil { + return false + } + return strings.EqualFold(o.String(), object.String()) } // ScopeInfo represents obligations passed to a PEP. For example a `Filter` is used to constrain the rows of a database. diff --git a/pkg/hexapolicy/hexa_policy_test.go b/pkg/hexapolicy/hexa_policy_test.go index 1925a91..950a409 100644 --- a/pkg/hexapolicy/hexa_policy_test.go +++ b/pkg/hexapolicy/hexa_policy_test.go @@ -25,9 +25,7 @@ var testPolicy1 = ` "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "aResourceId" - }, + "object": "aResourceId", "scope": { "filter": "idql:username eq smith", "attributes": ["username","emails"] @@ -45,9 +43,7 @@ var testPolicy2 = ` "subjects": [ "user:humanresources@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }` func getPolicies(t *testing.T) Policies { @@ -123,7 +119,7 @@ func TestObjectInfo_equals(t *testing.T) { p2 := policies.Policies[1] assert.True(t, p1.Object.equals(&p2.Object)) p3 := p1 - p3.Object.ResourceID = "abc" + p3.Object = "abc" assert.False(t, p1.Object.equals(&p3.Object)) } @@ -219,7 +215,7 @@ func TestPolicyInfo_CalculateEtag(t *testing.T) { assert.Equal(t, etag, p1.Meta.Etag) pnew := p1 - pnew.Object.ResourceID = "abc" + pnew.Object = "abc" etag2 := pnew.CalculateEtag() assert.NotEqual(t, etag, etag2, "Should be different etags") @@ -266,7 +262,7 @@ func TestPolicyInfo_Equals(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &tt.fields.testPolicy - assert.Equalf(t, tt.want, p.Equals(tt.args.hexaPolicy), "Equals(%v)", tt.args.hexaPolicy) + assert.Equalf(t, tt.want, p.Equals(tt.args.hexaPolicy), "equals(%v)", tt.args.hexaPolicy) }) } } @@ -344,7 +340,7 @@ func TestPolicyDif_Report(t *testing.T) { Meta: MetaInfo{PolicyId: &pid}, Subjects: []string{"user1"}, Actions: []ActionInfo{"actionUri"}, - Object: ObjectInfo{ResourceID: "aresource"}, + Object: "aresource", Condition: nil, } testPolicy.CalculateEtag() @@ -456,7 +452,7 @@ func TestPolicyInfo_String(t *testing.T) { pid := "abc" policyString := `{ "meta": { - "etag": "20-e3f1663e6b8664347598121df047911cbacd961b", + "etag": "20-6c1676cb067f5abe504031daef66a110f501a0f3", "policyId": "abc" }, "subjects": [ @@ -465,15 +461,13 @@ func TestPolicyInfo_String(t *testing.T) { "actions": [ "actionUri" ], - "object": { - "resource_id": "aresource" - } + "object": "aresource" }` testPolicy := PolicyInfo{ Meta: MetaInfo{PolicyId: &pid}, Subjects: []string{"user1"}, Actions: []ActionInfo{"actionUri"}, - Object: ObjectInfo{ResourceID: "aresource"}, + Object: "aresource", Condition: nil, } testPolicy.CalculateEtag() @@ -511,11 +505,11 @@ func TestReconcilePolicies(t *testing.T) { policiesWithChangesIds.Policies[1].Meta.PolicyId = &pid2 assert.Nil(t, policies.Policies[0].Meta.PolicyId) - policiesWithChangesIds.Policies[0].Object.ResourceID = "changed" + policiesWithChangesIds.Policies[0].Object = "changed" policiesWithChangesIds.Policies[0].CalculateEtag() - assert.NotEqual(t, policiesWithIds.Policies[0].Object.ResourceID, "changed") + assert.NotEqual(t, policiesWithIds.Policies[0].Object, "changed") - policiesWithChangesHash.Policies[0].Object.ResourceID = "anotherchange" + policiesWithChangesHash.Policies[0].Object = "anotherchange" policiesWithChangesHash.Policies[0].CalculateEtag() policiesEmpty := Policies{ @@ -531,7 +525,7 @@ func TestReconcilePolicies(t *testing.T) { }, Subjects: []string{"phil.hunt@independentid.com"}, Actions: []ActionInfo{"http:GET:/admin", "http:POST:/admin"}, - Object: ObjectInfo{ResourceID: "hexaindustries"}, + Object: "hexaindustries", } newPolicy.CalculateEtag() diff --git a/pkg/hexapolicysupport/test/data.json b/pkg/hexapolicysupport/test/data.json index 95276b6..1051714 100644 --- a/pkg/hexapolicysupport/test/data.json +++ b/pkg/hexapolicysupport/test/data.json @@ -14,9 +14,7 @@ "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -31,9 +29,7 @@ "user:sales@hexaindustries.io", "user:marketing@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -50,9 +46,7 @@ "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -64,9 +58,7 @@ "subjects": [ "user:humanresources@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" } ] } \ No newline at end of file diff --git a/providers/aws/avpProvider/avp_provider.go b/providers/aws/avpProvider/avp_provider.go index afe1559..17fad01 100644 --- a/providers/aws/avpProvider/avp_provider.go +++ b/providers/aws/avpProvider/avp_provider.go @@ -450,7 +450,7 @@ func isTemplate(hexaPolicy hexapolicy.PolicyInfo) bool { if slices.Contains(hexaPolicy.Subjects.String(), "?principal") { return true } - if strings.Contains(hexaPolicy.Object.ResourceID, "?resource") { + if strings.Contains(hexaPolicy.Object.String(), "?resource") { return true } diff --git a/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go b/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go index 86f84ee..8bd2fcb 100644 --- a/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go +++ b/providers/aws/awsapigwProvider/aws_apigw_provider_service_test.go @@ -105,7 +105,7 @@ func TestGetPolicyInfo(t *testing.T) { for _, actPol := range actPolicies { actMembers := actPol.Subjects.String() - actPolRar := rar.NewResourceActionUriRoles(actPol.Object.ResourceID, actPol.Actions[0].String(), actMembers) + actPolRar := rar.NewResourceActionUriRoles(actPol.Object.String(), actPol.Actions[0].String(), actMembers) actLookupKey := actPolRar.Action + actPolRar.Resource expMembers, found := existingActionRoles[actLookupKey] assert.True(t, found) diff --git a/providers/aws/cognitoProvider/cognito_provider.go b/providers/aws/cognitoProvider/cognito_provider.go index 6dbf71a..b4b6d9f 100644 --- a/providers/aws/cognitoProvider/cognito_provider.go +++ b/providers/aws/cognitoProvider/cognito_provider.go @@ -58,9 +58,7 @@ func (a *CognitoProvider) GetPolicyInfo(info policyprovider.IntegrationInfo, app }, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(groupName)}, Subjects: members, - Object: hexapolicy.ObjectInfo{ - ResourceID: applicationInfo.Name, - }, + Object: hexapolicy.ObjectInfo(applicationInfo.Name), }) } diff --git a/providers/aws/cognitoProvider/cognito_provider_test.go b/providers/aws/cognitoProvider/cognito_provider_test.go index 69793dc..e85e442 100644 --- a/providers/aws/cognitoProvider/cognito_provider_test.go +++ b/providers/aws/cognitoProvider/cognito_provider_test.go @@ -156,26 +156,26 @@ func TestSetPolicy_withInvalidArguments(t *testing.T) { Meta: hexapolicy.MetaInfo{Version: "0"}, Actions: []hexapolicy.ActionInfo{"azure:anAppRoleId"}, Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, - Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, + Object: "anObjectId", }}) assert.Equal(t, http.StatusInternalServerError, status) assert.EqualError(t, err, "Key: 'ApplicationInfo.ObjectID' Error:Field validation for 'ObjectID' failed on the 'required' tag") - status, err = p.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: "aDescription"}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{"azure:anAppRoleId"}, - Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, - Object: hexapolicy.ObjectInfo{}, - }}) - - assert.Equal(t, http.StatusInternalServerError, status) - assert.EqualError(t, err, "Key: '[0].Object.ResourceID' Error:Field validation for 'ResourceID' failed on the 'required' tag") + /* test not valid after restructure + status, err = p.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: "aDescription"}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{"azure:anAppRoleId"}, + Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, + Object: nil, + }}) + + assert.Equal(t, http.StatusInternalServerError, status) + assert.EqualError(t, err, "Key: '[0].Object' Error:Field validation for 'ResourceID' failed on the 'required' tag") + */ } func TestSetPolicyInfo_CognitoClientError(t *testing.T) { diff --git a/providers/azure/azureProvider/azure_policy_mapper.go b/providers/azure/azureProvider/azure_policy_mapper.go index ab173a7..2e868ac 100644 --- a/providers/azure/azureProvider/azure_policy_mapper.go +++ b/providers/azure/azureProvider/azure_policy_mapper.go @@ -67,7 +67,7 @@ func (azm *AzurePolicyMapper) appRoleAssignmentToIDQL(assignments []azad.AzureAp }, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(role.Value)}, Subjects: members, - Object: hexapolicy.ObjectInfo{ResourceID: azm.objectId}, + Object: hexapolicy.ObjectInfo(azm.objectId), } } diff --git a/providers/azure/azureProvider/azure_policy_mapper_test.go b/providers/azure/azureProvider/azure_policy_mapper_test.go index e333820..8a6c056 100644 --- a/providers/azure/azureProvider/azure_policy_mapper_test.go +++ b/providers/azure/azureProvider/azure_policy_mapper_test.go @@ -23,7 +23,7 @@ func TestAzurePolicyMapper_ToIDQL(t *testing.T) { actActionMembersMap := make(map[string][]string) for _, pol := range actPolicies { assert.Equal(t, 1, len(pol.Actions)) - assert.Equal(t, sps.List[0].Name, pol.Object.ResourceID) + assert.Equal(t, sps.List[0].Name, pol.Object.String()) actActionMembersMap[pol.Actions[0].String()] = pol.Subjects } @@ -54,7 +54,7 @@ func TestAzurePolicyMapper_ToIDQL_NoRoleAssignments(t *testing.T) { log.Println(actPolicies) for _, pol := range actPolicies { assert.Equal(t, 1, len(pol.Actions)) - assert.Equal(t, sps.List[0].Name, pol.Object.ResourceID) + assert.Equal(t, sps.List[0].Name, pol.Object.String()) assert.NotNil(t, pol.Subjects) assert.Empty(t, pol.Subjects) actPolicyActionMap[pol.Actions[0].String()] = true diff --git a/providers/azure/azureProvider/azure_provider_test.go b/providers/azure/azureProvider/azure_provider_test.go index 5094668..3e432e6 100644 --- a/providers/azure/azureProvider/azure_provider_test.go +++ b/providers/azure/azureProvider/azure_provider_test.go @@ -67,7 +67,7 @@ func TestGetPolicy_WithoutUserEmail(t *testing.T) { assert.True(t, len(pol.Actions) > 0) assert.NotEmpty(t, pol.Actions[0].String()) assert.Equal(t, 0, len(pol.Subjects)) - assert.Equal(t, policytestsupport.PolicyObjectResourceId, pol.Object.ResourceID) + assert.Equal(t, policytestsupport.PolicyObjectResourceId, pol.Object.String()) } mockAzClient.AssertExpectations(t) } @@ -161,26 +161,27 @@ func TestSetPolicy_withInvalidArguments(t *testing.T) { Meta: hexapolicy.MetaInfo{Version: "0"}, Actions: []hexapolicy.ActionInfo{"azure:anAppRoleId"}, Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, - Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, + Object: "anObjectId", }}) assert.Equal(t, http.StatusInternalServerError, status) assert.EqualError(t, err, "Key: 'ApplicationInfo.ObjectID' Error:Field validation for 'ObjectID' failed on the 'required' tag") - status, err = provider.SetPolicyInfo( - policyprovider.IntegrationInfo{Name: "azure", Key: key}, - policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: "aDescription"}, - []hexapolicy.PolicyInfo{{ - Meta: hexapolicy.MetaInfo{Version: "0"}, - Actions: []hexapolicy.ActionInfo{"azure:anAppRoleId"}, - Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, - Object: hexapolicy.ObjectInfo{}, - }}) + /* Test removed because ResourceID no longer used. + status, err = provider.SetPolicyInfo( + policyprovider.IntegrationInfo{Name: "azure", Key: key}, + policyprovider.ApplicationInfo{ObjectID: "anObjectId", Name: "anAppName", Description: "aDescription"}, + []hexapolicy.PolicyInfo{{ + Meta: hexapolicy.MetaInfo{Version: "0"}, + Actions: []hexapolicy.ActionInfo{"azure:anAppRoleId"}, + Subjects: []string{"aPrincipalId:aPrincipalDisplayName", "yetAnotherPrincipalId:yetAnotherPrincipalDisplayName", "andAnotherPrincipalId:andAnotherPrincipalDisplayName"}, + Object: hexapolicy.ObjectInfo{}, + }}) - assert.Equal(t, http.StatusInternalServerError, status) - assert.EqualError(t, err, "Key: '[0].Object.ResourceID' Error:Field validation for 'ResourceID' failed on the 'required' tag") + assert.Equal(t, http.StatusInternalServerError, status) + assert.EqualError(t, err, "Key: '[0].Object.String()' Error:Field validation for 'ResourceID' failed on the 'required' tag") + + */ } func TestSetPolicy_IgnoresAllPrincipalIdsNotFound(t *testing.T) { @@ -202,9 +203,7 @@ func TestSetPolicy_IgnoresAllPrincipalIdsNotFound(t *testing.T) { Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs, "user:" + policytestsupport.UserEmailGetProfile}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, + Object: policytestsupport.PolicyObjectResourceId, }}) assert.NoError(t, err) @@ -232,9 +231,7 @@ func TestSetPolicy_IgnoresAnyNotFoundPrincipalId(t *testing.T) { Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs, "user:" + policytestsupport.UserEmailGetProfile}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, + Object: policytestsupport.PolicyObjectResourceId, }}) assert.NoError(t, err) @@ -259,9 +256,7 @@ func TestSetPolicy_AddAssignment_IgnoresInvalidAction(t *testing.T) { Subjects: []string{ "user:" + policytestsupport.UserEmailGetHrUs, "user:" + policytestsupport.UserEmailGetProfile}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, + Object: policytestsupport.PolicyObjectResourceId, }}) assert.NoError(t, err) @@ -286,9 +281,7 @@ func TestSetPolicy(t *testing.T) { Meta: hexapolicy.MetaInfo{Version: "0"}, Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, + Object: policytestsupport.PolicyObjectResourceId, }}) assert.NoError(t, err) @@ -313,9 +306,7 @@ func TestSetPolicy_RemovedAllMembers_FromOnePolicy(t *testing.T) { Meta: hexapolicy.MetaInfo{Version: "0"}, Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, + Object: policytestsupport.PolicyObjectResourceId, }}) assert.NoError(t, err) @@ -343,17 +334,13 @@ func TestSetPolicy_RemovedAllMembers_FromAllPolicies(t *testing.T) { Meta: hexapolicy.MetaInfo{Version: "0"}, Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, + Object: policytestsupport.PolicyObjectResourceId, }, { Meta: hexapolicy.MetaInfo{Version: "0"}, Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetProfile}, Subjects: []string{}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, + Object: policytestsupport.PolicyObjectResourceId, }, }) @@ -383,17 +370,13 @@ func TestSetPolicy_MultipleAppRolePolicies(t *testing.T) { Meta: hexapolicy.MetaInfo{Version: "0"}, Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetHrUs}, Subjects: []string{"user:" + policytestsupport.UserEmailGetHrUs}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, + Object: policytestsupport.PolicyObjectResourceId, }, { Meta: hexapolicy.MetaInfo{Version: "0"}, Actions: []hexapolicy.ActionInfo{"azure:" + policytestsupport.ActionGetProfile}, Subjects: []string{"user:" + policytestsupport.UserEmailGetProfile}, - Object: hexapolicy.ObjectInfo{ - ResourceID: policytestsupport.PolicyObjectResourceId, - }, + Object: policytestsupport.PolicyObjectResourceId, }, }) diff --git a/providers/googlecloud/iapProvider/google_client_test.go b/providers/googlecloud/iapProvider/google_client_test.go index cae7073..3e89f4e 100644 --- a/providers/googlecloud/iapProvider/google_client_test.go +++ b/providers/googlecloud/iapProvider/google_client_test.go @@ -148,10 +148,9 @@ func TestGoogleClient_GetBackendPolicies_withBadJson(t *testing.T) { func TestGoogleClient_SetAppEnginePolicies(t *testing.T) { policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"roles/iap.httpsResourceAccessor"}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"roles/iap.httpsResourceAccessor"}, Subjects: []string{"aUser"}, Object: "anObjectId", } + mapper := gcpBind.New(map[string]string{}) bindPolicy, err := mapper.MapPolicyToBinding(policy) assert.NoError(t, err) @@ -179,9 +178,7 @@ func TestGoogleClient_SetAppEnginePolicies(t *testing.T) { func TestGoogleClient_SetBackendPolicies(t *testing.T) { policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"gcp:roles/iap.httpsResourceAccessor"}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"gcp:roles/iap.httpsResourceAccessor"}, Subjects: []string{"aUser"}, Object: "anObjectId", } mapper := gcpBind.New(map[string]string{}) bindPolicy, err := mapper.MapPolicyToBinding(policy) @@ -206,9 +203,7 @@ func TestGoogleClient_SetBackendPolicies(t *testing.T) { func TestGoogleClient_SetBackendPolicies_withRequestError(t *testing.T) { policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"gcp:roles/iap.httpsResourceAccessor"}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"gcp:roles/iap.httpsResourceAccessor"}, Subjects: []string{"aUser"}, Object: "anObjectId", } mapper := gcpBind.New(map[string]string{}) bindPolicy, err := mapper.MapPolicyToBinding(policy) diff --git a/providers/googlecloud/iapProvider/google_cloud_provider_test.go b/providers/googlecloud/iapProvider/google_cloud_provider_test.go index 09308b5..1dd283a 100644 --- a/providers/googlecloud/iapProvider/google_cloud_provider_test.go +++ b/providers/googlecloud/iapProvider/google_cloud_provider_test.go @@ -104,9 +104,7 @@ func TestGoogleProvider_GetPolicy(t *testing.T) { func TestGoogleProvider_SetPolicy(t *testing.T) { policy := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"anAction"}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, + Meta: hexapolicy.MetaInfo{Version: "aVersion"}, Actions: []hexapolicy.ActionInfo{"anAction"}, Subjects: []string{"aUser"}, Object: "anObjectId", } m := testsupport.NewMockHTTPClient() @@ -119,9 +117,7 @@ func TestGoogleProvider_SetPolicy(t *testing.T) { func TestGoogleProvider_SetPolicy_withInvalidArguments(t *testing.T) { missingMeta := hexapolicy.PolicyInfo{ - Actions: []hexapolicy.ActionInfo{"anAction"}, Subjects: []string{"aUser"}, Object: hexapolicy.ObjectInfo{ - ResourceID: "anObjectId", - }, + Actions: []hexapolicy.ActionInfo{"anAction"}, Subjects: []string{"aUser"}, Object: "anObjectId", } m := testsupport.NewMockHTTPClient() diff --git a/providers/openpolicyagent/http_bundle_client_test.go b/providers/openpolicyagent/http_bundle_client_test.go index 73ba82e..e7a7a92 100644 --- a/providers/openpolicyagent/http_bundle_client_test.go +++ b/providers/openpolicyagent/http_bundle_client_test.go @@ -32,9 +32,7 @@ const expectedBundleData = `{ "any", "anyauthenticated" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -49,9 +47,7 @@ const expectedBundleData = `{ "sales@hexaindustries.io", "marketing@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -64,9 +60,7 @@ const expectedBundleData = `{ "subjects:": [ "accounting@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -78,9 +72,7 @@ const expectedBundleData = `{ "subjects:": [ "humanresources@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" } ] }` diff --git a/providers/openpolicyagent/opa_provider.go b/providers/openpolicyagent/opa_provider.go index d898d86..bf1e668 100644 --- a/providers/openpolicyagent/opa_provider.go +++ b/providers/openpolicyagent/opa_provider.go @@ -129,8 +129,8 @@ func (o *OpaProvider) SetPolicyInfo(integration policyprovider.IntegrationInfo, // Assign a default policy id based on the resourceId. If not available, use the Pap ObjectID. An alias is appended to ensure uniqueness alias := generateAliasOfSize(3) resId := *meta.PapId - if p.Object.ResourceID != "" { - resId = p.Object.ResourceID + if p.Object != "" { + resId = p.Object.String() } pid := fmt.Sprintf("%s_%s", resId, alias) meta.PolicyId = &pid diff --git a/providers/openpolicyagent/opa_provider_test.go b/providers/openpolicyagent/opa_provider_test.go index 2466373..255dccc 100644 --- a/providers/openpolicyagent/opa_provider_test.go +++ b/providers/openpolicyagent/opa_provider_test.go @@ -305,9 +305,11 @@ func TestSetPolicyInfo(t *testing.T) { policyprovider.IntegrationInfo{Name: openpolicyagent.ProviderTypeOpa, Key: key}, policyprovider.ApplicationInfo{ObjectID: "anotherResourceId"}, []hexapolicy.PolicyInfo{ - {Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{"http:GET"}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ - ResourceID: "aResourceId", - }}, + { + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, + Actions: []hexapolicy.ActionInfo{"http:GET"}, + Subjects: []string{"allusers"}, + Object: "aResourceId"}, }, ) @@ -338,7 +340,7 @@ func TestSetPolicyInfo(t *testing.T) { assert.True(t, now.Before(*modified)) actionURi := policies[0].Actions[0].String() assert.Equal(t, "http:GET", actionURi) - assert.Equal(t, "aResourceId", policies[0].Object.ResourceID) + assert.Equal(t, "aResourceId", policies[0].Object.String()) // assert.JSONEq(t, `{"policies":[{"meta":{"version":"0.5"},"actions":[{"actionUri":"http:GET"}],"subject":{"members":["allusers"]},"object":{"resource_id":"anotherResourceId"}}]}`, string(readFile)) assert.Equal(t, "anotherResourceId", *meta.PapId) assert.Equal(t, hexapolicy.IdqlVersion, meta.Version, "check version") @@ -368,9 +370,10 @@ func TestSetPolicyInfo_withInvalidArguments(t *testing.T) { policyprovider.ApplicationInfo{ObjectID: "aResourceId"}, []hexapolicy.PolicyInfo{ { - Actions: []hexapolicy.ActionInfo{"http:GET"}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ - ResourceID: "aResourceId", - }}, + Actions: []hexapolicy.ActionInfo{"http:GET"}, + Subjects: []string{"allusers"}, + Object: "aResourceId", + }, }, ) @@ -422,9 +425,12 @@ func TestSetPolicyInfo_WithHTTPSBundleServer(t *testing.T) { policyprovider.IntegrationInfo{Name: openpolicyagent.ProviderTypeOpa, Key: key}, policyprovider.ApplicationInfo{ObjectID: "aResourceId"}, []hexapolicy.PolicyInfo{ - {Meta: hexapolicy.MetaInfo{Version: "0.5"}, Actions: []hexapolicy.ActionInfo{"http:GET"}, Subjects: []string{"allusers"}, Object: hexapolicy.ObjectInfo{ - ResourceID: "aResourceId", - }}, + { + Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Actions: []hexapolicy.ActionInfo{"http:GET"}, + Subjects: []string{"allusers"}, + Object: "aResourceId", + }, }, ) assert.Equal(t, http.StatusCreated, status) @@ -445,9 +451,7 @@ func TestMakeDefaultBundle(t *testing.T) { "anyauthenticated" ] }, - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" } ] }`) @@ -483,7 +487,7 @@ func TestMakeDefaultBundle(t *testing.T) { actionURi := policies[0].Actions[0].String() assert.Equal(t, "ietf:http:GET", actionURi) - assert.Equal(t, "aResourceId", policies[0].Object.ResourceID) + assert.Equal(t, "aResourceId", policies[0].Object.String()) // assert.JSONEq(t, `{"policies":[{"meta":{"version":"0.5"},"actions":[{"actionUri":"http:GET"}],"subject":{"members":["allusers"]},"object":{"resource_id":"anotherResourceId"}}]}`, string(readFile)) // assert.Equal(t, "anotherResourceId", *meta.PapId) assert.Equal(t, "0.6", meta.Version, "check version") diff --git a/providers/openpolicyagent/resources/bundles/bundle/data.json b/providers/openpolicyagent/resources/bundles/bundle/data.json index 5e8d880..267922e 100644 --- a/providers/openpolicyagent/resources/bundles/bundle/data.json +++ b/providers/openpolicyagent/resources/bundles/bundle/data.json @@ -12,9 +12,7 @@ "any", "anyauthenticated" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -29,9 +27,7 @@ "sales@hexaindustries.io", "marketing@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -44,9 +40,7 @@ "subjects:": [ "accounting@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" }, { "meta": { @@ -58,9 +52,7 @@ "subjects:": [ "humanresources@hexaindustries.io" ], - "object": { - "resource_id": "aResourceId" - } + "object": "aResourceId" } ] } \ No newline at end of file diff --git a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego index 5be5889..164ea63 100644 --- a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego +++ b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego @@ -143,14 +143,14 @@ check_http_match(actionUri, req) if { } object_match(object, _) if { - not object.resource_id + not object } object_match(object, req) if { - object.resource_id + object some request_uri in req.resourceIds - lower(object.resource_id) == lower(request_uri) + lower(object) == lower(request_uri) } check_http_method(allowMask, _) if { diff --git a/providers/openpolicyagent/test/mock_client.go b/providers/openpolicyagent/test/mock_client.go index 0f2f8ec..00f959c 100644 --- a/providers/openpolicyagent/test/mock_client.go +++ b/providers/openpolicyagent/test/mock_client.go @@ -1,25 +1,25 @@ -package openpolicyagent_test +package openpolicyagent const BundleTypeMock string = "Mock" type MockBundleClient struct { - GetResponse []byte - GetErr error - PostStatusCode int - PostErr error + GetResponse []byte + GetErr error + PostStatusCode int + PostErr error - ArgPostBundle []byte + ArgPostBundle []byte } func (m *MockBundleClient) Type() string { - return BundleTypeMock + return BundleTypeMock } func (m *MockBundleClient) GetDataFromBundle(_ string) ([]byte, error) { - return m.GetResponse, m.GetErr + return m.GetResponse, m.GetErr } func (m *MockBundleClient) PostBundle(bundle []byte) (int, error) { - m.ArgPostBundle = bundle - return m.PostStatusCode, m.PostErr + m.ArgPostBundle = bundle + return m.PostStatusCode, m.PostErr } diff --git a/providers/test/data/example_idql.json b/providers/test/data/example_idql.json index 5159b27..c6c7bc8 100644 --- a/providers/test/data/example_idql.json +++ b/providers/test/data/example_idql.json @@ -1,62 +1,66 @@ { "policies": [ { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/"}], - "subject": { - "members": [ - "allusers", "allauthenticated" - ] + "meta": { + "version": "0.7" }, + "actions": [ + { + "actionUri": "http:GET:/" + } + ], + "subjects": [ + "any" + ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "aResourceId1" - } + "object": "aResourceId1" }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/sales"}, {"actionUri": "http:GET:/marketing"}], - "subject": { - "members": [ - "allauthenticated", - "sales@hexaindustries.io", - "marketing@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, - "object": { - "resource_id": "aResourceId2" - } + "actions": [ + "http:GET:/sales", + "http:GET:/marketing" + ], + "subjects": [ + "anyauthenticated", + "user:sales@hexaindustries.io", + "User:marketing@hexaindustries.io" + ], + "object": "aResourceId2" }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/accounting"}, {"actionUri": "http:POST:/accounting"}], - "subject": { - "members": [ - "accounting@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, + "actions": [ + "http:GET:/accounting", + "http:POST:/accounting" + ], + "subjects": [ + "user:accounting@hexaindustries.io" + ], "condition": { "rule": "req.ip sw 127 and req.method eq POST", "action": "allow" }, - "object": { - "resource_id": "aResourceId3" - } + "object": "aResourceId3" }, { - "meta": {"version": "0.6"}, - "actions": [{"actionUri": "http:GET:/humanresources"}], - "subject": { - "members": [ - "humanresources@hexaindustries.io" - ] + "meta": { + "version": "0.7" }, - "object": { - "resource_id": "aResourceId1" - } + "actions": [ + "http:GET:/humanresources" + ], + "subjects": [ + "user:humanresources@hexaindustries.io" + ], + "object": "aResourceId1" } ] } \ No newline at end of file From 66d5a976e36b5147841ed9dd10068de1c70102bf Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Mon, 9 Sep 2024 13:48:02 -0700 Subject: [PATCH 07/13] Issue #59, Condition Equals now compares equivalent AST trees, cleaned up interface reference issues with Expressions Signed-off-by: Phil Hunt --- .../cedarConditions/map_hexa.go | 10 +- .../gcpcel/gcp_condition_mapper.go | 58 +- pkg/hexapolicy/conditions/conditions.go | 299 ++++--- pkg/hexapolicy/conditions/conditions_test.go | 65 ++ pkg/hexapolicy/conditions/parser/parser.go | 752 +++++++++--------- .../conditions/parser/parser_test.go | 4 +- pkg/hexapolicy/conditions/parser/types.go | 24 + pkg/hexapolicy/hexa_policy.go | 22 +- pkg/hexapolicy/hexa_policy_test.go | 30 +- 9 files changed, 708 insertions(+), 556 deletions(-) diff --git a/models/conditionLangs/cedarConditions/map_hexa.go b/models/conditionLangs/cedarConditions/map_hexa.go index 4faf520..6aadccd 100644 --- a/models/conditionLangs/cedarConditions/map_hexa.go +++ b/models/conditionLangs/cedarConditions/map_hexa.go @@ -83,7 +83,7 @@ func (mapper *CedarConditionMapper) MapConditionToCedar(condition *conditions.Co if err != nil { return "", err } - root := *ast + root := ast err = checkCompatibility(root) if err != nil { @@ -94,7 +94,7 @@ func (mapper *CedarConditionMapper) MapConditionToCedar(condition *conditions.Co if condition.Action == conditions.ADeny { isUnless = true } - // This logic needs to decide whether to start with multiple whens/unlesses + // This logic needs to decide whether to start with multiple when/unless phrases // we have a lot of layers of ands/ors need to split into multiple clauses switch exp := root.(type) { @@ -210,7 +210,7 @@ func (mapper *CedarConditionMapper) mapFilterExpression(node hexaParser.Expressi } -func (mapper *CedarConditionMapper) mapFilterNot(notFilter *hexaParser.NotExpression, isChild bool) string { +func (mapper *CedarConditionMapper) mapFilterNot(notFilter *hexaParser.NotExpression, _ bool) string { subExpression := notFilter.Expression var clause string switch subFilter := subExpression.(type) { @@ -224,8 +224,8 @@ func (mapper *CedarConditionMapper) mapFilterNot(notFilter *hexaParser.NotExpres return fmt.Sprintf("!( %v )", clause) } -func (mapper *CedarConditionMapper) mapFilterPrecedence(pfilter *hexaParser.PrecedenceExpression, isChild bool) string { - subExpression := pfilter.Expression +func (mapper *CedarConditionMapper) mapFilterPrecedence(precedenceExpression *hexaParser.PrecedenceExpression, _ bool) string { + subExpression := precedenceExpression.Expression var clause string switch subFilter := subExpression.(type) { case hexaParser.LogicalExpression: diff --git a/models/conditionLangs/gcpcel/gcp_condition_mapper.go b/models/conditionLangs/gcpcel/gcp_condition_mapper.go index df7c99e..cf1d2d5 100644 --- a/models/conditionLangs/gcpcel/gcp_condition_mapper.go +++ b/models/conditionLangs/gcpcel/gcp_condition_mapper.go @@ -46,31 +46,28 @@ func (mapper *GoogleConditionMapper) MapConditionToProvider(condition conditions } -func (mapper *GoogleConditionMapper) MapFilter(ast *parser.Expression) (string, error) { - err := checkCompatibility(*ast) +func (mapper *GoogleConditionMapper) MapFilter(ast parser.Expression) (string, error) { + err := checkCompatibility(ast) if err != nil { return "", err } return mapper.mapFilterInternal(ast, false), nil } -func (mapper *GoogleConditionMapper) mapFilterInternal(ast *parser.Expression, isChild bool) string { +func (mapper *GoogleConditionMapper) mapFilterInternal(ast parser.Expression, isChild bool) string { - // dereference - deref := *ast - - switch element := deref.(type) { + switch element := ast.(type) { case parser.NotExpression: - return mapper.mapFilterNot(&element, isChild) + return mapper.mapFilterNot(element, isChild) case parser.PrecedenceExpression: - return mapper.mapFilterPrecedence(&element, true) + return mapper.mapFilterPrecedence(element, true) case parser.LogicalExpression: - return mapper.mapFilterLogical(&element, isChild) + return mapper.mapFilterLogical(element, isChild) default: - attrExpression := deref.(parser.AttributeExpression) - return mapper.mapFilterAttrExpr(&attrExpression) + attrExpression := ast.(parser.AttributeExpression) + return mapper.mapFilterAttrExpr(attrExpression) } // return mapTool.mapFilterValuePath(deref.(idqlCondition.ValuePathExpression)) } @@ -85,38 +82,38 @@ func (mapTool *GoogleConditionMapper) mapFilterValuePath(vpFilter idqlCondition. } */ -func (mapper *GoogleConditionMapper) mapFilterNot(notFilter *parser.NotExpression, isChild bool) string { +func (mapper *GoogleConditionMapper) mapFilterNot(notFilter parser.NotExpression, _ bool) string { subExpression := notFilter.Expression var celFilter string switch subFilter := subExpression.(type) { case parser.LogicalExpression: // For the purpose of a not idqlCondition, the logical expression is not a child - celFilter = mapper.mapFilterLogical(&subFilter, false) + celFilter = mapper.mapFilterLogical(subFilter, false) celFilter = "(" + celFilter + ")" break default: - celFilter = mapper.mapFilterInternal(&subFilter, false) + celFilter = mapper.mapFilterInternal(subFilter, false) } return fmt.Sprintf("!%v", celFilter) } -func (mapper *GoogleConditionMapper) mapFilterPrecedence(pfilter *parser.PrecedenceExpression, isChild bool) string { +func (mapper *GoogleConditionMapper) mapFilterPrecedence(pfilter parser.PrecedenceExpression, _ bool) string { subExpression := pfilter.Expression var celFilter string switch subFilter := subExpression.(type) { case parser.LogicalExpression: // For the purpose of a not idqlCondition, the logical expression is not a child - celFilter = mapper.mapFilterLogical(&subFilter, false) + celFilter = mapper.mapFilterLogical(subFilter, false) celFilter = "(" + celFilter + ")" break default: - celFilter = mapper.mapFilterInternal(&subFilter, false) + celFilter = mapper.mapFilterInternal(subFilter, false) } return fmt.Sprintf("%v", celFilter) } -func (mapper *GoogleConditionMapper) mapFilterLogical(logicFilter *parser.LogicalExpression, isChild bool) string { +func (mapper *GoogleConditionMapper) mapFilterLogical(logicFilter parser.LogicalExpression, isChild bool) string { isDouble := false var celLeft, celRight string switch subFilter := logicFilter.Left.(type) { @@ -126,9 +123,9 @@ func (mapper *GoogleConditionMapper) mapFilterLogical(logicFilter *parser.Logica } } - celLeft = mapper.mapFilterInternal(&logicFilter.Left, !isDouble) + celLeft = mapper.mapFilterInternal(logicFilter.Left, !isDouble) - celRight = mapper.mapFilterInternal(&logicFilter.Right, !isDouble) + celRight = mapper.mapFilterInternal(logicFilter.Right, !isDouble) switch logicFilter.Operator { default: @@ -143,7 +140,7 @@ func (mapper *GoogleConditionMapper) mapFilterLogical(logicFilter *parser.Logica } } -func (mapper *GoogleConditionMapper) mapFilterAttrExpr(attrExpr *parser.AttributeExpression) string { +func (mapper *GoogleConditionMapper) mapFilterAttrExpr(attrExpr parser.AttributeExpression) string { compareValue := prepareValue(attrExpr) mapPath := mapper.NameMapper.GetProviderAttributeName(attrExpr.AttributePath) @@ -179,7 +176,7 @@ func (mapper *GoogleConditionMapper) mapFilterAttrExpr(attrExpr *parser.Attribut /* If the value type is string, it needs to be quoted. */ -func prepareValue(attrExpr *parser.AttributeExpression) string { +func prepareValue(attrExpr parser.AttributeExpression) string { compValue := attrExpr.CompareValue if compValue == "" { return "" @@ -206,15 +203,20 @@ func (mapper *GoogleConditionMapper) MapProviderToCondition(expression string) ( if issues != nil { return conditions.ConditionInfo{}, errors.New("CEL Mapping Error: " + issues.String()) } - - idqlAst, err := mapper.mapCelExpr(celAst.Expr(), false) + parsedAst, err := cel.AstToParsedExpr(celAst) + if err != nil { + return conditions.ConditionInfo{ + Rule: "", + }, errors.New("IDQL condition mapTool error: " + err.Error()) + } + idqlAst, err := mapper.mapCelExpr(parsedAst.GetExpr(), false) if err != nil { return conditions.ConditionInfo{ Rule: "", }, errors.New("IDQL condition mapTool error: " + err.Error()) } - condString := conditions.SerializeExpression(&idqlAst) + condString := conditions.SerializeExpression(idqlAst) return conditions.ConditionInfo{ Rule: condString, @@ -390,7 +392,7 @@ func (mapper *GoogleConditionMapper) mapCelAttrCompare(expressions []*expr.Expr, return attrFilter, nil } -func (mapper *GoogleConditionMapper) mapCelNot(expressions []*expr.Expr, isChild bool) parser.Expression { +func (mapper *GoogleConditionMapper) mapCelNot(expressions []*expr.Expr, _ bool) parser.Expression { expression, _ := mapper.mapCelExpr(expressions[0], false) // ischild is ignored because of not @@ -400,7 +402,7 @@ func (mapper *GoogleConditionMapper) mapCelNot(expressions []*expr.Expr, isChild return notFilter } -func (mapper *GoogleConditionMapper) mapCelLogical(expressions []*expr.Expr, isAnd bool, isChild bool) (parser.Expression, error) { +func (mapper *GoogleConditionMapper) mapCelLogical(expressions []*expr.Expr, isAnd bool, _ bool) (parser.Expression, error) { filters := make([]parser.Expression, len(expressions)) var err error // collapse n clauses back into a series of nested pairwise and/or clauses diff --git a/pkg/hexapolicy/conditions/conditions.go b/pkg/hexapolicy/conditions/conditions.go index 6bb0af5..42b26bf 100644 --- a/pkg/hexapolicy/conditions/conditions.go +++ b/pkg/hexapolicy/conditions/conditions.go @@ -1,172 +1,231 @@ package conditions import ( - "fmt" + "fmt" + "sort" - "strings" + "strings" - conditionparser "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions/parser" + conditionparser "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions/parser" ) const ( - AAllow string = "allow" - ADeny string = "deny" - AAudit string = "audit" + AAllow string = "allow" + ADeny string = "deny" + AAudit string = "audit" ) type ConditionInfo struct { - Rule string `json:"Rule,omitempty" validate:"required"` // in RFC7644 idqlCondition form - Action string `json:"Action,omitempty"` // allow/deny/audit default is allow + Rule string `json:"Rule,omitempty" validate:"required"` // in RFC7644 idqlCondition form + Action string `json:"Action,omitempty"` // allow/deny/audit default is allow } -// Equals does a simple string compare (no semantic compare for equivalency) +// Equals performs an AST level compare to test filters are equivalent. NOTE: does not test equivalent attribute expressions at this time +// e.g. level < 5 vs. not(level >= 5) will return as unequal though logically equal. So while a true is always correct, some equivalent expressions will report false func (c *ConditionInfo) Equals(compare *ConditionInfo) bool { - // first just do a simple compare - if compare == nil { - return false - } - - if c.Action != compare.Action { - return false - } - if c.Rule == compare.Rule { - return true - } - - // try re-parsing to get consistent spacing etc. - expression, err := conditionparser.ParseFilter(c.Rule) - compareExpression, err2 := conditionparser.ParseFilter(compare.Rule) - if err != nil || err2 != nil { - return false - } - - ast1 := *expression - exp1 := ast1.String() - ast2 := *compareExpression - exp2 := ast2.String() + // first just do a simple compare + if compare == nil { + return false + } + + if c.Action != compare.Action { + return false + } + if c.Rule == compare.Rule { + return true + } + + // try re-parsing to get consistent spacing etc. + expression, err := conditionparser.ParseFilter(c.Rule) + compareExpression, err2 := conditionparser.ParseFilter(compare.Rule) + if err != nil || err2 != nil { + return false + } + + ch1 := make(chan string) + ch2 := make(chan string) + var seq1 []string + var seq2 []string + go compareWalk(expression, ch1) + go compareWalk(compareExpression, ch2) + + for item := range ch1 { + seq1 = append(seq1, item) + } + for item := range ch2 { + seq2 = append(seq2, item) + } + sort.Strings(seq1) + sort.Strings(seq2) + + return slicesAreEqual(seq1, seq2) + /* + exp1 := SerializeExpression(expression) + exp2 := SerializeExpression(compareExpression) + + return exp1 == exp2 + + */ +} - return exp1 == exp2 +func slicesAreEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if !strings.EqualFold(v, b[i]) { + return false + } + } + return true } type AttributeMap struct { - forward map[string]string - reverse map[string]string + forward map[string]string + reverse map[string]string } type NameMapper interface { - // GetProviderAttributeName returns a simple string representation of the mapped attribute name (usually in name[.sub-attribute] form). - GetProviderAttributeName(hexaName string) string + // GetProviderAttributeName returns a simple string representation of the mapped attribute name (usually in name[.sub-attribute] form). + GetProviderAttributeName(hexaName string) string - // GetHexaFilterAttributePath returns a filterAttributePath which is used to build a SCIM Filter AST - GetHexaFilterAttributePath(provName string) string + // GetHexaFilterAttributePath returns a filterAttributePath which is used to build a SCIM Filter AST + GetHexaFilterAttributePath(provName string) string } type ConditionMapper interface { - /* - MapConditionToProvider takes an IDQL Condition expression and converts it to a string - usable the target provider. For example from RFC7644, Section-3.4.2.2 to Google Common Expression Language - */ - MapConditionToProvider(condition ConditionInfo) interface{} - - /* - MapProviderToCondition take a string expression from a platform policy and converts it to RFC7644: Section-3.4.2.2. - */ - MapProviderToCondition(expression string) (ConditionInfo, error) + /* + MapConditionToProvider takes an IDQL Condition expression and converts it to a string + usable the target provider. For example from RFC7644, Section-3.4.2.2 to Google Common Expression Language + */ + MapConditionToProvider(condition ConditionInfo) interface{} + + /* + MapProviderToCondition take a string expression from a platform policy and converts it to RFC7644: Section-3.4.2.2. + */ + MapProviderToCondition(expression string) (ConditionInfo, error) } // NewNameMapper is called by a condition mapTool provider to instantiate an attribute name translator using interface NameMapper func NewNameMapper(attributeMap map[string]string) *AttributeMap { - reverse := make(map[string]string, len(attributeMap)) - forward := make(map[string]string, len(attributeMap)) - for k, v := range attributeMap { - reverse[strings.ToLower(v)] = k - forward[strings.ToLower(k)] = v - } - - return &AttributeMap{ - forward: forward, - reverse: reverse, - } + reverse := make(map[string]string, len(attributeMap)) + forward := make(map[string]string, len(attributeMap)) + for k, v := range attributeMap { + reverse[strings.ToLower(v)] = k + forward[strings.ToLower(k)] = v + } + + return &AttributeMap{ + forward: forward, + reverse: reverse, + } } func (n *AttributeMap) GetProviderAttributeName(hexaName string) string { - val, exists := n.forward[strings.ToLower(hexaName)] - if exists { - return val - } - return hexaName + val, exists := n.forward[strings.ToLower(hexaName)] + if exists { + return val + } + return hexaName } func (n *AttributeMap) GetHexaFilterAttributePath(provName string) string { - val, exists := n.reverse[provName] - if !exists { - val = provName - } - return val + val, exists := n.reverse[provName] + if !exists { + val = provName + } + return val } // ParseConditionRuleAst is used by mapping providers to get the IDQL condition rule AST tree -func ParseConditionRuleAst(condition ConditionInfo) (*conditionparser.Expression, error) { - return conditionparser.ParseFilter(condition.Rule) +func ParseConditionRuleAst(condition ConditionInfo) (conditionparser.Expression, error) { + return conditionparser.ParseFilter(condition.Rule) } -func ParseExpressionAst(expression string) (*conditionparser.Expression, error) { - return conditionparser.ParseFilter(expression) +func ParseExpressionAst(expression string) (conditionparser.Expression, error) { + return conditionparser.ParseFilter(expression) } // SerializeExpression walks the AST and emits the condition in string form. It preserves precedence over the normal idqlCondition.String() method -func SerializeExpression(ast *conditionparser.Expression) string { - - return walk(*ast, false) +func SerializeExpression(ast conditionparser.Expression) string { + return walk(ast, false) } func checkNestedLogic(e conditionparser.Expression, op conditionparser.LogicalOperator) string { - // if the child is a repeat of the parent eliminate brackets (e.g. a or b or c) - - switch v := e.(type) { - case conditionparser.PrecedenceExpression: - e = v.Expression - } + // if the child is a repeat of the parent eliminate brackets (e.g. a or b or c) + + switch v := e.(type) { + case conditionparser.PrecedenceExpression: + e = v.Expression + } + + switch v := e.(type) { + case conditionparser.LogicalExpression: + if v.Operator == op { + return walk(e, false) + } else { + return walk(e, true) + } + + default: + return walk(e, true) + } +} - switch v := e.(type) { - case conditionparser.LogicalExpression: - if v.Operator == op { - return walk(e, false) - } else { - return walk(e, true) - } +func compareWalk(e conditionparser.Expression, ch chan string) { + defer close(ch) + if e != nil { + compareWalkRecursively(e, ch) + } +} - default: - return walk(e, true) - } +func compareWalkRecursively(e conditionparser.Expression, ch chan string) { + if e != nil { + val := e.Dif() + if val != "" { + ch <- val + } + switch exp := e.(type) { + case conditionparser.PrecedenceExpression: + compareWalkRecursively(exp.Expression, ch) + case conditionparser.LogicalExpression: + compareWalkRecursively(exp.Left, ch) + compareWalkRecursively(exp.Right, ch) + case conditionparser.NotExpression: + compareWalkRecursively(exp.Expression, ch) + default: // conditionparser.AttributeExpression etc. + + } + } + return } func walk(e conditionparser.Expression, isChild bool) string { - switch v := e.(type) { - case conditionparser.LogicalExpression: - lhVal := checkNestedLogic(v.Left, v.Operator) - - rhVal := checkNestedLogic(v.Right, v.Operator) - - if isChild && v.Operator == conditionparser.OR { - return fmt.Sprintf("(%v or %v)", lhVal, rhVal) - } else { - return fmt.Sprintf("%v %v %v", lhVal, v.Operator, rhVal) - } - case conditionparser.NotExpression: - subExpression := v.Expression - // Note, because of not() brackets, can treat as top level - subExpressionString := walk(subExpression, false) - - return fmt.Sprintf("not(%v)", subExpressionString) - case conditionparser.PrecedenceExpression: - subExpressionString := walk(v.Expression, false) - - return fmt.Sprintf("(%v)", subExpressionString) - case conditionparser.ValuePathExpression: - return walk(v.VPathFilter, true) - // case idqlCondition.AttributeExpression: - default: - return v.String() - } + switch v := e.(type) { + case conditionparser.LogicalExpression: + lhVal := checkNestedLogic(v.Left, v.Operator) + + rhVal := checkNestedLogic(v.Right, v.Operator) + + if isChild && v.Operator == conditionparser.OR { + return fmt.Sprintf("(%v or %v)", lhVal, rhVal) + } else { + return fmt.Sprintf("%v %v %v", lhVal, v.Operator, rhVal) + } + case conditionparser.NotExpression: + subExpression := v.Expression + // Note, because of not() brackets, can treat as top level + subExpressionString := walk(subExpression, false) + + return fmt.Sprintf("not(%v)", subExpressionString) + case conditionparser.PrecedenceExpression: + subExpressionString := walk(v.Expression, false) + + return fmt.Sprintf("(%v)", subExpressionString) + case conditionparser.ValuePathExpression: + return walk(v.VPathFilter, true) + // case idqlCondition.AttributeExpression: + default: + return v.String() + } } diff --git a/pkg/hexapolicy/conditions/conditions_test.go b/pkg/hexapolicy/conditions/conditions_test.go index c187a31..18cd85c 100644 --- a/pkg/hexapolicy/conditions/conditions_test.go +++ b/pkg/hexapolicy/conditions/conditions_test.go @@ -92,3 +92,68 @@ func TestWalker(t *testing.T) { back2 := conditions.SerializeExpression(ast) fmt.Println(back2) } + +func TestEquals(t *testing.T) { + + tests := []struct { + Name string + R1 string + R2 string + Expected bool + }{ + { + "Same with Not", + "level gt 6 and not(expired eq true)", + "level gt 6 and not(expired eq true)", + true, + }, + { + "Same with precedence", + "level gt 6 and (expired eq true)", + "level gt 6 and expired eq true", + true, + }, + { + "Same but reverse", + "level gt 6 and not(expired eq true)", + " not(expired eq true) and level gt 6 ", + true, + }, + { + "Not same", + "level gt 6 and not(expired eq true)", + "(expired eq true) and level gt 6", + false, + }, + { + "Same different not", + "level gt 6 and not(expired eq true)", + "not(level gt 6) and expired eq true", + false, + }, + { + "Lots of logical", + "(level gt 5 or test eq \"abc\" or level lt 10) and (username sw \"emp\" or username eq \"guest\")", + "(username eq \"guest\" or username sw \"emp\") and (level gt 5 or test eq \"abc\" or level lt 10)", + true, + }, + { + "Switched operator ", + "(level gt 5 or test eq \"abc\" or level lt 10) and (username sw \"emp\" or username eq \"guest\")", + "(username sw \"emp\" or username eq \"guest\") or (level gt 5 or test eq \"abc\" or level lt 10)", + false, + }, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + fmt.Println(fmt.Sprintf("R1:\t%s", test.R1)) + fmt.Println(fmt.Sprintf("R2:\t%s", test.R2)) + condition1 := conditions.ConditionInfo{Rule: test.R1} + condition2 := conditions.ConditionInfo{Rule: test.R2} + + assert.Equal(t, test.Expected, condition1.Equals(&condition2), "Check expected result matches: %s", test.Expected) + }) + } + +} diff --git a/pkg/hexapolicy/conditions/parser/parser.go b/pkg/hexapolicy/conditions/parser/parser.go index e6372f9..de9478b 100644 --- a/pkg/hexapolicy/conditions/parser/parser.go +++ b/pkg/hexapolicy/conditions/parser/parser.go @@ -1,404 +1,406 @@ package parser import ( - "errors" + "errors" - "strings" + "strings" ) -func ParseFilter(expression string) (*Expression, error) { - return parseFilterSub(expression, "") +// ParseFilter parses a SCIM-like (RFC7644) filter expression string and returns an AST as an Expression +func ParseFilter(expression string) (Expression, error) { + return parseFilterSub(expression, "") } -func parseFilterSub(expression string, parentAttr string) (*Expression, error) { - bracketCount := 0 - bracketIndex := -1 - valPathCnt := 0 - vPathStartIndex := -1 - wordIndex := -1 - var clauses []*Expression - cond := "" +// parseFilterSub is the main lexer for converting strings into Expressions +func parseFilterSub(expression string, parentAttr string) (Expression, error) { + bracketCount := 0 + bracketIndex := -1 + valPathCnt := 0 + vPathStartIndex := -1 + wordIndex := -1 + var clauses []Expression + cond := "" - isLogic := false - isAnd := false - isNot := false - isAttr := false - attr := "" - isExpr := false - isValue := false - value := "" - isQuote := false + isLogic := false + isAnd := false + isNot := false + isAttr := false + attr := "" + isExpr := false + isValue := false + value := "" + isQuote := false - expRunes := []rune(expression) - var charPos int - for charPos = 0; charPos < len(expRunes); charPos++ { + expRunes := []rune(expression) + var charPos int + for charPos = 0; charPos < len(expRunes); charPos++ { - c := expRunes[charPos] - switch c { - case '(': - if isQuote || isValue { - break - } - bracketCount++ - if bracketCount == 1 { - bracketIndex = charPos - } - charPos++ - quotedBracket := false - for charPos < len(expRunes) && bracketCount > 0 { - cc := expRunes[charPos] - switch cc { - case '"': - quotedBracket = !quotedBracket - break - case '(': - if quotedBracket { - break - } - bracketCount++ - break - case ')': - // ignore brackets in values - if quotedBracket { - break - } - bracketCount-- - if bracketCount == 0 { - subExpression := expression[bracketIndex+1 : charPos] - subFilter, err := parseFilterSub(subExpression, parentAttr) - if err != nil { - return nil, err - } - var filter Expression - sFilter := *subFilter - switch sFilter.(type) { - case AttributeExpression: + c := expRunes[charPos] + switch c { + case '(': + if isQuote || isValue { + break + } + bracketCount++ + if bracketCount == 1 { + bracketIndex = charPos + } + charPos++ + quotedBracket := false + for charPos < len(expRunes) && bracketCount > 0 { + cc := expRunes[charPos] + switch cc { + case '"': + quotedBracket = !quotedBracket + break + case '(': + if quotedBracket { + break + } + bracketCount++ + break + case ')': + // ignore brackets in values + if quotedBracket { + break + } + bracketCount-- + if bracketCount == 0 { + subExpression := expression[bracketIndex+1 : charPos] + subFilter, err := parseFilterSub(subExpression, parentAttr) + if err != nil { + return nil, err + } + var filter Expression + sFilter := subFilter + switch sFilter.(type) { + case AttributeExpression: - if isNot { - filter = NotExpression{ - Expression: sFilter, - } - } else { - filter = PrecedenceExpression{Expression: sFilter} - } - clauses = append(clauses, &filter) + if isNot { + filter = NotExpression{ + Expression: sFilter, + } + } else { + filter = PrecedenceExpression{Expression: sFilter} + } + clauses = append(clauses, filter) - default: - if isNot { - filter = NotExpression{Expression: sFilter} - clauses = append(clauses, &filter) - } else { - filter = PrecedenceExpression{Expression: sFilter} - clauses = append(clauses, &filter) - } - } - bracketIndex = -1 - } + default: + if isNot { + filter = NotExpression{Expression: sFilter} + clauses = append(clauses, filter) + } else { + filter = PrecedenceExpression{Expression: sFilter} + clauses = append(clauses, filter) + } + } + bracketIndex = -1 + } - } - if bracketCount > 0 { - charPos++ - } - } - break - case '[': - if isQuote || isValue { - break - } - valPathCnt++ - if valPathCnt == 1 { - vPathStartIndex = charPos - } - charPos++ - quotedSqBracket := false - for charPos < len(expression) && valPathCnt > 0 { - cc := expRunes[charPos] - switch cc { - case '"': - quotedSqBracket = !quotedSqBracket - break - case '[': - if quotedSqBracket { - break - } - if valPathCnt >= 1 { - return nil, errors.New("invalid IDQL idqlCondition: A second '[' was detected while looking for a ']' in a value path idqlCondition") - } - valPathCnt++ - break - case ']': - if quotedSqBracket { - break - } - valPathCnt-- - if valPathCnt == 0 { - name := expression[wordIndex:vPathStartIndex] - valueFilterStr := expression[vPathStartIndex+1 : charPos] - subExpression, err := parseFilterSub(valueFilterStr, "") - if err != nil { - return nil, err - } - var filter Expression - filter = ValuePathExpression{ - Attribute: name, - VPathFilter: *subExpression, - } - clauses = append(clauses, &filter) + } + if bracketCount > 0 { + charPos++ + } + } + break + case '[': + if isQuote || isValue { + break + } + valPathCnt++ + if valPathCnt == 1 { + vPathStartIndex = charPos + } + charPos++ + quotedSqBracket := false + for charPos < len(expression) && valPathCnt > 0 { + cc := expRunes[charPos] + switch cc { + case '"': + quotedSqBracket = !quotedSqBracket + break + case '[': + if quotedSqBracket { + break + } + if valPathCnt >= 1 { + return nil, errors.New("invalid IDQL idqlCondition: A second '[' was detected while looking for a ']' in a value path idqlCondition") + } + valPathCnt++ + break + case ']': + if quotedSqBracket { + break + } + valPathCnt-- + if valPathCnt == 0 { + name := expression[wordIndex:vPathStartIndex] + valueFilterStr := expression[vPathStartIndex+1 : charPos] + subExpression, err := parseFilterSub(valueFilterStr, "") + if err != nil { + return nil, err + } + var filter Expression + filter = ValuePathExpression{ + Attribute: name, + VPathFilter: subExpression, + } + clauses = append(clauses, filter) - // This code checks for text after ] ... in future attr[type eq value].subattr may be permissible - if charPos+1 < len(expression) && expRunes[charPos+1] != ' ' { - return nil, errors.New("invalid IDQL idqlCondition: expecting space after ']' in value path expression") - /* - charPos++ - for charPos < len(expression) && expRunes[charPos] != ' ' { - charPos++ - } - */ - } - // reset for the next phrase - vPathStartIndex = -1 - wordIndex = -1 - isAttr = false - } - default: - } - // only increment if we are still processing ( ) phrases - if valPathCnt > 0 { - charPos++ - } - } - if charPos == len(expression) && valPathCnt > 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing close ']' bracket") - } - break + // This code checks for text after ] ... in future attr[type eq value].subattr may be permissible + if charPos+1 < len(expression) && expRunes[charPos+1] != ' ' { + return nil, errors.New("invalid IDQL idqlCondition: expecting space after ']' in value path expression") + /* + charPos++ + for charPos < len(expression) && expRunes[charPos] != ' ' { + charPos++ + } + */ + } + // reset for the next phrase + vPathStartIndex = -1 + wordIndex = -1 + isAttr = false + } + default: + } + // only increment if we are still processing ( ) phrases + if valPathCnt > 0 { + charPos++ + } + } + if charPos == len(expression) && valPathCnt > 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing close ']' bracket") + } + break - case ' ': - if isQuote { - break - } - // end of phrase - if wordIndex > -1 { - phrase := expression[wordIndex:charPos] - if strings.EqualFold(phrase, "or") || strings.EqualFold(phrase, "and") { - isLogic = true - isAnd = strings.EqualFold(phrase, "and") - wordIndex = -1 - break - } - if isAttr && attr == "" { - attr = phrase - wordIndex = -1 - } else { - if isExpr && cond == "" { - cond = phrase - wordIndex = -1 - if strings.EqualFold(cond, "pr") { - var attrFilter Expression - attrFilter = AttributeExpression{ - AttributePath: attr, - Operator: CompareOperator("pr"), - } - attr = "" - isAttr = false - cond = "" - isExpr = false - isValue = false - clauses = append(clauses, &attrFilter) - } - } else { - if isValue { - value = phrase - if strings.HasSuffix(value, ")") && bracketCount == 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") - } - if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") { - value = value[1 : len(value)-1] - } - wordIndex = -1 - filterAttr := attr - if parentAttr != "" { - filterAttr = parentAttr + "." + attr - } + case ' ': + if isQuote { + break + } + // end of phrase + if wordIndex > -1 { + phrase := expression[wordIndex:charPos] + if strings.EqualFold(phrase, "or") || strings.EqualFold(phrase, "and") { + isLogic = true + isAnd = strings.EqualFold(phrase, "and") + wordIndex = -1 + break + } + if isAttr && attr == "" { + attr = phrase + wordIndex = -1 + } else { + if isExpr && cond == "" { + cond = phrase + wordIndex = -1 + if strings.EqualFold(cond, "pr") { + var attrFilter Expression + attrFilter = AttributeExpression{ + AttributePath: attr, + Operator: "pr", + } + attr = "" + isAttr = false + cond = "" + isExpr = false + isValue = false + clauses = append(clauses, attrFilter) + } + } else { + if isValue { + value = phrase + if strings.HasSuffix(value, ")") && bracketCount == 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") + } + if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") { + value = value[1 : len(value)-1] + } + wordIndex = -1 + filterAttr := attr + if parentAttr != "" { + filterAttr = parentAttr + "." + attr + } - var attrFilter Expression - attrFilter, err := createExpression(filterAttr, cond, value) - if err != nil { - return nil, err - } + var attrFilter Expression + attrFilter, err := createExpression(filterAttr, cond, value) + if err != nil { + return nil, err + } - attr = "" - isAttr = false - cond = "" - isExpr = false - isValue = false - clauses = append(clauses, &attrFilter) - break - } - } - } - } - break - case ')': - if isQuote || isValue { - break - } - if bracketCount == 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") - } - break - case ']': - if isQuote || isValue { - break - } - if valPathCnt == 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing open '[' bracket") - } - case 'n', 'N': - if !isValue { - if charPos+3 < len(expression) && - strings.EqualFold(expression[charPos:charPos+3], "not") { - isNot = true - charPos = charPos + 2 - break - } - } + attr = "" + isAttr = false + cond = "" + isExpr = false + isValue = false + clauses = append(clauses, attrFilter) + break + } + } + } + } + break + case ')': + if isQuote || isValue { + break + } + if bracketCount == 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") + } + break + case ']': + if isQuote || isValue { + break + } + if valPathCnt == 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing open '[' bracket") + } + case 'n', 'N': + if !isValue { + if charPos+3 < len(expression) && + strings.EqualFold(expression[charPos:charPos+3], "not") { + isNot = true + charPos = charPos + 2 + break + } + } - // we want this to fall through to default in case it is an attribute starting with n - if wordIndex == -1 { - wordIndex = charPos - } - if !isAttr { - isAttr = true - } else { - if !isExpr && attr != "" { - isExpr = true - } else { - if !isValue && cond != "" { - isValue = true - } - } - } - break - default: - if c == '"' { - isQuote = !isQuote - } - if wordIndex == -1 { - wordIndex = charPos - } - if !isAttr { - isAttr = true - } else { - if !isExpr && attr != "" { - isExpr = true - } else { - if !isValue && cond != "" { - isValue = true - } - } - } - } - // combine logic here - if isLogic && len(clauses) == 2 { - var oper LogicalOperator - if isAnd { - oper = "and" - } else { - oper = "or" - } - var filter Expression - filter = LogicalExpression{ - Operator: oper, - Left: *clauses[0], - Right: *clauses[1], - } - clauses = []*Expression{} - clauses = append(clauses, &filter) - isLogic = false - } - } + // we want this to fall through to default in case it is an attribute starting with n + if wordIndex == -1 { + wordIndex = charPos + } + if !isAttr { + isAttr = true + } else { + if !isExpr && attr != "" { + isExpr = true + } else { + if !isValue && cond != "" { + isValue = true + } + } + } + break + default: + if c == '"' { + isQuote = !isQuote + } + if wordIndex == -1 { + wordIndex = charPos + } + if !isAttr { + isAttr = true + } else { + if !isExpr && attr != "" { + isExpr = true + } else { + if !isValue && cond != "" { + isValue = true + } + } + } + } + // combine logic here + if isLogic && len(clauses) == 2 { + var oper LogicalOperator + if isAnd { + oper = "and" + } else { + oper = "or" + } + var filter Expression + filter = LogicalExpression{ + Operator: oper, + Left: clauses[0], + Right: clauses[1], + } + clauses = []Expression{} + clauses = append(clauses, filter) + isLogic = false + } + } - if bracketCount > 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing close ')' bracket") - } - if valPathCnt > 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing ']' bracket") - } - if wordIndex > -1 && charPos == len(expression) { - filterAttr := attr - if parentAttr != "" { - filterAttr = parentAttr + "." + attr - } - if filterAttr == "" { - return nil, errors.New("invalid IDQL idqlCondition: Incomplete expression") - } - if isAttr && cond != "" { - value = expression[wordIndex:] - if strings.HasSuffix(value, ")") && bracketCount == 0 { - return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") - } - if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") { - value = value[1 : len(value)-1] - } - var filter Expression - filter, err := createExpression(filterAttr, cond, value) - if err != nil { - return nil, err - } - clauses = append(clauses, &filter) - } else { - // a presence match at the end of the idqlCondition string - if isAttr { - cond = expression[wordIndex:] - } - var filter Expression - filter = AttributeExpression{ - AttributePath: filterAttr, - Operator: CompareOperator("pr"), - } - clauses = append(clauses, &filter) + if bracketCount > 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing close ')' bracket") + } + if valPathCnt > 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing ']' bracket") + } + if wordIndex > -1 && charPos == len(expression) { + filterAttr := attr + if parentAttr != "" { + filterAttr = parentAttr + "." + attr + } + if filterAttr == "" { + return nil, errors.New("invalid IDQL idqlCondition: Incomplete expression") + } + if isAttr && cond != "" { + value = expression[wordIndex:] + if strings.HasSuffix(value, ")") && bracketCount == 0 { + return nil, errors.New("invalid IDQL idqlCondition: Missing open '(' bracket") + } + if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") { + value = value[1 : len(value)-1] + } + var filter Expression + filter, err := createExpression(filterAttr, cond, value) + if err != nil { + return nil, err + } + clauses = append(clauses, filter) + } else { + // a presence match at the end of the idqlCondition string + if isAttr { + cond = expression[wordIndex:] + } + var filter Expression + filter = AttributeExpression{ + AttributePath: filterAttr, + Operator: "pr", + } + clauses = append(clauses, filter) - } - } + } + } - if isLogic && len(clauses) == 2 { - var oper LogicalOperator - if isAnd { - oper = "and" - } else { - oper = "or" - } - var filter Expression - filter = LogicalExpression{ - Operator: oper, - Left: *clauses[0], - Right: *clauses[1], - } - clauses = []*Expression{} - clauses = append(clauses, &filter) + if isLogic && len(clauses) == 2 { + var oper LogicalOperator + if isAnd { + oper = "and" + } else { + oper = "or" + } + var filter Expression + filter = LogicalExpression{ + Operator: oper, + Left: clauses[0], + Right: clauses[1], + } + clauses = []Expression{} + clauses = append(clauses, filter) - return &filter, nil - } - if len(clauses) == 1 { - return clauses[0], nil - } + return filter, nil + } + if len(clauses) == 1 { + return clauses[0], nil + } - return nil, errors.New("invalid IDQL idqlCondition: Missing and/or clause") + return nil, errors.New("invalid IDQL idqlCondition: Missing and/or clause") } func createExpression(attribute string, cond string, value string) (AttributeExpression, error) { - lCond := strings.ToLower(cond) - var attrFilter AttributeExpression - switch CompareOperator(lCond) { - case EQ, NE, SW, EW, GT, LT, GE, LE, CO, IN: - attrFilter = AttributeExpression{ - AttributePath: attribute, - Operator: CompareOperator(strings.ToLower(cond)), - CompareValue: value, - } + lCond := strings.ToLower(cond) + var attrFilter AttributeExpression + switch CompareOperator(lCond) { + case EQ, NE, SW, EW, GT, LT, GE, LE, CO, IN: + attrFilter = AttributeExpression{ + AttributePath: attribute, + Operator: CompareOperator(strings.ToLower(cond)), + CompareValue: value, + } - default: - return AttributeExpression{}, errors.New("invalid IDQL idqlCondition: Unsupported comparison operator: " + cond) - } - return attrFilter, nil + default: + return AttributeExpression{}, errors.New("invalid IDQL idqlCondition: Unsupported comparison operator: " + cond) + } + return attrFilter, nil } diff --git a/pkg/hexapolicy/conditions/parser/parser_test.go b/pkg/hexapolicy/conditions/parser/parser_test.go index 44dd8ec..a46e4a8 100644 --- a/pkg/hexapolicy/conditions/parser/parser_test.go +++ b/pkg/hexapolicy/conditions/parser/parser_test.go @@ -47,9 +47,9 @@ func TestParseFilter(t *testing.T) { t.Run(example[0], func(t *testing.T) { var err error fmt.Println(fmt.Sprintf("Input:\t%s", example[0])) - ast, err := ParseFilter(example[0]) + element, err := ParseFilter(example[0]) assert.NoError(t, err, "Example not parsed: "+example[0]) - element := *ast + out := element.String() fmt.Println(fmt.Sprintf("Parsed:\t%s", out)) match := example[1] diff --git a/pkg/hexapolicy/conditions/parser/types.go b/pkg/hexapolicy/conditions/parser/types.go index df55444..7626411 100644 --- a/pkg/hexapolicy/conditions/parser/types.go +++ b/pkg/hexapolicy/conditions/parser/types.go @@ -46,6 +46,7 @@ type LogicalOperator string type Expression interface { exprNode() String() string + Dif() string } type LogicalExpression struct { @@ -54,9 +55,13 @@ type LogicalExpression struct { } func (LogicalExpression) exprNode() {} + func (e LogicalExpression) String() string { return fmt.Sprintf("%s %s %s", e.Left.String(), e.Operator, e.Right.String()) } +func (e LogicalExpression) Dif() string { + return string(e.Operator) +} type NotExpression struct { Expression Expression @@ -68,6 +73,16 @@ func (e NotExpression) String() string { func (NotExpression) exprNode() {} +func (e NotExpression) Dif() string { + + switch exp := e.Expression.(type) { + case AttributeExpression, ValuePathExpression: + return fmt.Sprintf("not(%s)", exp.String()) + default: + return "not" + } +} + type PrecedenceExpression struct { Expression Expression } @@ -78,6 +93,10 @@ func (e PrecedenceExpression) String() string { return fmt.Sprintf("(%s)", e.Expression.String()) } +func (PrecedenceExpression) Dif() string { + return "" +} + type AttributeExpression struct { AttributePath string Operator CompareOperator @@ -113,6 +132,10 @@ func (e AttributeExpression) String() string { return fmt.Sprintf("%s %s \"%s\"", e.AttributePath, e.Operator, e.CompareValue) } +func (e AttributeExpression) Dif() string { + return e.String() +} + type ValuePathExpression struct { Attribute string VPathFilter Expression @@ -122,3 +145,4 @@ func (ValuePathExpression) exprNode() {} func (e ValuePathExpression) String() string { return fmt.Sprintf("%s[%s]", e.Attribute, e.VPathFilter.String()) } +func (e ValuePathExpression) Dif() string { return e.String() } diff --git a/pkg/hexapolicy/hexa_policy.go b/pkg/hexapolicy/hexa_policy.go index 0320c45..43cb5eb 100644 --- a/pkg/hexapolicy/hexa_policy.go +++ b/pkg/hexapolicy/hexa_policy.go @@ -101,7 +101,7 @@ func (p *PolicyInfo) Equals(hexaPolicy PolicyInfo) bool { } // check for semantic equivalence. - if !(p.Subjects.equals(hexaPolicy.Subjects) && p.actionEquals(hexaPolicy.Actions) && p.Object.equals(&hexaPolicy.Object)) { + if !(p.Subjects.Equals(hexaPolicy.Subjects) && p.ActionsEqual(hexaPolicy.Actions) && p.Object.equals(&hexaPolicy.Object)) { return false } @@ -121,7 +121,7 @@ func (p *PolicyInfo) Equals(hexaPolicy PolicyInfo) bool { if hexaPolicy.Scope == nil { return false } - if !p.Scope.equals(hexaPolicy.Scope) { + if !p.Scope.Equals(hexaPolicy.Scope) { return false } } @@ -149,11 +149,11 @@ func (p *PolicyInfo) Compare(hexaPolicy PolicyInfo) []string { var difs = make([]string, 0) // Now do a semantic compare (e.g. things can be different order but the same) - if !p.Subjects.equals(hexaPolicy.Subjects) { + if !p.Subjects.Equals(hexaPolicy.Subjects) { difs = append(difs, CompareDifSubject) } - if !p.actionEquals(hexaPolicy.Actions) { + if !p.ActionsEqual(hexaPolicy.Actions) { difs = append(difs, CompareDifAction) } @@ -176,14 +176,14 @@ func (p *PolicyInfo) Compare(hexaPolicy PolicyInfo) []string { return difs } -func (p *PolicyInfo) actionEquals(actions []ActionInfo) bool { - if len(p.Actions) != len(actions) { +func (p *PolicyInfo) ActionsEqual(actions []ActionInfo) bool { + if actions == nil || len(p.Actions) != len(actions) { return false } for _, action := range p.Actions { isMatch := false - for _, caction := range actions { - if action.Equals(caction) { + for _, compareAction := range actions { + if action.Equals(compareAction) { isMatch = true break } @@ -223,7 +223,7 @@ func (s SubjectInfo) String() []string { return s } -func (s SubjectInfo) equals(subjects SubjectInfo) bool { +func (s SubjectInfo) Equals(subjects SubjectInfo) bool { if len(s) != len(subjects) { return false } @@ -268,9 +268,9 @@ const ( ScopeTypeUnassigned string = "na" ) -// ScopeInfo.equals returns equality based on string compare. This does not lexically compare filters. +// Equals returns equality based on string compare. This does not lexically compare filters. // This function is intended to determine if a policy element has changed. -func (s *ScopeInfo) equals(scope *ScopeInfo) bool { +func (s *ScopeInfo) Equals(scope *ScopeInfo) bool { if s.Type() != scope.Type() { return false } diff --git a/pkg/hexapolicy/hexa_policy_test.go b/pkg/hexapolicy/hexa_policy_test.go index 950a409..01858cb 100644 --- a/pkg/hexapolicy/hexa_policy_test.go +++ b/pkg/hexapolicy/hexa_policy_test.go @@ -82,11 +82,11 @@ func TestSubjectInfo_equals(t *testing.T) { p1 := policies.Policies[0] p2 := policies.Policies[1] assert.NotNil(t, p1.Subjects, "Subjects should not be nil") - assert.False(t, p1.Subjects.equals(p2.Subjects)) + assert.False(t, p1.Subjects.Equals(p2.Subjects)) p3 := p1 // check case sensitivity p3.Subjects = []string{"user:Accounting@Hexaindustries.io"} - assert.True(t, p1.Subjects.equals(p3.Subjects)) + assert.True(t, p1.Subjects.Equals(p3.Subjects)) } func TestPolicyInfo_actionEquals(t *testing.T) { @@ -94,23 +94,23 @@ func TestPolicyInfo_actionEquals(t *testing.T) { p1 := policies.Policies[0] p2 := policies.Policies[1] - assert.False(t, p1.actionEquals(p2.Actions)) + assert.False(t, p1.ActionsEqual(p2.Actions)) p3 := p2 // check that equivalence works with the same elements in the same order p3.Actions = p1.Actions - assert.True(t, p1.actionEquals(p3.Actions)) + assert.True(t, p1.ActionsEqual(p3.Actions)) // Check that equivalence works out of order p3.Actions = []ActionInfo{"http:POST:/accounting", "http:GET:/accounting"} - assert.True(t, p1.actionEquals(p3.Actions)) + assert.True(t, p1.ActionsEqual(p3.Actions)) p3.Actions = []ActionInfo{"http:POST:/accounting"} - assert.False(t, p1.actionEquals(p3.Actions)) + assert.False(t, p1.ActionsEqual(p3.Actions)) } func TestObjectInfo_equals(t *testing.T) { @@ -153,21 +153,21 @@ func TestScope_equals(t *testing.T) { assert.Equal(t, ScopeTypeIDQL, scope1.Type()) assert.Equal(t, "username eq smith", scope2.Value()) - assert.True(t, scope1.equals(&scope2)) + assert.True(t, scope1.Equals(&scope2)) filter = filter + "and surname eq smith" - assert.False(t, scope1.equals(&scope2)) + assert.False(t, scope1.Equals(&scope2)) filter = "idql:username eq smith" scope2.Attributes = []string{"username"} - assert.False(t, scope1.equals(&scope2)) + assert.False(t, scope1.Equals(&scope2)) scope2.Attributes = []string{"emails", "username"} - assert.True(t, scope1.equals(&scope2)) + assert.True(t, scope1.Equals(&scope2)) scope2.Attributes = []string{"emails", "xyz"} - assert.False(t, scope1.equals(&scope2)) + assert.False(t, scope1.Equals(&scope2)) filter = "dummy" scope2.Filter = &filter @@ -177,15 +177,15 @@ func TestScope_equals(t *testing.T) { scope2.Filter = nil assert.Equal(t, ScopeTypeUnassigned, scope2.Type()) - assert.False(t, scope1.equals(&scope2), "Test one filter is null") - assert.False(t, scope2.equals(scope1), "Test one filter is null") + assert.False(t, scope1.Equals(&scope2), "Test one filter is null") + assert.False(t, scope2.Equals(scope1), "Test one filter is null") filter = "sQl:where username is \"sam\"" scope2.Filter = &filter assert.Equal(t, "where username is \"sam\"", scope2.Value()) assert.Equal(t, ScopeTypeSQL, scope2.Type()) - assert.False(t, scope1.equals(&scope2), "Test filters of different types") + assert.False(t, scope1.Equals(&scope2), "Test filters of different types") } @@ -262,7 +262,7 @@ func TestPolicyInfo_Equals(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &tt.fields.testPolicy - assert.Equalf(t, tt.want, p.Equals(tt.args.hexaPolicy), "equals(%v)", tt.args.hexaPolicy) + assert.Equalf(t, tt.want, p.Equals(tt.args.hexaPolicy), "Equals(%v)", tt.args.hexaPolicy) }) } } From 98e6253f7a2ae57d100b71757be543f80d43735c Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Tue, 10 Sep 2024 13:40:15 -0700 Subject: [PATCH 08/13] Issue #59, Added diagnostics to Rego to detect some IDQL errors (e.g. old policy) Signed-off-by: Phil Hunt --- .../resources/bundles/bundle/hexaPolicy.rego | 89 +++++++++++++------ 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego index 164ea63..8754174 100644 --- a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego +++ b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego @@ -9,6 +9,41 @@ hexa_rego_version := "0.7.0" policies_evaluated := count(policies) +error_idql contains item if { + some policy in policies + item := diag_error_idundef(policy) +} + +error_idql contains item if { + some policy in policies + count(policy.subjects) < 1 + item := { + "policyId": policy.meta.policyId, + "error": "missing value for subjects", + } +} + +error_idql contains item if { + some policy in policies + item := diag_error_version(policy) +} + +diag_error_idundef(policy) := diag if { + not policy.meta.policyId + diag := { + "policyId": "undefined", + "error": "idql policy missing value for meta.policyId", + } +} + +diag_error_version(policy) := diag if { + policy.meta.version < "0.7" + diag := { + "policyId": policy.meta.policyId, + "error": "Hexa Rego 0.7 requires IDQL version 0.7 or later", + } +} + # Returns the list of matching policy names based on current request allow_set contains policy_id if { some policy in policies @@ -16,23 +51,23 @@ allow_set contains policy_id if { # return id of the policy policy_id := sprintf("%s", [policy.meta.policyId]) - subject_match(policy.subject, input.subject, input.req) + subject_match(policy.subjects, input.subject, input.req) actions_match(policy.actions, input.req) - object_match(policy.object, input.req) + 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 + some policy in policies + policy.meta.policyId in allow_set - scope := { - "policyId": policy.meta.policyId, - "scope": policy.scope - } + scope := { + "policyId": policy.meta.policyId, + "scope": policy.scope, + } } # Returns the list of possible actions allowed (e.g. for UI buttons) @@ -49,15 +84,15 @@ allow if { count(allow_set) > 0 } -subject_match(psubject, _, _) if { +subject_match(subject, _, _) if { # Match if no value specified - treat as wildcard - not psubject + not subject } -subject_match(psubject, insubject, req) if { +subject_match(subject, inputsubject, req) if { # Match if a member matches - some member in psubject - subject_member_match(member, insubject, req) + some member in subject + subject_member_match(member, inputsubject, req) } subject_member_match(member, _, _) if { @@ -65,31 +100,31 @@ subject_member_match(member, _, _) if { lower(member) == "any" } -subject_member_match(member, insubj, _) if { +subject_member_match(member, inputsubject, _) if { # anyAutheticated - A match occurs if input.subject has a value other than anonymous and exists. - insubj.sub # check sub exists + inputsubject.sub # check sub exists lower(member) == "anyauthenticated" } # Check for match based on user: -subject_member_match(member, insubj, _) if { +subject_member_match(member, inputsubject, _) if { startswith(lower(member), "user:") user := substring(member, 5, -1) - lower(user) == lower(insubj.sub) + lower(user) == lower(inputsubject.sub) } # Check for match if sub ends with domain -subject_member_match(member, insubj, _) if { +subject_member_match(member, inputsubject, _) if { startswith(lower(member), "domain:") domain := lower(substring(member, 7, -1)) - endswith(lower(insubj.sub), domain) + endswith(lower(inputsubject.sub), domain) } # Check for match based on role -subject_member_match(member, insubj, _) if { +subject_member_match(member, inputsubject, _) if { startswith(lower(member), "role:") role := substring(member, 5, -1) - role in insubj.roles + role in inputsubject.roles } subject_member_match(member, _, req) if { @@ -142,15 +177,15 @@ check_http_match(actionUri, req) if { check_path(path, req) } -object_match(object, _) if { - not object +is_object_match(resource, _) if { + not resource } -object_match(object, req) if { - object +is_object_match(resource, req) if { + resource some request_uri in req.resourceIds - lower(object) == lower(request_uri) + lower(resource) == lower(request_uri) } check_http_method(allowMask, _) if { @@ -170,7 +205,7 @@ check_http_method(allowMask, reqMethod) if { check_path(path, req) if { path # if path specified it must match - glob.match(path, ["*"], req.path) + glob.match(path, ["*"], req.path) } check_path(path, _) if { From 487d3b130a38fef068526a252e59d64b6df49a5a Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Wed, 11 Sep 2024 11:50:17 -0700 Subject: [PATCH 09/13] Issue #59, Updated docs to 0.7, updated some more JSON policies Signed-off-by: Phil Hunt --- cmd/hexa/main.go | 2 +- docs/Developer.md | 138 +++++------- docs/HexaAdmin.md | 46 ++-- .../bundleServer/bundles/bundle/data.json | 201 ++++++------------ .../bundles/bundle/hexaPolicy.rego | 99 ++++++--- .../bundleServer/bundles/bundle/policy.rego | 30 --- models/rar/policy_transformer_test.go | 18 +- .../openpolicyagent/opa_provider_test.go | 14 +- 8 files changed, 216 insertions(+), 332 deletions(-) delete mode 100644 examples/opa-server/bundleServer/bundles/bundle/policy.rego diff --git a/cmd/hexa/main.go b/cmd/hexa/main.go index aea3ec4..995e542 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.6.13" +const Version string = "0.7.0" type ParserData struct { parser *kong.Kong diff --git a/docs/Developer.md b/docs/Developer.md index eb26d9f..8744fdd 100644 --- a/docs/Developer.md +++ b/docs/Developer.md @@ -123,7 +123,7 @@ Policies retrieved for shK: "policies": [ { "Meta": { - "Version": "0.6", + "Version": "0.7", "SourceData": { "policyType": "STATIC", "principal": null, @@ -137,35 +137,22 @@ Policies retrieved for shK: "PapId": "K21RFtX...A93DH7z5", "ProviderType": "avp" }, - "Subject": { - "Members": [ + "Subjects": [ "any" ] - }, + , "Actions": [ - { - "ActionUri": "cedar:hexa_avp::Action::ReadAccount" - }, - { - "ActionUri": "cedar:hexa_avp::Action::Transfer" - }, - { - "ActionUri": "cedar:hexa_avp::Action::Deposit" - }, - { - "ActionUri": "cedar:hexa_avp::Action::Withdrawl" - }, - { - "ActionUri": "cedar:hexa_avp::Action::UpdateAccount" - } + "cedar:hexa_avp::Action::ReadAccount", + "cedar:hexa_avp::Action::Transfer", + "cedar:hexa_avp::Action::Deposit", + "cedar:hexa_avp::Action::Withdrawl", + "cedar:hexa_avp::Action::UpdateAccount" ], - "Object": { - "resource_id": "" - } + "Object": "" }, { "Meta": { - "Version": "0.6", + "Version": "0.7", "SourceData": { "policyType": "TEMPLATE_LINKED", "principal": { @@ -185,19 +172,14 @@ Policies retrieved for shK: "PapId": "K21RFtX...A93DH7z5", "ProviderType": "avp" }, - "Subject": { - "Members": [ + "Subjects": [ "?principal" ] - }, + , "Actions": [ - { - "ActionUri": "cedar:hexa_avp::Action::ReadAccount" - } + "cedar:hexa_avp::Action::ReadAccount" ], - "Object": { - "resource_id": "cedar:?resource" - } + "Object": "cedar:?resource" } ], "app": "K21RFtX...A93DH7z5" @@ -270,47 +252,37 @@ Ignoring AVP policyid UaN2xdjgv1Dhdpuoa3ebRU. Template updates not currently sup 0: DIF: UPDATE [ACTION] { "Meta": { - "Version": "0.6", - "SourceData": { - "policyType": "STATIC", - "principal": null, - "resource": null - }, - "Description": "Hexa demo canary policy", - "Created": "2023-12-26T21:45:53.558204Z", - "Modified": "2023-12-27T22:20:18.592795Z", - "Etag": "20-f2ec1edc53e44c07e4d790d8936ade24b27f04eb", - "PolicyId": "KDqUKMRNEg6aEjZ6mz9dJq", - "PapId": "K21...93DH7z5", - "ProviderType": "avp" - }, - "Subject": { - "Members": [ - "any" - ] - }, - "Actions": [ - { - "ActionUri": "cedar:hexa_avp::Action::ReadAccount" - }, - { - "ActionUri": "cedar:hexa_avp::Action::Transfer" - }, - { - "ActionUri": "cedar:hexa_avp::Action::Deposit" +{ + "Meta": { + "Version": "0.7", + "SourceData": { + "policyType": "STATIC", + "principal": null, + "resource": null + }, + "Description": "Hexa demo canary policy", + "Created": "2023-12-26T21:45:53.558204Z", + "Modified": "2023-12-27T22:20:18.592795Z", + "Etag": "20-f2ec1edc53e44c07e4d790d8936ade24b27f04eb", + "PolicyId": "KDqUKMRNEg6aEjZ6mz9dJq", + "PapId": "K21...93DH7z5", + "ProviderType": "avp" }, - { - "ActionUri": "cedar:hexa_avp::Action::Withdrawl" - } - ], - "Object": { - "resource_id": "" - } + "Subjects": [ + "any" + ], + "Actions": [ + "cedar:hexa_avp::Action::ReadAccount", + "cedar:hexa_avp::Action::Transfer", + "cedar:hexa_avp::Action::Deposit", + "cedar:hexa_avp::Action::Withdrawl" + ], + "Object": "" } 1: DIF: UNSUPPORTED { "Meta": { - "Version": "0.6", + "Version": "0.7", "SourceData": { "policyType": "TEMPLATE_LINKED", "principal": { @@ -330,19 +302,13 @@ Ignoring AVP policyid UaN2xdjgv1Dhdpuoa3ebRU. Template updates not currently sup "PapId": "K21...93DH7z5", "ProviderType": "avp" }, - "Subject": { - "Members": [ + "Subjects": [ "?principal" - ] - }, + ], "Actions": [ - { - "ActionUri": "cedar:hexa_avp::Action::ReadAccount" - } + "cedar:hexa_avp::Action::ReadAccount" ], - "Object": { - "resource_id": "cedar:?resource" - } + "Object": "cedar:?resource" } Applying 2 policies to rKO @@ -405,21 +371,15 @@ or an attribute "policies" which is assigned an array of policies. For example: "policies": [ { "Meta": { - "Version": "0.6" + "Version": "0.7" }, "Actions": [ - { - "ActionUri": "cedar:Action::view" - } + "cedar:Action::view" ], - "Subject": { - "Members": [ + "Subjects": [ "User:\"alice\"" - ] - }, - "Object": { - "resource_id": "cedar:Photo::VacationPhoto94.jpg" - } + ], + "Object": "cedar:Photo::VacationPhoto94.jpg" } ] } diff --git a/docs/HexaAdmin.md b/docs/HexaAdmin.md index 71f18f0..44c2be6 100644 --- a/docs/HexaAdmin.md +++ b/docs/HexaAdmin.md @@ -60,7 +60,7 @@ Policies retrieved for rKO: "policies": [ { "Meta": { - "Version": "0.6", + "Version": "0.7", "SourceData": { "policyType": "STATIC", "principal": null, @@ -104,7 +104,7 @@ Ignoring AVP policyid UaN2xdjgv1Dhdpuoa3ebRU. Template updates not currently sup 0: DIF: UPDATE [ACTION] { "Meta": { - "Version": "0.6", + "Version": "0.7", "SourceData": { "policyType": "STATIC", "principal": null, @@ -118,33 +118,21 @@ Ignoring AVP policyid UaN2xdjgv1Dhdpuoa3ebRU. Template updates not currently sup "PapId": "K21...93DH7z5", "ProviderType": "avp" }, - "Subject": { - "Members": [ + "Subjects": [ "any" - ] - }, + ], "Actions": [ - { - "ActionUri": "cedar:hexa_avp::Action::\"ReadAccount\"" - }, - { - "ActionUri": "cedar:hexa_avp::Action::\"Transfer\"" - }, - { - "ActionUri": "cedar:hexa_avp::Action::\"Deposit\"" - }, - { - "ActionUri": "cedar:hexa_avp::Action::\"Withdrawl\"" - } + "cedar:hexa_avp::Action::\"ReadAccount\"", + "cedar:hexa_avp::Action::\"Transfer\"", + "cedar:hexa_avp::Action::\"Deposit\"", + "cedar:hexa_avp::Action::\"Withdrawl\"" ], - "Object": { - "resource_id": "" - } + "Object": "" } 1: DIF: UNSUPPORTED { "Meta": { - "Version": "0.6", + "Version": "0.7", "SourceData": { "policyType": "TEMPLATE_LINKED", "principal": { @@ -164,19 +152,13 @@ Ignoring AVP policyid UaN2xdjgv1Dhdpuoa3ebRU. Template updates not currently sup "PapId": "K21...93DH7z5", "ProviderType": "avp" }, - "Subject": { - "Members": [ + "Subjects": [ "?principal" - ] - }, + ], "Actions": [ - { - "ActionUri": "cedar:hexa_avp::Action::\"ReadAccount\"" - } + "cedar:hexa_avp::Action::\"ReadAccount\"" ], - "Object": { - "resource_id": "cedar:?resource" - } + "Object": "cedar:?resource" } Applying 2 policies to rKO diff --git a/examples/opa-server/bundleServer/bundles/bundle/data.json b/examples/opa-server/bundleServer/bundles/bundle/data.json index 3da49a5..f616d60 100644 --- a/examples/opa-server/bundleServer/bundles/bundle/data.json +++ b/examples/opa-server/bundleServer/bundles/bundle/data.json @@ -3,27 +3,19 @@ { "meta": { "policyId": "TestBasicCanary", - "version": "0.6", + "version": "0.7", "date": "2021-08-01 21:32:44 UTC", "description": "Access enabling user self service for users with role" }, - "subject": { - "members": ["anyAuthenticated"] - }, + "subjects": [ + "anyAuthenticated" + ], "actions": [ - { - "actionUri": "ietf:http:POST:/testpath*" - }, - { - "actionUri": "ietf:http:PUT:/testpath*" - }, - { - "actionUri": "ietf:http:GET:/testpath*" - } - ], - "object": { - "resource_id": "CanaryProfileService" - }, + "http:POST:/testpath*", + "http:PUT:/testpath*", + "http:GET:/testpath*" + ], + "object": "CanaryProfileService", "condition": { "rule": "subject.type eq basic", "action": "allow" @@ -32,56 +24,40 @@ { "meta": { "policyId": "TestBasicCanaryCondition", - "version": "0.6", + "version": "0.7", "date": "2021-08-01 21:32:44 UTC", "description": "Access enabling user self service for users with role with condition" }, - "subject": { - "members": ["anyAuthenticated"] - }, + "subjects": [ + "anyAuthenticated" + ], "actions": [ - { - "actionUri": "ietf:http:POST:/testpath*" - }, - { - "actionUri": "ietf:http:PUT:/testpath*" - }, - { - "actionUri": "ietf:http:GET:/testpath*" - } + "http:POST:/testpath*", + "http:PUT:/testpath*", + "http:GET:/testpath*" ], "condition": { "rule": "req.ip sw 127.0.0.1 and subject.type eq basic", "action": "allow" }, - "object": { - "resource_id": "CanaryProfileService" - } + "object": "CanaryProfileService" }, { "meta": { - "version": "0.6", + "version": "0.7", "date": "2021-08-01 21:32:44 UTC", "description": "Tests any authenticated JWT user", "policyId": "TestJwtCanary" }, - "subject": { - "members": ["anyAuthenticated"] - }, + "subjects": [ + "anyAuthenticated" + ], "actions": [ - { - "actionUri": "ietf:http:POST:/testpath*" - }, - { - "actionUri": "ietf:http:!PUT:/testpath*" - }, - { - "actionUri": "ietf:http:GET:/testpath*" - } - ], - "object": { - "resource_id": "CanaryProfileService" - }, + "http:POST:/testpath*", + "http:!PUT:/testpath*", + "http:GET:/testpath*" + ], + "object": "CanaryProfileService", "condition": { "rule": "subject.type eq jwt and subject.iss eq testIssuer and subject.aud co testAudience", "action": "allow" @@ -89,109 +65,78 @@ }, { "meta": { - "version": "0.6", + "version": "0.7", "date": "2021-08-01 21:32:44 UTC", "description": "Access enabling user self service for users with role", "policyId": "TestIPMaskCanary" }, - "subject": { - "comment": "any used, but the condition is what restricts subjects", - "members": ["net:127.0.0.1/24"] - }, + "subjects": [ + "net:127.0.0.1/24" + ], "actions": [ - { - "actionUri": "ietf:http:POST:/testpath*" - }, - { - "actionUri": "ietf:http:!PUT:/testpath*" - }, - { - "actionUri": "ietf:http:GET:/testpath*" - } + "http:POST:/testpath*", + "http:!PUT:/testpath*", + "http:GET:/testpath*" ], "condition": { "rule": "req.method eq GET", "action": "allow" }, - "object": { - "resource_id": "CanaryProfileService" - } + "object": "CanaryProfileService" }, { "meta": { - "version": "0.6", + "version": "0.7", "date": "2021-08-01 21:32:44 UTC", "description": "Access enabling user self service for users with role", "policyId": "TestIPMaskCanaryPOST" }, - "subject": { - "members": ["net:192.1.0.1/24"] - }, + "subjects": [ + "net:192.1.0.1/24" + ], "actions": [ - { - "actionUri": "ietf:http:POST:/testpath*" - }, - { - "actionUri": "ietf:http:!PUT:/testpath*" - }, - { - "actionUri": "ietf:http:GET:/testpath*" - } - ], - "object": { - "resource_id": "CanaryProfileService" - } + "http:POST:/testpath*", + "http:!PUT:/testpath*", + "http:GET:/testpath*" + ], + "object": "CanaryProfileService" }, { "meta": { - "version": "0.6", + "version": "0.7", "date": "2021-08-01 21:32:44 UTC", "description": "Access enabling user self service for users with role", "policyId": "TestIPMaskCanaryNotDelete" }, - "subject": { - "members": ["any"] - }, + "subjects": [ + "any" + ], "actions": [ - { - "actionUri": "ietf:http:POST:/testpath*" - }, - { - "actionUri": "ietf:http:!PUT:/testpath*" - }, - { - "actionUri": "ietf:http:GET:/testpath*" - } + "http:POST:/testpath*", + "http:!PUT:/testpath*", + "http:GET:/testpath*" ], "condition": { "rule": "req.ip sw 127 and req.method NE DELETE", "action": "allow" }, - "object": { - "resource_id": "CanaryProfileService" - } + "object": "CanaryProfileService" }, { "meta": { - "version": "0.6", + "version": "0.7", "date": "2021-08-01 21:32:44 UTC", "description": "Test that allows jwt authenticated specific subject *and* has a role", "policyId": "TestJwtRole" }, - "subject": { - "members" : ["user:BaSicBob"] - }, + "subjects": [ + "user:BaSicBob" + ], "actions": [ - { - "actionUri": "ietf:http:POST:/testpath*" - }, - { - "actionUri": "ietf:http:GET:/testpath*" - } - ], - "object": { - "resource_id": "CanaryProfileService" - }, + "http:POST:/testpath*", + "http:GET:/testpath*" + ], + "object": "CanaryProfileService", "condition": { "rule": "subject.type eq jwt and subject.iss eq testIssuer and subject.aud co testAudience and subject.roles co abc", "action": "allow" @@ -199,28 +144,22 @@ }, { "meta": { - "version": "0.6", + "version": "0.7", "date": "2021-08-01 21:32:44 UTC", "description": "test that allows JWT authenticated subjects with a role or specific users", "policyId": "TestJwtMember" }, - "subject": { - "members": ["role:abc","user:JwtAlice","user:BasicBoB"] - }, + "subjects": [ + "role:abc", + "user:JwtAlice", + "user:BasicBoB" + ], "actions": [ - { - "actionUri": "ietf:http:POST:/testpath*" - }, - { - "actionUri": "ietf:http:PUT:/testpath*" - }, - { - "actionUri": "ietf:http:GET:/testpath*" - } - ], - "object": { - "resource_id": "CanaryProfileService" - }, + "http:POST:/testpath*", + "http:PUT:/testpath*", + "http:GET:/testpath*" + ], + "object": "CanaryProfileService", "condition": { "rule": "subject.type eq jwt and subject.iss eq testIssuer and subject.aud co testAudience", "action": "allow" diff --git a/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego b/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego index f60e1e2..8754174 100644 --- a/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego +++ b/examples/opa-server/bundleServer/bundles/bundle/hexaPolicy.rego @@ -9,6 +9,41 @@ hexa_rego_version := "0.7.0" policies_evaluated := count(policies) +error_idql contains item if { + some policy in policies + item := diag_error_idundef(policy) +} + +error_idql contains item if { + some policy in policies + count(policy.subjects) < 1 + item := { + "policyId": policy.meta.policyId, + "error": "missing value for subjects", + } +} + +error_idql contains item if { + some policy in policies + item := diag_error_version(policy) +} + +diag_error_idundef(policy) := diag if { + not policy.meta.policyId + diag := { + "policyId": "undefined", + "error": "idql policy missing value for meta.policyId", + } +} + +diag_error_version(policy) := diag if { + policy.meta.version < "0.7" + diag := { + "policyId": policy.meta.policyId, + "error": "Hexa Rego 0.7 requires IDQL version 0.7 or later", + } +} + # Returns the list of matching policy names based on current request allow_set contains policy_id if { some policy in policies @@ -16,23 +51,23 @@ allow_set contains policy_id if { # return id of the policy policy_id := sprintf("%s", [policy.meta.policyId]) - subject_match(policy.subject, input.subject, input.req) + subject_match(policy.subjects, input.subject, input.req) actions_match(policy.actions, input.req) - object_match(policy.object, input.req) + 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 + some policy in policies + policy.meta.policyId in allow_set - scope := { - "policyId": policy.meta.policyId, - "scope": policy.scope - } + scope := { + "policyId": policy.meta.policyId, + "scope": policy.scope, + } } # Returns the list of possible actions allowed (e.g. for UI buttons) @@ -41,7 +76,7 @@ action_rights contains name if { policy.meta.policyId in allow_set some action in policy.actions - name := sprintf("%s:%s", [policy.meta.policyId, action.actionUri]) + name := sprintf("%s:%s", [policy.meta.policyId, action]) } # Returns whether the current operation is allowed @@ -49,15 +84,15 @@ allow if { count(allow_set) > 0 } -subject_match(psubject, _, _) if { +subject_match(subject, _, _) if { # Match if no value specified - treat as wildcard - not psubject + not subject } -subject_match(psubject, insubject, req) if { +subject_match(subject, inputsubject, req) if { # Match if a member matches - some member in psubject - subject_member_match(member, insubject, req) + some member in subject + subject_member_match(member, inputsubject, req) } subject_member_match(member, _, _) if { @@ -65,35 +100,35 @@ subject_member_match(member, _, _) if { lower(member) == "any" } -subject_member_match(member, insubj, _) if { +subject_member_match(member, inputsubject, _) if { # anyAutheticated - A match occurs if input.subject has a value other than anonymous and exists. - insubj.sub # check sub exists + inputsubject.sub # check sub exists lower(member) == "anyauthenticated" } # Check for match based on user: -subject_member_match(member, insubj, _) if { +subject_member_match(member, inputsubject, _) if { startswith(lower(member), "user:") user := substring(member, 5, -1) - lower(user) == lower(insubj.sub) + lower(user) == lower(inputsubject.sub) } # Check for match if sub ends with domain -subject_member_match(member, insubj, _) if { +subject_member_match(member, inputsubject, _) if { startswith(lower(member), "domain:") domain := lower(substring(member, 7, -1)) - endswith(lower(insubj.sub), domain) + endswith(lower(inputsubject.sub), domain) } # Check for match based on role -subject_member_match(member, insubj, _) if { +subject_member_match(member, inputsubject, _) if { startswith(lower(member), "role:") role := substring(member, 5, -1) - role in insubj.roles + role in inputsubject.roles } subject_member_match(member, _, req) if { - startswith(lower(member), "net:") + startswith(lower(member), "net:") cidr := substring(member, 4, -1) addr := split(req.ip, ":") # Split because IP is address:port net.cidr_contains(cidr, addr[0]) @@ -111,15 +146,15 @@ actions_match(actions, req) if { action_match(action, req) if { # Check for match based on ietf http - check_http_match(action.actionUri, req) + check_http_match(action, req) } action_match(action, req) if { - action.actionUri # check for an action + action # check for an action count(req.actionUris) > 0 # Check for a match based on req.ActionUris and actionUri - check_urn_match(action.actionUri, req.actionUris) + check_urn_match(action, req.actionUris) } check_urn_match(policyUri, actionUris) if { @@ -142,15 +177,15 @@ check_http_match(actionUri, req) if { check_path(path, req) } -object_match(object, _) if { - not object.resource_id +is_object_match(resource, _) if { + not resource } -object_match(object, req) if { - object.resource_id +is_object_match(resource, req) if { + resource some request_uri in req.resourceIds - lower(object.resource_id) == lower(request_uri) + lower(resource) == lower(request_uri) } check_http_method(allowMask, _) if { @@ -194,7 +229,7 @@ condition_match(policy, inreq) if { } condition_match(policy, inreq) if { - # If action is deny, then hexaFilter must be false + # If action is deny, then hexaFilter must be false policy.condition not action_allow(policy.condition.action) not hexaFilter(policy.condition.rule, inreq) # HexaFilter evaluations the rule for a match against input diff --git a/examples/opa-server/bundleServer/bundles/bundle/policy.rego b/examples/opa-server/bundleServer/bundles/bundle/policy.rego deleted file mode 100644 index 71122c8..0000000 --- a/examples/opa-server/bundleServer/bundles/bundle/policy.rego +++ /dev/null @@ -1,30 +0,0 @@ -package authz -import future.keywords.in -default allow = false -allow { - anyMatching -} -anyMatching { - some i - matches(data.bundle.policies[i]) -} -matches(policy) { - matchesAction(policy.actions[i]) - matchesPrincipal(policy.subject) -} -matchesAction(action) { - input.method == action.action_uri -} -matchesPrincipal(subject) { - "allusers" in subject.members -} -matchesPrincipal(subject) { - principalExists(input.principal) - "allauthenticated" in subject.members -} -matchesPrincipal(subject) { - input.principal in subject.members -} -principalExists(principal) { - "" != principal -} diff --git a/models/rar/policy_transformer_test.go b/models/rar/policy_transformer_test.go index 72ee723..3db49be 100644 --- a/models/rar/policy_transformer_test.go +++ b/models/rar/policy_transformer_test.go @@ -183,7 +183,7 @@ func TestFlattenPolicy_ReturnsEmpty(t *testing.T) { func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { pol1 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{ hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("2act")}, Subjects: []string{"1mem", "", "2mem"}, @@ -191,7 +191,7 @@ func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { } pol2 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{ hexapolicy.ActionInfo(""), hexapolicy.ActionInfo("3act"), hexapolicy.ActionInfo(" "), hexapolicy.ActionInfo("4act")}, Subjects: []string{"1mem", "", "2mem"}, @@ -216,12 +216,12 @@ func TestFlattenPolicy_DupResourceDupMembers(t *testing.T) { func TestFlattenPolicy_NoResource(t *testing.T) { pol1 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, Subjects: []string{"1mem", "", "2mem"}, } pol2 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo("1act")}, Subjects: []string{"1mem", "2mem"}, Object: hexapolicy.ObjectInfo("resource1"), @@ -256,7 +256,7 @@ func TestFlattenPolicy_NoResource(t *testing.T) { func TestFlattenPolicy_NoActions(t *testing.T) { pol1 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Subjects: []string{"1mem", "", "2mem"}, Object: hexapolicy.ObjectInfo("resource1"), } @@ -268,7 +268,7 @@ func TestFlattenPolicy_NoActions(t *testing.T) { func TestFlattenPolicy_NoMembers(t *testing.T) { pol1 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, Object: hexapolicy.ObjectInfo("resource1"), } @@ -288,7 +288,7 @@ func TestFlattenPolicy_NoMembers(t *testing.T) { func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { pol1a := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{ hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, Subjects: []string{"1mem", "2mem"}, @@ -296,7 +296,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { } pol1b := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{ hexapolicy.ActionInfo("1act"), hexapolicy.ActionInfo("2act")}, Subjects: []string{"3mem", "4mem"}, @@ -304,7 +304,7 @@ func TestFlattenPolicy_MergeSameResourceAction(t *testing.T) { } pol2 := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{ hexapolicy.ActionInfo("3act"), hexapolicy.ActionInfo("4act")}, Subjects: []string{"1mem", "2mem"}, diff --git a/providers/openpolicyagent/opa_provider_test.go b/providers/openpolicyagent/opa_provider_test.go index 255dccc..d379c6f 100644 --- a/providers/openpolicyagent/opa_provider_test.go +++ b/providers/openpolicyagent/opa_provider_test.go @@ -443,14 +443,12 @@ func TestMakeDefaultBundle(t *testing.T) { "policies": [ { "meta": { - "version": "0.6" + "version": "0.7" }, - "actions": [ "ietf:http:GET" ], - "subject": { - "members": [ + "actions": [ "http:GET" ], + "subjects": [ "anyauthenticated" - ] - }, + ], "object": "aResourceId" } ] @@ -486,11 +484,11 @@ func TestMakeDefaultBundle(t *testing.T) { meta := policies[0].Meta actionURi := policies[0].Actions[0].String() - assert.Equal(t, "ietf:http:GET", actionURi) + assert.Equal(t, "http:GET", actionURi) assert.Equal(t, "aResourceId", policies[0].Object.String()) // assert.JSONEq(t, `{"policies":[{"meta":{"version":"0.5"},"actions":[{"actionUri":"http:GET"}],"subject":{"members":["allusers"]},"object":{"resource_id":"anotherResourceId"}}]}`, string(readFile)) // assert.Equal(t, "anotherResourceId", *meta.PapId) - assert.Equal(t, "0.6", meta.Version, "check version") + assert.Equal(t, hexapolicy.IdqlVersion, meta.Version, "check version") /* dcreated, _ := os.ReadFile(filepath.Join(path, "/bundle/data.json")) From c8a1cb3f251300dd64b40605492790350148578d Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Thu, 12 Sep 2024 14:08:22 -0700 Subject: [PATCH 10/13] Issue #59, Added auto-upgrade to support existing authzen deployment Signed-off-by: Phil Hunt --- go.mod | 32 ++++----- go.sum | 64 +++++++++--------- models/formats/cedar/parse.go | 2 +- pkg/hexapolicy/hexa_policy.go | 104 +++++++++++++++++++++++++++-- pkg/hexapolicy/hexa_policy_test.go | 38 +++++++++++ 5 files changed, 185 insertions(+), 55 deletions(-) diff --git a/go.mod b/go.mod index 9050186..5c6e5c1 100644 --- a/go.mod +++ b/go.mod @@ -9,23 +9,23 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/MicahParks/jwkset v0.5.18 github.com/MicahParks/keyfunc/v3 v3.3.3 - github.com/alecthomas/assert/v2 v2.6.0 - github.com/alecthomas/kong v0.9.0 + github.com/alecthomas/assert/v2 v2.10.0 + github.com/alecthomas/kong v1.2.1 github.com/alecthomas/participle/v2 v2.1.1 github.com/alexedwards/scs/v2 v2.8.0 github.com/aws/aws-sdk-go-v2 v1.30.5 github.com/aws/aws-sdk-go-v2/config v1.27.33 github.com/aws/aws-sdk-go-v2/credentials v1.17.32 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.2 - github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.4 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.8 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3 + github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.44.0 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9 github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.6 github.com/cedar-policy/cedar-go v0.1.0 github.com/chzyer/readline v1.5.1 github.com/coreos/go-oidc/v3 v3.11.0 github.com/envoyproxy/go-control-plane v0.13.0 - github.com/go-playground/validator/v10 v10.22.0 + github.com/go-playground/validator/v10 v10.22.1 github.com/gofrs/flock v0.12.1 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/cel-go v0.21.0 @@ -36,16 +36,16 @@ require ( github.com/prometheus/client_golang v1.20.3 github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 github.com/stretchr/testify v1.9.0 - golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 golang.org/x/oauth2 v0.23.0 - google.golang.org/api v0.196.0 + google.golang.org/api v0.197.0 google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 ) require ( - cloud.google.com/go/auth v0.9.3 // indirect + cloud.google.com/go/auth v0.9.4 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect - cloud.google.com/go/compute/metadata v0.5.0 // indirect + cloud.google.com/go/compute/metadata v0.5.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/alecthomas/repr v0.4.0 // indirect @@ -79,7 +79,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/s2a-go v0.1.8 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.3 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -96,17 +96,17 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/tj/assert v0.0.3 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect + go.opentelemetry.io/otel v1.30.0 // indirect + go.opentelemetry.io/otel/metric v1.30.0 // indirect + go.opentelemetry.io/otel/trace v1.30.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.6.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.66.0 // indirect + google.golang.org/grpc v1.66.2 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3e4ba95..8f37144 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= -cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= +cloud.google.com/go/auth v0.9.4 h1:DxF7imbEbiFu9+zdKC6cKBko1e8XeJnipNqIbWZ+kDI= +cloud.google.com/go/auth v0.9.4/go.mod h1:SHia8n6//Ya940F1rLimhJCjjx7KE17t0ctFEci3HkA= cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= -cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= -cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs= +cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= @@ -18,10 +18,10 @@ github.com/MicahParks/jwkset v0.5.18 h1:WLdyMngF7rCrnstQxA7mpRoxeaWqGzPM/0z40PJU github.com/MicahParks/jwkset v0.5.18/go.mod h1:q8ptTGn/Z9c4MwbcfeCDssADeVQb3Pk7PnVxrvi+2QY= github.com/MicahParks/keyfunc/v3 v3.3.3 h1:c6j9oSu1YUo0k//KwF1miIQlEMtqNlj7XBFLB8jtEmY= github.com/MicahParks/keyfunc/v3 v3.3.3/go.mod h1:f/UMyXdKfkZzmBeBFUeYk+zu066J1Fcl48f7Wnl5Z48= -github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= -github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= -github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= +github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= +github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v1.2.1 h1:E8jH4Tsgv6wCRX2nGrdPyHDUCSG83WH2qE4XLACD33Q= +github.com/alecthomas/kong v1.2.1/go.mod h1:rKTSFhbdp3Ryefn8x5MOEprnRFQ7nlmMC01GKhehhBM= github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= @@ -38,8 +38,8 @@ github.com/aws/aws-sdk-go-v2/config v1.27.33 h1:Nof9o/MsmH4oa0s2q9a0k7tMz5x/Yj5k github.com/aws/aws-sdk-go-v2/config v1.27.33/go.mod h1:kEqdYzRb8dd8Sy2pOdEbExTTF5v7ozEXX0McgPE7xks= github.com/aws/aws-sdk-go-v2/credentials v1.17.32 h1:7Cxhp/BnT2RcGy4VisJ9miUPecY+lyE9I8JvcZofn9I= github.com/aws/aws-sdk-go-v2/credentials v1.17.32/go.mod h1:P5/QMF3/DCHbXGEGkdbilXHsyTBX5D3HSwcrSc9p20I= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.2 h1:ss2pLhKcLRqzzWR08Z3arJN1R/9gcjDbzlYHyYNZ/F0= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.2/go.mod h1:luXuuIR1T/EQo8PO3rkxKajO0hMRa7NYUhComrBpgW0= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3 h1:/BPXKQ6n1cDWPmc5FWF6fCSaUtK+dWkWd0x9dI4dgaI= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3/go.mod h1:qabLXChRlJREypX5RN/Z47GU+RaMsjotNCZfZ85oD0M= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= @@ -50,10 +50,10 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvK github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4= -github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.4 h1:C8uf+nwieFWZtdPTCYOM8u/UyaIsDPfr95TJrfYekwQ= -github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.43.4/go.mod h1:hsciKQ2xFfOPEuebyKmFo7wOSVNoLuzmCi6Qtol4UDc= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.8 h1:XTz8pSCsPiM9FpT+gTPIL6ryiu/T4Z3dpR/FBtPaBXA= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.8/go.mod h1:N3YdUYxyxhiuAelUgCpSVBuBI1klobJxZrDtL+olu10= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.44.0 h1:V7S7cQbD7rBgxCtKPcaZFvQVxtjIa9d7LK2qGtkYbYI= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.44.0/go.mod h1:hsciKQ2xFfOPEuebyKmFo7wOSVNoLuzmCi6Qtol4UDc= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9 h1:jbqgtdKfAXebx2/l2UhDEe/jmmCIhaCO3HFK71M7VzM= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9/go.mod h1:N3YdUYxyxhiuAelUgCpSVBuBI1klobJxZrDtL+olu10= github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 h1:VTBHXWkSeFgT3sfYB4U92qMgzHl0nz9H1tYNHHutLg0= github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7/go.mod h1:F/ybU7YfgFcktSp+biKgiHjyscGhlZxOz4QFFQqHXGw= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= @@ -126,8 +126,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= @@ -163,8 +163,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.3 h1:QRje2j5GZimBzlbhGA2V2QlGNgL8G6e+wGo/+/2bWI0= -github.com/googleapis/enterprise-certificate-proxy v0.3.3/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -226,21 +226,21 @@ github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= -golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -280,8 +280,8 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.196.0 h1:k/RafYqebaIJBO3+SMnfEGtFVlvp5vSgqTUF54UN/zg= -google.golang.org/api v0.196.0/go.mod h1:g9IL21uGkYgvQ5BZg6BAtoGJQIm8r6EgaAbpNey5wBE= +google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= +google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -296,8 +296,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= +google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/models/formats/cedar/parse.go b/models/formats/cedar/parse.go index 97bae37..aa113f7 100644 --- a/models/formats/cedar/parse.go +++ b/models/formats/cedar/parse.go @@ -63,7 +63,7 @@ func MapCedarPolicyBytes(location string, cedarBytes []byte) (*hexapolicy.Polici } func (t *ParseSet) MapCedarPolicy(policy cedarParser.Policy) error { - hexaPolicy := hexapolicy.PolicyInfo{} + hexaPolicy := hexapolicy.PolicyInfo{Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}} pair := PolicyPair{ HexaPolicy: &hexaPolicy, CedarPolicy: &policy, diff --git a/pkg/hexapolicy/hexa_policy.go b/pkg/hexapolicy/hexa_policy.go index 43cb5eb..1557618 100644 --- a/pkg/hexapolicy/hexa_policy.go +++ b/pkg/hexapolicy/hexa_policy.go @@ -9,6 +9,7 @@ import ( "github.com/hexa-org/policy-mapper/pkg/hexapolicy/conditions" "github.com/hhsnopek/etag" + log "golang.org/x/exp/slog" ) const ( @@ -48,12 +49,12 @@ func (p *Policies) AddPolicies(policies Policies) { // PolicyInfo holds a single IDQL Policy Statement type PolicyInfo struct { - Meta MetaInfo `json:"meta" validate:"required"` // Meta holds additional information about the policy including policy management data - Subjects SubjectInfo `json:"subjects" validate:"required"` // Subjects holds the subject clause of an IDQL policy - Actions []ActionInfo `json:"actions" validate:"required"` // Actions holds one or moe action uris - Object ObjectInfo `json:"object" validate:"required"` // Object the resource, application, or system to which a policy applies - Condition *conditions.ConditionInfo `json:",omitempty"` // Condition is optional // Condition is an IDQL filter condition (e.g. ABAC rule) which must also be met - Scope *ScopeInfo `json:"scope,omitempty"` // Scope represents obligations returned to a PEP (e.g. attributes, where clause) + Meta MetaInfo `json:"meta" validate:"required"` // Meta holds additional information about the policy including policy management data + Subjects SubjectInfo `json:"subjects,subject" validate:"required"` // Subjects holds the subject clause of an IDQL policy + Actions []ActionInfo `json:"actions" validate:"required"` // Actions holds one or moe action uris + Object ObjectInfo `json:"object" validate:"required"` // Object the resource, application, or system to which a policy applies + Condition *conditions.ConditionInfo `json:"condition,omitempty"` // Condition is optional // Condition is an IDQL filter condition (e.g. ABAC rule) which must also be met + Scope *ScopeInfo `json:"scope,omitempty"` // Scope represents obligations returned to a PEP (e.g. attributes, where clause) } func (p *PolicyInfo) String() string { @@ -61,6 +62,85 @@ func (p *PolicyInfo) String() string { return string(policyBytes) } +func (p *PolicyInfo) UnmarshalJSON(data []byte) error { + if data == nil || len(data) == 0 { + return nil + } + var fieldMap map[string]*json.RawMessage + if err := json.Unmarshal(data, &fieldMap); err != nil { + return err + } + var meta MetaInfo + var subjects SubjectInfo + var actions []ActionInfo + var object ObjectInfo + var scope *ScopeInfo + var condition *conditions.ConditionInfo + + for k, v := range fieldMap { + var err error + switch k { + case "Meta", "meta": + err = json.Unmarshal(*v, &meta) + if !strings.EqualFold(meta.Version, IdqlVersion) { + log.Warn("Auto-upgrading policy to "+IdqlVersion, "PolicyId", meta.PolicyId) + meta.Version = IdqlVersion + } + case "Subjects", "subjects": + + err = json.Unmarshal(*v, &subjects) + case "Subject", "subject": + var oldSub OldSubjectInfo + err = json.Unmarshal(*v, &oldSub) + if err == nil { + subjects = oldSub.Members + } + case "Actions", "actions": + if v == nil { + continue // if null passed to "actions" + } + err = json.Unmarshal(*v, &actions) + if err != nil { + // try old action format + var oldAction []OldActionInfo + err = json.Unmarshal(*v, &oldAction) + if err == nil { + var items []ActionInfo + for _, v := range oldAction { + items = append(items, ActionInfo(v.ActionUri)) + } + actions = items + } + } + case "Object", "object": + err = json.Unmarshal(*v, &object) + if err != nil { + // try old object + var oldObject OldObjectInfo + err = json.Unmarshal(*v, &oldObject) + if err == nil { + object = ObjectInfo(oldObject.ResourceID) + } + } + case "Scope", "scope": + err = json.Unmarshal(*v, &scope) + case "Condition", "condition": + err = json.Unmarshal(*v, &condition) + } + if err != nil { + return err + } + } + + p.Meta = meta + p.Subjects = subjects + p.Actions = actions + p.Object = object + p.Scope = scope + p.Condition = condition + return nil +} + /* CalculateEtag calculates an ETAG hash value for the policy which includes the Subjects, Actions, Object, and Conditions objects only */ @@ -207,6 +287,10 @@ type MetaInfo struct { ProviderType string `json:"providerType,omitempty"` // ProviderType is the SDK provider type indicating the source of the policy } +type OldActionInfo struct { + ActionUri string `json:"actionUri" validate:"required"` +} + type ActionInfo string func (a ActionInfo) String() string { @@ -217,6 +301,10 @@ func (a ActionInfo) Equals(action ActionInfo) bool { return strings.EqualFold(string(a), string(action)) } +type OldSubjectInfo struct { + Members []string `json:"members" validate:"required"` +} + type SubjectInfo []string func (s SubjectInfo) String() []string { @@ -242,6 +330,10 @@ func (s SubjectInfo) Equals(subjects SubjectInfo) bool { return true } +type OldObjectInfo struct { + ResourceID string `json:"resource_id" validate:"required"` +} + type ObjectInfo string func (o *ObjectInfo) String() string { diff --git a/pkg/hexapolicy/hexa_policy_test.go b/pkg/hexapolicy/hexa_policy_test.go index 01858cb..6d3eadc 100644 --- a/pkg/hexapolicy/hexa_policy_test.go +++ b/pkg/hexapolicy/hexa_policy_test.go @@ -46,6 +46,33 @@ var testPolicy2 = ` "object": "aResourceId" }` +var oldPolicy1 = `{ + "Meta": { + "Version": "0.6" + }, + "Actions": [ + { + "ActionUri": "cedar:Action::view" + } + ], + "Subject": { + "Members": [ + "User:\"alice\"" + ] + }, + "condition": { + "rule": "req.ip sw 127 and req.method eq POST", + "action": "allow" + }, + "Object": { + "resource_id": "cedar:Photo::\"VacationPhoto94.jpg\"" + }, +"scope": { + "filter": "idql:username eq smith", + "attributes": ["username","emails"] + } +}` + func getPolicies(t *testing.T) Policies { t.Helper() var policy1, policy2 PolicyInfo @@ -77,6 +104,17 @@ func TestReadPolicy(t *testing.T) { } +func TestReadOldPolicy(t *testing.T) { + var pol PolicyInfo + err := json.Unmarshal([]byte(oldPolicy1), &pol) + assert.NoError(t, err, "Check no policy parse error on old policy") + assert.Equal(t, IdqlVersion, pol.Meta.Version, "policy should have current version") + assert.NotNil(t, pol.Subjects, "Subjects should not be nil") + assert.Len(t, pol.Subjects, 1, "should be one subject") + assert.Len(t, pol.Actions, 1, "should be one action") + assert.Equal(t, "cedar:Photo::\"VacationPhoto94.jpg\"", pol.Object.String(), "resource id should be converted") +} + func TestSubjectInfo_equals(t *testing.T) { policies := getPolicies(t) p1 := policies.Policies[0] From d559e072e35c8447ae346c203a5e3c11bb71b7e7 Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Sat, 14 Sep 2024 13:19:00 -0700 Subject: [PATCH 11/13] Issue #59, Switch some older "0.5" IDQL versions and switched to hexapolicy.IdqlVersion Signed-off-by: Phil Hunt --- models/rar/policy_transformer.go | 2 +- .../policy_checker_support.go | 2 +- .../policytestsupport/policy_data_support.go | 4 +- pkg/hexapolicy/hexa_policy_test.go | 68 ++++++++++++ pkg/hexapolicysupport/test/oldPolicy.json | 103 ++++++++++++++++++ .../aws/avpProvider/avp_provider_test.go | 2 +- .../openpolicyagent/opa_provider_test.go | 2 +- sdk/providerTools_test.go | 6 +- 8 files changed, 181 insertions(+), 8 deletions(-) create mode 100644 pkg/hexapolicysupport/test/oldPolicy.json diff --git a/models/rar/policy_transformer.go b/models/rar/policy_transformer.go index ef1c953..a483584 100644 --- a/models/rar/policy_transformer.go +++ b/models/rar/policy_transformer.go @@ -131,7 +131,7 @@ func ResourcePolicyMap(origPolicies []hexapolicy.PolicyInfo) map[string]hexapoli newMembers := CompactMembers(existingMembers, pol.Subjects) newPol := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: mergedActions, Subjects: hexapolicy.SubjectInfo{Members: newMembers}, Object: hexapolicy.ObjectInfo{ResourceID: resource}, diff --git a/models/rar/testsupport/policytestsupport/policy_checker_support.go b/models/rar/testsupport/policytestsupport/policy_checker_support.go index 3050e2a..29e6dbe 100644 --- a/models/rar/testsupport/policytestsupport/policy_checker_support.go +++ b/models/rar/testsupport/policytestsupport/policy_checker_support.go @@ -55,7 +55,7 @@ func MakePolicies(actionMembers map[string][]string, resourceId string) []hexapo } pol := hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(action)}, Subjects: members, Object: hexapolicy.ObjectInfo(resourceId), diff --git a/models/rar/testsupport/policytestsupport/policy_data_support.go b/models/rar/testsupport/policytestsupport/policy_data_support.go index f09814a..1437133 100644 --- a/models/rar/testsupport/policytestsupport/policy_data_support.go +++ b/models/rar/testsupport/policytestsupport/policy_data_support.go @@ -81,7 +81,7 @@ func MakeRoleSubjectTestPolicies(actionMembers map[string][]string) []hexapolicy func MakeRoleSubjectTestPolicy(resourceId string, action string, roles []string) hexapolicy.PolicyInfo { return hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(action)}, Subjects: roles, Object: hexapolicy.ObjectInfo(resourceId), @@ -90,7 +90,7 @@ func MakeRoleSubjectTestPolicy(resourceId string, action string, roles []string) func MakeTestPolicy(resourceId string, action string, actionMembers ActionMembers) hexapolicy.PolicyInfo { return hexapolicy.PolicyInfo{ - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{hexapolicy.ActionInfo(action)}, Subjects: MakePolicyTestUsers(actionMembers), Object: hexapolicy.ObjectInfo(resourceId), diff --git a/pkg/hexapolicy/hexa_policy_test.go b/pkg/hexapolicy/hexa_policy_test.go index 6d3eadc..8ad526b 100644 --- a/pkg/hexapolicy/hexa_policy_test.go +++ b/pkg/hexapolicy/hexa_policy_test.go @@ -73,6 +73,69 @@ var oldPolicy1 = `{ } }` +var oldPolicies = `{ + "policies": [ + { + "meta": {"version": "0.6"}, + "actions": [{"actionUri": "http:GET:/"}], + "subject": { + "members": [ + "allusers", "allauthenticated" + ] + }, + "condition": { + "rule": "req.ip sw 127 and req.method eq POST", + "action": "allow" + }, + "object": { + "resource_id": "aResourceId1" + } + }, + { + "meta": {"version": "0.6"}, + "actions": [{"actionUri": "http:GET:/sales"}, {"actionUri": "http:GET:/marketing"}], + "subject": { + "members": [ + "allauthenticated", + "sales@hexaindustries.io", + "marketing@hexaindustries.io" + ] + }, + "object": { + "resource_id": "aResourceId2" + } + }, + { + "meta": {"version": "0.6"}, + "actions": [{"actionUri": "http:GET:/accounting"}, {"actionUri": "http:POST:/accounting"}], + "subject": { + "members": [ + "accounting@hexaindustries.io" + ] + }, + "condition": { + "rule": "req.ip sw 127 and req.method eq POST", + "action": "allow" + }, + "object": { + "resource_id": "aResourceId3" + } + }, + { + "meta": {"version": "0.6"}, + "actions": [{"actionUri": "http:GET:/humanresources"}], + "subject": { + "members": [ + "humanresources@hexaindustries.io" + ] + }, + "object": { + "resource_id": "aResourceId1" + } + } + ] +}` + func getPolicies(t *testing.T) Policies { t.Helper() var policy1, policy2 PolicyInfo @@ -113,6 +176,11 @@ func TestReadOldPolicy(t *testing.T) { assert.Len(t, pol.Subjects, 1, "should be one subject") assert.Len(t, pol.Actions, 1, "should be one action") assert.Equal(t, "cedar:Photo::\"VacationPhoto94.jpg\"", pol.Object.String(), "resource id should be converted") + + var policies Policies + err = json.Unmarshal([]byte(oldPolicies), &policies) + assert.NoError(t, err, "Check no policy parse error on old policy") + assert.Len(t, policies.Policies, 4, "should be 4 policies") } func TestSubjectInfo_equals(t *testing.T) { diff --git a/pkg/hexapolicysupport/test/oldPolicy.json b/pkg/hexapolicysupport/test/oldPolicy.json new file mode 100644 index 0000000..32defd9 --- /dev/null +++ b/pkg/hexapolicysupport/test/oldPolicy.json @@ -0,0 +1,103 @@ +{ + "policies": [ + { + "meta": { + "policyId": "GetUsers", + "version": "0.6", + "description": "Get information (e.g. email, picture) associated with a user" + }, + "subject": { + "members": ["anyAuthenticated"] + }, + "actions": [ + { + "actionUri": "can_read_user" + } + ], + "object": { + "resource_id": "todo" + } + }, + { + "meta": { + "policyId": "GetTodos", + "version": "0.6", + + "description": "Get the list of todos. Always returns true for every user??" + }, + "subject": { + "members": ["anyAuthenticated"] + }, + "actions": [ + { + "actionUri": "can_read_todos" + } + ], + "object": { + "resource_id": "todo" + } + }, + { + "meta": { + "version": "0.6", + "description": "Create a new Todo", + "policyId": "PostTodo" + }, + "subject": { + "members": ["role:admin","role:editor"] + }, + "actions": [ + { + "actionUri": "can_create_todo" + } + ], + "object": { + "resource_id": "todo" + } + }, + { + "meta": { + "version": "0.6", + "description": "Edit(complete) a todo.", + "policyId": "PutTodo" + }, + "subject": { + "members": ["anyAuthenticated"] + }, + "actions": [ + { + "actionUri": "can_update_todo" + } + ], + "condition": { + "rule": "subject.roles co evil_genius or ( subject.roles co editor and resource.ownerID eq subject.claims.id )", + "action": "allow" + }, + "object": { + "resource_id": "todo" + } + }, + { + "meta": { + "version": "0.6", + "description": "Delete a todo if admin or owner of todo", + "policyId": "DeleteTodo" + }, + "subject": { + "members": ["anyAuthenticated"] + }, + "actions": [ + { + "actionUri": "can_delete_todo" + } + ], + "condition": { + "rule": "subject.roles co admin or ( subject.roles co editor and resource.ownerID eq subject.claims.id )", + "action": "allow" + }, + "object": { + "resource_id": "todo" + } + } + ] +} \ No newline at end of file diff --git a/providers/aws/avpProvider/avp_provider_test.go b/providers/aws/avpProvider/avp_provider_test.go index 8dd4d63..a1423e9 100644 --- a/providers/aws/avpProvider/avp_provider_test.go +++ b/providers/aws/avpProvider/avp_provider_test.go @@ -260,7 +260,7 @@ func TestAvp_3_Reconcile(t *testing.T) { // now append a policy by copying and modifying the first newPolicy := policies[0] newPolicy.Meta = hexapolicy.MetaInfo{ - Version: "0.5", + Version: hexapolicy.IdqlVersion, Description: "Test New Policy", Created: &now, Modified: &now, diff --git a/providers/openpolicyagent/opa_provider_test.go b/providers/openpolicyagent/opa_provider_test.go index d379c6f..4a31755 100644 --- a/providers/openpolicyagent/opa_provider_test.go +++ b/providers/openpolicyagent/opa_provider_test.go @@ -426,7 +426,7 @@ func TestSetPolicyInfo_WithHTTPSBundleServer(t *testing.T) { policyprovider.ApplicationInfo{ObjectID: "aResourceId"}, []hexapolicy.PolicyInfo{ { - Meta: hexapolicy.MetaInfo{Version: "0.5"}, + Meta: hexapolicy.MetaInfo{Version: hexapolicy.IdqlVersion}, Actions: []hexapolicy.ActionInfo{"http:GET"}, Subjects: []string{"allusers"}, Object: "aResourceId", diff --git a/sdk/providerTools_test.go b/sdk/providerTools_test.go index dc41b44..c96c1e7 100644 --- a/sdk/providerTools_test.go +++ b/sdk/providerTools_test.go @@ -68,6 +68,7 @@ func (s *testSuite) Test2_GetPolicies() { s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") policySet, err := s.Integration.GetPolicies(s.papId) + assert.NotNil(s.T(), policySet) policies := policySet.Policies assert.NoError(s.T(), err) assert.NotNil(s.T(), policies) @@ -85,7 +86,7 @@ func (s *testSuite) Test3_Reconcile() { s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") policySet, err := s.Integration.GetPolicies(s.papId) - + assert.NotNil(s.T(), policySet) policies := policySet.Policies assert.True(s.T(), s.mockClient.VerifyCalled()) @@ -115,7 +116,7 @@ func (s *testSuite) Test3_Reconcile() { // now append a policy by copying and modifying the first newPolicy := policies[0] newPolicy.Meta = hexapolicy.MetaInfo{ - Version: "0.5", + Version: hexapolicy.IdqlVersion, Description: "Test New Policy", Created: &now, Modified: &now, @@ -152,6 +153,7 @@ func (s *testSuite) Test4_SetPolicies() { s.mockClient.MockGetPolicyWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarStaticPolicyId+"0") s.mockClient.MockGetPolicyTemplateWithHttpStatus(http.StatusOK, avpTestSupport.TestCedarTemplatePolicyId+"0") policySet, err := s.Integration.GetPolicies(s.papId) + assert.NotNil(s.T(), policySet) policies := policySet.Policies assert.True(s.T(), s.mockClient.VerifyCalled()) From a47414fb3406b438761570e6e0d4cd5382ccf40a Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Sat, 14 Sep 2024 17:25:19 -0700 Subject: [PATCH 12/13] Issue #59, Fixed an issue where oidcsupport and session support were both issuing redirects to start a login causing a superfluous write error in the log. Signed-off-by: Phil Hunt --- pkg/oidcSupport/client.go | 1 + pkg/sessionSupport/session.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/oidcSupport/client.go b/pkg/oidcSupport/client.go index 437184b..27d7bfd 100644 --- a/pkg/oidcSupport/client.go +++ b/pkg/oidcSupport/client.go @@ -197,6 +197,7 @@ func (o *OidcClientHandler) HandleSessionScope(next http.HandlerFunc, _ []string if o.SessionHandler.ValidateSession(w, r) { // TODO Check scopes next.ServeHTTP(w, r) + return } http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } else { diff --git a/pkg/sessionSupport/session.go b/pkg/sessionSupport/session.go index b1a0361..b735cca 100644 --- a/pkg/sessionSupport/session.go +++ b/pkg/sessionSupport/session.go @@ -73,7 +73,7 @@ func (s *sessionManager) SetSessionMiddleware(router *mux.Router) { router.Use(s.manager.LoadAndSave) } -func (s *sessionManager) ValidateSession(w http.ResponseWriter, r *http.Request) bool { +func (s *sessionManager) ValidateSession(_ http.ResponseWriter, r *http.Request) bool { // If sub is not set, we assume the session was not authenticated // Note the value of sessionId is just a unique ID for logging purposes @@ -82,7 +82,7 @@ func (s *sessionManager) ValidateSession(w http.ResponseWriter, r *http.Request) if s.loginEnabled { sub := s.manager.GetString(r.Context(), KeySubject) if sub == "" { - http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + // http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return false } } From 4180a7ef6121400e3deb654413380456ace88d80 Mon Sep 17 00:00:00 2001 From: Phil Hunt Date: Wed, 25 Sep 2024 13:12:52 -0700 Subject: [PATCH 13/13] Issue #67, Added support for empty Actions to Rego and updated dependencies Signed-off-by: Phil Hunt --- cmd/hexa/main.go | 2 +- go.mod | 32 ++++++++--------- go.sum | 34 ++++++++++++++++++ .../resources/bundles/bundle/hexaPolicy.rego | 35 ++++++++++++++++--- 4 files changed, 82 insertions(+), 21 deletions(-) diff --git a/cmd/hexa/main.go b/cmd/hexa/main.go index 995e542..573a215 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.0" +const Version string = "0.7.1" type ParserData struct { parser *kong.Kong diff --git a/go.mod b/go.mod index 5c6e5c1..834009c 100644 --- a/go.mod +++ b/go.mod @@ -2,26 +2,26 @@ module github.com/hexa-org/policy-mapper go 1.23 -toolchain go1.23.0 +toolchain go1.23.1 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 - github.com/MicahParks/jwkset v0.5.18 - github.com/MicahParks/keyfunc/v3 v3.3.3 + github.com/MicahParks/jwkset v0.5.19 + github.com/MicahParks/keyfunc/v3 v3.3.5 github.com/alecthomas/assert/v2 v2.10.0 github.com/alecthomas/kong v1.2.1 github.com/alecthomas/participle/v2 v2.1.1 github.com/alexedwards/scs/v2 v2.8.0 github.com/aws/aws-sdk-go-v2 v1.30.5 - github.com/aws/aws-sdk-go-v2/config v1.27.33 - github.com/aws/aws-sdk-go-v2/credentials v1.17.32 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3 - github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.44.0 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9 - github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 - github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.6 - github.com/cedar-policy/cedar-go v0.1.0 + github.com/aws/aws-sdk-go-v2/config v1.27.35 + github.com/aws/aws-sdk-go-v2/credentials v1.17.33 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.4 + github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.44.1 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.10 + github.com/aws/aws-sdk-go-v2/service/s3 v1.61.3 + github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.7 + github.com/cedar-policy/cedar-go v0.3.1 github.com/chzyer/readline v1.5.1 github.com/coreos/go-oidc/v3 v3.11.0 github.com/envoyproxy/go-control-plane v0.13.0 @@ -33,7 +33,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/hhsnopek/etag v0.0.0-20171206181245-aea95f647346 - github.com/prometheus/client_golang v1.20.3 + github.com/prometheus/client_golang v1.20.4 github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 @@ -56,15 +56,15 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.8 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.8 // indirect github.com/aws/smithy-go v1.20.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index 8f37144..8fbbbe0 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= cloud.google.com/go/auth v0.9.4 h1:DxF7imbEbiFu9+zdKC6cKBko1e8XeJnipNqIbWZ+kDI= cloud.google.com/go/auth v0.9.4/go.mod h1:SHia8n6//Ya940F1rLimhJCjjx7KE17t0ctFEci3HkA= cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= @@ -16,8 +17,12 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/MicahParks/jwkset v0.5.18 h1:WLdyMngF7rCrnstQxA7mpRoxeaWqGzPM/0z40PJUK4w= github.com/MicahParks/jwkset v0.5.18/go.mod h1:q8ptTGn/Z9c4MwbcfeCDssADeVQb3Pk7PnVxrvi+2QY= +github.com/MicahParks/jwkset v0.5.19 h1:XZCsgJv05DBCvxEHYEHlSafqiuVn5ESG0VRB331Fxhw= +github.com/MicahParks/jwkset v0.5.19/go.mod h1:q8ptTGn/Z9c4MwbcfeCDssADeVQb3Pk7PnVxrvi+2QY= github.com/MicahParks/keyfunc/v3 v3.3.3 h1:c6j9oSu1YUo0k//KwF1miIQlEMtqNlj7XBFLB8jtEmY= github.com/MicahParks/keyfunc/v3 v3.3.3/go.mod h1:f/UMyXdKfkZzmBeBFUeYk+zu066J1Fcl48f7Wnl5Z48= +github.com/MicahParks/keyfunc/v3 v3.3.5 h1:7ceAJLUAldnoueHDNzF8Bx06oVcQ5CfJnYwNt1U3YYo= +github.com/MicahParks/keyfunc/v3 v3.3.5/go.mod h1:SdCCyMJn/bYqWDvARspC6nCT8Sk74MjuAY22C7dCST8= github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/kong v1.2.1 h1:E8jH4Tsgv6wCRX2nGrdPyHDUCSG83WH2qE4XLACD33Q= @@ -36,10 +41,16 @@ github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5L github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw= github.com/aws/aws-sdk-go-v2/config v1.27.33 h1:Nof9o/MsmH4oa0s2q9a0k7tMz5x/Yj5k06lDODWz3BU= github.com/aws/aws-sdk-go-v2/config v1.27.33/go.mod h1:kEqdYzRb8dd8Sy2pOdEbExTTF5v7ozEXX0McgPE7xks= +github.com/aws/aws-sdk-go-v2/config v1.27.35 h1:jeFgiWYNV0vrgdZqB4kZBjYNdy0IKkwrAjr2fwpHIig= +github.com/aws/aws-sdk-go-v2/config v1.27.35/go.mod h1:qnpEvTq8ZfjrCqmJGRfWZuF+lGZ/vG8LK2K0L/TY1gQ= github.com/aws/aws-sdk-go-v2/credentials v1.17.32 h1:7Cxhp/BnT2RcGy4VisJ9miUPecY+lyE9I8JvcZofn9I= github.com/aws/aws-sdk-go-v2/credentials v1.17.32/go.mod h1:P5/QMF3/DCHbXGEGkdbilXHsyTBX5D3HSwcrSc9p20I= +github.com/aws/aws-sdk-go-v2/credentials v1.17.33 h1:lBHAQQznENv0gLHAZ73ONiTSkCtr8q3pSqWrpbBBZz0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.33/go.mod h1:MBuqCUOT3ChfLuxNDGyra67eskx7ge9e3YKYBce7wpI= github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3 h1:/BPXKQ6n1cDWPmc5FWF6fCSaUtK+dWkWd0x9dI4dgaI= github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3/go.mod h1:qabLXChRlJREypX5RN/Z47GU+RaMsjotNCZfZ85oD0M= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.4 h1:m7h6UwsHIyx0L8K7T17aYeDcevwd6hGrbQiAid2XyKA= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.4/go.mod h1:Hkre8GbTJ7Y6IKDVMoWRD12pIHQ6GH4JNEwzKiz6xyw= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= @@ -52,10 +63,16 @@ github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0Cp github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4= github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.44.0 h1:V7S7cQbD7rBgxCtKPcaZFvQVxtjIa9d7LK2qGtkYbYI= github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.44.0/go.mod h1:hsciKQ2xFfOPEuebyKmFo7wOSVNoLuzmCi6Qtol4UDc= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.44.1 h1:Nn3bGq+1nNw+kAfcwUs6LZXi175KoejFyvtjB49WvHw= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.44.1/go.mod h1:hsciKQ2xFfOPEuebyKmFo7wOSVNoLuzmCi6Qtol4UDc= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9 h1:jbqgtdKfAXebx2/l2UhDEe/jmmCIhaCO3HFK71M7VzM= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9/go.mod h1:N3YdUYxyxhiuAelUgCpSVBuBI1klobJxZrDtL+olu10= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.10 h1:ozHHSE9Hflrf2DZmJEoqIO+bK6E6rAfID8PSCv2rgG8= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.10/go.mod h1:N3YdUYxyxhiuAelUgCpSVBuBI1klobJxZrDtL+olu10= github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 h1:VTBHXWkSeFgT3sfYB4U92qMgzHl0nz9H1tYNHHutLg0= github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7/go.mod h1:F/ybU7YfgFcktSp+biKgiHjyscGhlZxOz4QFFQqHXGw= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.8 h1:lje864O92lma0+TnDNHAMpiehauR02sTo+xfoSsw3DE= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.8/go.mod h1:F/ybU7YfgFcktSp+biKgiHjyscGhlZxOz4QFFQqHXGw= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 h1:FLMkfEiRjhgeDTCjjLoc3URo/TBkgeQbocA78lfkzSI= @@ -68,20 +85,34 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY= github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg= github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.3 h1:O/rjUvLED2dWzrSY6wv3njBjJlH4LT2xYRnUm402ovI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.3/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc= github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc= github.com/aws/aws-sdk-go-v2/service/sso v1.22.7/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.8 h1:JRwuL+S1Qe1owZQoxblV7ORgRf2o0SrtzDVIbaVCdQ0= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.8/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 h1:/Cfdu0XV3mONYKaOt1Gr0k1KvQzkzPyiKUdlWJqy+J4= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.8 h1:+HpGETD9463PFSj7lX5+eq7aLDs85QUIA+NBkeAsscA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.8/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA= github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 h1:NKTa1eqZYw8tiHSRGpP0VtTdub/8KNk8sDkNPFaOKDE= github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.8 h1:bAi+4p5EKnni+jrfcAhb7iHFQ24bthOAV9t0taf3DCE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.8/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o= github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.6 h1:OALTvlqxlJysbfpPN02yEaQbq+i0mupm14m28IadjXs= github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.6/go.mod h1:/il6CcYy1TceX8GhBT8qbEUiqIGP/R+OvlztiT8OMEw= +github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.7 h1:1zLYrijPJeJ9BkrfRaC8xvoeo5Mlyyjgnt289MVb2eg= +github.com/aws/aws-sdk-go-v2/service/verifiedpermissions v1.17.7/go.mod h1:/il6CcYy1TceX8GhBT8qbEUiqIGP/R+OvlztiT8OMEw= github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cedar-policy/cedar-go v0.1.0 h1:2tZwWn8tNO/896YAM7OQmH3vn98EeHEA3g9anwdVZvA= github.com/cedar-policy/cedar-go v0.1.0/go.mod h1:pEgiK479O5dJfzXnTguOMm+bCplzy5rEEFPGdZKPWz4= +github.com/cedar-policy/cedar-go v0.3.0 h1:lsduja7VzttsRZUXoIidnJZFLvC6FIAB9Ib2R5kZ45o= +github.com/cedar-policy/cedar-go v0.3.0/go.mod h1:pEgiK479O5dJfzXnTguOMm+bCplzy5rEEFPGdZKPWz4= +github.com/cedar-policy/cedar-go v0.3.1 h1:Zyd4NYwhP0mBdUkzBSxDAp13FJDGo17lcVaRXlWnHK0= +github.com/cedar-policy/cedar-go v0.3.1/go.mod h1:pEgiK479O5dJfzXnTguOMm+bCplzy5rEEFPGdZKPWz4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -198,6 +229,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= @@ -287,6 +320,7 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= diff --git a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego b/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego index 8754174..cc486a7 100644 --- a/providers/openpolicyagent/resources/bundles/bundle/hexaPolicy.rego +++ b/providers/openpolicyagent/resources/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" +}