mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: add Azure Policy constraint parser (#658)
Add constraint.rs module that parses Azure Policy JSON constraints into span-annotated AST nodes: - Logical combinators: allOf, anyOf, not - Leaf conditions: field/value with all 19 operators - Count blocks: field-count and value-count with where clauses Public API: parse_constraint() parses a standalone constraint from JSON. Includes YAML-driven test suite with 6 test files covering operators, fields, expressions, logical combinators, count, and parse errors.
This commit is contained in:
committed by
GitHub
parent
95bffcb5f9
commit
687be2850b
@@ -2,3 +2,4 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
mod normalization;
|
||||
mod parser_tests;
|
||||
|
||||
617
tests/azure_policy/parser_tests/cases/count.yaml
Normal file
617
tests/azure_policy/parser_tests/cases/count.yaml
Normal file
@@ -0,0 +1,617 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Count Expressions Test Suite
|
||||
# Tests field count and value count with optional where clauses and name bindings.
|
||||
|
||||
cases:
|
||||
# =========================================================================
|
||||
# Field count — direct path (core subset, no alias resolution)
|
||||
# =========================================================================
|
||||
|
||||
- note: field_count_direct_path_core
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "securityRules[*]"
|
||||
},
|
||||
"greater": 2
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
securityRules:
|
||||
- { "name": "r1" }
|
||||
- { "name": "r2" }
|
||||
- { "name": "r3" }
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_count_direct_path_where_core
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "securityRules[*]",
|
||||
"where": {
|
||||
"field": "securityRules[*].access",
|
||||
"equals": "Allow"
|
||||
}
|
||||
},
|
||||
"equals": 2
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
securityRules:
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Deny" }
|
||||
- { "access": "Allow" }
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Field count — basic
|
||||
# =========================================================================
|
||||
|
||||
- note: field_count_basic
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "securityRules[*]"
|
||||
},
|
||||
"greater": 10
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityRules:
|
||||
- { "name": "r1" }
|
||||
- { "name": "r2" }
|
||||
- { "name": "r3" }
|
||||
- { "name": "r4" }
|
||||
- { "name": "r5" }
|
||||
- { "name": "r6" }
|
||||
- { "name": "r7" }
|
||||
- { "name": "r8" }
|
||||
- { "name": "r9" }
|
||||
- { "name": "r10" }
|
||||
- { "name": "r11" }
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_count_equals_zero
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "storageProfile.dataDisks[*]"
|
||||
},
|
||||
"equals": 0
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Field count — with where clause
|
||||
# =========================================================================
|
||||
|
||||
- note: field_count_with_where
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "securityRules[*]",
|
||||
"where": {
|
||||
"field": "securityRules[*].access",
|
||||
"equals": "Allow"
|
||||
}
|
||||
},
|
||||
"greater": 5
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityRules:
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_count_where_allOf
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "securityRules[*]",
|
||||
"where": {
|
||||
"allOf": [
|
||||
{
|
||||
"field": "securityRules[*].access",
|
||||
"equals": "Allow"
|
||||
},
|
||||
{
|
||||
"field": "securityRules[*].direction",
|
||||
"equals": "Inbound"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"greaterOrEquals": 1
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityRules:
|
||||
- { "access": "Allow", "direction": "Inbound" }
|
||||
- { "access": "Deny", "direction": "Outbound" }
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_count_where_anyOf
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "securityRules[*]",
|
||||
"where": {
|
||||
"anyOf": [
|
||||
{
|
||||
"field": "securityRules[*].destinationPortRange",
|
||||
"equals": "22"
|
||||
},
|
||||
{
|
||||
"field": "securityRules[*].destinationPortRange",
|
||||
"equals": "3389"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"notEquals": 0
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityRules:
|
||||
- { "destinationPortRange": "22" }
|
||||
- { "destinationPortRange": "443" }
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_count_where_not
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "securityRules[*]",
|
||||
"where": {
|
||||
"not": {
|
||||
"field": "securityRules[*].access",
|
||||
"equals": "Deny"
|
||||
}
|
||||
}
|
||||
},
|
||||
"greater": 0
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityRules:
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Deny" }
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Value count
|
||||
# =========================================================================
|
||||
|
||||
- note: value_count_basic
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": ["eastus", "westus", "centralus"]
|
||||
},
|
||||
"equals": 3
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: value_count_with_name
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": ["eastus", "westus", "centralus"],
|
||||
"name": "location"
|
||||
},
|
||||
"greater": 0
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: value_count_with_name_and_where
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": ["eastus", "westus", "centralus", "northeurope"],
|
||||
"name": "loc",
|
||||
"where": {
|
||||
"value": "[current('loc')]",
|
||||
"like": "*us"
|
||||
}
|
||||
},
|
||||
"equals": 3
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: value_count_expression
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": "[parameters('allowedLocations')]",
|
||||
"name": "loc"
|
||||
},
|
||||
"greater": 0
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
parameters:
|
||||
allowedLocations:
|
||||
- "eastus"
|
||||
- "westus"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Count in allOf/anyOf
|
||||
# =========================================================================
|
||||
|
||||
- note: count_in_allOf
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Network/networkSecurityGroups" },
|
||||
{
|
||||
"count": {
|
||||
"field": "securityRules[*]",
|
||||
"where": {
|
||||
"field": "securityRules[*].access",
|
||||
"equals": "Allow"
|
||||
}
|
||||
},
|
||||
"greater": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Network/networkSecurityGroups"
|
||||
securityRules:
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
- { "access": "Allow" }
|
||||
want_effect: "deny"
|
||||
|
||||
- note: count_in_not
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"not": {
|
||||
"count": {
|
||||
"field": "storageProfile.dataDisks[*]"
|
||||
},
|
||||
"lessOrEquals": 4
|
||||
}
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
storageProfile:
|
||||
dataDisks:
|
||||
- { "name": "d1" }
|
||||
- { "name": "d2" }
|
||||
- { "name": "d3" }
|
||||
- { "name": "d4" }
|
||||
- { "name": "d5" }
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# Count with nested where containing count
|
||||
# =========================================================================
|
||||
|
||||
- note: value_count_nested_where
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": "[parameters('requiredTags')]",
|
||||
"name": "tag",
|
||||
"where": {
|
||||
"field": "[concat('tags[', current('tag'), ']')]",
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
"notEquals": "[length(parameters('requiredTags'))]"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
parameters:
|
||||
requiredTags:
|
||||
- "environment"
|
||||
- "costCenter"
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
tags:
|
||||
environment: "prod"
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# Alias field refs inside count resolve to current loop element
|
||||
# =========================================================================
|
||||
|
||||
- note: field_count_multiple_alias_refs_same_element
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "securityRules[*]",
|
||||
"where": {
|
||||
"allOf": [
|
||||
{ "field": "securityRules[*].access", "equals": "Allow" },
|
||||
{ "field": "securityRules[*].direction", "equals": "Inbound" },
|
||||
{ "field": "securityRules[*].protocol", "equals": "Tcp" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"equals": 1
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
securityRules:
|
||||
- { "access": "Allow", "direction": "Inbound", "protocol": "Tcp" }
|
||||
- { "access": "Allow", "direction": "Outbound", "protocol": "Tcp" }
|
||||
- { "access": "Deny", "direction": "Inbound", "protocol": "Tcp" }
|
||||
want_effect: "audit"
|
||||
|
||||
- note: field_count_nested_field_access
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "storageProfile.dataDisks[*]",
|
||||
"where": {
|
||||
"field": "storageProfile.dataDisks[*].managedDisk.storageAccountType",
|
||||
"notEquals": "Premium_LRS"
|
||||
}
|
||||
},
|
||||
"greater": 0
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
storageProfile:
|
||||
dataDisks:
|
||||
- { "name": "d1", "managedDisk": { "storageAccountType": "Premium_LRS" } }
|
||||
- { "name": "d2", "managedDisk": { "storageAccountType": "Standard_LRS" } }
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_count_where_zero_matches
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "items[*]",
|
||||
"where": {
|
||||
"field": "items[*].status",
|
||||
"equals": "failed"
|
||||
}
|
||||
},
|
||||
"equals": 0
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
items:
|
||||
- { "status": "ok" }
|
||||
- { "status": "ok" }
|
||||
- { "status": "ok" }
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Nested count: count inside another count's where clause
|
||||
# =========================================================================
|
||||
|
||||
- note: nested_field_and_value_count
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": "[parameters('requiredPorts')]",
|
||||
"name": "port",
|
||||
"where": {
|
||||
"count": {
|
||||
"field": "securityRules[*]",
|
||||
"where": {
|
||||
"allOf": [
|
||||
{ "field": "securityRules[*].destinationPortRange", "equals": "[current('port')]" },
|
||||
{ "field": "securityRules[*].access", "equals": "Allow" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"greater": 0
|
||||
}
|
||||
},
|
||||
"equals": "[length(parameters('requiredPorts'))]"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
parameters:
|
||||
requiredPorts:
|
||||
- "443"
|
||||
- "80"
|
||||
resource:
|
||||
securityRules:
|
||||
- { "destinationPortRange": "443", "access": "Allow" }
|
||||
- { "destinationPortRange": "80", "access": "Allow" }
|
||||
- { "destinationPortRange": "22", "access": "Deny" }
|
||||
want_effect: "audit"
|
||||
|
||||
- note: nested_field_and_value_count_fail
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": "[parameters('requiredPorts')]",
|
||||
"name": "port",
|
||||
"where": {
|
||||
"count": {
|
||||
"field": "securityRules[*]",
|
||||
"where": {
|
||||
"allOf": [
|
||||
{ "field": "securityRules[*].destinationPortRange", "equals": "[current('port')]" },
|
||||
{ "field": "securityRules[*].access", "equals": "Allow" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"greater": 0
|
||||
}
|
||||
},
|
||||
"equals": "[length(parameters('requiredPorts'))]"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
parameters:
|
||||
requiredPorts:
|
||||
- "443"
|
||||
- "80"
|
||||
- "8080"
|
||||
resource:
|
||||
securityRules:
|
||||
- { "destinationPortRange": "443", "access": "Allow" }
|
||||
- { "destinationPortRange": "80", "access": "Allow" }
|
||||
- { "destinationPortRange": "22", "access": "Deny" }
|
||||
want_effect: ~
|
||||
|
||||
# =========================================================================
|
||||
# current() — zero-arg form (innermost count element)
|
||||
# =========================================================================
|
||||
|
||||
- note: current_zero_arg_value_count
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": ["Allow", "Allow", "Deny"],
|
||||
"name": "access",
|
||||
"where": {
|
||||
"value": "[current()]",
|
||||
"equals": "Allow"
|
||||
}
|
||||
},
|
||||
"equals": 2
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: current_zero_arg_field_count
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "items[*]",
|
||||
"where": {
|
||||
"value": "[current()]",
|
||||
"equals": "yes"
|
||||
}
|
||||
},
|
||||
"equals": 2
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
items: ["yes", "no", "yes"]
|
||||
want_effect: "deny"
|
||||
|
||||
- note: current_zero_arg_with_function
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": ["HELLO", "WORLD"],
|
||||
"name": "word",
|
||||
"where": {
|
||||
"value": "[startsWith(current(), 'HE')]",
|
||||
"equals": true
|
||||
}
|
||||
},
|
||||
"equals": 1
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: current_zero_arg_nested_innermost
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": ["a", "b"],
|
||||
"name": "outer",
|
||||
"where": {
|
||||
"count": {
|
||||
"value": ["x", "y"],
|
||||
"name": "inner",
|
||||
"where": {
|
||||
"value": "[current()]",
|
||||
"equals": "x"
|
||||
}
|
||||
},
|
||||
"greater": 0
|
||||
}
|
||||
},
|
||||
"greater": 0
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
554
tests/azure_policy/parser_tests/cases/expressions.yaml
Normal file
554
tests/azure_policy/parser_tests/cases/expressions.yaml
Normal file
@@ -0,0 +1,554 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# ARM Template Expressions Test Suite
|
||||
# Tests [parameters(...)], [concat(...)], [field(...)], [if(...)], and other
|
||||
# ARM template expression patterns in field, value, and effect positions.
|
||||
|
||||
cases:
|
||||
# =========================================================================
|
||||
# parameters() references
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_parameters_in_value
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[parameters('environment')]",
|
||||
"equals": "production"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
parameters:
|
||||
environment: "production"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: expr_parameters_in_rhs
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "location",
|
||||
"in": "[parameters('allowedLocations')]"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
parameters:
|
||||
allowedLocations:
|
||||
- "eastus"
|
||||
- "westus"
|
||||
resource:
|
||||
location: "eastus"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: expr_parameters_in_effect
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
"then": {
|
||||
"effect": "[parameters('effect')]"
|
||||
}
|
||||
}
|
||||
parameters:
|
||||
effect: "deny"
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# concat()
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_concat_strings
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[concat('Microsoft.Compute/', 'virtualMachines')]",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: expr_concat_with_parameters
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[concat(parameters('prefix'), '-vm')]",
|
||||
"equals": "prod-vm"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
parameters:
|
||||
prefix: "prod"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: expr_concat_nested
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[concat(concat('a', 'b'), 'c')]",
|
||||
"equals": "abc"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# field() function
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_field_function
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[field('name')]",
|
||||
"contains": "prod"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "my-prod-vm"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: expr_field_in_concat
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[concat(field('type'), '/', field('name'))]",
|
||||
"contains": "Microsoft.Compute"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
name: "vm1"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# if() conditional
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_if_conditional
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[if(equals(parameters('env'), 'prod'), 'deny', 'audit')]",
|
||||
"equals": "deny"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
parameters:
|
||||
env: "prod"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# String functions
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_toLower
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[toLower(field('name'))]",
|
||||
"equals": "my-vm"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "My-VM"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: expr_toUpper
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[toUpper(parameters('prefix'))]",
|
||||
"equals": "PROD"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
parameters:
|
||||
prefix: "prod"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: expr_replace
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[replace(field('name'), '-', '_')]",
|
||||
"equals": "my_prod_vm"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "my-prod-vm"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: expr_substring
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[substring(field('name'), 0, 4)]",
|
||||
"equals": "prod"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "prod-vm-01"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Numeric functions
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_length
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[length(parameters('allowedLocations'))]",
|
||||
"greater": 0
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
parameters:
|
||||
allowedLocations:
|
||||
- "eastus"
|
||||
- "westus"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: expr_add
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[add(parameters('base'), 1)]",
|
||||
"greater": 5
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
parameters:
|
||||
base: 10
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Context functions
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_resourceGroup
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[resourceGroup().location]",
|
||||
"notIn": "[parameters('allowedLocations')]"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
parameters:
|
||||
allowedLocations:
|
||||
- "westus2"
|
||||
- "centralus"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: expr_subscription
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[subscription().subscriptionId]",
|
||||
"equals": "00000000-0000-0000-0000-000000000000"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: expr_requestContext_apiVersion
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[requestContext().apiVersion]",
|
||||
"greaterOrEquals": "2021-04-01"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
context:
|
||||
resourceGroup:
|
||||
name: "myResourceGroup"
|
||||
location: "eastus"
|
||||
subscription:
|
||||
subscriptionId: "00000000-0000-0000-0000-000000000000"
|
||||
requestContext:
|
||||
apiVersion: "2023-01-01"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: expr_requestContext_apiVersion_older
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[requestContext().apiVersion]",
|
||||
"greaterOrEquals": "2024-06-01"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
context:
|
||||
resourceGroup:
|
||||
name: "myResourceGroup"
|
||||
location: "eastus"
|
||||
subscription:
|
||||
subscriptionId: "00000000-0000-0000-0000-000000000000"
|
||||
requestContext:
|
||||
apiVersion: "2023-01-01"
|
||||
resource:
|
||||
type: "any"
|
||||
want_undefined: true
|
||||
|
||||
- note: expr_policy_assignmentId
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[policy().assignmentId]",
|
||||
"equals": "/subscriptions/sub1/providers/Microsoft.Authorization/policyAssignments/myAssignment"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
context:
|
||||
resourceGroup:
|
||||
name: "myResourceGroup"
|
||||
location: "eastus"
|
||||
subscription:
|
||||
subscriptionId: "00000000-0000-0000-0000-000000000000"
|
||||
policy:
|
||||
assignmentId: "/subscriptions/sub1/providers/Microsoft.Authorization/policyAssignments/myAssignment"
|
||||
definitionId: "/providers/Microsoft.Authorization/policyDefinitions/myDefinition"
|
||||
setDefinitionId: ""
|
||||
definitionReferenceId: ""
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: expr_policy_definitionId
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[policy().definitionId]",
|
||||
"contains": "myDefinition"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
context:
|
||||
resourceGroup:
|
||||
name: "myResourceGroup"
|
||||
location: "eastus"
|
||||
subscription:
|
||||
subscriptionId: "00000000-0000-0000-0000-000000000000"
|
||||
policy:
|
||||
assignmentId: "/subscriptions/sub1/providers/Microsoft.Authorization/policyAssignments/myAssignment"
|
||||
definitionId: "/providers/Microsoft.Authorization/policyDefinitions/myDefinition"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# current() in count contexts
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_current_in_value_count
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": "[parameters('requiredTags')]",
|
||||
"name": "tagName",
|
||||
"where": {
|
||||
"value": "[current('tagName')]",
|
||||
"notEquals": ""
|
||||
}
|
||||
},
|
||||
"greater": 0
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
parameters:
|
||||
requiredTags:
|
||||
- "environment"
|
||||
- "costCenter"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Dot and index access in expressions
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_dot_access
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[resourceGroup().name]",
|
||||
"equals": "myResourceGroup"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: expr_index_access
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[parameters('allowedLocations')[0]]",
|
||||
"equals": "eastus"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
parameters:
|
||||
allowedLocations:
|
||||
- "eastus"
|
||||
- "westus"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Escaped bracket literals (not expressions)
|
||||
# =========================================================================
|
||||
|
||||
- note: escaped_bracket_literal
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"equals": "[[not-an-expression]"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "[not-an-expression]"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Complex nested expressions
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_complex_nested
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[if(contains(toLower(field('location')), 'us'), 'allowed', 'blocked')]",
|
||||
"equals": "blocked"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
location: "northeurope"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: expr_multiple_expression_fields
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{
|
||||
"value": "[parameters('environment')]",
|
||||
"equals": "production"
|
||||
},
|
||||
{
|
||||
"value": "[concat(parameters('prefix'), '-', parameters('suffix'))]",
|
||||
"notEquals": ""
|
||||
},
|
||||
{
|
||||
"value": "[length(parameters('allowedLocations'))]",
|
||||
"greater": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
parameters:
|
||||
environment: "production"
|
||||
prefix: "prod"
|
||||
suffix: "01"
|
||||
allowedLocations:
|
||||
- "eastus"
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# Unary minus for negative number literals
|
||||
# =========================================================================
|
||||
|
||||
- note: expr_unary_minus_literal
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[add(-1, 5)]",
|
||||
"equals": 4
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: expr_unary_minus_float
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[add(-2.5, 3.5)]",
|
||||
"equals": 1
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: expr_unary_minus_sub_expression
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[sub(10, -3)]",
|
||||
"equals": 13
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: expr_zero_arg_function_call
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[concat()]",
|
||||
"equals": ""
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "any"
|
||||
want_effect: "deny"
|
||||
323
tests/azure_policy/parser_tests/cases/fields.yaml
Normal file
323
tests/azure_policy/parser_tests/cases/fields.yaml
Normal file
@@ -0,0 +1,323 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Built-in Fields Test Suite
|
||||
# Tests all built-in field types: type, id, kind, name, location, fullName,
|
||||
# tags, identity.type, and tag indexing patterns.
|
||||
|
||||
cases:
|
||||
# =========================================================================
|
||||
# Core built-in fields
|
||||
# =========================================================================
|
||||
|
||||
- note: field_type
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_id
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "id",
|
||||
"contains": "/resourceGroups/myRg/"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
id: "/subscriptions/sub1/resourceGroups/myRg/providers/Microsoft.Compute/virtualMachines/vm1"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: field_kind
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "kind",
|
||||
"equals": "StorageV2"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
kind: "StorageV2"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: field_name
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"contains": "prod"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
name: "my-prod-vm"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_location
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "location",
|
||||
"equals": "eastus"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
location: "eastus"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_fullName
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "fullName",
|
||||
"contains": "Microsoft.Compute"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
fullName: "Microsoft.Compute/virtualMachines/vm1"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: field_identity_type
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "identity.type",
|
||||
"equals": "SystemAssigned"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
identity:
|
||||
type: "SystemAssigned"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Tags
|
||||
# =========================================================================
|
||||
|
||||
- note: field_tags_object
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "tags",
|
||||
"containsKey": "environment"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
tags:
|
||||
environment: "production"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: field_tags_dot_notation
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "tags.environment",
|
||||
"equals": "production"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
tags:
|
||||
environment: "production"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: field_tags_bracket_notation
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "tags['environment']",
|
||||
"equals": "production"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
tags:
|
||||
environment: "production"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_tags_dot_hyphen
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "tags.cost-center",
|
||||
"equals": "engineering"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
tags:
|
||||
cost-center: "engineering"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: field_tags_bracket_space
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "tags['Created By']",
|
||||
"exists": true
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
tags:
|
||||
Created By: "admin"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: field_tags_missing
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "tags.environment",
|
||||
"exists": false
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
tags: {}
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# Nested property fields (aliases)
|
||||
# =========================================================================
|
||||
# Note: These test parsing of alias-like dotted paths in field position.
|
||||
# Actual alias resolution is out of scope; these confirm the parser
|
||||
# correctly handles them.
|
||||
|
||||
- note: field_deep_property
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.securityProfile.uefiSettings.secureBootEnabled",
|
||||
"equals": true
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
securityProfile:
|
||||
uefiSettings:
|
||||
secureBootEnabled: true
|
||||
want_effect: "audit"
|
||||
|
||||
- note: field_multiple_field_conditions
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Network/networkSecurityGroups/securityRules" },
|
||||
{ "field": "name", "contains": "allow" },
|
||||
{ "field": "location", "in": ["eastus", "westus", "centralus"] },
|
||||
{ "field": "tags.team", "equals": "security" }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Network/networkSecurityGroups/securityRules"
|
||||
name: "allow-https"
|
||||
location: "eastus"
|
||||
tags:
|
||||
team: "security"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# Bracket notation in field paths
|
||||
# =========================================================================
|
||||
|
||||
- note: field_bracket_notation_string_key
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.networkAcls['default-action']",
|
||||
"equals": "Allow"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
networkAcls:
|
||||
default-action: "Allow"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_bracket_notation_double_quote
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.settings['log-level']",
|
||||
"equals": "debug"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
settings:
|
||||
log-level: "debug"
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# Array index access in field paths
|
||||
# =========================================================================
|
||||
|
||||
- note: field_array_index_zero
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.ipConfigurations[0].name",
|
||||
"equals": "primary"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
ipConfigurations:
|
||||
- name: "primary"
|
||||
properties:
|
||||
subnet: "default"
|
||||
- name: "secondary"
|
||||
properties:
|
||||
subnet: "dmz"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_array_index_one
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.ipConfigurations[1].name",
|
||||
"equals": "secondary"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
ipConfigurations:
|
||||
- name: "primary"
|
||||
- name: "secondary"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: field_array_index_out_of_bounds
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.ipConfigurations[5].name",
|
||||
"exists": true
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
ipConfigurations:
|
||||
- name: "primary"
|
||||
want_undefined: true
|
||||
344
tests/azure_policy/parser_tests/cases/logical_combinators.yaml
Normal file
344
tests/azure_policy/parser_tests/cases/logical_combinators.yaml
Normal file
@@ -0,0 +1,344 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Logical Combinators Test Suite
|
||||
# Tests allOf, anyOf, not, and nested combinations.
|
||||
|
||||
cases:
|
||||
# =========================================================================
|
||||
# allOf
|
||||
# =========================================================================
|
||||
|
||||
- note: allOf_two_conditions
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
|
||||
{ "field": "location", "equals": "eastus" }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
location: "eastus"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: allOf_partial_match
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
|
||||
{ "field": "location", "equals": "westus" }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
location: "eastus"
|
||||
want_undefined: true
|
||||
|
||||
- note: allOf_three_conditions
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
|
||||
{ "field": "location", "equals": "eastus" },
|
||||
{ "field": "name", "contains": "prod" }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
location: "eastus"
|
||||
name: "my-prod-vm"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: allOf_single_condition
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: allOf_empty_array
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": []
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "anything"
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# anyOf
|
||||
# =========================================================================
|
||||
|
||||
- note: anyOf_first_matches
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"anyOf": [
|
||||
{ "field": "location", "equals": "eastus" },
|
||||
{ "field": "location", "equals": "westus" }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
location: "eastus"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: anyOf_second_matches
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"anyOf": [
|
||||
{ "field": "location", "equals": "eastus" },
|
||||
{ "field": "location", "equals": "westus" }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
location: "westus"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: anyOf_no_match
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"anyOf": [
|
||||
{ "field": "location", "equals": "eastus" },
|
||||
{ "field": "location", "equals": "westus" }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
location: "northeurope"
|
||||
want_undefined: true
|
||||
|
||||
- note: anyOf_three_options
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"anyOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
|
||||
{ "field": "type", "equals": "Microsoft.Compute/virtualMachineScaleSets" },
|
||||
{ "field": "type", "equals": "Microsoft.Compute/disks" }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/disks"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# not
|
||||
# =========================================================================
|
||||
|
||||
- note: not_condition
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"not": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: not_condition_no_match
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"not": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
want_undefined: true
|
||||
|
||||
- note: not_allOf
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"not": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
|
||||
{ "field": "location", "equals": "eastus" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
location: "westus"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: not_anyOf
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"not": {
|
||||
"anyOf": [
|
||||
{ "field": "location", "equals": "eastus" },
|
||||
{ "field": "location", "equals": "westus" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
location: "northeurope"
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# Nested combinations
|
||||
# =========================================================================
|
||||
|
||||
- note: allOf_with_nested_anyOf
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
|
||||
{
|
||||
"anyOf": [
|
||||
{ "field": "location", "equals": "eastus" },
|
||||
{ "field": "location", "equals": "westus" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
location: "westus"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: anyOf_with_nested_allOf
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"anyOf": [
|
||||
{
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
|
||||
{ "field": "location", "equals": "eastus" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
|
||||
{ "field": "location", "equals": "westus" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
location: "westus"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: allOf_with_not
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
|
||||
{
|
||||
"not": {
|
||||
"field": "location",
|
||||
"equals": "eastus"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
location: "westus"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: deeply_nested_combinators
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Network/networkSecurityGroups/securityRules" },
|
||||
{
|
||||
"not": {
|
||||
"anyOf": [
|
||||
{
|
||||
"allOf": [
|
||||
{ "field": "properties.protocol", "equals": "TCP" },
|
||||
{ "field": "properties.destinationPortRange", "in": ["443", "8443"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{ "field": "properties.protocol", "equals": "UDP" },
|
||||
{ "field": "properties.destinationPortRange", "equals": "53" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Network/networkSecurityGroups/securityRules"
|
||||
properties:
|
||||
protocol: "TCP"
|
||||
destinationPortRange: "80"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: double_negation
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"not": {
|
||||
"not": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
want_effect: "deny"
|
||||
453
tests/azure_policy/parser_tests/cases/operators.yaml
Normal file
453
tests/azure_policy/parser_tests/cases/operators.yaml
Normal file
@@ -0,0 +1,453 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Operators Test Suite
|
||||
# Tests all 19 Azure Policy condition operators with field-based conditions.
|
||||
|
||||
cases:
|
||||
# =========================================================================
|
||||
# equals / notEquals
|
||||
# =========================================================================
|
||||
|
||||
- note: equals_string
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: equals_string_no_match
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
want_undefined: true
|
||||
|
||||
- note: equals_number
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.count",
|
||||
"equals": 5
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
count: 5
|
||||
want_effect: "audit"
|
||||
|
||||
- note: equals_boolean
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.enabled",
|
||||
"equals": true
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
enabled: true
|
||||
want_effect: "audit"
|
||||
|
||||
- note: equals_null
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.optionalField",
|
||||
"equals": null
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
properties: {}
|
||||
want_effect: "audit"
|
||||
|
||||
- note: notEquals_string
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"notEquals": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Storage/storageAccounts"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: notEquals_no_match
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"notEquals": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
type: "Microsoft.Compute/virtualMachines"
|
||||
want_undefined: true
|
||||
|
||||
# =========================================================================
|
||||
# contains / notContains
|
||||
# =========================================================================
|
||||
|
||||
- note: contains_string
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"contains": "prod"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "my-prod-vm"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: contains_no_match
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"contains": "staging"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "my-prod-vm"
|
||||
want_undefined: true
|
||||
|
||||
- note: notContains_string
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"notContains": "staging"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "my-prod-vm"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# containsKey / notContainsKey
|
||||
# =========================================================================
|
||||
|
||||
- note: containsKey_field
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "tags",
|
||||
"containsKey": "environment"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
tags:
|
||||
environment: "production"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: notContainsKey_field
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "tags",
|
||||
"notContainsKey": "costCenter"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
tags:
|
||||
environment: "production"
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# greater / greaterOrEquals / less / lessOrEquals
|
||||
# =========================================================================
|
||||
|
||||
- note: greater_number
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.maxRetries",
|
||||
"greater": 5
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
maxRetries: 10
|
||||
want_effect: "deny"
|
||||
|
||||
- note: greater_no_match
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.maxRetries",
|
||||
"greater": 5
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
maxRetries: 3
|
||||
want_undefined: true
|
||||
|
||||
- note: greaterOrEquals_equal
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.minInstances",
|
||||
"greaterOrEquals": 3
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
minInstances: 3
|
||||
want_effect: "audit"
|
||||
|
||||
- note: less_number
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.retentionDays",
|
||||
"less": 30
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
retentionDays: 7
|
||||
want_effect: "deny"
|
||||
|
||||
- note: lessOrEquals_number
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.maxConnections",
|
||||
"lessOrEquals": 100
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
maxConnections: 50
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# in / notIn
|
||||
# =========================================================================
|
||||
|
||||
- note: in_string_array
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "location",
|
||||
"in": ["eastus", "westus", "centralus"]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
location: "eastus"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: in_no_match
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "location",
|
||||
"in": ["eastus", "westus"]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
location: "northeurope"
|
||||
want_undefined: true
|
||||
|
||||
- note: notIn_string_array
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "location",
|
||||
"notIn": ["eastus", "westus"]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
location: "northeurope"
|
||||
want_effect: "deny"
|
||||
|
||||
- note: in_number_array
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.port",
|
||||
"in": [80, 443, 8080]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
port: 443
|
||||
want_effect: "deny"
|
||||
|
||||
# =========================================================================
|
||||
# like / notLike
|
||||
# =========================================================================
|
||||
|
||||
- note: like_wildcard
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"like": "prod-*"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "prod-server-01"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: like_question_mark
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"like": "vm-?"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "vm-1"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: notLike_wildcard
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"notLike": "test-*"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "prod-server-01"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# match / matchInsensitively
|
||||
# =========================================================================
|
||||
|
||||
- note: match_pattern
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"match": "vm-##"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "vm-01"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: matchInsensitively_pattern
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"matchInsensitively": "VM-##"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "vm-01"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: notMatch_pattern
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"notMatch": "test-*"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "prod-server-01"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: notMatchInsensitively_pattern
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "name",
|
||||
"notMatchInsensitively": "TEST-##"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
name: "prod-01"
|
||||
want_effect: "audit"
|
||||
|
||||
# =========================================================================
|
||||
# exists
|
||||
# =========================================================================
|
||||
|
||||
- note: exists_true
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.optionalSetting",
|
||||
"exists": true
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
optionalSetting: "value"
|
||||
want_effect: "audit"
|
||||
|
||||
- note: exists_false
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.optionalSetting",
|
||||
"exists": false
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
properties: {}
|
||||
want_effect: "audit"
|
||||
|
||||
- note: exists_string_true
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "properties.optionalSetting",
|
||||
"exists": "true"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}
|
||||
resource:
|
||||
properties:
|
||||
optionalSetting: "value"
|
||||
want_effect: "audit"
|
||||
273
tests/azure_policy/parser_tests/cases/parse_errors.yaml
Normal file
273
tests/azure_policy/parser_tests/cases/parse_errors.yaml
Normal file
@@ -0,0 +1,273 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Parse Error Test Suite
|
||||
# Tests that malformed policy JSON and invalid constructs are properly rejected.
|
||||
# These test cases are expected to fail parsing.
|
||||
|
||||
cases:
|
||||
# =========================================================================
|
||||
# Missing required keys
|
||||
# =========================================================================
|
||||
|
||||
- note: missing_if_key
|
||||
skip: true # Tests policy_rule-level error; needs parse_policy_rule
|
||||
policy_rule: |
|
||||
{
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
- note: missing_then_key
|
||||
skip: true # Tests policy_rule-level error; needs parse_policy_rule
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
}
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
- note: missing_effect_in_then
|
||||
skip: true # Tests policy_rule-level error; needs parse_policy_rule
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
"then": {}
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
# =========================================================================
|
||||
# Missing operator in condition
|
||||
# =========================================================================
|
||||
|
||||
- note: field_without_operator
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
- note: value_without_operator
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[parameters('x')]"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
# =========================================================================
|
||||
# Invalid JSON structure
|
||||
# =========================================================================
|
||||
|
||||
- note: allOf_not_array
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": "not-an-array"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
- note: anyOf_not_array
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"anyOf": 42
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
- note: not_not_object
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"not": [1, 2, 3]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
# =========================================================================
|
||||
# Unknown keys in condition objects
|
||||
# =========================================================================
|
||||
|
||||
- note: unknown_key_in_condition
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines",
|
||||
"unknownKey": "value"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
# =========================================================================
|
||||
# Count structure issues
|
||||
# =========================================================================
|
||||
|
||||
- note: count_missing_field_and_value
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {},
|
||||
"equals": 0
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
- note: count_with_both_field_and_value
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "some.alias[*]",
|
||||
"value": ["a", "b"]
|
||||
},
|
||||
"equals": 0
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
# =========================================================================
|
||||
# Invalid ARM template expressions
|
||||
# =========================================================================
|
||||
|
||||
- note: malformed_expression_unclosed_paren
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"value": "[parameters('x']",
|
||||
"equals": "something"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
# =========================================================================
|
||||
# Both field and value LHS
|
||||
# =========================================================================
|
||||
|
||||
- note: both_field_and_value_lhs
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"value": "something",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
# =========================================================================
|
||||
# Empty input
|
||||
# =========================================================================
|
||||
|
||||
- note: empty_object
|
||||
policy_rule: |
|
||||
{}
|
||||
want_parse_error: true
|
||||
|
||||
- note: not_an_object
|
||||
policy_rule: |
|
||||
"just a string"
|
||||
want_parse_error: true
|
||||
|
||||
# =========================================================================
|
||||
# Extra keys in logical operators
|
||||
# =========================================================================
|
||||
|
||||
- note: extra_key_in_allOf
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "X" }
|
||||
],
|
||||
"field": "name",
|
||||
"equals": "Y"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
- note: extra_key_in_not
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"not": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines"
|
||||
},
|
||||
"field": "name",
|
||||
"equals": "something"
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
# =========================================================================
|
||||
# count.name errors
|
||||
# =========================================================================
|
||||
|
||||
- note: count_name_with_field_not_value
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"field": "items[*]",
|
||||
"name": "item"
|
||||
},
|
||||
"equals": 0
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
- note: count_name_not_string
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"count": {
|
||||
"value": ["a", "b"],
|
||||
"name": 42
|
||||
},
|
||||
"equals": 2
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
# =========================================================================
|
||||
# Multiple operators in a single condition
|
||||
# =========================================================================
|
||||
|
||||
- note: multiple_operators
|
||||
policy_rule: |
|
||||
{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Compute/virtualMachines",
|
||||
"in": ["Microsoft.Compute/virtualMachines"]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
want_parse_error: true
|
||||
|
||||
|
||||
175
tests/azure_policy/parser_tests/mod.rs
Normal file
175
tests/azure_policy/parser_tests/mod.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! YAML-driven test suite for Azure Policy constraint parser.
|
||||
//!
|
||||
//! Each YAML file in `tests/azure_policy/parser_tests/cases/` contains a list
|
||||
//! of test cases. Each case specifies a `policy_rule` JSON string with
|
||||
//! `"if"` / `"then"` structure. The test runner extracts the `"if"` constraint
|
||||
//! JSON and parses it with `parse_constraint`.
|
||||
|
||||
use anyhow::Result;
|
||||
use regorus::languages::azure_policy::parser;
|
||||
use regorus::Source;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use test_generator::test_resources;
|
||||
|
||||
/// A single test case in the YAML file.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct TestCase {
|
||||
/// Short identifier for the test case.
|
||||
pub note: String,
|
||||
|
||||
/// The Azure Policy `policyRule` JSON string.
|
||||
#[serde(default)]
|
||||
pub policy_rule: Option<String>,
|
||||
|
||||
/// If true, the constraint is expected to fail parsing.
|
||||
#[serde(default)]
|
||||
pub want_parse_error: Option<bool>,
|
||||
|
||||
/// If true, skip this test case.
|
||||
#[serde(default)]
|
||||
pub skip: Option<bool>,
|
||||
}
|
||||
|
||||
/// Top-level YAML test file structure.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct YamlTest {
|
||||
/// Optional global policy rule JSON string.
|
||||
#[serde(default)]
|
||||
pub policy_rule: Option<String>,
|
||||
|
||||
pub cases: Vec<TestCase>,
|
||||
}
|
||||
|
||||
/// Filter test cases by the `TEST_CASE_FILTER` environment variable.
|
||||
fn should_run_test_case(case_note: &str) -> bool {
|
||||
if let Ok(filter) = std::env::var("TEST_CASE_FILTER") {
|
||||
case_note.contains(&filter)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the `"if"` sub-object from a policy rule JSON string.
|
||||
///
|
||||
/// Returns `None` if parsing fails or there is no `"if"` key (the caller
|
||||
/// should feed the raw string to `parse_constraint` for error tests).
|
||||
fn extract_if_json(policy_rule_json: &str) -> Option<String> {
|
||||
let v: serde_json::Value = serde_json::from_str(policy_rule_json).ok()?;
|
||||
let if_value = v.get("if")?;
|
||||
Some(if_value.to_string())
|
||||
}
|
||||
|
||||
/// Run all test cases from a YAML file.
|
||||
fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
let yaml_str = fs::read_to_string(file)?;
|
||||
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
||||
|
||||
println!("running {file}");
|
||||
if let Ok(filter) = std::env::var("TEST_CASE_FILTER") {
|
||||
println!(" Test case filter active: '{filter}'");
|
||||
}
|
||||
|
||||
let mut executed_count = 0usize;
|
||||
let mut skipped_count = 0usize;
|
||||
|
||||
for case in &test.cases {
|
||||
if !should_run_test_case(&case.note) {
|
||||
println!(" case {} filtered out", case.note);
|
||||
skipped_count += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
print!(" case {} ", case.note);
|
||||
|
||||
if case.skip == Some(true) {
|
||||
println!("skipped");
|
||||
skipped_count += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
executed_count += 1;
|
||||
|
||||
let expects_parse_error = case.want_parse_error == Some(true);
|
||||
|
||||
let policy_rule_json = if let Some(ref rule) = case.policy_rule {
|
||||
rule.clone()
|
||||
} else if let Some(ref rule) = test.policy_rule {
|
||||
rule.clone()
|
||||
} else {
|
||||
panic!("case '{}': must specify 'policy_rule'", case.note);
|
||||
};
|
||||
|
||||
// Extract the "if" constraint JSON. If extraction fails (malformed
|
||||
// JSON or missing "if" key), feed the raw policy_rule to
|
||||
// parse_constraint — it should fail, matching want_parse_error.
|
||||
let constraint_json =
|
||||
extract_if_json(&policy_rule_json).unwrap_or_else(|| policy_rule_json.clone());
|
||||
|
||||
let source = Source::from_contents(format!("test:{}", case.note), constraint_json)?;
|
||||
|
||||
let parse_result = parser::parse_constraint(&source).map(|_| ());
|
||||
|
||||
match parse_result {
|
||||
Ok(()) => {
|
||||
if expects_parse_error {
|
||||
panic!(
|
||||
"case '{}': expected parse error but parsing succeeded",
|
||||
case.note
|
||||
);
|
||||
}
|
||||
println!("passed (parsed ok)");
|
||||
}
|
||||
Err(e) => {
|
||||
if expects_parse_error {
|
||||
println!("passed (expected parse error: {})", e);
|
||||
} else {
|
||||
panic!("case '{}': unexpected parse error: {}", case.note, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!(
|
||||
" Summary: {executed_count} executed, {skipped_count} skipped, {} total",
|
||||
test.cases.len()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_resources("tests/azure_policy/parser_tests/cases/**/*.yaml")]
|
||||
fn yaml_test(file: &str) {
|
||||
yaml_test_impl(file).unwrap();
|
||||
}
|
||||
|
||||
/// Test duplicate-key detection directly (bypassing serde_json which
|
||||
/// silently deduplicates keys).
|
||||
#[test]
|
||||
fn duplicate_key_in_condition() {
|
||||
// Two "field" keys in a single condition object.
|
||||
let json = r#"{"field": "type", "field": "name", "equals": "X"}"#;
|
||||
let source = Source::from_contents("test:dup_field".to_string(), json.to_string()).unwrap();
|
||||
let err = parser::parse_constraint(&source).unwrap_err();
|
||||
let msg = err.to_string();
|
||||
assert!(
|
||||
msg.contains("duplicate key"),
|
||||
"expected duplicate key error, got: {msg}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_key_in_count() {
|
||||
// Two "field" keys inside a count block.
|
||||
let json = r#"{"count": {"field": "a[*]", "field": "b[*]"}, "equals": 0}"#;
|
||||
let source =
|
||||
Source::from_contents("test:dup_count_field".to_string(), json.to_string()).unwrap();
|
||||
let err = parser::parse_constraint(&source).unwrap_err();
|
||||
let msg = err.to_string();
|
||||
assert!(
|
||||
msg.contains("duplicate key"),
|
||||
"expected duplicate key error, got: {msg}"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user