# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # Casing & Case-Insensitive Comparison Test Suite # # Locks down the two-tier case-insensitivity semantics: # 1. ARM property keys: OrdinalIgnoreCase (ASCII fold only) # 2. Azure Policy condition values: InvariantCultureIgnoreCase (full Unicode # case folding via ICU — ß=SS, ffi=FFI, Σ/σ/ς unified, etc.) # # See docs/azure-policy/casing.md for the full specification. cases: # ========================================================================= # equals — ASCII case-insensitive string values # ========================================================================= - note: equals_ascii_case_insensitive policy_rule: | { "if": { "field": "type", "equals": "microsoft.compute/virtualmachines" }, "then": { "effect": "deny" } } resource: type: "Microsoft.Compute/virtualMachines" want_effect: "deny" - note: equals_ascii_mixed_case policy_rule: | { "if": { "field": "location", "equals": "WESTUS2" }, "then": { "effect": "audit" } } resource: location: "westus2" want_effect: "audit" - note: equals_ascii_exact_match policy_rule: | { "if": { "field": "name", "equals": "MyVM" }, "then": { "effect": "audit" } } resource: name: "myvm" want_effect: "audit" - note: equals_ascii_no_match policy_rule: | { "if": { "field": "name", "equals": "MyVM" }, "then": { "effect": "audit" } } resource: name: "OtherVM" want_undefined: true # ========================================================================= # equals — Unicode case folding (InvariantCultureIgnoreCase) # ========================================================================= - note: equals_unicode_sharp_s policy_rule: | { "if": { "field": "tags.description", "equals": "Straße" }, "then": { "effect": "deny" } } resource: tags: description: "STRASSE" want_effect: "deny" - note: equals_unicode_sharp_s_reverse policy_rule: | { "if": { "field": "tags.description", "equals": "STRASSE" }, "then": { "effect": "deny" } } resource: tags: description: "Straße" want_effect: "deny" - note: equals_unicode_ligature_ffi policy_rule: | { "if": { "field": "tags.label", "equals": "ffice" }, "then": { "effect": "audit" } } resource: tags: label: "FFICE" want_effect: "audit" # ========================================================================= # notEquals — casing # ========================================================================= - note: notEquals_case_insensitive_no_trigger policy_rule: | { "if": { "field": "location", "notEquals": "westus2" }, "then": { "effect": "deny" } } resource: location: "WestUS2" want_undefined: true - note: notEquals_case_insensitive_trigger policy_rule: | { "if": { "field": "location", "notEquals": "eastus" }, "then": { "effect": "deny" } } resource: location: "WestUS2" want_effect: "deny" # ========================================================================= # Tag name case insensitivity # ========================================================================= - note: tag_name_case_insensitive_lowercase_policy policy_rule: | { "if": { "field": "tags.environment", "equals": "production" }, "then": { "effect": "deny" } } resource: tags: Environment: "production" want_effect: "deny" - note: tag_name_case_insensitive_uppercase_policy policy_rule: | { "if": { "field": "tags.Environment", "equals": "production" }, "then": { "effect": "deny" } } resource: tags: environment: "production" want_effect: "deny" - note: tag_name_case_insensitive_mixed policy_rule: | { "if": { "field": "tags.CostCenter", "equals": "finance" }, "then": { "effect": "audit" } } resource: tags: costcenter: "finance" want_effect: "audit" - note: tag_value_case_insensitive policy_rule: | { "if": { "field": "tags.environment", "equals": "PRODUCTION" }, "then": { "effect": "deny" } } resource: tags: environment: "Production" want_effect: "deny" # ========================================================================= # contains — string case insensitivity # ========================================================================= - note: contains_string_case_insensitive policy_rule: | { "if": { "field": "name", "contains": "PROD" }, "then": { "effect": "audit" } } resource: name: "my-prod-vm" want_effect: "audit" - note: contains_string_unicode_sharp_s policy_rule: | { "if": { "field": "tags.description", "contains": "STRASSE" }, "then": { "effect": "audit" } } resource: tags: description: "Die Straße ist lang" want_effect: "audit" - note: notContains_case_insensitive policy_rule: | { "if": { "field": "name", "notContains": "PROD" }, "then": { "effect": "audit" } } resource: name: "my-prod-vm" want_undefined: true # ========================================================================= # containsKey — key name case insensitivity # ========================================================================= - note: containsKey_case_insensitive policy_rule: | { "if": { "field": "tags", "containsKey": "Environment" }, "then": { "effect": "audit" } } resource: tags: environment: "production" want_effect: "audit" - note: notContainsKey_case_insensitive policy_rule: | { "if": { "field": "tags", "notContainsKey": "Environment" }, "then": { "effect": "deny" } } resource: tags: environment: "production" want_undefined: true # ========================================================================= # in / notIn — case-insensitive membership # ========================================================================= - note: in_case_insensitive policy_rule: | { "if": { "field": "location", "in": ["EASTUS", "WESTUS2", "CENTRALUS"] }, "then": { "effect": "audit" } } resource: location: "westus2" want_effect: "audit" - note: in_no_match policy_rule: | { "if": { "field": "location", "in": ["eastus", "westus2"] }, "then": { "effect": "audit" } } resource: location: "northeurope" want_undefined: true - note: notIn_case_insensitive policy_rule: | { "if": { "field": "location", "notIn": ["EASTUS", "WESTUS2"] }, "then": { "effect": "deny" } } resource: location: "westus2" want_undefined: true # ========================================================================= # greater / less — case-insensitive string ordering # ========================================================================= - note: greater_string_case_insensitive policy_rule: | { "if": { "field": "name", "greater": "ALPHA" }, "then": { "effect": "audit" } } resource: name: "beta" want_effect: "audit" - note: greater_string_equal_different_case policy_rule: | { "if": { "field": "name", "greater": "HELLO" }, "then": { "effect": "audit" } } resource: name: "hello" want_undefined: true - note: less_string_case_insensitive policy_rule: | { "if": { "field": "name", "less": "BETA" }, "then": { "effect": "audit" } } resource: name: "alpha" want_effect: "audit" - note: greaterOrEquals_string_case_insensitive policy_rule: | { "if": { "field": "name", "greaterOrEquals": "HELLO" }, "then": { "effect": "audit" } } resource: name: "hello" want_effect: "audit" - note: lessOrEquals_string_case_insensitive policy_rule: | { "if": { "field": "name", "lessOrEquals": "HELLO" }, "then": { "effect": "audit" } } resource: name: "hello" want_effect: "audit" # ========================================================================= # like / notLike — case-insensitive wildcard matching # ========================================================================= - note: like_case_insensitive policy_rule: | { "if": { "field": "name", "like": "MY-*-VM" }, "then": { "effect": "audit" } } resource: name: "my-prod-vm" want_effect: "audit" - note: like_case_insensitive_no_match policy_rule: | { "if": { "field": "name", "like": "MY-*-VM" }, "then": { "effect": "audit" } } resource: name: "your-prod-vm" want_undefined: true - note: notLike_case_insensitive policy_rule: | { "if": { "field": "name", "notLike": "MY-*-VM" }, "then": { "effect": "audit" } } resource: name: "my-prod-vm" want_undefined: true # ========================================================================= # match / matchInsensitively # ========================================================================= - note: match_is_case_sensitive policy_rule: | { "if": { "field": "name", "match": "abc-##" }, "then": { "effect": "audit" } } resource: name: "abc-12" want_effect: "audit" - note: match_case_sensitive_literal_no_match policy_rule: | { "if": { "field": "name", "match": "abc-##" }, "then": { "effect": "audit" } } resource: name: "ABC-12" want_undefined: true - note: matchInsensitively_case_insensitive policy_rule: | { "if": { "field": "name", "matchInsensitively": "abc-##" }, "then": { "effect": "audit" } } resource: name: "ABC-12" want_effect: "audit" # ========================================================================= # Property key casing (ARM OrdinalIgnoreCase) # ========================================================================= - note: property_key_case_insensitive policy_rule: | { "if": { "field": "properties.enableHttpsTrafficOnly", "equals": true }, "then": { "effect": "audit" } } resource: properties: EnableHttpsTrafficOnly: true want_effect: "audit" # ========================================================================= # type field — case-insensitive resource type matching # ========================================================================= - note: type_case_insensitive_upper policy_rule: | { "if": { "field": "type", "equals": "MICROSOFT.COMPUTE/VIRTUALMACHINES" }, "then": { "effect": "deny" } } resource: type: "Microsoft.Compute/virtualMachines" want_effect: "deny" - note: type_case_insensitive_lower policy_rule: | { "if": { "field": "type", "equals": "microsoft.compute/virtualmachines" }, "then": { "effect": "deny" } } resource: type: "Microsoft.Compute/virtualMachines" want_effect: "deny" # ========================================================================= # contains with array — case-insensitive element matching # ========================================================================= - note: contains_array_case_insensitive policy_rule: | { "if": { "field": "properties.allowedLocations", "contains": "WESTUS2" }, "then": { "effect": "audit" } } resource: properties: allowedLocations: - "eastus" - "westus2" - "centralus" want_effect: "audit" - note: notContains_array_case_insensitive policy_rule: | { "if": { "field": "properties.allowedLocations", "notContains": "WESTUS2" }, "then": { "effect": "deny" } } resource: properties: allowedLocations: - "eastus" - "westus2" want_undefined: true # ========================================================================= # exists — not affected by casing (boolean check) # ========================================================================= - note: exists_tag_case_insensitive_name policy_rule: | { "if": { "field": "tags.Environment", "exists": true }, "then": { "effect": "deny" } } resource: tags: environment: "production" want_effect: "deny" - note: exists_tag_missing policy_rule: | { "if": { "field": "tags.costCenter", "exists": false }, "then": { "effect": "deny" } } resource: tags: environment: "production" want_effect: "deny" # ========================================================================= # Edge cases: empty strings, numbers, booleans # ========================================================================= - note: equals_empty_string policy_rule: | { "if": { "field": "tags.label", "equals": "" }, "then": { "effect": "audit" } } resource: tags: label: "" want_effect: "audit" - note: equals_number_not_affected_by_casing policy_rule: | { "if": { "field": "properties.count", "equals": 42 }, "then": { "effect": "audit" } } resource: properties: count: 42 want_effect: "audit" - note: equals_boolean_not_affected_by_casing policy_rule: | { "if": { "field": "properties.enabled", "equals": true }, "then": { "effect": "audit" } } resource: properties: enabled: true want_effect: "audit" - note: equals_string_number_coercion_with_casing policy_rule: | { "if": { "field": "properties.port", "equals": "443" }, "then": { "effect": "audit" } } resource: properties: port: 443 want_effect: "audit" # ========================================================================= # Corner cases: undefined / missing fields # ========================================================================= - note: equals_missing_field_is_undefined policy_rule: | { "if": { "field": "tags.missing", "equals": "anything" }, "then": { "effect": "deny" } } resource: tags: environment: "production" want_undefined: true - note: notEquals_missing_field_fires policy_rule: | { "if": { "field": "tags.missing", "notEquals": "anything" }, "then": { "effect": "deny" } } resource: tags: environment: "production" want_effect: "deny" - note: contains_string_missing_field_is_undefined policy_rule: | { "if": { "field": "tags.missing", "contains": "X" }, "then": { "effect": "audit" } } resource: tags: environment: "production" want_undefined: true - note: in_missing_field_is_undefined policy_rule: | { "if": { "field": "tags.missing", "in": ["a", "b"] }, "then": { "effect": "audit" } } resource: tags: environment: "production" want_undefined: true - note: greater_missing_field_is_undefined policy_rule: | { "if": { "field": "tags.missing", "greater": "z" }, "then": { "effect": "audit" } } resource: tags: environment: "production" want_undefined: true # ========================================================================= # Tag bracket notation — tags['Key'] equivalent to tags.Key # ========================================================================= - note: tag_bracket_notation_case_insensitive policy_rule: | { "if": { "field": "tags['Environment']", "equals": "production" }, "then": { "effect": "deny" } } resource: tags: environment: "production" want_effect: "deny" - note: tag_bracket_notation_value_ci policy_rule: | { "if": { "field": "tags['costCenter']", "equals": "FINANCE" }, "then": { "effect": "audit" } } resource: tags: CostCenter: "finance" want_effect: "audit" # ========================================================================= # String ↔ Bool coercion with mixed casing # ========================================================================= - note: equals_string_true_uppercase_coercion policy_rule: | { "if": { "field": "properties.flag", "equals": true }, "then": { "effect": "audit" } } resource: properties: flag: "TRUE" want_effect: "audit" - note: equals_string_false_mixed_case_coercion policy_rule: | { "if": { "field": "properties.flag", "equals": false }, "then": { "effect": "audit" } } resource: properties: flag: "False" want_effect: "audit" # ========================================================================= # Unicode: Greek sigma variants (Σ/σ/ς all fold to σ) # ========================================================================= - note: equals_greek_sigma_upper_vs_lower policy_rule: | { "if": { "field": "tags.label", "equals": "ΣΕΛΑΣ" }, "then": { "effect": "audit" } } resource: tags: label: "σελας" want_effect: "audit" - note: equals_greek_final_sigma policy_rule: | { "if": { "field": "tags.label", "equals": "ΚΟΣΜΟΣ" }, "then": { "effect": "audit" } } resource: tags: label: "κοσμος" want_effect: "audit" # ========================================================================= # notEquals with Unicode — should NOT fire when values match after folding # ========================================================================= - note: notEquals_unicode_sharp_s_equal policy_rule: | { "if": { "field": "tags.description", "notEquals": "STRASSE" }, "then": { "effect": "deny" } } resource: tags: description: "Straße" want_undefined: true # ========================================================================= # in/notIn with Unicode values # ========================================================================= - note: in_unicode_sharp_s_in_array policy_rule: | { "if": { "field": "tags.street", "in": ["STRASSE", "AVENUE", "BOULEVARD"] }, "then": { "effect": "audit" } } resource: tags: street: "Straße" want_effect: "audit" - note: notIn_unicode_value_present policy_rule: | { "if": { "field": "tags.street", "notIn": ["STRASSE", "AVENUE"] }, "then": { "effect": "deny" } } resource: tags: street: "Straße" want_undefined: true # ========================================================================= # contains — empty needle/haystack edge cases # ========================================================================= - note: contains_empty_needle_always_matches policy_rule: | { "if": { "field": "name", "contains": "" }, "then": { "effect": "audit" } } resource: name: "anything" want_effect: "audit" - note: contains_empty_haystack_no_match policy_rule: | { "if": { "field": "name", "contains": "something" }, "then": { "effect": "audit" } } resource: name: "" want_undefined: true - note: notContains_string_truly_absent policy_rule: | { "if": { "field": "name", "notContains": "STAGING" }, "then": { "effect": "audit" } } resource: name: "my-prod-vm" want_effect: "audit" # ========================================================================= # contains with Unicode ligature as substring # ========================================================================= - note: contains_unicode_ligature_substring policy_rule: | { "if": { "field": "tags.description", "contains": "ffice" }, "then": { "effect": "audit" } } resource: tags: description: "The OFFICE is closed" want_effect: "audit" # ========================================================================= # containsKey — key truly missing vs case mismatch # ========================================================================= - note: containsKey_key_truly_missing policy_rule: | { "if": { "field": "tags", "containsKey": "billing" }, "then": { "effect": "audit" } } resource: tags: environment: "production" want_undefined: true - note: notContainsKey_key_truly_missing policy_rule: | { "if": { "field": "tags", "notContainsKey": "billing" }, "then": { "effect": "deny" } } resource: tags: environment: "production" want_effect: "deny" # ========================================================================= # greater/less with Unicode — ß folds to "ss" for ordering # ========================================================================= - note: greater_unicode_sharp_s_equal_after_fold policy_rule: | { "if": { "field": "tags.street", "greater": "strasse" }, "then": { "effect": "audit" } } resource: tags: street: "Straße" want_undefined: true - note: lessOrEquals_unicode_sharp_s policy_rule: | { "if": { "field": "tags.street", "lessOrEquals": "STRASSE" }, "then": { "effect": "audit" } } resource: tags: street: "Straße" want_effect: "audit" # ========================================================================= # like with Unicode — input folded before wildcard matching # ========================================================================= - note: like_unicode_folded_match policy_rule: | { "if": { "field": "tags.description", "like": "die strasse*" }, "then": { "effect": "audit" } } resource: tags: description: "Die Straße ist lang" want_effect: "audit" # ========================================================================= # matchInsensitively with Unicode literals # ========================================================================= - note: matchInsensitively_unicode_literal policy_rule: | { "if": { "field": "tags.code", "matchInsensitively": "strasse-##" }, "then": { "effect": "audit" } } resource: tags: code: "STRASSE-42" want_effect: "audit" # ========================================================================= # Nested property key casing (deep paths) # ========================================================================= - note: nested_property_key_deep_path policy_rule: | { "if": { "field": "properties.networkProfile.primaryNic", "equals": "nic-01" }, "then": { "effect": "audit" } } resource: properties: NetworkProfile: PrimaryNic: "NIC-01" want_effect: "audit" # ========================================================================= # Multiple tags — lookup finds correct tag by name # ========================================================================= - note: multiple_tags_lookup_correct_one policy_rule: | { "if": { "allOf": [ { "field": "tags.Environment", "equals": "production" }, { "field": "tags.CostCenter", "equals": "FINANCE" } ] }, "then": { "effect": "deny" } } resource: tags: environment: "PRODUCTION" costCenter: "finance" want_effect: "deny" # ========================================================================= # in with single-element array # ========================================================================= - note: in_single_element_case_insensitive policy_rule: | { "if": { "field": "location", "in": ["WestUS2"] }, "then": { "effect": "audit" } } resource: location: "westus2" want_effect: "audit" # ========================================================================= # Null value interactions # ========================================================================= - note: equals_null_explicit policy_rule: | { "if": { "field": "properties.optionalField", "equals": null }, "then": { "effect": "audit" } } resource: properties: optionalField: null want_effect: "audit" - note: equals_null_missing_field policy_rule: | { "if": { "field": "properties.optionalField", "equals": null }, "then": { "effect": "audit" } } resource: properties: {} want_effect: "audit" - note: notEquals_null_with_string_value policy_rule: | { "if": { "field": "properties.optionalField", "notEquals": null }, "then": { "effect": "audit" } } resource: properties: optionalField: "has-a-value" want_effect: "audit" # ========================================================================= # String self-comparison with different casing # ========================================================================= - note: equals_self_different_case policy_rule: | { "if": { "field": "name", "equals": "My-Production-VM" }, "then": { "effect": "audit" } } resource: name: "MY-PRODUCTION-VM" want_effect: "audit"