mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: Complete target system with C# bindings and resource inference (#458)
* feat: Add Schema Registry and Validation Framework This commit introduces a comprehensive schema registry and validation framework, providing schema-based validation of resources and policy effects. - Thread-safe, in-memory registry for schema storage and management - Global registry patterns for effects and resources - Concurrent access with proper error handling - Unicode schema names support - JSON Schema-compliant validation for all primitive types - Advanced constraint validation (patterns, ranges, length limits) - Discriminated union support with anyOf schemas - Detailed error reporting with nested validation paths - Discriminated subobject validation for polymorphic schemas - **Registry Tests**: All registry operations - **Effect Tests**: Policy effect validation - **Resource Tests**: Resource validation - **Validation Tests**: Core validation engine - Thread-safety, error handling, integration scenarios, edge cases - **Dependencies**: dashmap, once_cell, regex - **Thread Safety**: Minimal locking with Rc<Schema> sharing - **Error Types**: TypeMismatch, OutOfRange, PatternMismatch, etc. - Complete schema registry and validation subsystem - Comprehensive test coverage - Foundation for policy validation in Regorus Benchmarks: - Criterion benchmarks for basic types, effects and Azure resources - Performance range: 3.22ns (string) to 34.74µs (Azure VM resource schema validation) - String withs patterns validation: 30.2µs. Need to explore whether regex caching helps bring this down. - Azure policy effects: 188ns-1.4µs Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> * feat: Complete target system with C# bindings and resource inference - Add comprehensive target system with TargetRegistry and target-aware compilation - Implement resource type inference from policy equality expressions - Create modular C# bindings with separate wrapper classes for each concept - Add thread-safe CompiledPolicy with reference counting for safe disposal - Enhance FFI with detailed error propagation and target functionality - Create TargetExampleApp demonstrating Azure Policy integration - Add CI/CD pipeline testing for all C# applications - Support target definitions with schema validation and resource selectors - Implement PolicyModule struct and target-aware compilation methods - Add comprehensive test coverage for target functionality Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> --------- Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
3c33d31d08
commit
cc917ea75d
@@ -3,10 +3,9 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: basic string formatting with spaces
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
test1 := sprintf("User %s from %s has access", ["alice", "engineering"])
|
||||
test2 := sprintf("A %s B %s C", ["X", "Y"])
|
||||
test3 := sprintf("No spaces%s%s", ["A", "B"])
|
||||
test4 := sprintf("Single %s", ["word"])
|
||||
test5 := sprintf("Start %s end", ["middle"])
|
||||
query: data.test
|
||||
want_result:
|
||||
test1: "User alice from engineering has access"
|
||||
test2: "A X B Y C"
|
||||
test3: "No spacesAB"
|
||||
test4: "Single word"
|
||||
test5: "Start middle end"
|
||||
|
||||
- note: numeric formatting
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
decimal := sprintf("Number: %d", [42])
|
||||
float := sprintf("Float: %f", [3.14])
|
||||
hex := sprintf("Hex: %x", [255])
|
||||
octal := sprintf("Octal: %o", [64])
|
||||
binary := sprintf("Binary: %b", [15])
|
||||
query: data.test
|
||||
want_result:
|
||||
decimal: "Number: 42"
|
||||
float: "Float: 3.14"
|
||||
hex: "Hex: ff"
|
||||
octal: "Octal: 0O100"
|
||||
binary: "Binary: 1111"
|
||||
|
||||
- note: mixed types and escaping
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
mixed := sprintf("String: %s, Number: %d, Percent: %%", ["hello", 123])
|
||||
verbose := sprintf("Value: %v", [{"key": "value"}])
|
||||
query: data.test
|
||||
want_result:
|
||||
mixed: "String: hello, Number: 123, Percent: %"
|
||||
verbose: "Value: {\"key\": \"value\"}"
|
||||
|
||||
- note: width formatting
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
padded := sprintf("Padded: %5d", [42])
|
||||
zero_padded := sprintf("Zero padded: %05d", [42])
|
||||
decimal_places := sprintf("Decimal: %.2f", [3.14159])
|
||||
query: data.test
|
||||
want_result:
|
||||
padded: "Padded: 42"
|
||||
zero_padded: "Zero padded: 00042"
|
||||
decimal_places: "Decimal: 3.14"
|
||||
|
||||
- note: error cases
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
# This should cause an error - missing argument
|
||||
error_case := sprintf("Value: %s %d", ["only_one"])
|
||||
query: data.test
|
||||
error: "no argument specified for format verb 1"
|
||||
@@ -0,0 +1,79 @@
|
||||
cases:
|
||||
- note: "Azure Policy Basic Allow Test"
|
||||
data: {}
|
||||
input:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
name: "mystorageaccount"
|
||||
location: "East US"
|
||||
kind: "StorageV2"
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: true
|
||||
minimumTlsVersion: "TLS1_2"
|
||||
tags:
|
||||
environment: "production"
|
||||
modules:
|
||||
- |
|
||||
package azure.policy.allow
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.azure_policy"
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.type == "Microsoft.Storage/storageAccounts"
|
||||
input.properties.supportsHttpsTrafficOnly == true
|
||||
}
|
||||
query: data.azure.policy.allow.allow
|
||||
want_result: true
|
||||
|
||||
- note: "Azure Policy Deny Test - HTTP Traffic"
|
||||
data: {}
|
||||
input:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
name: "insecurestorage"
|
||||
location: "West US"
|
||||
kind: "Storage"
|
||||
properties:
|
||||
supportsHttpsTrafficOnly: false
|
||||
minimumTlsVersion: "TLS1_0"
|
||||
modules:
|
||||
- |
|
||||
package azure.policy.deny
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.azure_policy"
|
||||
|
||||
deny := {
|
||||
"message": "HTTPS traffic must be enabled"
|
||||
} if {
|
||||
input.type == "Microsoft.Storage/storageAccounts"
|
||||
input.properties.supportsHttpsTrafficOnly == false
|
||||
}
|
||||
query: data.azure.policy.deny.deny
|
||||
want_result:
|
||||
message: "HTTPS traffic must be enabled"
|
||||
|
||||
- note: "Azure Policy Invalid Resource Type"
|
||||
data: {}
|
||||
input:
|
||||
type: "Microsoft.UnknownService/unknownResource"
|
||||
name: "test"
|
||||
modules:
|
||||
- |
|
||||
package azure.policy.invalid
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.azure_policy"
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.type == "Microsoft.Storage/storageAccounts"
|
||||
input.properties.supportsHttpsTrafficOnly == true
|
||||
}
|
||||
query: data.azure.policy.invalid.allow
|
||||
want_result: false
|
||||
@@ -0,0 +1,682 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: "target/valid_target"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.allow
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.allow.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/missing_assignment_operator"
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test.missing_assignment
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.missing_assignment.allow
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: "target/effect_validation_undefined_result"
|
||||
data: {}
|
||||
input: {"name": "invalid"}
|
||||
modules:
|
||||
- |
|
||||
package test.undefined
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# No default, rule doesn't match, so allow is undefined
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.undefined.allow
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: "target/target_resolution_timing"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
__target__ "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.allow
|
||||
error: "expected ':=' after __target__"
|
||||
|
||||
- note: "target/missing_string_literal"
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := 123
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.allow
|
||||
error: "expected string literal"
|
||||
|
||||
- note: "target/invalid_string_format"
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "unterminated string
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.allow
|
||||
error: "unterminated string"
|
||||
|
||||
- note: "target/nonexistent_target"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "nonexistent.target.name"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.allow
|
||||
error: "Target 'nonexistent.target.name' not found in registry"
|
||||
|
||||
- note: "target/target_before_imports"
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
import rego.v1
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.allow
|
||||
error: "unexpected keyword `import`"
|
||||
|
||||
- note: "target/target_after_rule"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
import rego.v1
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
query: data.test.allow
|
||||
error: "__target__ must be defined before any rules"
|
||||
|
||||
- note: "target/multiple_modules_same_target"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.module1
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
- |
|
||||
package test.module2
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
deny if {
|
||||
input.name == "invalid"
|
||||
}
|
||||
query: data.test.module1.allow
|
||||
error: "Modules with target 'target.tests.sample_test_target' have different packages: 'test.module1' and 'test.module2'"
|
||||
|
||||
- note: "target/multiple_modules_same_target_same_package"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.shared
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
- |
|
||||
package test.shared
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "invalid"
|
||||
}
|
||||
query: data.test.shared.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/multiple_modules_different_targets"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.module1
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
- |
|
||||
package test.module2
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.azure_compute"
|
||||
|
||||
deny if {
|
||||
input.name == "invalid"
|
||||
}
|
||||
query: data.test.module1.allow
|
||||
error: "Multiple different targets specified: 'target.tests.sample_test_target' and 'target.tests.azure_compute'"
|
||||
|
||||
- note: "target/multiple_different_effects_same_package"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.multieffect
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
|
||||
deny if {
|
||||
input.name == "invalid"
|
||||
}
|
||||
query: data.test.multieffect.allow
|
||||
error: "Multiple effects have rules defined for target 'target.tests.sample_test_target': allow, deny. Only one effect should have rules defined in package 'test.multieffect'"
|
||||
|
||||
- note: "target/no_effect_rules_defined"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.noeffects
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
helper_function(x) if {
|
||||
x == "valid"
|
||||
}
|
||||
query: data.test.noeffects.helper_function("valid")
|
||||
error: "Target 'target.tests.sample_test_target' requires a rule with name allow, deny or test_effect in package 'test.noeffects'"
|
||||
|
||||
- note: "target/valid_deny_effect"
|
||||
data: {}
|
||||
input: {"name": "invalid"}
|
||||
modules:
|
||||
- |
|
||||
package test.deny
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default deny := false
|
||||
|
||||
deny if {
|
||||
input.name == "invalid"
|
||||
}
|
||||
query: data.test.deny.deny
|
||||
want_result: true
|
||||
|
||||
- note: "target/valid_test_effect"
|
||||
data: {}
|
||||
input: {"level": "warning", "message": "test message"}
|
||||
modules:
|
||||
- |
|
||||
package test.custom
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
test_effect := {
|
||||
"level": input.level,
|
||||
"message": input.message
|
||||
} if {
|
||||
input.level
|
||||
input.message
|
||||
}
|
||||
query: data.test.custom.test_effect
|
||||
want_result: {"level": "warning", "message": "test message"}
|
||||
|
||||
- note: "target/multiple_rules_same_effect_same_module"
|
||||
data: {}
|
||||
input: {"name": "valid", "role": "admin"}
|
||||
modules:
|
||||
- |
|
||||
package test.multirules
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# Multiple rules for the same effect in the same module
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
|
||||
allow if {
|
||||
input.role == "admin"
|
||||
}
|
||||
query: data.test.multirules.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/multiple_rules_same_effect_different_modules"
|
||||
data: {}
|
||||
input: {"name": "valid", "role": "admin"}
|
||||
modules:
|
||||
- |
|
||||
package test.distributed
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
- |
|
||||
package test.distributed
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.role == "admin"
|
||||
}
|
||||
query: data.test.distributed.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/effect_with_non_effect_rules_same_module"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.mixed
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
|
||||
# Non-effect helper rule
|
||||
is_admin if {
|
||||
input.role == "admin"
|
||||
}
|
||||
|
||||
# Another non-effect rule
|
||||
helper_data := {"status": "active"}
|
||||
query: data.test.mixed.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/effect_with_non_effect_rules_different_modules"
|
||||
data: {}
|
||||
input: {"name": "valid", "role": "admin"}
|
||||
modules:
|
||||
- |
|
||||
package test.separate
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
- |
|
||||
package test.separate
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# Non-effect helper rules in different module
|
||||
is_admin if {
|
||||
input.role == "admin"
|
||||
}
|
||||
|
||||
user_data := {"type": "user", "active": true}
|
||||
query: data.test.separate.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/effect_subpath_should_fail"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.subpath
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# This creates a rule at data.test.subpath.allow.nested.rule
|
||||
# which is a subpath of the expected effect path data.test.subpath.allow
|
||||
allow.nested.rule if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.subpath.allow.nested.rule
|
||||
error: "Target 'target.tests.sample_test_target' requires a rule with name allow, deny or test_effect in package 'test.subpath'"
|
||||
|
||||
- note: "target/multiple_subpath_rules_should_fail"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.multisubpath
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# Multiple subpath rules under allow
|
||||
allow.users.admin if {
|
||||
input.name == "valid"
|
||||
}
|
||||
|
||||
allow.users.guest if {
|
||||
input.name == "guest"
|
||||
}
|
||||
query: data.test.multisubpath.allow.users.admin
|
||||
error: "Target 'target.tests.sample_test_target' requires a rule with name allow, deny or test_effect in package 'test.multisubpath'"
|
||||
|
||||
- note: "target/effect_schema_validation_valid"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.schema_valid
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.schema_valid.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/effect_schema_validation_invalid_type"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.schema_invalid
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# test_effect schema expects object with string properties, but we return a string
|
||||
test_effect := "invalid_string_instead_of_object" if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.schema_invalid.test_effect
|
||||
error: "Type mismatch"
|
||||
|
||||
- note: "target/effect_schema_validation_missing_required_field"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.schema_missing_field
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# test_effect expects an object, but with invalid property types
|
||||
test_effect := {
|
||||
"level": 123, # should be string, not number
|
||||
"message": "test message"
|
||||
} if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.schema_missing_field.test_effect
|
||||
error: "Type mismatch"
|
||||
|
||||
- note: "target/effect_schema_validation_complex_object"
|
||||
data: {}
|
||||
input: {"name": "valid", "details": {"severity": "high", "category": "security"}}
|
||||
modules:
|
||||
- |
|
||||
package test.schema_complex
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
test_effect := {
|
||||
"level": "error",
|
||||
"message": "Security violation detected",
|
||||
"details": input.details
|
||||
} if {
|
||||
input.name == "valid"
|
||||
input.details.severity == "high"
|
||||
}
|
||||
query: data.test.schema_complex.test_effect
|
||||
want_result: {"level": "error", "message": "Security violation detected", "details": {"severity": "high", "category": "security"}}
|
||||
|
||||
- note: "target/effect_schema_validation_enum_constraint"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.schema_enum
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# test_effect requires an object, but we return an array instead
|
||||
test_effect := ["invalid", "array", "instead", "of", "object"] if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.schema_enum.test_effect
|
||||
error: "Type mismatch"
|
||||
|
||||
- note: "target/multiple_targets_different_schemas"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.multi_target_a
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
- |
|
||||
package test.multi_target_b
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.azure_compute"
|
||||
|
||||
deny if {
|
||||
input.name == "invalid"
|
||||
}
|
||||
query: data.test.multi_target_a.allow
|
||||
error: "Multiple different targets specified"
|
||||
|
||||
- note: "target/effect_validation_with_default_value"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.with_default
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.with_default.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/effect_validation_undefined_result"
|
||||
data: {}
|
||||
input: {"name": "invalid"}
|
||||
modules:
|
||||
- |
|
||||
package test.undefined
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# No default, rule doesn't match, so allow is undefined
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.undefined.allow
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: "target/target_resolution_timing"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.timing
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# Rule defined after target - should work due to proper timing
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
|
||||
# Helper rule that's not an effect
|
||||
helper := "test" if true
|
||||
query: data.test.timing.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/nested_package_structure"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.nested.deep.structures
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
query: data.test.nested.deep.structures.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/package_validation_error_consistency"
|
||||
data: {}
|
||||
input: {"name": "valid"}
|
||||
modules:
|
||||
- |
|
||||
package test.package_a
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.name == "valid"
|
||||
}
|
||||
- |
|
||||
package test.package_b
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
deny if {
|
||||
input.name == "invalid"
|
||||
}
|
||||
query: data.test.package_a.allow
|
||||
error: "Modules with target 'target.tests.sample_test_target' have different packages: 'test.package_a' and 'test.package_b'"
|
||||
@@ -0,0 +1,561 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: "target/complex_nested_object_validation"
|
||||
data: {}
|
||||
input: {"user": {"name": "alice", "roles": ["admin", "user"], "metadata": {"department": "engineering", "level": 5}}}
|
||||
modules:
|
||||
- |
|
||||
package policy.complex
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
test_effect := {
|
||||
"level": "info",
|
||||
"message": sprintf("User %s from %s has access", [input.user.name, input.user.metadata.department]),
|
||||
"details": {
|
||||
"user_roles": input.user.roles,
|
||||
"access_level": input.user.metadata.level,
|
||||
"timestamp": "2025-08-14T10:00:00Z"
|
||||
}
|
||||
} if {
|
||||
"admin" in input.user.roles
|
||||
input.user.metadata.level >= 3
|
||||
}
|
||||
query: data.policy.complex.test_effect
|
||||
want_result: {
|
||||
"details": {
|
||||
"access_level": 5,
|
||||
"timestamp": "2025-08-14T10:00:00Z",
|
||||
"user_roles": ["admin", "user"]
|
||||
},
|
||||
"level": "info",
|
||||
"message": "User alice from engineering has access"
|
||||
}
|
||||
|
||||
- note: "target/complex_conditional_logic_with_multiple_rules"
|
||||
data: {}
|
||||
input: {"resource": {"type": "document", "classification": "confidential", "owner": "alice"}, "requester": {"id": "bob", "clearance": "secret"}}
|
||||
modules:
|
||||
- |
|
||||
package policy.access_control
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Multiple complex rules for the same effect
|
||||
allow if {
|
||||
input.resource.classification == "public"
|
||||
}
|
||||
|
||||
allow if {
|
||||
input.resource.owner == input.requester.id
|
||||
}
|
||||
|
||||
allow if {
|
||||
input.resource.classification == "confidential"
|
||||
input.requester.clearance in ["secret", "top_secret"]
|
||||
count([role | role := input.requester.roles[_]; role == "analyst"]) > 0
|
||||
}
|
||||
|
||||
allow if {
|
||||
input.resource.type == "document"
|
||||
input.requester.clearance == "top_secret"
|
||||
}
|
||||
query: data.policy.access_control.allow
|
||||
want_result: false
|
||||
|
||||
- note: "target/complex_data_manipulation_with_comprehensions"
|
||||
data: {
|
||||
"users": [
|
||||
{"id": "u1", "name": "alice", "department": "eng", "salary": 100000},
|
||||
{"id": "u2", "name": "bob", "department": "sales", "salary": 80000},
|
||||
{"id": "u3", "name": "charlie", "department": "eng", "salary": 120000}
|
||||
]
|
||||
}
|
||||
input: {"department_filter": "eng", "min_salary": 90000}
|
||||
modules:
|
||||
- |
|
||||
package policy.hr_analysis
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
test_effect := {
|
||||
"level": "info",
|
||||
"message": sprintf("Found %d qualified users", [count(qualified_users)]),
|
||||
"details": {
|
||||
"qualified_users": qualified_users,
|
||||
"avg_salary": avg_salary,
|
||||
"total_budget": total_budget
|
||||
}
|
||||
} if {
|
||||
count(qualified_users) > 0
|
||||
}
|
||||
|
||||
qualified_users := [user |
|
||||
user := data.users[_]
|
||||
user.department == input.department_filter
|
||||
user.salary >= input.min_salary
|
||||
]
|
||||
|
||||
# Extract total_budget calculation to avoid scheduling error
|
||||
total_budget := v if {
|
||||
count(qualified_users) > 0
|
||||
v := sum([salary |
|
||||
true
|
||||
user := qualified_users[_]
|
||||
salary := user.salary
|
||||
])
|
||||
}
|
||||
|
||||
# Extract avg_salary calculation to avoid scheduling error
|
||||
avg_salary := v if {
|
||||
count(qualified_users) > 0
|
||||
v := sum([salary |
|
||||
true
|
||||
user := qualified_users[_]
|
||||
salary := user.salary
|
||||
]) / count(qualified_users)
|
||||
}
|
||||
query: data.policy.hr_analysis.test_effect
|
||||
want_result: {
|
||||
"level": "info",
|
||||
"message": "Found 2 qualified users",
|
||||
"details": {
|
||||
"qualified_users": [
|
||||
{"id": "u1", "name": "alice", "department": "eng", "salary": 100000},
|
||||
{"id": "u3", "name": "charlie", "department": "eng", "salary": 120000}
|
||||
],
|
||||
"avg_salary": 110000,
|
||||
"total_budget": 220000
|
||||
}
|
||||
}
|
||||
|
||||
- note: "target/corner_case_empty_collections"
|
||||
data: {"empty_array": [], "empty_object": {}}
|
||||
input: {"filters": []}
|
||||
modules:
|
||||
- |
|
||||
package policy.empty_collections
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
count(data.empty_array) == 0
|
||||
count(object.keys(data.empty_object)) == 0
|
||||
count(input.filters) == 0
|
||||
}
|
||||
query: data.policy.empty_collections.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/corner_case_null_and_undefined_handling"
|
||||
data: {"nullable_field": null}
|
||||
input: {"optional_field": null}
|
||||
modules:
|
||||
- |
|
||||
package policy.null_handling
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default test_effect := {}
|
||||
|
||||
test_effect := {
|
||||
"level": "warning",
|
||||
"message": "Handling null values correctly"
|
||||
} if {
|
||||
data.nullable_field == null
|
||||
input.optional_field == null
|
||||
# Check that missing_field is undefined by ensuring it's not present
|
||||
not "missing_field" in object.keys(data)
|
||||
}
|
||||
query: data.policy.null_handling.test_effect
|
||||
want_result: {
|
||||
"level": "warning",
|
||||
"message": "Handling null values correctly"
|
||||
}
|
||||
|
||||
- note: "target/corner_case_deeply_nested_structures"
|
||||
data: {}
|
||||
input: {
|
||||
"request": {
|
||||
"metadata": {
|
||||
"auth": {
|
||||
"user": {
|
||||
"profile": {
|
||||
"permissions": {
|
||||
"read": ["doc1", "doc2"],
|
||||
"write": ["doc1"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
modules:
|
||||
- |
|
||||
package policy.deep_nesting
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
permissions := input.request.metadata.auth.user.profile.permissions
|
||||
"doc1" in permissions.read
|
||||
"doc1" in permissions.write
|
||||
}
|
||||
query: data.policy.deep_nesting.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/corner_case_unicode_and_special_characters"
|
||||
data: {}
|
||||
input: {
|
||||
"user": "測試用戶",
|
||||
"message": "Hello, 世界! 🌍",
|
||||
"special_chars": "!@#$%^&*()_+-=[]{}|;':\",./<>?"
|
||||
}
|
||||
modules:
|
||||
- |
|
||||
package policy.unicode_handling
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
test_effect := {
|
||||
"level": "info",
|
||||
"message": sprintf("Processing for user: %s", [input.user])
|
||||
} if {
|
||||
contains(input.message, "世界")
|
||||
startswith(input.special_chars, "!")
|
||||
}
|
||||
query: data.policy.unicode_handling.test_effect
|
||||
want_result: {
|
||||
"level": "info",
|
||||
"message": "Processing for user: 測試用戶"
|
||||
}
|
||||
|
||||
- note: "target/corner_case_large_numbers_and_precision"
|
||||
data: {}
|
||||
input: {
|
||||
"large_int": 9223372036854775807,
|
||||
"small_float": 0.000000000001,
|
||||
"large_float": 1.7976931348623157e+308
|
||||
}
|
||||
modules:
|
||||
- |
|
||||
package policy.numeric_precision
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
allow if {
|
||||
input.large_int > 9000000000000000000
|
||||
input.small_float < 0.001
|
||||
input.large_float > 1e100
|
||||
}
|
||||
query: data.policy.numeric_precision.allow
|
||||
want_result: true
|
||||
|
||||
- note: "target/corner_case_circular_references_in_data"
|
||||
data: {
|
||||
"users": {
|
||||
"alice": {"id": "alice", "manager": "bob"},
|
||||
"bob": {"id": "bob", "manager": "charlie"},
|
||||
"charlie": {"id": "charlie", "manager": "alice"}
|
||||
}
|
||||
}
|
||||
input: {"check_user": "alice"}
|
||||
modules:
|
||||
- |
|
||||
package policy.circular_refs
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# Detect circular management chain
|
||||
deny if {
|
||||
has_circular_management(input.check_user, set())
|
||||
}
|
||||
|
||||
has_circular_management(user_id, visited) if {
|
||||
user_id in visited
|
||||
}
|
||||
|
||||
has_circular_management(user_id, visited) if {
|
||||
not user_id in visited
|
||||
manager := data.users[user_id].manager
|
||||
manager != null
|
||||
has_circular_management(manager, visited | {user_id})
|
||||
}
|
||||
query: data.policy.circular_refs.deny
|
||||
want_result: true
|
||||
|
||||
- note: "target/complex_schema_validation_failure_detailed"
|
||||
data: {}
|
||||
input: {"name": "test"}
|
||||
modules:
|
||||
- |
|
||||
package policy.schema_failure
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
# This should fail schema validation - wrong structure entirely
|
||||
test_effect := {
|
||||
"invalid_field": "should_not_exist",
|
||||
"level": 123, # should be string
|
||||
"message": ["array", "instead", "of", "string"], # should be string
|
||||
"extra_nested": {
|
||||
"deep": {
|
||||
"structure": "not_allowed"
|
||||
}
|
||||
}
|
||||
} if {
|
||||
input.name == "test"
|
||||
}
|
||||
query: data.policy.schema_failure.test_effect
|
||||
error: "Type mismatch"
|
||||
|
||||
- note: "target/complex_multiple_modules_with_helper_functions"
|
||||
data: {}
|
||||
input: {"operation": "delete", "resource_id": "sensitive_doc", "user_role": "admin"}
|
||||
modules:
|
||||
- |
|
||||
package policy.authorization
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Reference helper functions from the same package
|
||||
allow if {
|
||||
is_admin(input.user_role)
|
||||
is_allowed_operation(input.operation)
|
||||
not is_sensitive_resource(input.resource_id)
|
||||
}
|
||||
- |
|
||||
package policy.authorization
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
is_admin(role) if {
|
||||
role == "admin"
|
||||
}
|
||||
|
||||
is_allowed_operation(op) if {
|
||||
op in ["read", "write", "delete"]
|
||||
}
|
||||
|
||||
is_sensitive_resource(resource_id) if {
|
||||
startswith(resource_id, "sensitive_")
|
||||
}
|
||||
query: data.policy.authorization.allow
|
||||
want_result: false
|
||||
|
||||
- note: "target/corner_case_very_long_strings"
|
||||
data: {}
|
||||
input: {
|
||||
"long_string": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo."
|
||||
}
|
||||
modules:
|
||||
- |
|
||||
package policy.long_strings
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
test_effect := {
|
||||
"level": "info",
|
||||
"message": sprintf("String length: %d characters", [count(input.long_string)])
|
||||
} if {
|
||||
count(input.long_string) > 500
|
||||
contains(input.long_string, "Lorem ipsum")
|
||||
}
|
||||
query: data.policy.long_strings.test_effect
|
||||
want_result: {
|
||||
"level": "info",
|
||||
"message": "String length: 661 characters"
|
||||
}
|
||||
|
||||
- note: "target/complex_regex_pattern_matching"
|
||||
data: {}
|
||||
input: {
|
||||
"emails": [
|
||||
"valid@example.com",
|
||||
"also.valid+tag@domain.co.uk",
|
||||
"invalid.email",
|
||||
"another@valid-domain.org"
|
||||
]
|
||||
}
|
||||
modules:
|
||||
- |
|
||||
package policy.email_validation
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
test_effect := {
|
||||
"level": "info",
|
||||
"message": sprintf("Validated %d emails, %d valid", [count(input.emails), count(valid_emails)])
|
||||
} if {
|
||||
count(valid_emails) > 0
|
||||
}
|
||||
|
||||
valid_emails := [email |
|
||||
email := input.emails[_]
|
||||
regex.match(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`, email)
|
||||
]
|
||||
query: data.policy.email_validation.test_effect
|
||||
want_result: {
|
||||
"level": "info",
|
||||
"message": "Validated 4 emails, 3 valid"
|
||||
}
|
||||
|
||||
- note: "target/corner_case_recursive_data_structures"
|
||||
data: {
|
||||
"filesystem": {
|
||||
"root": {
|
||||
"type": "directory",
|
||||
"children": {
|
||||
"home": {
|
||||
"type": "directory",
|
||||
"children": {
|
||||
"user": {
|
||||
"type": "directory",
|
||||
"children": {
|
||||
"document.txt": {"type": "file", "size": 1024}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"etc": {
|
||||
"type": "directory",
|
||||
"children": {
|
||||
"config.ini": {"type": "file", "size": 512}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
input: {"search_type": "file"}
|
||||
modules:
|
||||
- |
|
||||
package policy.filesystem
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
test_effect := {
|
||||
"level": "info",
|
||||
"message": sprintf("Found %d files", [count(all_files)])
|
||||
} if {
|
||||
count(all_files) > 0
|
||||
}
|
||||
|
||||
all_files[path] := file if {
|
||||
walk(data.filesystem, [path, file])
|
||||
file.type == input.search_type
|
||||
}
|
||||
query: data.policy.filesystem.test_effect
|
||||
want_result: {
|
||||
"level": "info",
|
||||
"message": "Found 2 files"
|
||||
}
|
||||
|
||||
- note: "target/complex_error_propagation_and_recovery"
|
||||
data: {}
|
||||
input: {"values": [1, 2, 0, 4, 5]}
|
||||
modules:
|
||||
- |
|
||||
package policy.error_handling
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
test_effect := {
|
||||
"level": "warning",
|
||||
"message": sprintf("Division results: %v", [safe_divisions])
|
||||
} if {
|
||||
count(safe_divisions) > 0
|
||||
}
|
||||
|
||||
safe_divisions := [result |
|
||||
value := input.values[i]
|
||||
value != 0 # Skip zero values to avoid division by zero
|
||||
result := 100 / value
|
||||
]
|
||||
query: data.policy.error_handling.test_effect
|
||||
want_result: {
|
||||
"level": "warning",
|
||||
"message": "Division results: [100, 50, 25, 20]"
|
||||
}
|
||||
|
||||
- note: "target/corner_case_edge_conditions_with_sets"
|
||||
data: {}
|
||||
input: {
|
||||
"set1": ["a", "b", "c"],
|
||||
"set2": ["b", "c", "d"],
|
||||
"set3": ["c", "d", "e"]
|
||||
}
|
||||
modules:
|
||||
- |
|
||||
package policy.set_operations
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
test_effect := {
|
||||
"level": "info",
|
||||
"message": "Set operations completed",
|
||||
"details": {
|
||||
"intersection_all": intersection_all,
|
||||
"union_all": union_all,
|
||||
"symmetric_diff": symmetric_diff
|
||||
}
|
||||
} if {
|
||||
count(intersection_all) > 0
|
||||
}
|
||||
|
||||
s1 := {x | x := input.set1[_]}
|
||||
s2 := {x | x := input.set2[_]}
|
||||
s3 := {x | x := input.set3[_]}
|
||||
|
||||
intersection_all := s1 & s2 & s3
|
||||
union_all := s1 | s2 | s3
|
||||
symmetric_diff := (s1 | s2) - (s1 & s2)
|
||||
query: data.policy.set_operations.test_effect
|
||||
want_result: {
|
||||
"level": "info",
|
||||
"message": "Set operations completed",
|
||||
"details": {
|
||||
"intersection_all": { "set!": ["c"] },
|
||||
"union_all": { "set!": ["a", "b", "c", "d", "e"] },
|
||||
"symmetric_diff": { "set!": ["a", "d"] }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "target.tests.azure_compute",
|
||||
"description": "Azure compute resources target for testing",
|
||||
"version": "1.0.0",
|
||||
"resource_schema_selector": "type",
|
||||
"resource_schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "const": "Microsoft.Compute/virtualMachines" },
|
||||
"name": { "type": "string" },
|
||||
"location": { "type": "string" },
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"vmSize": { "type": "string" },
|
||||
"storageProfile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"imageReference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"publisher": { "type": "string" },
|
||||
"offer": { "type": "string" },
|
||||
"sku": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["type", "name", "location"]
|
||||
}
|
||||
],
|
||||
"effects": {
|
||||
"allow": { "type": "boolean" },
|
||||
"deny": { "type": "boolean" },
|
||||
"audit": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"level": { "enum": ["info", "warning", "error"] },
|
||||
"message": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"name": "target.tests.azure_policy",
|
||||
"description": "Azure Policy target for comprehensive policy evaluation testing",
|
||||
"version": "1.0.0",
|
||||
"resource_schema_selector": "type",
|
||||
"resource_schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "const": "Microsoft.Resources/subscriptions" },
|
||||
"subscriptionId": { "type": "string" },
|
||||
"tenantId": { "type": "string" },
|
||||
"displayName": { "type": "string" }
|
||||
},
|
||||
"required": ["type", "subscriptionId"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "const": "Microsoft.Storage/storageAccounts" },
|
||||
"name": { "type": "string" },
|
||||
"location": { "type": "string" },
|
||||
"kind": { "enum": ["Storage", "StorageV2", "BlobStorage", "FileStorage", "BlockBlobStorage"] },
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"supportsHttpsTrafficOnly": { "type": "boolean" },
|
||||
"minimumTlsVersion": { "enum": ["TLS1_0", "TLS1_1", "TLS1_2"] },
|
||||
"allowBlobPublicAccess": { "type": "boolean" },
|
||||
"encryption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"services": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"blob": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
|
||||
"file": { "type": "object", "properties": { "enabled": { "type": "boolean" } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": { "type": "object" }
|
||||
},
|
||||
"required": ["type", "name", "location"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "const": "Microsoft.Network/networkSecurityGroups" },
|
||||
"name": { "type": "string" },
|
||||
"location": { "type": "string" },
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"securityRules": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"direction": { "enum": ["Inbound", "Outbound"] },
|
||||
"access": { "enum": ["Allow", "Deny"] },
|
||||
"protocol": { "enum": ["Tcp", "Udp", "*"] },
|
||||
"sourcePortRange": { "type": "string" },
|
||||
"destinationPortRange": { "type": "string" },
|
||||
"sourceAddressPrefix": { "type": "string" },
|
||||
"destinationAddressPrefix": { "type": "string" },
|
||||
"priority": { "type": "integer", "minimum": 100, "maximum": 4096 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["type", "name", "location"]
|
||||
}
|
||||
],
|
||||
"effects": {
|
||||
"allow": { "type": "boolean" },
|
||||
"deny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"audit": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"level": { "enum": ["info", "warning", "error"] },
|
||||
"message": { "type": "string" },
|
||||
"complianceState": { "enum": ["Compliant", "NonCompliant", "Unknown"] }
|
||||
}
|
||||
},
|
||||
"modify": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operation": { "enum": ["add", "replace", "remove"] },
|
||||
"field": { "type": "string" },
|
||||
"value": { "type": "any" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deployIfNotExists": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"template": { "type": "object" },
|
||||
"parameters": { "type": "object" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
{
|
||||
"name": "target.tests.complex_target",
|
||||
"description": "A complex target for testing advanced features including discriminated unions",
|
||||
"version": "2.0.0",
|
||||
"resource_schema_selector": "resourceType",
|
||||
"resource_schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"resourceType": { "const": "compute" },
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cpu": { "type": "integer", "minimum": 1, "maximum": 64 },
|
||||
"memory": { "type": "string", "pattern": "^[0-9]+[GM]i$" }
|
||||
},
|
||||
"required": ["cpu", "memory"]
|
||||
}
|
||||
},
|
||||
"required": ["name", "resourceType", "spec"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"resourceType": { "const": "storage" },
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"size": { "type": "string", "pattern": "^[0-9]+[GTM]i$" },
|
||||
"type": { "enum": ["ssd", "hdd", "nvme"] }
|
||||
},
|
||||
"required": ["size", "type"]
|
||||
}
|
||||
},
|
||||
"required": ["name", "resourceType", "spec"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"resourceType": { "type": "string" },
|
||||
"spec": { "type": "object" }
|
||||
},
|
||||
"required": ["name"],
|
||||
"additionalProperties": true
|
||||
}
|
||||
],
|
||||
"effects": {
|
||||
"allow": { "type": "boolean" },
|
||||
"deny": { "type": "boolean" },
|
||||
"audit": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": { "type": "string" },
|
||||
"severity": { "enum": ["low", "medium", "high", "critical"] },
|
||||
"details": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"eventType": { "type": "string" }
|
||||
},
|
||||
"required": ["eventType"],
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"eventType": { "const": "access" }
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"user": { "type": "string" },
|
||||
"resource": { "type": "string" },
|
||||
"timestamp": { "type": "string" }
|
||||
},
|
||||
"required": ["user", "resource", "timestamp"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"eventType": { "const": "modification" }
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"user": { "type": "string" },
|
||||
"resource": { "type": "string" },
|
||||
"changes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": { "type": "string" },
|
||||
"oldValue": { "type": "any" },
|
||||
"newValue": { "type": "any" }
|
||||
},
|
||||
"required": ["field", "oldValue", "newValue"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["user", "resource", "changes"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"eventType": { "const": "security" }
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"threatLevel": { "enum": ["low", "medium", "high"] },
|
||||
"source": { "type": "string" },
|
||||
"indicators": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"required": ["threatLevel", "source", "indicators"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["action", "severity", "details"]
|
||||
},
|
||||
"remediate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"actionType": { "type": "string" },
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operation": { "type": "string" }
|
||||
},
|
||||
"required": ["operation"],
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"operation": { "const": "quarantine" }
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"duration": { "type": "string", "pattern": "^[0-9]+[hmd]$" },
|
||||
"reason": { "type": "string" }
|
||||
},
|
||||
"required": ["duration", "reason"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"operation": { "const": "scale" }
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"targetSize": { "type": "integer", "minimum": 0, "maximum": 100 },
|
||||
"metric": { "enum": ["cpu", "memory", "requests"] }
|
||||
},
|
||||
"required": ["targetSize", "metric"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"operation": { "const": "replace" }
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"newResource": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "type": "string" },
|
||||
"spec": { "type": "object" }
|
||||
},
|
||||
"required": ["type", "spec"]
|
||||
},
|
||||
"preserveData": { "type": "boolean" }
|
||||
},
|
||||
"required": ["newResource", "preserveData"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["actionType", "config"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
{
|
||||
"name": "target.tests.msgraph",
|
||||
"description": "Microsoft Graph API target for identity and access management testing",
|
||||
"version": "1.0.0",
|
||||
"resource_schema_selector": "@odata.type",
|
||||
"resource_schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@odata.type": { "const": "#microsoft.graph.user" },
|
||||
"id": { "type": "string" },
|
||||
"userPrincipalName": { "type": "string" },
|
||||
"displayName": { "type": "string" },
|
||||
"givenName": { "type": "string" },
|
||||
"surname": { "type": "string" },
|
||||
"mail": { "type": "string" },
|
||||
"jobTitle": { "type": "string" },
|
||||
"department": { "type": "string" },
|
||||
"accountEnabled": { "type": "boolean" },
|
||||
"userType": { "enum": ["Member", "Guest"] },
|
||||
"assignedLicenses": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skuId": { "type": "string" },
|
||||
"disabledPlans": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
}
|
||||
},
|
||||
"signInActivity": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lastSignInDateTime": { "type": "string" },
|
||||
"lastNonInteractiveSignInDateTime": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["@odata.type", "id", "userPrincipalName"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@odata.type": { "const": "#microsoft.graph.group" },
|
||||
"id": { "type": "string" },
|
||||
"displayName": { "type": "string" },
|
||||
"description": { "type": "string" },
|
||||
"groupTypes": { "type": "array", "items": { "type": "string" } },
|
||||
"securityEnabled": { "type": "boolean" },
|
||||
"mailEnabled": { "type": "boolean" },
|
||||
"mail": { "type": "string" },
|
||||
"visibility": { "enum": ["Public", "Private", "HiddenMembership"] },
|
||||
"members": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"@odata.type": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["@odata.type", "id", "displayName"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@odata.type": { "const": "#microsoft.graph.application" },
|
||||
"id": { "type": "string" },
|
||||
"appId": { "type": "string" },
|
||||
"displayName": { "type": "string" },
|
||||
"publisherDomain": { "type": "string" },
|
||||
"signInAudience": { "enum": ["AzureADMyOrg", "AzureADMultipleOrgs", "AzureADandPersonalMicrosoftAccount", "PersonalMicrosoftAccount"] },
|
||||
"requiredResourceAccess": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"resourceAppId": { "type": "string" },
|
||||
"resourceAccess": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"type": { "enum": ["Scope", "Role"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"redirectUris": { "type": "array", "items": { "type": "string" } },
|
||||
"implicitGrantSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enableAccessTokenIssuance": { "type": "boolean" },
|
||||
"enableIdTokenIssuance": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["@odata.type", "id", "appId", "displayName"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@odata.type": { "const": "#microsoft.graph.conditionalAccessPolicy" },
|
||||
"id": { "type": "string" },
|
||||
"displayName": { "type": "string" },
|
||||
"state": { "enum": ["enabled", "disabled", "enabledForReportingButNotEnforced"] },
|
||||
"conditions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"users": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"includeUsers": { "type": "array", "items": { "type": "string" } },
|
||||
"excludeUsers": { "type": "array", "items": { "type": "string" } },
|
||||
"includeGroups": { "type": "array", "items": { "type": "string" } },
|
||||
"excludeGroups": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
},
|
||||
"applications": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"includeApplications": { "type": "array", "items": { "type": "string" } },
|
||||
"excludeApplications": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
},
|
||||
"locations": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"includeLocations": { "type": "array", "items": { "type": "string" } },
|
||||
"excludeLocations": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
},
|
||||
"riskLevels": { "type": "array", "items": { "enum": ["low", "medium", "high", "none"] } }
|
||||
}
|
||||
},
|
||||
"grantControls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operator": { "enum": ["AND", "OR"] },
|
||||
"builtInControls": { "type": "array", "items": { "enum": ["block", "mfa", "compliantDevice", "domainJoinedDevice", "approvedApplication", "compliantApplication"] } }
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["@odata.type", "id", "displayName", "state"]
|
||||
}
|
||||
],
|
||||
"effects": {
|
||||
"allow": { "type": "boolean" },
|
||||
"block": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reason": { "type": "string" },
|
||||
"blockType": { "enum": ["signin", "access", "registration"] }
|
||||
}
|
||||
},
|
||||
"requireMfa": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"methods": { "type": "array", "items": { "enum": ["sms", "voice", "app", "oath"] } }
|
||||
}
|
||||
},
|
||||
"audit": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"level": { "enum": ["info", "warning", "error"] },
|
||||
"message": { "type": "string" },
|
||||
"category": { "enum": ["signin", "audit", "risk", "provisioning"] }
|
||||
}
|
||||
},
|
||||
"remediate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": { "enum": ["disable", "enable", "reset", "notify"] },
|
||||
"target": { "type": "string" },
|
||||
"parameters": { "type": "object" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "target.tests.no_default_schema_target",
|
||||
"description": "A target without a default schema for testing missing default schema error",
|
||||
"version": "1.0.0",
|
||||
"resource_schema_selector": "type",
|
||||
"resource_schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"type": { "const": "specific_resource_type" },
|
||||
"value": { "type": "string" }
|
||||
},
|
||||
"required": ["name", "type"]
|
||||
}
|
||||
],
|
||||
"effects": {
|
||||
"allow": { "type": "boolean" },
|
||||
"deny": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "target.tests.sample_test_target",
|
||||
"description": "A sample target for testing target loading functionality",
|
||||
"version": "1.0.0",
|
||||
"resource_schema_selector": "type",
|
||||
"resource_schemas": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"type": { "const": "test_resource" },
|
||||
"value": { "type": "string" }
|
||||
},
|
||||
"required": ["name", "type"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"type": { "type": "string" },
|
||||
"value": { "type": "string" }
|
||||
},
|
||||
"required": ["name"],
|
||||
"additionalProperties": true
|
||||
}
|
||||
],
|
||||
"effects": {
|
||||
"allow": { "type": "boolean" },
|
||||
"deny": { "type": "boolean" },
|
||||
"test_effect": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"level": { "type": "string" },
|
||||
"message": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
cases:
|
||||
- note: "Microsoft Graph User Access Control - Allow Active User"
|
||||
data: {}
|
||||
input:
|
||||
"@odata.type": "#microsoft.graph.user"
|
||||
id: "12345678-1234-1234-1234-123456789012"
|
||||
userPrincipalName: "john.doe@company.com"
|
||||
displayName: "John Doe"
|
||||
givenName: "John"
|
||||
surname: "Doe"
|
||||
mail: "john.doe@company.com"
|
||||
jobTitle: "Software Engineer"
|
||||
department: "Engineering"
|
||||
accountEnabled: true
|
||||
userType: "Member"
|
||||
modules:
|
||||
- |
|
||||
package msgraph.user.allow
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.msgraph"
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input["@odata.type"] == "#microsoft.graph.user"
|
||||
input.accountEnabled == true
|
||||
input.userType == "Member"
|
||||
}
|
||||
query: data.msgraph.user.allow.allow
|
||||
want_result: true
|
||||
|
||||
- note: "Microsoft Graph User Access Control - Block Disabled User"
|
||||
data: {}
|
||||
input:
|
||||
"@odata.type": "#microsoft.graph.user"
|
||||
id: "87654321-4321-4321-4321-210987654321"
|
||||
userPrincipalName: "disabled.user@company.com"
|
||||
displayName: "Disabled User"
|
||||
accountEnabled: false
|
||||
userType: "Member"
|
||||
modules:
|
||||
- |
|
||||
package msgraph.user.block
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.msgraph"
|
||||
|
||||
block := {
|
||||
"reason": "User account is disabled",
|
||||
"blockType": "signin"
|
||||
} if {
|
||||
input["@odata.type"] == "#microsoft.graph.user"
|
||||
input.accountEnabled == false
|
||||
}
|
||||
query: data.msgraph.user.block.block
|
||||
want_result:
|
||||
reason: "User account is disabled"
|
||||
blockType: "signin"
|
||||
|
||||
- note: "Microsoft Graph Invalid Resource Type"
|
||||
data: {}
|
||||
input:
|
||||
"@odata.type": "#microsoft.graph.unknownResource"
|
||||
id: "test"
|
||||
modules:
|
||||
- |
|
||||
package msgraph.invalid
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.msgraph"
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
input["@odata.type"] == "#microsoft.graph.user"
|
||||
input.accountEnabled == true
|
||||
}
|
||||
query: data.msgraph.invalid.allow
|
||||
want_result: false
|
||||
@@ -0,0 +1,455 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Tests for resource type inference functionality
|
||||
# The infer_resource_type function looks for patterns like input.<selector> == "resource_type"
|
||||
# in effect rules and builds a mapping of queries to their inferred resource types and schemas.
|
||||
|
||||
cases:
|
||||
- note: "target/infer_resource_type_basic_equality"
|
||||
data: {}
|
||||
input: {"type": "test_resource", "name": "example"}
|
||||
modules:
|
||||
- |
|
||||
package policy.basic_inference
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# This should infer resource type "test_resource" from the equality check
|
||||
allow if {
|
||||
input.type == "test_resource"
|
||||
input.name != ""
|
||||
}
|
||||
query: data.policy.basic_inference.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["test_resource"]
|
||||
|
||||
- note: "target/infer_resource_type_multiple_equality_checks"
|
||||
data: {}
|
||||
input: {"type": "test_resource", "name": "example", "status": "active"}
|
||||
modules:
|
||||
- |
|
||||
package policy.multiple_checks
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Multiple conditions with resource type check
|
||||
allow if {
|
||||
input.type == "test_resource"
|
||||
input.status == "active"
|
||||
input.name != ""
|
||||
}
|
||||
query: data.policy.multiple_checks.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["test_resource"]
|
||||
|
||||
- note: "target/infer_resource_type_complex_target_compute"
|
||||
data: {}
|
||||
input: {"resourceType": "compute", "name": "vm1", "spec": {"cpu": 4, "memory": "8Gi"}}
|
||||
modules:
|
||||
- |
|
||||
package policy.complex_compute
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.complex_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Should infer "compute" resource type from complex target
|
||||
allow if {
|
||||
input.resourceType == "compute"
|
||||
input.spec.cpu >= 2
|
||||
input.spec.memory
|
||||
}
|
||||
query: data.policy.complex_compute.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["compute"]
|
||||
|
||||
- note: "target/infer_resource_type_complex_target_storage"
|
||||
data: {}
|
||||
input: {"resourceType": "storage", "name": "disk1", "spec": {"size": "100Gi", "type": "ssd"}}
|
||||
modules:
|
||||
- |
|
||||
package policy.complex_storage
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.complex_target"
|
||||
|
||||
default deny := false
|
||||
|
||||
# Should infer "storage" resource type from complex target
|
||||
deny if {
|
||||
input.resourceType == "storage"
|
||||
input.spec.type == "hdd" # Deny HDDs
|
||||
}
|
||||
query: data.policy.complex_storage.deny
|
||||
want_result: false
|
||||
want_inferred_resource_types: ["storage"]
|
||||
|
||||
- note: "target/infer_resource_type_default_schema_usage"
|
||||
data: {}
|
||||
input: {"resourceType": "unknown_type", "name": "mystery_resource"}
|
||||
modules:
|
||||
- |
|
||||
package policy.default_schema
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.complex_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Should use default schema for unknown resource types
|
||||
allow if {
|
||||
input.resourceType == "unknown_type"
|
||||
input.name != ""
|
||||
}
|
||||
query: data.policy.default_schema.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["unknown_type"]
|
||||
|
||||
- note: "target/infer_resource_type_multiple_rules_same_effect"
|
||||
data: {}
|
||||
input: {"type": "test_resource", "name": "example"}
|
||||
modules:
|
||||
- |
|
||||
package policy.multiple_rules
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# First rule with resource type check
|
||||
allow if {
|
||||
input.type == "test_resource"
|
||||
input.name == "example"
|
||||
}
|
||||
|
||||
# Second rule with different resource type check (should also be inferred)
|
||||
allow if {
|
||||
input.type == "other_resource"
|
||||
input.status == "approved"
|
||||
}
|
||||
query: data.policy.multiple_rules.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["test_resource", "other_resource"] # Should infer both types from different rules
|
||||
|
||||
- note: "target/infer_resource_type_no_equality_check"
|
||||
data: {}
|
||||
input: {"name": "example", "status": "active"}
|
||||
modules:
|
||||
- |
|
||||
package policy.no_type_check
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# No resource type equality check - should resolve to default schema
|
||||
allow if {
|
||||
input.name == "example"
|
||||
input.status == "active"
|
||||
}
|
||||
query: data.policy.no_type_check.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["default"] # Should resolve to default schema
|
||||
|
||||
- note: "target/infer_resource_type_wrong_equality_direction"
|
||||
data: {}
|
||||
input: {"type": "test_resource", "name": "example"}
|
||||
modules:
|
||||
- |
|
||||
package policy.wrong_direction
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Equality check in wrong direction (should still be detected)
|
||||
allow if {
|
||||
"test_resource" == input.type
|
||||
input.name != ""
|
||||
}
|
||||
query: data.policy.wrong_direction.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["test_resource"]
|
||||
|
||||
- note: "target/infer_resource_type_array_access_selector"
|
||||
data: {}
|
||||
input: {"metadata": {"type": "test_resource"}, "name": "example"}
|
||||
modules:
|
||||
- |
|
||||
package policy.array_access
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Using array-style access for the selector field
|
||||
allow if {
|
||||
input["type"] == "test_resource"
|
||||
input.name != ""
|
||||
}
|
||||
query: data.policy.array_access.allow
|
||||
want_result: false # This input doesn't have input.type, only input.metadata.type
|
||||
want_inferred_resource_types: ["test_resource"]
|
||||
|
||||
- note: "target/infer_resource_type_variable_in_equality"
|
||||
data: {}
|
||||
input: {"type": "test_resource", "name": "example"}
|
||||
modules:
|
||||
- |
|
||||
package policy.variable_equality
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Using a variable in the equality (resolves to default schema since not a literal string)
|
||||
resource_type := "test_resource"
|
||||
|
||||
allow if {
|
||||
input.type == resource_type
|
||||
input.name != ""
|
||||
}
|
||||
query: data.policy.variable_equality.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["default"] # Should resolve to default schema since not a literal string
|
||||
|
||||
- note: "target/infer_resource_type_non_string_literal"
|
||||
data: {}
|
||||
input: {"priority": 5, "name": "example"}
|
||||
modules:
|
||||
- |
|
||||
package policy.non_string
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Non-string literal equality (resolves to default schema)
|
||||
allow if {
|
||||
input.priority == 5
|
||||
input.name != ""
|
||||
}
|
||||
query: data.policy.non_string.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["default"] # Should resolve to default schema
|
||||
|
||||
- note: "target/infer_resource_type_not_first_statement"
|
||||
data: {}
|
||||
input: {"type": "test_resource", "name": "example", "status": "active"}
|
||||
modules:
|
||||
- |
|
||||
package policy.not_first_statement
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Resource type check is not the first statement (should still be inferred)
|
||||
allow if {
|
||||
input.name != ""
|
||||
input.status == "active"
|
||||
input.type == "test_resource"
|
||||
}
|
||||
query: data.policy.not_first_statement.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["test_resource"]
|
||||
|
||||
- note: "target/infer_resource_type_nested_condition_not_inferred"
|
||||
data: {}
|
||||
input: {"type": "test_resource", "name": "example", "nested": {"resourceType": "compute"}}
|
||||
modules:
|
||||
- |
|
||||
package policy.nested_condition
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Nested condition with resource type check (should not be inferred at nested level)
|
||||
allow if {
|
||||
input.name == "example"
|
||||
some condition
|
||||
condition := input.nested.resourceType == "compute"
|
||||
condition
|
||||
}
|
||||
query: data.policy.nested_condition.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["default"] # Should resolve to default schema (nested not inferred)
|
||||
|
||||
- note: "target/infer_resource_type_nested_rule_not_inferred"
|
||||
data: {}
|
||||
input: {"type": "test_resource", "name": "example"}
|
||||
modules:
|
||||
- |
|
||||
package policy.nested_rule
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Helper rule with nested type check (should not be inferred from helper rules)
|
||||
is_valid_resource(resource_type) if {
|
||||
resource_type == "test_resource"
|
||||
}
|
||||
|
||||
# Main rule that uses helper (should resolve to default schema)
|
||||
allow if {
|
||||
input.name == "example"
|
||||
is_valid_resource(input.type)
|
||||
}
|
||||
query: data.policy.nested_rule.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["default"] # Should resolve to default schema (helper rule not inferred)
|
||||
|
||||
- note: "target/infer_resource_type_comprehension_not_inferred"
|
||||
data: {}
|
||||
input: {"type": "test_resource", "name": "example", "resources": [{"type": "compute"}, {"type": "storage"}]}
|
||||
modules:
|
||||
- |
|
||||
package policy.comprehension
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Type check inside comprehension (should not be inferred)
|
||||
allow if {
|
||||
input.name == "example"
|
||||
valid_resources := [r | r := input.resources[_]; r.type == "compute"]
|
||||
count(valid_resources) > 0
|
||||
}
|
||||
query: data.policy.comprehension.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["default"] # Should resolve to default schema (comprehension not inferred)
|
||||
|
||||
- note: "target/infer_resource_type_multiple_types_same_rule"
|
||||
data: {}
|
||||
input: {"type": "test_resource", "name": "example"}
|
||||
modules:
|
||||
- |
|
||||
package policy.multiple_types_same_rule
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.sample_test_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Single rule with multiple resource type checks (should infer both)
|
||||
allow if {
|
||||
input.name == "example"
|
||||
input.type == "test_resource"
|
||||
input.type == "other_resource" # This will never match, but should still be inferred
|
||||
}
|
||||
query: data.policy.multiple_types_same_rule.allow
|
||||
want_result: false # Will never match since input.type can't be both values
|
||||
want_inferred_resource_types: ["test_resource", "other_resource"]
|
||||
|
||||
- note: "target/infer_resource_type_multiple_types_different_rules"
|
||||
data: {}
|
||||
input: {"resourceType": "compute", "name": "vm1", "spec": {"cpu": 4}}
|
||||
modules:
|
||||
- |
|
||||
package policy.multiple_types_different_rules
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.complex_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# First rule checks for compute
|
||||
allow if {
|
||||
input.resourceType == "compute"
|
||||
input.spec.cpu >= 2
|
||||
}
|
||||
|
||||
# Second rule checks for storage
|
||||
allow if {
|
||||
input.resourceType == "storage"
|
||||
input.spec.size
|
||||
}
|
||||
|
||||
# Third rule checks for network
|
||||
allow if {
|
||||
input.resourceType == "network"
|
||||
input.spec.subnet
|
||||
}
|
||||
query: data.policy.multiple_types_different_rules.allow
|
||||
want_result: true
|
||||
want_inferred_resource_types: ["compute", "storage", "network"]
|
||||
|
||||
- note: "target/infer_resource_type_invalid_schema_error"
|
||||
data: {}
|
||||
input: {"type": "invalid_resource", "name": "example"}
|
||||
modules:
|
||||
- |
|
||||
package policy.invalid_schema
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.nonexistent_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# Reference to a target that doesn't exist in the registry
|
||||
allow if {
|
||||
input.type == "test_resource"
|
||||
input.name != ""
|
||||
}
|
||||
query: data.policy.invalid_schema.allow
|
||||
error: "Target 'target.tests.nonexistent_target' not found in registry"
|
||||
|
||||
- note: "target/infer_resource_type_missing_default_schema_error"
|
||||
data: {}
|
||||
input: {"name": "example", "status": "active"}
|
||||
modules:
|
||||
- |
|
||||
package policy.missing_default_schema
|
||||
|
||||
import rego.v1
|
||||
|
||||
__target__ := "target.tests.no_default_schema_target"
|
||||
|
||||
default allow := false
|
||||
|
||||
# No type check, should trigger missing default schema error
|
||||
allow if {
|
||||
input.name == "example"
|
||||
input.status == "active"
|
||||
}
|
||||
query: data.policy.missing_default_schema.allow
|
||||
error: "Missing default resource schema: Target 'target.tests.no_default_schema_target' has no default resource schema"
|
||||
Reference in New Issue
Block a user