From afdb894d8508009375c481737bdb3bffc5013195 Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi <35780660+anakrish@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:04:50 -0500 Subject: [PATCH] test(azure_policy): add end-to-end policy test cases (#699) 50 end-to-end test cases derived from real Azure built-in policies. Each file contains a complete policy definition, sample resources, and expected evaluation results. Coverage spans storage, networking, compute, security, monitoring, database, identity, governance, and update management scenarios. Signed-off-by: Anand Krishnamoorthi Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../e2e_activitylog_capture_all_regions.yaml | 103 ++ .../cases/e2e_aks_zone_redundant.yaml | 227 +++ .../cases/e2e_approved_subnets_deny.yaml | 192 ++ .../cases/e2e_approved_vnet_audit.yaml | 134 ++ .../e2e_asc_internet_traffic_firewall.yaml | 250 +++ .../cases/e2e_automanage_deployv2.yaml | 785 ++++++++ .../e2e_azupdate_crp_autoassess_modify.yaml | 1243 +++++++++++++ .../e2e_azupdate_customer_managed_dine.yaml | 1605 ++++++++++++++++ .../e2e_azupdate_scheduled_patching.yaml | 1609 +++++++++++++++++ .../cases/e2e_cmk_disk_encryption.yaml | 572 ++++++ .../e2e_container_diagnostics_append.yaml | 162 ++ .../cases/e2e_cosmos_firewall_audit.yaml | 238 +++ .../cases/e2e_cosmos_locations_deny.yaml | 170 ++ .../cases/e2e_cosmos_max_throughput.yaml | 445 +++++ .../cases/e2e_cosmos_private_modify.yaml | 118 ++ .../cases/e2e_custom_owner_role.yaml | 231 +++ .../cases/e2e_datafactory_linked_secrets.yaml | 458 +++++ .../cases/e2e_dcra_vmss_linux_dine.yaml | 757 ++++++++ .../cases/e2e_double_encryption.yaml | 130 ++ .../cases/e2e_fic_aks_issuer.yaml | 247 +++ .../cases/e2e_fic_github_issuer.yaml | 184 ++ .../cases/e2e_functionapp_https_modify.yaml | 183 ++ .../cases/e2e_guest_config_user_identity.yaml | 836 +++++++++ .../cases/e2e_keyvault_firewall_enabled.yaml | 127 ++ .../e2e_managed_disk_encryption_sets.yaml | 326 ++++ .../cases/e2e_monitoring_dine_existence.yaml | 182 ++ .../cases/e2e_nic_public_ip_deny.yaml | 138 ++ .../cases/e2e_nsg_rdp_access.yaml | 370 ++++ .../cases/e2e_nsg_ssh_access.yaml | 379 ++++ .../azure_policy/cases/e2e_pg_audit_aine.yaml | 121 ++ .../cases/e2e_portal_dashboard_deny.yaml | 240 +++ .../cases/e2e_servicebus_diagnostic_logs.yaml | 116 ++ .../cases/e2e_shared_dashboard_deny.yaml | 204 +++ .../e2e_signalr_public_network_modify.yaml | 183 ++ .../cases/e2e_sql_audit_actions_aine.yaml | 183 ++ .../cases/e2e_sql_server_auditing.yaml | 104 ++ .../cases/e2e_ssh_security_baseline_dine.yaml | 1029 +++++++++++ .../cases/e2e_storage_bypass_modify.yaml | 243 +++ .../cases/e2e_storage_ip_allowlist.yaml | 114 ++ .../e2e_storage_ip_allowlist_custom.yaml | 125 ++ .../cases/e2e_storage_public_blob_modify.yaml | 158 ++ .../cases/e2e_storage_vnet_rules.yaml | 180 ++ .../cases/e2e_stream_analytics_audit.yaml | 428 +++++ tests/azure_policy/cases/e2e_tags_append.yaml | 115 ++ .../cases/e2e_tags_inherit_modify.yaml | 147 ++ .../cases/e2e_vm_backup_dine.yaml | 861 +++++++++ .../cases/e2e_vm_managed_disk.yaml | 156 ++ .../cases/e2e_vm_replication_dine.yaml | 342 ++++ .../cases/e2e_vm_skus_allowed.yaml | 150 ++ .../cases/e2e_vnet_ddos_modify.yaml | 157 ++ 50 files changed, 17757 insertions(+) create mode 100644 tests/azure_policy/cases/e2e_activitylog_capture_all_regions.yaml create mode 100644 tests/azure_policy/cases/e2e_aks_zone_redundant.yaml create mode 100644 tests/azure_policy/cases/e2e_approved_subnets_deny.yaml create mode 100644 tests/azure_policy/cases/e2e_approved_vnet_audit.yaml create mode 100644 tests/azure_policy/cases/e2e_asc_internet_traffic_firewall.yaml create mode 100644 tests/azure_policy/cases/e2e_automanage_deployv2.yaml create mode 100644 tests/azure_policy/cases/e2e_azupdate_crp_autoassess_modify.yaml create mode 100644 tests/azure_policy/cases/e2e_azupdate_customer_managed_dine.yaml create mode 100644 tests/azure_policy/cases/e2e_azupdate_scheduled_patching.yaml create mode 100644 tests/azure_policy/cases/e2e_cmk_disk_encryption.yaml create mode 100644 tests/azure_policy/cases/e2e_container_diagnostics_append.yaml create mode 100644 tests/azure_policy/cases/e2e_cosmos_firewall_audit.yaml create mode 100644 tests/azure_policy/cases/e2e_cosmos_locations_deny.yaml create mode 100644 tests/azure_policy/cases/e2e_cosmos_max_throughput.yaml create mode 100644 tests/azure_policy/cases/e2e_cosmos_private_modify.yaml create mode 100644 tests/azure_policy/cases/e2e_custom_owner_role.yaml create mode 100644 tests/azure_policy/cases/e2e_datafactory_linked_secrets.yaml create mode 100644 tests/azure_policy/cases/e2e_dcra_vmss_linux_dine.yaml create mode 100644 tests/azure_policy/cases/e2e_double_encryption.yaml create mode 100644 tests/azure_policy/cases/e2e_fic_aks_issuer.yaml create mode 100644 tests/azure_policy/cases/e2e_fic_github_issuer.yaml create mode 100644 tests/azure_policy/cases/e2e_functionapp_https_modify.yaml create mode 100644 tests/azure_policy/cases/e2e_guest_config_user_identity.yaml create mode 100644 tests/azure_policy/cases/e2e_keyvault_firewall_enabled.yaml create mode 100644 tests/azure_policy/cases/e2e_managed_disk_encryption_sets.yaml create mode 100644 tests/azure_policy/cases/e2e_monitoring_dine_existence.yaml create mode 100644 tests/azure_policy/cases/e2e_nic_public_ip_deny.yaml create mode 100644 tests/azure_policy/cases/e2e_nsg_rdp_access.yaml create mode 100644 tests/azure_policy/cases/e2e_nsg_ssh_access.yaml create mode 100644 tests/azure_policy/cases/e2e_pg_audit_aine.yaml create mode 100644 tests/azure_policy/cases/e2e_portal_dashboard_deny.yaml create mode 100644 tests/azure_policy/cases/e2e_servicebus_diagnostic_logs.yaml create mode 100644 tests/azure_policy/cases/e2e_shared_dashboard_deny.yaml create mode 100644 tests/azure_policy/cases/e2e_signalr_public_network_modify.yaml create mode 100644 tests/azure_policy/cases/e2e_sql_audit_actions_aine.yaml create mode 100644 tests/azure_policy/cases/e2e_sql_server_auditing.yaml create mode 100644 tests/azure_policy/cases/e2e_ssh_security_baseline_dine.yaml create mode 100644 tests/azure_policy/cases/e2e_storage_bypass_modify.yaml create mode 100644 tests/azure_policy/cases/e2e_storage_ip_allowlist.yaml create mode 100644 tests/azure_policy/cases/e2e_storage_ip_allowlist_custom.yaml create mode 100644 tests/azure_policy/cases/e2e_storage_public_blob_modify.yaml create mode 100644 tests/azure_policy/cases/e2e_storage_vnet_rules.yaml create mode 100644 tests/azure_policy/cases/e2e_stream_analytics_audit.yaml create mode 100644 tests/azure_policy/cases/e2e_tags_append.yaml create mode 100644 tests/azure_policy/cases/e2e_tags_inherit_modify.yaml create mode 100644 tests/azure_policy/cases/e2e_vm_backup_dine.yaml create mode 100644 tests/azure_policy/cases/e2e_vm_managed_disk.yaml create mode 100644 tests/azure_policy/cases/e2e_vm_replication_dine.yaml create mode 100644 tests/azure_policy/cases/e2e_vm_skus_allowed.yaml create mode 100644 tests/azure_policy/cases/e2e_vnet_ddos_modify.yaml diff --git a/tests/azure_policy/cases/e2e_activitylog_capture_all_regions.yaml b/tests/azure_policy/cases/e2e_activitylog_capture_all_regions.yaml new file mode 100644 index 0000000..1e3aa34 --- /dev/null +++ b/tests/azure_policy/cases/e2e_activitylog_capture_all_regions.yaml @@ -0,0 +1,103 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Monitoring/ActivityLog_CaptureAllRegions +# Features: AuditIfNotExists with inline existenceCondition using +# implicit allOf over [*] wildcard fields + not-wrapping. +# +# NOTE: Without an alias catalog, fully-qualified field paths like +# "Microsoft.Insights/logProfiles/locations[*]" resolve as raw object +# keys. The test response structure mirrors this resolution. + +policy_definition: | + { + "properties": { + "displayName": "Azure Monitor should collect activity logs from all regions", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "AuditIfNotExists", + "allowedValues": ["AuditIfNotExists", "Disabled"] + } + }, + "policyRule": { + "if": { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Insights/logProfiles", + "existenceCondition": { + "allOf": [ + { + "not": { + "field": "Microsoft.Insights/logProfiles/locations[*]", + "notEquals": "eastus" + } + }, + { + "not": { + "field": "Microsoft.Insights/logProfiles/locations[*]", + "notEquals": "westus" + } + }, + { + "not": { + "field": "Microsoft.Insights/logProfiles/locations[*]", + "notEquals": "global" + } + } + ] + } + } + } + } + } + } + +cases: + # Related resource not found → non-compliant + - note: non_compliant_resource_not_found + resource: + type: "Microsoft.Resources/subscriptions" + name: "sub-a" + properties: {} + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Insights/logProfiles" + response: null + want_effect: "AuditIfNotExists" + + # Related resource found with all required regions → compliant + - note: compliant_all_regions_present + resource: + type: "Microsoft.Resources/subscriptions" + name: "sub-b" + properties: {} + host_await: + - response: + Microsoft: + "Insights/logProfiles/locations": + - eastus + - westus + - global + want_undefined: true + + # Related resource found but missing a region → non-compliant + - note: non_compliant_missing_region + resource: + type: "Microsoft.Resources/subscriptions" + name: "sub-c" + properties: {} + host_await: + - response: + Microsoft: + "Insights/logProfiles/locations": + - eastus + - westus + want_effect: "AuditIfNotExists" diff --git a/tests/azure_policy/cases/e2e_aks_zone_redundant.yaml b/tests/azure_policy/cases/e2e_aks_zone_redundant.yaml new file mode 100644 index 0000000..c6de32e --- /dev/null +++ b/tests/azure_policy/cases/e2e_aks_zone_redundant.yaml @@ -0,0 +1,227 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Resilience/ContainerService_managedclusters_ZoneRedundant_Audit +# Real Azure Policy: "Azure Kubernetes Service Managed Clusters should be +# Zone Redundant" +# Features: allOf, anyOf, field (type + alias), equals, field count with where, +# nested field count ([*] inside [*]), less, greater, parameters() with +# defaultValue + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "[Preview]: Azure Kubernetes Service Managed Clusters should be Zone Redundant", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect" + }, + "allowedValues": ["Audit", "Deny", "Disabled"], + "defaultValue": "Audit" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.ContainerService/managedclusters" + }, + { + "anyOf": [ + { + "count": { + "field": "Microsoft.ContainerService/managedClusters/agentPoolProfiles[*]", + "where": { + "count": { + "field": "Microsoft.ContainerService/managedClusters/agentPoolProfiles[*].availabilityZones[*]" + }, + "less": 3 + } + }, + "greater": 0 + }, + { + "count": { + "field": "Microsoft.ContainerService/managedClusters/agentPoolProfiles[*]", + "where": { + "field": "Microsoft.ContainerService/managedClusters/agentPoolProfiles[*].count", + "less": 3 + } + }, + "greater": 0 + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # No effect — all pools have 3 AZs and count >= 3 + # ========================================================================= + + - note: pass_fully_zone_redundant + resource: + type: "Microsoft.ContainerService/managedclusters" + name: "myAKS" + location: "eastus" + properties: + agentPoolProfiles: + - name: "system" + count: 3 + availabilityZones: ["1", "2", "3"] + - name: "user" + count: 5 + availabilityZones: ["1", "2", "3"] + want_undefined: true + + # ========================================================================= + # Audit — one pool has fewer than 3 AZs + # ========================================================================= + + - note: audit_pool_missing_az + resource: + type: "Microsoft.ContainerService/managedclusters" + name: "myAKS" + location: "eastus" + properties: + agentPoolProfiles: + - name: "system" + count: 3 + availabilityZones: ["1", "2", "3"] + - name: "user" + count: 3 + availabilityZones: ["1", "2"] + want_effect: "Audit" + + # ========================================================================= + # Audit — one pool has no AZs at all + # ========================================================================= + + - note: audit_pool_no_azs + resource: + type: "Microsoft.ContainerService/managedclusters" + name: "myAKS" + location: "westus" + properties: + agentPoolProfiles: + - name: "system" + count: 3 + availabilityZones: ["1", "2", "3"] + - name: "badpool" + count: 3 + availabilityZones: [] + want_effect: "Audit" + + # ========================================================================= + # Audit — pool count < 3 (even with 3 AZs) + # ========================================================================= + + - note: audit_pool_low_count + resource: + type: "Microsoft.ContainerService/managedclusters" + name: "myAKS" + location: "eastus" + properties: + agentPoolProfiles: + - name: "system" + count: 2 + availabilityZones: ["1", "2", "3"] + want_effect: "Audit" + + # ========================================================================= + # Audit — both: pool has 2 AZs and count = 1 + # ========================================================================= + + - note: audit_both_violations + resource: + type: "Microsoft.ContainerService/managedclusters" + name: "tinyAKS" + location: "eastus" + properties: + agentPoolProfiles: + - name: "system" + count: 1 + availabilityZones: ["1"] + want_effect: "Audit" + + # ========================================================================= + # No effect — wrong resource type + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "myVM" + location: "eastus" + properties: {} + want_undefined: true + + # ========================================================================= + # Audit — single pool, exactly 3 AZs but count = 2 + # ========================================================================= + + - note: audit_three_azs_low_count + resource: + type: "Microsoft.ContainerService/managedclusters" + name: "myAKS" + location: "eastus" + properties: + agentPoolProfiles: + - name: "system" + count: 2 + availabilityZones: ["1", "2", "3"] + want_effect: "Audit" + + # ========================================================================= + # Deny — explicit effect parameter override + # ========================================================================= + + - note: deny_with_explicit_effect + resource: + type: "Microsoft.ContainerService/managedclusters" + name: "myAKS" + location: "eastus" + properties: + agentPoolProfiles: + - name: "system" + count: 1 + availabilityZones: [] + parameters: + effect: "Deny" + want_effect: "Deny" + + # ========================================================================= + # No effect — three pools, all fully zone-redundant + # ========================================================================= + + - note: pass_three_pools_all_good + resource: + type: "Microsoft.ContainerService/managedclusters" + name: "bigAKS" + location: "eastus" + properties: + agentPoolProfiles: + - name: "system" + count: 3 + availabilityZones: ["1", "2", "3"] + - name: "user1" + count: 6 + availabilityZones: ["1", "2", "3"] + - name: "user2" + count: 9 + availabilityZones: ["1", "2", "3"] + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_approved_subnets_deny.yaml b/tests/azure_policy/cases/e2e_approved_subnets_deny.yaml new file mode 100644 index 0000000..c2ff1d3 --- /dev/null +++ b/tests/azure_policy/cases/e2e_approved_subnets_deny.yaml @@ -0,0 +1,192 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: VirtualEnclaves/ApprovedVirtualNetworkSubnets_Deny +# Real Azure Policy: "Network interfaces should be connected to an approved subnet +# of the approved virtual network" +# Source: regolator/policyDefinitions/VirtualEnclaves/ApprovedVirtualNetworkSubnets_Deny.json +# +# Features exercised: +# - Value count with named iterator: count { value: params, name: "subnetName" } +# - current('subnetName') to reference the iterator value +# - concat() to build dynamic resource IDs +# - Boolean parameter branching (allowAllSubnets true vs false) +# - not { field like concat(...) } double-negation on wildcard array +# - Two distinct allOf branches inside anyOf based on parameter value + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Network interfaces should be connected to an approved subnet of the approved virtual network", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "String", + "defaultValue": "Deny", + "allowedValues": ["Audit", "Deny", "Disabled"] + }, + "virtualNetworkId": { + "type": "String", + "metadata": { + "displayName": "Virtual network Id" + } + }, + "allowedSubnetList": { + "type": "Array", + "defaultValue": [] + }, + "allowAllSubnets": { + "type": "Boolean", + "defaultValue": true + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/networkInterfaces" + }, + { + "anyOf": [ + { + "allOf": [ + { + "value": "[parameters('allowAllSubnets')]", + "equals": true + }, + { + "not": { + "field": "Microsoft.Network/networkInterfaces/ipconfigurations[*].subnet.id", + "like": "[concat(parameters('virtualNetworkId'),'/*')]" + } + } + ] + }, + { + "allOf": [ + { + "value": "[parameters('allowAllSubnets')]", + "equals": false + }, + { + "count": { + "value": "[parameters('allowedSubnetList')]", + "name": "subnetName", + "where": { + "field": "Microsoft.Network/networkInterfaces/ipconfigurations[*].subnet.id", + "equals": "[concat(parameters('virtualNetworkId'),'/subnets/',current('subnetName'))]" + } + }, + "equals": 0 + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Branch 1: allowAllSubnets = true — any subnet in the VNet is OK + # ========================================================================= + + - note: pass_allow_all_subnets_correct_vnet + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-good-vnet" + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1/subnets/default" + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1" + allowAllSubnets: true + want_undefined: true + + - note: deny_allow_all_subnets_wrong_vnet + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-wrong-vnet" + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/default" + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1" + allowAllSubnets: true + want_effect: "Deny" + + # ========================================================================= + # Branch 2: allowAllSubnets = false — value count with subnet list + # ========================================================================= + + - note: pass_specific_subnet_allowed + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-allowed-subnet" + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1/subnets/frontend" + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1" + allowAllSubnets: false + allowedSubnetList: ["frontend", "backend"] + want_undefined: true + + - note: deny_subnet_not_in_allowed_list + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-bad-subnet" + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1/subnets/management" + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1" + allowAllSubnets: false + allowedSubnetList: ["frontend", "backend"] + want_effect: "Deny" + + - note: deny_empty_subnet_list + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-no-list" + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1/subnets/default" + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1" + allowAllSubnets: false + allowedSubnetList: [] + want_effect: "Deny" + + # ========================================================================= + # Wrong type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "not-a-nic" + properties: {} + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_approved_vnet_audit.yaml b/tests/azure_policy/cases/e2e_approved_vnet_audit.yaml new file mode 100644 index 0000000..4381a64 --- /dev/null +++ b/tests/azure_policy/cases/e2e_approved_vnet_audit.yaml @@ -0,0 +1,134 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Network/ApprovedVirtualNetwork_Audit +# Real Azure Policy: "Virtual machines should be connected to an approved virtual network" +# Source: regolator/policyDefinitions/Network/ApprovedVirtualNetwork_Audit.json +# +# Features exercised: +# - not { field like concat(...) } — double negation on wildcard array +# - concat() to build VNet prefix pattern +# - Wildcard array alias: ipconfigurations[*].subnet.id + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Virtual machines should be connected to an approved virtual network", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "Audit", + "allowedValues": ["Audit", "Deny", "Disabled"] + }, + "virtualNetworkId": { + "type": "string", + "metadata": { + "displayName": "Virtual network Id" + } + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/networkInterfaces" + }, + { + "not": { + "field": "Microsoft.Network/networkInterfaces/ipconfigurations[*].subnet.id", + "like": "[concat(parameters('virtualNetworkId'),'/*')]" + } + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # NIC in approved VNet → pass + # ========================================================================= + + - note: pass_nic_in_approved_vnet + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-good" + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/approved-vnet/subnets/default" + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/approved-vnet" + want_undefined: true + + - note: pass_multiple_ips_all_in_approved_vnet + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-multi-good" + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/approved-vnet/subnets/subnet1" + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/approved-vnet/subnets/subnet2" + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/approved-vnet" + want_undefined: true + + # ========================================================================= + # NIC in wrong VNet → audit + # ========================================================================= + + - note: audit_nic_in_wrong_vnet + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-bad" + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/other-vnet/subnets/default" + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/approved-vnet" + want_effect: "Audit" + + - note: audit_one_ip_in_wrong_vnet + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-mixed-vnet" + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/approved-vnet/subnets/subnet1" + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/other-vnet/subnets/subnet1" + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/approved-vnet" + want_effect: "Audit" + + # ========================================================================= + # Wrong type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "not-nic" + properties: {} + parameters: + virtualNetworkId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/approved-vnet" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_asc_internet_traffic_firewall.yaml b/tests/azure_policy/cases/e2e_asc_internet_traffic_firewall.yaml new file mode 100644 index 0000000..ca87f97 --- /dev/null +++ b/tests/azure_policy/cases/e2e_asc_internet_traffic_firewall.yaml @@ -0,0 +1,250 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Network/ASC_All_Internet_traffic_should_be_routed_via_Azure_Firewall +# Real Azure Policy: "[Preview]: All Internet traffic should be routed via +# your deployed Azure Firewall" +# Source: regolator/policyDefinitions/Network/ASC_All_Internet_traffic_should_be_routed_via_Azure_Firewall.json +# +# Features exercised: +# - Nested array count: subnets[*] containing ipConfigurations[*] +# - Count in existenceCondition (503-policy gap) +# - Double negation: not { anyOf [name excludes] } +# - subscription().subscriptionId, first(), split(), field('fullName') +# - empty() on doubly-nested array field +# - AuditIfNotExists with existence count check +# - like operator with wildcard pattern in existenceCondition + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "[Preview]: All Internet traffic should be routed via your deployed Azure Firewall", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "AuditIfNotExists", + "allowedValues": ["AuditIfNotExists", "Disabled"] + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/virtualNetworks" + }, + { + "count": { + "field": "Microsoft.Network/virtualNetworks/subnets[*]", + "where": { + "allOf": [ + { + "count": { + "field": "Microsoft.Network/virtualNetworks/subnets[*].ipConfigurations[*]", + "where": { + "value": "[empty(field('Microsoft.Network/virtualNetworks/subnets[*].ipConfigurations[*].id'))]", + "equals": false + } + }, + "greaterOrEquals": 2 + }, + { + "field": "Microsoft.Network/virtualNetworks/subnets[*].routeTable", + "exists": false + }, + { + "not": { + "anyOf": [ + { + "field": "Microsoft.Network/virtualNetworks/subnets[*].name", + "equals": "AzureBastionSubnet" + }, + { + "field": "Microsoft.Network/virtualNetworks/subnets[*].name", + "equals": "GatewaySubnet" + } + ] + } + } + ] + } + }, + "greater": 0 + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Network/azureFirewalls", + "existenceCondition": { + "count": { + "field": "Microsoft.Network/azureFirewalls/ipConfigurations[*]", + "where": { + "field": "Microsoft.Network/azureFirewalls/ipConfigurations[*].subnet.id", + "like": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/*/providers/Microsoft.Network/virtualNetworks/', first(split(field('fullName'), '/')), '/subnets/AzureFirewallSubnet')]" + } + }, + "equals": 1 + } + } + } + } + } + } + +cases: + # ========================================================================= + # AINE — VNet has a qualifying subnet (2+ ipConfigs, no routeTable, not + # excluded name) and no firewall found + # ========================================================================= + + - note: aine_qualifying_subnet_no_firewall + resource: + type: "Microsoft.Network/virtualNetworks" + name: "vnet-no-fw" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet-no-fw" + properties: + subnets: + - name: "WorkloadSubnet" + properties: + ipConfigurations: + - id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/networkInterfaces/nic1/ipConfigurations/ipconfig1" + - id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/networkInterfaces/nic2/ipConfigurations/ipconfig1" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Network/azureFirewalls" + response: null + want_effect: "AuditIfNotExists" + + # ========================================================================= + # Pass — VNet has subnet but only 1 ipConfiguration (threshold is >=2) + # ========================================================================= + + - note: pass_subnet_only_one_ip_config + resource: + type: "Microsoft.Network/virtualNetworks" + name: "vnet-single-ip" + properties: + subnets: + - name: "AppSubnet" + properties: + ipConfigurations: + - id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/networkInterfaces/nic1/ipConfigurations/ipconfig1" + want_undefined: true + + # ========================================================================= + # Pass — all qualifying subnets have routeTable set + # ========================================================================= + + - note: pass_all_subnets_have_route_table + resource: + type: "Microsoft.Network/virtualNetworks" + name: "vnet-routed" + properties: + subnets: + - name: "WorkloadSubnet" + properties: + routeTable: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/routeTables/rt1" + ipConfigurations: + - id: "/subscriptions/sub1/resourceGroups/rg1/providers/nic1/ipconfig1" + - id: "/subscriptions/sub1/resourceGroups/rg1/providers/nic2/ipconfig1" + - id: "/subscriptions/sub1/resourceGroups/rg1/providers/nic3/ipconfig1" + want_undefined: true + + # ========================================================================= + # Pass — only excluded subnets (AzureBastionSubnet, GatewaySubnet) + # ========================================================================= + + - note: pass_excluded_subnets_only + resource: + type: "Microsoft.Network/virtualNetworks" + name: "vnet-bastion-gw" + properties: + subnets: + - name: "AzureBastionSubnet" + properties: + ipConfigurations: + - id: "/subscriptions/sub1/rg1/nic1/ip1" + - id: "/subscriptions/sub1/rg1/nic2/ip1" + - id: "/subscriptions/sub1/rg1/nic3/ip1" + - name: "GatewaySubnet" + properties: + ipConfigurations: + - id: "/subscriptions/sub1/rg1/nic4/ip1" + - id: "/subscriptions/sub1/rg1/nic5/ip1" + want_undefined: true + + # ========================================================================= + # AINE — qualifying subnet exists + firewall found but no matching + # ipConfiguration for this VNet + # ========================================================================= + + - note: aine_firewall_no_matching_subnet + resource: + type: "Microsoft.Network/virtualNetworks" + name: "vnet-no-match" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet-no-match" + properties: + subnets: + - name: "AppSubnet" + properties: + ipConfigurations: + - id: "/subscriptions/sub1/rg1/nic1/ip1" + - id: "/subscriptions/sub1/rg1/nic2/ip1" + host_await: + - response: + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/other-vnet/subnets/AzureFirewallSubnet" + want_effect: "AuditIfNotExists" + + # ========================================================================= + # Pass — qualifying subnet exists + firewall with matching VNet + # ========================================================================= + + - note: pass_firewall_matches_vnet + context: + resourceGroup: + name: "rg1" + location: "eastus" + subscription: + subscriptionId: "sub1" + resource: + type: "Microsoft.Network/virtualNetworks" + name: "vnet-protected" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet-protected" + properties: + subnets: + - name: "WorkloadSubnet" + properties: + ipConfigurations: + - id: "/subscriptions/sub1/rg1/nic1/ip1" + - id: "/subscriptions/sub1/rg1/nic2/ip1" + host_await: + - response: + properties: + ipConfigurations: + - properties: + subnet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet-protected/subnets/AzureFirewallSubnet" + want_undefined: true + + # ========================================================================= + # Wrong type → skip + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Network/networkSecurityGroups" + name: "nsg1" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_automanage_deployv2.yaml b/tests/azure_policy/cases/e2e_automanage_deployv2.yaml new file mode 100644 index 0000000..a52677b --- /dev/null +++ b/tests/azure_policy/cases/e2e_automanage_deployv2.yaml @@ -0,0 +1,785 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Automanage/Deployv2 +# Real Azure Policy: "Configure virtual machines to be onboarded to Azure Automanage" +# Source: regolator/policyDefinitions/Automanage/Deployv2.json +# +# Features exercised: +# - 106 condition nodes (broad, depth 4) — wide register pressure test +# - Dynamic tag field: [concat('tags[', parameters('inclusionTagName'), ']')] +# - DeployIfNotExists with conditional deployment (VM vs Arc) +# - Large hardcoded location list +# - Extensive image publisher/offer/SKU matching + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "policyType": "BuiltIn", + "mode": "Indexed", + "displayName": "Configure virtual machines to be onboarded to Azure Automanage", + "description": "Azure Automanage enrolls, configures, and monitors virtual machines with best practice as defined in the Microsoft Cloud Adoption Framework for Azure. Use this policy to apply Automanage to your selected scope.", + "version": "2.4.0", + "parameters": { + "configurationProfileAssignment": { + "type": "String", + "metadata": { + "displayName": "Configuration profile", + "description": "The management services provided are based on whether the machine is intended to be used in a dev/test environment or production." + }, + "allowedValues": [ + "/providers/Microsoft.Automanage/bestPractices/azurebestpracticesproduction", + "/providers/Microsoft.Automanage/bestPractices/azurebestpracticesdevtest" + ], + "defaultValue": "/providers/Microsoft.Automanage/bestPractices/azurebestpracticesproduction" + }, + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of this policy" + }, + "allowedValues": [ + "AuditIfNotExists", + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + }, + "inclusionTagName": { + "type": "String", + "metadata": { + "displayName": "Inclusion Tag Name", + "description": "Name of the tag to use for including VMs in the scope of this policy. This should be used along with the Inclusion Tag Value parameter." + }, + "defaultValue": "" + }, + "inclusionTagValues": { + "type": "Array", + "metadata": { + "displayName": "Inclusion Tag Values", + "description": "Value of the tag to use for including VMs in the scope of this policy (in case of multiple values, use a comma-separated list). This should be used along with the Inclusion Tag Name parameter." + }, + "defaultValue": [] + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "anyOf": [ + { + "field": "[concat('tags[', parameters('inclusionTagName'), ']')]", + "in": "[parameters('inclusionTagValues')]" + }, + { + "value": "[empty(parameters('inclusionTagValues'))]", + "equals": "true" + }, + { + "value": "[empty(parameters('inclusionTagName'))]", + "equals": "true" + } + ] + }, + { + "field": "location", + "in": [ + "eastus", + "eastus2", + "westus", + "westus2", + "centralus", + "southcentralus", + "westcentralus", + "northeurope", + "westeurope", + "canadacentral", + "japaneast", + "uksouth", + "australiaeast", + "australiasoutheast", + "southeastasia" + ] + }, + { + "field": "type", + "in": [ + "Microsoft.Compute/virtualMachines", + "Microsoft.HybridCompute/machines" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "in": [ + "esri", + "incredibuild", + "MicrosoftDynamicsAX", + "MicrosoftSharepoint", + "MicrosoftVisualStudio", + "MicrosoftWindowsDesktop", + "MicrosoftWindowsServerHPCPack" + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftWindowsServer" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "2008*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftSQLServer" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "SQL2008*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-dsvm" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "dsvm-windows" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-ads" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "standard-data-science-vm", + "windows-data-science-vm" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "batch" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "rendering-windows2016" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "center-for-internet-security-inc" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "cis-windows-server-201*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "pivotal" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "bosh-windows-server*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "cloud-infrastructure-services" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "ad*" + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "like": "Windows*" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.imageReference.id", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.imageReference.sku", + "exists": "false" + } + ] + }, + { + "field": "Microsoft.Compute/imagePublisher", + "in": [ + "microsoft-aks", + "qubole-inc", + "datastax", + "couchbase", + "scalegrid", + "checkpoint", + "paloaltonetworks", + "debian" + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "OpenLogic" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "CentOS*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "OpenLogic" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "CentOS*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "8*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "RedHat" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "RHEL", + "RHEL-HA", + "RHEL-SAP", + "RHEL-SAP-APPS", + "RHEL-SAP-HA", + "RHEL-SAP-HANA", + "rhel-raw" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "RedHat" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "RHEL", + "RHEL-HA", + "RHEL-SAP", + "RHEL-SAP-APPS", + "RHEL-SAP-HA", + "RHEL-SAP-HANA", + "rhel-raw" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "8*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "RedHat" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "osa", + "rhel-byos" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "center-for-internet-security-inc" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "cis-centos-7-l1", + "cis-centos-7-v2-1-1-l1", + "cis-nginx-centos-7-v1-1-0-l1", + "cis-oracle-linux-7-v2-0-0-l1", + "cis-postgresql-11-centos-linux-7-level-1", + "cis-rhel-7-l2", + "cis-rhel-7-v2-2-0-l1", + "cis-suse-linux-12-v2-0-0-l1", + "cis-suse15-l1", + "cis-ubuntu-linux-1604-v1-0-0-l1", + "cis-ubuntu-linux-1804-l1", + "cis-ubuntu-linux-2004-l1" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "credativ" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Suse" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "SLES*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "11*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Canonical" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "UbuntuServer" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "12*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-dsvm" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "linux-data-science-vm-ubuntu", + "azureml" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "cloudera" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "cloudera-centos-os" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "cloudera" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "cloudera-altus-centos-os" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-ads" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "linux*" + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "like": "Linux*" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.imageReference.id", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.imageReference.sku", + "exists": "false" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.HybridCompute/machines/osSku", + "like": "CentOS*" + }, + { + "field": "Microsoft.HybridCompute/machines/osSku", + "notLike": "Linux 6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.HybridCompute/machines/osSku", + "like": "Windows Server*" + }, + { + "field": "Microsoft.HybridCompute/machines/osSku", + "notLike": "2008*" + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.HybridCompute/machines/osSku", + "like": "Red Hat Enterprise Linux 8.*" + }, + { + "field": "Microsoft.HybridCompute/machines/osSku", + "like": "Red Hat Enterprise Linux 7.*" + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.HybridCompute/machines/osSku", + "like": "Ubuntu 18.04*" + }, + { + "field": "Microsoft.HybridCompute/machines/osSku", + "like": "Ubuntu 16.04*" + }, + { + "field": "Microsoft.HybridCompute/machines/osSku", + "like": "Ubuntu 20.04*" + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.HybridCompute/machines/osSku", + "in": [ + "SUSE Linux Enterprise Server 12 SP3", + "SUSE Linux Enterprise Server 12 SP4", + "SUSE Linux Enterprise Server 12 SP5" + ] + }, + { + "field": "Microsoft.HybridCompute/machines/osSku", + "like": "SUSE Linux Enterprise Server 15*" + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + ], + "type": "Microsoft.Automanage/configurationProfileAssignments", + "name": "default", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Automanage/configurationProfileAssignments/configurationProfile", + "equals": "[parameters('configurationProfileAssignment')]" + } + ] + }, + "deployment": { + "properties": { + "mode": "incremental", + "parameters": { + "machineName": { + "value": "[field('Name')]" + }, + "resourceType": { + "value": "[field('Type')]" + }, + "configurationProfileAssignment": { + "value": "[parameters('configurationProfileAssignment')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "machineName": { + "type": "String" + }, + "resourceType": { + "type": "String" + }, + "configurationProfileAssignment": { + "type": "string" + } + }, + "resources": [ + { + "condition": "[equals(toLower(parameters('resourceType')), 'microsoft.compute/virtualmachines')]", + "type": "Microsoft.Compute/virtualMachines/providers/configurationProfileAssignments", + "apiVersion": "2022-05-04", + "name": "[concat(parameters('machineName'), '/Microsoft.Automanage/', 'default')]", + "properties": { + "configurationProfile": "[parameters('configurationProfileAssignment')]" + } + }, + { + "condition": "[equals(toLower(parameters('resourceType')), 'microsoft.hybridcompute/machines')]", + "type": "Microsoft.HybridCompute/machines/providers/configurationProfileAssignments", + "apiVersion": "2022-05-04", + "name": "[concat(parameters('machineName'), '/Microsoft.Automanage/', 'default')]", + "properties": { + "configurationProfile": "[parameters('configurationProfileAssignment')]" + } + } + ] + } + } + } + } + } + } + } + } + +cases: + # ========================================================================= + # DINE — Windows VM in supported region, matching publisher + # ========================================================================= + + - note: dine_windows_vm_canonical + resource: + type: "Microsoft.Compute/virtualMachines" + name: "win-vm-01" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/win-vm-01" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + osProfile: + windowsConfiguration: {} + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Automanage/configurationProfileAssignments" + name: "default" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass — VM in unsupported region + # ========================================================================= + + - note: pass_unsupported_region + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-brazil" + location: "brazilsoutheast" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + osProfile: + windowsConfiguration: {} + want_undefined: true + + # ========================================================================= + # DINE — Linux VM with Canonical publisher + # ========================================================================= + + - note: dine_linux_vm_canonical + resource: + type: "Microsoft.Compute/virtualMachines" + name: "linux-vm-01" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/linux-vm-01" + location: "westus2" + properties: + storageProfile: + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + osDisk: + osType: "Linux" + osProfile: + linuxConfiguration: {} + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Automanage/configurationProfileAssignments" + name: "default" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass — VM with tag filter that doesn't match + # ========================================================================= + + - note: pass_tag_filter_no_match + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-tagged" + location: "eastus" + tags: + env: "dev" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + osProfile: + windowsConfiguration: {} + parameters: + inclusionTagName: "env" + inclusionTagValues: + - "prod" + want_undefined: true + + # ========================================================================= + # Skip — Wrong resource type + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "storageacct1" + location: "eastus" + properties: {} + want_undefined: true + + # ========================================================================= + # Pass — VM with unsupported publisher (not in any publisher allowlist) + # ========================================================================= + + - note: pass_unsupported_publisher + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-unknown-pub" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-unknown-pub" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "unknown-vendor" + offer: "some-offer" + sku: "some-sku" + osDisk: + osType: "Linux" + osProfile: + linuxConfiguration: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_azupdate_crp_autoassess_modify.yaml b/tests/azure_policy/cases/e2e_azupdate_crp_autoassess_modify.yaml new file mode 100644 index 0000000..92b5f1b --- /dev/null +++ b/tests/azure_policy/cases/e2e_azupdate_crp_autoassess_modify.yaml @@ -0,0 +1,1243 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Azure Update Manager/AzUpdateMgmtCenter_CRP_AutoAssessmentMode_Modify +# Real Azure Policy: "Configure periodic checking for missing system updates on azure virtual machines" +# Source: regolator/policyDefinitions/Azure Update Manager/AzUpdateMgmtCenter_CRP_AutoAssessmentMode_Modify.json +# +# Features exercised: +# - Conditional modify operations (2 operations, each with condition) +# - Dynamic field name: field computed via if() expression +# - 214 condition nodes, 1031 lines — register pressure test +# - requestContext().apiVersion guard +# - Tag matching via intersection/length/empty +# - Hardcoded "modify" effect (not parameterized) +# - conflictEffect: "audit" + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Configure periodic checking for missing system updates on azure virtual machines", + "policyType": "BuiltIn", + "mode": "Indexed", + "description": "Configure auto-assessment (every 24 hours) for OS updates on native Azure virtual machines. You can control the scope of assignment according to machine subscription, resource group, location or tag. Learn more about this for Windows: https://aka.ms/computevm-windowspatchassessmentmode, for Linux: https://aka.ms/computevm-linuxpatchassessmentmode.", + "version": "4.8.0", + "parameters": { + "assessmentMode": { + "type": "String", + "metadata": { + "displayName": "Assessment mode", + "description": "Assessment mode for the machines." + }, + "allowedValues": [ + "ImageDefault", + "AutomaticByPlatform" + ], + "defaultValue": "AutomaticByPlatform" + }, + "osType": { + "type": "String", + "metadata": { + "displayName": "OS type", + "description": "OS type for the machines." + }, + "allowedValues": [ + "Windows", + "Linux" + ], + "defaultValue": "Windows" + }, + "locations": { + "type": "Array", + "metadata": { + "displayName": "Machines locations", + "description": "The list of locations from which machines need to be targeted.", + "strongType": "location" + }, + "defaultValue": [] + }, + "tagValues": { + "type": "Object", + "metadata": { + "displayName": "Tags on machines", + "description": "The list of tags that need to matched for getting target machines." + }, + "defaultValue": {} + }, + "tagOperator": { + "type": "String", + "metadata": { + "displayName": "Tag operator", + "description": "Matching condition for resource tags" + }, + "allowedValues": [ + "All", + "Any" + ], + "defaultValue": "Any" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "anyOf": [ + { + "value": "[empty(parameters('locations'))]", + "equals": true + }, + { + "field": "location", + "in": "[parameters('locations')]" + } + ] + }, + { + "field": "[if(equals(tolower(parameters('osType')), 'windows'), 'Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration.patchSettings.assessmentMode', 'Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration.patchSettings.assessmentMode')]", + "notEquals": "[parameters('assessmentMode')]" + }, + { + "anyOf": [ + { + "value": "[empty(parameters('tagValues'))]", + "equals": true + }, + { + "allOf": [ + { + "value": "[parameters('tagOperator')]", + "equals": "Any" + }, + { + "value": "[greaterOrEquals(if(empty(field('tags')), 0, length(intersection(parameters('tagValues'), field('tags')))), 1)]", + "equals": true + } + ] + }, + { + "allOf": [ + { + "value": "[parameters('tagOperator')]", + "equals": "All" + }, + { + "value": "[equals(if(empty(field('tags')), 0, length(intersection(parameters('tagValues'), field('tags')))), length(parameters('tagValues')))]", + "equals": true + } + ] + } + ] + }, + { + "anyOf": [ + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "like": "Linux*" + } + ] + }, + { + "value": "[parameters('osType')]", + "equals": "Linux" + }, + { + "anyOf": [ + { + "allOf": [ + { + "anyOf": [ + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/galleries" + }, + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/images" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "true" + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageId", + "contains": "Microsoft.Compute/galleries" + }, + { + "field": "Microsoft.Compute/imageId", + "contains": "Microsoft.Compute/images" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.createOption", + "equals": "Attach" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "false" + }, + { + "value": "[requestContext().apiVersion]", + "greaterOrEquals": "2023-07-01" + } + ] + }, + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Canonical" + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftcblmariner" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "cbl-mariner" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "cbl-mariner-1", + "1-gen2", + "cbl-mariner-2", + "cbl-mariner-2-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "oracle" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "oracle-linux" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "8", + "8-ci", + "81", + "81-ci", + "81-gen2" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol9*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol9-lvm*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "oracle-database" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "oracle_db_21" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "oracle-database-*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "18.*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "oracle-database-19-3" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "oracle-database-19-0904" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-aks" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "aks" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "aks-engine-ubuntu-1804-202112" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-dsvm" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "aml-workstation" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "ubuntu-20", + "ubuntu-20-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Redhat" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "RHEL" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "9*" + } + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "notEquals": "74-gen2" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "RHEL-RAW" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "9*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "rhel-sap-ha" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "equals": "90sapha-gen2" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + } + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "notEquals": "7.5" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "rhel-sap-apps" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "equals": "90sapha-gen2" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "rhel-sap-*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "9_0" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "rhel-ha" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notIn": [ + "7.4", + "7.5", + "7.6", + "8.1", + "81_gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "rhel-sap" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notIn": [ + "7.4", + "7.5", + "7.7" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "OpenLogic" + }, + { + "allOf": [ + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "Centos" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "8*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "centos-lvm" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "7-lvm", + "8-lvm", + "7-lvm-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "centos-ci" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "7-ci" + } + ] + } + ] + }, + { + "field": "Microsoft.Compute/imageOffer", + "notEquals": "centos-hpc" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "SUSE" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-12-sp5", + "sles-15-sp2", + "sle-hpc-15-sp4", + "sles-15-sp1-sapcal", + "sles-15-sp3-sapcal", + "sles-15-sp4-basic", + "sles-15-sp4" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "gen1", + "gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles", + "sles-standard" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "12-sp4-gen2" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-15-sp2-basic", + "sles-15-sp2-hpc" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "gen2" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "sles-15-sp4-sapcal" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "gen1" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-byos", + "sles-sap" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "12-sp4", + "12-sp4-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "sles-sap-byos" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "12-sp4", + "12-sp4-gen2", + "gen2-12-sp4" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "sles-sapcal" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "12-sp3" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "gen*" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "opensuse-leap-15-*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-12-sp5-*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-sap-12-sp5*" + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-sap-15-*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "sles-sap-15-*-byos" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftSQLServer" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "sql2019-sles*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notIn": [ + "sql2019-rhel7", + "sql2017-rhel7" + ] + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "like": "Windows*" + } + ] + }, + { + "value": "[parameters('osType')]", + "equals": "Windows" + }, + { + "anyOf": [ + { + "allOf": [ + { + "anyOf": [ + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/galleries" + }, + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/images" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "true" + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageId", + "contains": "Microsoft.Compute/galleries" + }, + { + "field": "Microsoft.Compute/imageId", + "contains": "Microsoft.Compute/images" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.createOption", + "equals": "Attach" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "false" + }, + { + "value": "[requestContext().apiVersion]", + "greaterOrEquals": "2023-07-01" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftWindowsServer" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "windowsserver", + "windows-cvm", + "windowsserverdotnet", + "windowsserver-gen2preview", + "windowsserversemiannual", + "windowsserverupgrade" + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "microsoftserveroperatingsystems-previews" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "windows-server-vnext-azure-edition-core" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "windowsserverhotpatch-previews" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "windows-server-2022-azure-edition-hotpatch" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftSQLServer" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftdynamicsax" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "dynamics" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftazuresiterecovery" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "process-server" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "windows-2012-r2-datacenter" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftbiztalkserver" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "biztalk-server" + } + ] + }, + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftpowerbi" + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftsharepoint" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "microsoftsharepointserver" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftwindowsserverhpcpack" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "windowsserverhpcpack" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftvisualstudio" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "visualstudio*" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2012r2" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2016" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2019" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2022" + } + ] + } + ] + } + ] + }, + { + "field": "Microsoft.Compute/imagePublisher", + "notEquals": "microsoft-ads" + } + ] + } + ] + } + ] + }, + "then": { + "effect": "modify", + "details": { + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + ], + "conflictEffect": "audit", + "operations": [ + { + "condition": "[equals(tolower(parameters('osType')), 'windows')]", + "operation": "addOrReplace", + "field": "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration.patchSettings.assessmentMode", + "value": "[parameters('assessmentMode')]" + }, + { + "condition": "[equals(tolower(parameters('osType')), 'linux')]", + "operation": "addOrReplace", + "field": "Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration.patchSettings.assessmentMode", + "value": "[parameters('assessmentMode')]" + } + ] + } + } + } + } + } + +cases: + # ========================================================================= + # Windows VM with ImageDefault assessment mode -> modify + # ========================================================================= + + - note: modify_windows_vm_needs_assessment + resource: + type: "Microsoft.Compute/virtualMachines" + name: "win-vm-1" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + osProfile: + computerName: "win-vm-1" + windowsConfiguration: + patchSettings: + assessmentMode: "ImageDefault" + parameters: + assessmentMode: "AutomaticByPlatform" + osType: "Windows" + locations: [] + tagValues: {} + tagOperator: "Any" + request_context: + apiVersion: "2024-01-01" + want_effect: "modify" + + # ========================================================================= + # Linux VM with ImageDefault assessment mode -> modify + # ========================================================================= + + - note: modify_linux_vm_needs_assessment + resource: + type: "Microsoft.Compute/virtualMachines" + name: "linux-vm-1" + properties: + storageProfile: + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + osDisk: + osType: "Linux" + osProfile: + computerName: "linux-vm-1" + linuxConfiguration: + patchSettings: + assessmentMode: "ImageDefault" + parameters: + assessmentMode: "AutomaticByPlatform" + osType: "Linux" + locations: [] + tagValues: {} + tagOperator: "Any" + request_context: + apiVersion: "2024-01-01" + want_effect: "modify" + + # ========================================================================= + # Already has AutomaticByPlatform -> compliant (no effect) + # ========================================================================= + + - note: pass_already_autoplatform + resource: + type: "Microsoft.Compute/virtualMachines" + name: "win-vm-compliant" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + osProfile: + computerName: "win-vm-compliant" + windowsConfiguration: + patchSettings: + assessmentMode: "AutomaticByPlatform" + parameters: + assessmentMode: "AutomaticByPlatform" + osType: "Windows" + locations: [] + tagValues: {} + tagOperator: "Any" + request_context: + apiVersion: "2024-01-01" + want_undefined: true + + # ========================================================================= + # Old API version -> skip (requestContext().apiVersion guard fails) + # ========================================================================= + + - note: pass_old_api_version + resource: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + osProfile: + computerName: "win-vm-old-api" + windowsConfiguration: + patchSettings: + assessmentMode: "ImageDefault" + parameters: + assessmentMode: "AutomaticByPlatform" + osType: "Windows" + locations: [] + tagValues: {} + tagOperator: "Any" + request_context: + apiVersion: "2022-01-01" + want_undefined: true + + # ========================================================================= + # Wrong resource type -> skip + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "mystorage" + properties: {} + parameters: + assessmentMode: "AutomaticByPlatform" + osType: "Windows" + locations: [] + tagValues: {} + tagOperator: "Any" + want_undefined: true + + # ========================================================================= + # Pass — VM with excluded publisher "microsoft-ads" and gallery imageId + # → publisher exclusion gate (notEquals "microsoft-ads") fails + # ========================================================================= + + - note: pass_excluded_publisher + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-ads-win" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "microsoft-ads" + offer: "windows-data-science-vm" + sku: "windows2016" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/galleries/myGallery/images/myImage" + osDisk: + osType: "Windows" + osProfile: + computerName: "vm-ads-win" + windowsConfiguration: + patchSettings: + assessmentMode: "ImageDefault" + parameters: + assessmentMode: "AutomaticByPlatform" + osType: "Windows" + locations: [] + tagValues: {} + tagOperator: "Any" + request_context: + apiVersion: "2024-01-01" + want_undefined: true + + # ========================================================================= + # Pass — VM with unknown publisher, no gallery imageId + # → no publisher/image gate matches → if condition fails + # ========================================================================= + + - note: pass_unknown_publisher + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-unknown" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "unknown-publisher" + offer: "unknown-offer" + sku: "unknown-sku" + osDisk: + osType: "Linux" + osProfile: + computerName: "vm-unknown" + linuxConfiguration: + patchSettings: + assessmentMode: "ImageDefault" + parameters: + assessmentMode: "AutomaticByPlatform" + osType: "Linux" + locations: [] + tagValues: {} + tagOperator: "Any" + request_context: + apiVersion: "2024-01-01" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_azupdate_customer_managed_dine.yaml b/tests/azure_policy/cases/e2e_azupdate_customer_managed_dine.yaml new file mode 100644 index 0000000..a1f8abc --- /dev/null +++ b/tests/azure_policy/cases/e2e_azupdate_customer_managed_dine.yaml @@ -0,0 +1,1605 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Azure Update Manager/AzUpdateMgmtCenter_CustomerManagedSchedules_DINE +# Real Azure Policy: "[Preview]: Set prerequisite for Scheduling recurring updates on Azure virtual machines." +# Source: regolator/policyDefinitions/Azure Update Manager/AzUpdateMgmtCenter_CustomerManagedSchedules_DINE.json +# +# Features exercised: +# - Self-referential DeployIfNotExists (details.type matches resource type) +# - Value count with current() for tag matching +# - requestContext().apiVersion guard +# - resourceGroup().name, empty(), field('tags') +# - Deep allOf/anyOf nesting for OS image matching +# - Publisher exclusion (microsoft-ads notEquals gate) +# - existenceCondition checking patchMode + bypassPlatformSafetyChecksOnUserSchedule +# - DeployIfNotExists with deployment template + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "[Preview]: Set prerequisite for Scheduling recurring updates on Azure virtual machines.", + "policyType": "BuiltIn", + "mode": "Indexed", + "description": "This policy will set the prerequisite needed to schedule recurring updates on Azure Update Manager by configuring patch orchestration to 'Customer Managed Schedules'. This change will automatically set the patch mode to 'AutomaticByPlatform' and enables 'BypassPlatformSafetyChecksOnUserSchedule' to 'True' on Azure VMs. The prerequisite is not applicable for Arc-enabled servers. Learn more - https://learn.microsoft.com/en-us/azure/update-manager/dynamic-scope-overview?tabs=avms#prerequisites", + "metadata": { + "version": "1.1.0-preview", + "category": "Azure Update Manager", + "preview": true + }, + "version": "1.1.0-preview", + "parameters": { + "resourceGroups": { + "type": "Array", + "metadata": { + "displayName": "Resource groups", + "description": "The list of resource groups from which machines need to be targeted. Example: [\"rg1\", \"rg2\"]. Adding no resource group here would target all resource groups in the scope." + }, + "defaultValue": [] + }, + "operatingSystemTypes": { + "type": "Array", + "metadata": { + "displayName": "Operating System types", + "description": "The list of Operating System types from which machines need to be targeted. Adding no operating system types here would target all operating system types in the scope." + }, + "allowedValues": [ + "Windows", + "Linux" + ], + "defaultValue": [ + "Windows", + "Linux" + ] + }, + "locations": { + "type": "Array", + "metadata": { + "displayName": "Machines locations", + "description": "The list of locations from which machines need to be targeted. Adding no locations here would target all locations in the scope.", + "strongType": "location" + }, + "defaultValue": [] + }, + "tagValues": { + "type": "Array", + "metadata": { + "displayName": "Tags on machines", + "description": "The list of tags that need to matched for getting target machines (case sensitive). Example: [ {\"key\": \"tagKey1\", \"value\": \"value1\"}, {\"key\": \"tagKey2\", \"value\": \"value2\"}]." + }, + "defaultValue": [] + }, + "tagOperator": { + "type": "String", + "metadata": { + "displayName": "Tags operator", + "description": "Matching condition for resource tags" + }, + "allowedValues": [ + "All", + "Any" + ], + "defaultValue": "Any" + }, + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "anyOf": [ + { + "value": "[empty(parameters('operatingSystemTypes'))]", + "equals": true + }, + { + "allOf": [ + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "in": "[parameters('operatingSystemTypes')]" + }, + { + "anyOf": [ + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/galleries" + }, + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/images" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "true" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "in": "[parameters('operatingSystemTypes')]" + }, + { + "anyOf": [ + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/galleries" + }, + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/images" + }, + { + "value": "[field('Microsoft.Compute/virtualMachines/storageProfile.osDisk.createOption')]", + "equals": "Attach" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "false" + }, + { + "value": "[requestContext().apiVersion]", + "greaterOrEquals": "2023-07-01" + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "in": "[parameters('operatingSystemTypes')]" + }, + { + "allOf": [ + { + "value": "Linux", + "in": "[parameters('operatingSystemTypes')]" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-standard", + "sles-sapcal", + "sles-sap-byos", + "sles-sap", + "sles-byos", + "sles-15-sp4-sapcal", + "sles-15-sp4-basic", + "sles-15-sp4", + "sles-15-sp3-sapcal", + "sles-15-sp2-hpc", + "sles-15-sp2-basic", + "sles-15-sp1-sapcal", + "sles", + "sle-hpc-15-sp4", + "sles-12-sp5", + "sles-15-sp2", + "centos-hpc", + "centos-ci", + "centos-lvm", + "Centos", + "rhel-sap-ha", + "rhel-sap-apps", + "RHEL-RAW", + "RHEL", + "aml-workstation", + "aks", + "oracle-database-19-3", + "oracle-database", + "oracle-linux", + "cbl-mariner", + "0001-com-ubuntu-server-jammy", + "0001-com-ubuntu-pro-jammy", + "0001-com-ubuntu-pro-focal", + "0001-com-ubuntu-server-focal", + "0001-com-ubuntu-pro-bionic", + "UbuntuServer" + ] + } + ] + }, + { + "allOf": [ + { + "value": "Windows", + "in": "[parameters('operatingSystemTypes')]" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "WindowsServer", + "microsoftserveroperatingsystems-previews", + "windowsserverhotpatch-previews", + "sql2016sp1-ws2016", + "sql2016sp2-ws201", + "sql2017-ws2016", + "sql2019-ws2019", + "dynamics", + "process-server" + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "anyOf": [ + { + "value": "[empty(parameters('locations'))]", + "equals": true + }, + { + "field": "location", + "in": "[parameters('locations')]" + } + ] + }, + { + "anyOf": [ + { + "value": "[empty(parameters('resourceGroups'))]", + "equals": true + }, + { + "value": "[resourceGroup().name]", + "in": "[parameters('resourceGroups')]" + } + ] + }, + { + "anyOf": [ + { + "value": "[empty(parameters('tagValues'))]", + "equals": true + }, + { + "allOf": [ + { + "value": "[empty(field('tags'))]", + "equals": false + }, + { + "value": "[parameters('tagOperator')]", + "equals": "Any" + }, + { + "count": { + "value": "[parameters('tagValues')]", + "name": "tagKvp", + "where": { + "value": "[length(intersection(createObject(current('tagKvp').key, current('tagKvp').value), field('tags')))]", + "greater": 0 + } + }, + "greater": 0 + } + ] + }, + { + "allOf": [ + { + "value": "[empty(field('tags'))]", + "equals": false + }, + { + "value": "[parameters('tagOperator')]", + "equals": "All" + }, + { + "count": { + "value": "[parameters('tagValues')]", + "name": "tagKvp", + "where": { + "value": "[length(intersection(createObject(current('tagKvp').key, current('tagKvp').value), field('tags')))]", + "greater": 0 + } + }, + "equals": "[length(parameters('tagValues'))]" + } + ] + } + ] + }, + { + "anyOf": [ + { + "allOf": [ + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "exists": "true" + }, + { + "anyOf": [ + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/galleries" + }, + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/images" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "true" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "exists": "true" + }, + { + "anyOf": [ + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/galleries" + }, + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/images" + }, + { + "value": "[field('Microsoft.Compute/virtualMachines/storageProfile.osDisk.createOption')]", + "equals": "Attach" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "false" + }, + { + "value": "[requestContext().apiVersion]", + "greaterOrEquals": "2023-07-01" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Canonical" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftcblmariner" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "cbl-mariner" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "cbl-mariner-1", + "1-gen2", + "cbl-mariner-2", + "cbl-mariner-2-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "oracle" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "oracle-linux" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "8", + "8-ci", + "81", + "81-ci", + "81-gen2" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol9*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol9-lvm*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "oracle-database" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "oracle_db_21" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "oracle-database-*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "18.*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "oracle-database-19-3" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "oracle-database-19-0904" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-aks" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "aks" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "aks-engine-ubuntu-1804-202112" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-dsvm" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "aml-workstation" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "ubuntu-20", + "ubuntu-20-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Redhat" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "RHEL" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "9*" + } + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "notEquals": "74-gen2" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "RHEL-RAW" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "9*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "rhel-sap-ha" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "equals": "90sapha-gen2" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + } + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "notEquals": "7.5" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "rhel-sap-apps" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "equals": "90sapha-gen2" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "rhel-sap-*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "9_0" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "rhel-ha" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notIn": [ + "7.4", + "7.5", + "7.6", + "8.1", + "81_gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "rhel-sap" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notIn": [ + "7.4", + "7.5", + "7.7" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "OpenLogic" + }, + { + "allOf": [ + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "Centos" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "8*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "centos-lvm" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "7-lvm", + "8-lvm", + "7-lvm-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "centos-ci" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "7-ci" + } + ] + } + ] + }, + { + "field": "Microsoft.Compute/imageOffer", + "notEquals": "centos-hpc" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "SUSE" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-12-sp5", + "sles-15-sp2", + "sle-hpc-15-sp4", + "sles-15-sp1-sapcal", + "sles-15-sp3-sapcal", + "sles-15-sp4-basic", + "sles-15-sp4" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "gen1", + "gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles", + "sles-standard" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "12-sp4-gen2" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-15-sp2-basic", + "sles-15-sp2-hpc" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "gen2" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "sles-15-sp4-sapcal" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "gen1" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-byos", + "sles-sap" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "12-sp4", + "12-sp4-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "sles-sap-byos" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "12-sp4", + "12-sp4-gen2", + "gen2-12-sp4" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "sles-sapcal" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "12-sp3" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "gen*" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "opensuse-leap-15-*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-12-sp5-*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-sap-12-sp5*" + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-sap-15-*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "sles-sap-15-*-byos" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftWindowsServer" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "windowsserver", + "windows-cvm", + "windowsserverdotnet", + "windowsserver-gen2preview", + "windowsserversemiannual", + "windowsserverupgrade" + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "microsoftserveroperatingsystems-previews" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "windows-server-vnext-azure-edition-core" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "windowsserverhotpatch-previews" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "windows-server-2022-azure-edition-hotpatch" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftSQLServer" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "sql2019-sles*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notIn": [ + "sql2019-rhel7", + "sql2017-rhel7" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftdynamicsax" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "dynamics" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftazuresiterecovery" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "process-server" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "windows-2012-r2-datacenter" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftbiztalkserver" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "biztalk-server" + } + ] + }, + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftpowerbi" + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftsharepoint" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "microsoftsharepointserver" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftwindowsserverhpcpack" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "windowsserverhpcpack" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftvisualstudio" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "visualstudio*" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2012r2" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2016" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2019" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2022" + } + ] + } + ] + } + ] + }, + { + "field": "Microsoft.Compute/imagePublisher", + "notEquals": "microsoft-ads" + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + ], + "type": "Microsoft.Compute/virtualMachines", + "name": "[field('name')]", + "evaluationDelay": "AfterProvisioningSuccess", + "existenceCondition": { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration", + "exists": true + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration.patchSettings.automaticByPlatformSettings.bypassPlatformSafetyChecksOnUserSchedule", + "equals": true + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration.patchSettings.patchMode", + "equals": "AutomaticByPlatform" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration", + "exists": true + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration.patchSettings.automaticByPlatformSettings.bypassPlatformSafetyChecksOnUserSchedule", + "equals": true + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration.patchSettings.patchMode", + "equals": "AutomaticByPlatform" + } + ] + } + ] + }, + "deployment": { + "properties": { + "mode": "incremental", + "parameters": { + "machineResourceId": { + "value": "[field('id')]" + }, + "osType": { + "value": "[field('Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType')]" + }, + "imagePublisher": { + "value": "[tolower(field('Microsoft.Compute/imagePublisher'))]" + }, + "imageOffer": { + "value": "[tolower(field('Microsoft.Compute/imageOffer'))]" + }, + "location": { + "value": "[field('location')]" + }, + "patchMode": { + "value": "[if(or(equals(toLower(field('Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType')), 'windows'), contains( createArray('microsoftwindowsserver', 'microsoftsqlserver', 'microsoftdynamicsax', 'microsoftazuresiterecovery', 'microsoftbiztalkserver', 'microsoftpowerbi', 'microsoftsharepoint', 'microsoftwindowsserverhpcpack', 'microsoftvisualstudio'),tolower(field('Microsoft.Compute/imagePublisher')))), field('Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration.patchSettings.patchMode'), field('Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration.patchSettings.patchMode'))]" + }, + "bypassCheckValue": { + "value": "[if(or(equals(toLower(field('Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType')), 'windows'), contains( createArray('microsoftwindowsserver', 'microsoftsqlserver', 'microsoftdynamicsax', 'microsoftazuresiterecovery', 'microsoftbiztalkserver', 'microsoftpowerbi', 'microsoftsharepoint', 'microsoftwindowsserverhpcpack', 'microsoftvisualstudio') ,tolower(field('Microsoft.Compute/imagePublisher')))), if(contains(field('Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration.patchSettings.automaticByPlatformSettings'), 'bypassPlatformSafetyChecksOnUserSchedule'), field('Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration.patchSettings.automaticByPlatformSettings.bypassPlatformSafetyChecksOnUserSchedule'), bool('false')), if(contains(field('Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration.patchSettings.automaticByPlatformSettings'), 'bypassPlatformSafetyChecksOnUserSchedule'), field('Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration.patchSettings.automaticByPlatformSettings.bypassPlatformSafetyChecksOnUserSchedule'), bool('false')))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "machineResourceId": { + "type": "string" + }, + "osType": { + "type": "String" + }, + "imagePublisher": { + "type": "string" + }, + "imageOffer": { + "type": "string" + }, + "location": { + "type": "string" + }, + "bypassCheckValue": { + "type": "bool" + }, + "patchMode": { + "type": "string" + } + }, + "variables": { + "allowedWindowsPublishers": [ + "microsoftwindowsserver", + "microsoftsqlserver", + "microsoftdynamicsax", + "microsoftazuresiterecovery", + "microsoftbiztalkserver", + "microsoftpowerbi", + "microsoftsharepoint", + "microsoftwindowsserverhpcpack", + "microsoftvisualstudio" + ], + "imagePublisher": "[parameters('imagePublisher')]", + "imageOffer": "[parameters('imageOffer')]", + "osTypeFromAllowedListOfPublishers": "[if(contains(variables('allowedWindowsPublishers'), variables('imagePublisher')), 'windows', 'linux')]", + "osTypeFromPublisher": "[if(equals(variables('imagePublisher'), 'microsoftsqlserver'), if(contains(variables('imageOffer'), 'ws'), 'windows', 'linux'), variables('osTypeFromAllowedListOfPublishers'))]", + "osType": "[toLower(if(empty(parameters('osType')), variables('osTypeFromPublisher'), parameters('osType')))]", + "automaticByPlatformPatchMode": "AutomaticByPlatform", + "linuxOSProfile": { + "linuxConfiguration": { + "patchSettings": { + "patchMode": "[variables('automaticByPlatformPatchMode')]", + "automaticByPlatformSettings": { + "bypassPlatformSafetyChecksOnUserSchedule": true + } + } + } + }, + "windowsOSProfile": { + "windowsConfiguration": { + "patchSettings": { + "patchMode": "[variables('automaticByPlatformPatchMode')]", + "automaticByPlatformSettings": { + "bypassPlatformSafetyChecksOnUserSchedule": true + } + } + } + }, + "patchModeShouldBeChanged": "[or(not(equals(parameters('patchMode'), variables('automaticByPlatformPatchMode'))), not(equals(parameters('bypassCheckValue'), bool('true'))))]", + "machineName": "[last(split(parameters('machineResourceId'), '/'))]", + "updatedOSProfile": "[if(equals(variables('osType'), 'windows'), variables('windowsOSProfile'), variables('linuxOSProfile'))]" + }, + "resources": [ + { + "condition": "[variables('patchModeShouldBeChanged')]", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[variables('machineName')]", + "location": "[parameters('location')]", + "properties": { + "osProfile": "[variables('updatedOSProfile')]" + } + } + ], + "outputs": { + "OSProfile": { + "type": "object", + "value": "[variables('updatedOSProfile')]" + } + } + } + } + } + } + } + } + } + } + +cases: + # ========================================================================= + # DINE — Windows VM with MicrosoftWindowsServer publisher, patchMode not + # configured for AutomaticByPlatform → DeployIfNotExists + # ========================================================================= + + - note: dine_windows_vm_no_config + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-win-server" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-win-server" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + createOption: "FromImage" + osProfile: + computerName: "vm-win-server" + windowsConfiguration: + patchSettings: + patchMode: "AutomaticByOS" + automaticByPlatformSettings: + bypassPlatformSafetyChecksOnUserSchedule: false + request_context: + apiVersion: "2024-01-01" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Compute/virtualMachines" + response: + properties: + osProfile: + windowsConfiguration: + patchSettings: + patchMode: "AutomaticByOS" + automaticByPlatformSettings: + bypassPlatformSafetyChecksOnUserSchedule: false + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass — Windows VM already configured with correct patchMode and bypass + # → existenceCondition satisfied → compliant + # ========================================================================= + + - note: pass_windows_vm_with_config + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-win-configured" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-win-configured" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + createOption: "FromImage" + osProfile: + computerName: "vm-win-configured" + windowsConfiguration: + patchSettings: + patchMode: "AutomaticByPlatform" + automaticByPlatformSettings: + bypassPlatformSafetyChecksOnUserSchedule: true + request_context: + apiVersion: "2024-01-01" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Compute/virtualMachines" + response: + properties: + osProfile: + windowsConfiguration: + patchSettings: + patchMode: "AutomaticByPlatform" + automaticByPlatformSettings: + bypassPlatformSafetyChecksOnUserSchedule: true + want_undefined: true + + # ========================================================================= + # DINE — Linux VM with Canonical publisher, patchMode not configured + # → DeployIfNotExists + # ========================================================================= + + - note: dine_linux_vm_no_config + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-ubuntu-prod" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-ubuntu-prod" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + osDisk: + osType: "Linux" + createOption: "FromImage" + osProfile: + computerName: "vm-ubuntu-prod" + linuxConfiguration: + patchSettings: + patchMode: "ImageDefault" + automaticByPlatformSettings: + bypassPlatformSafetyChecksOnUserSchedule: false + request_context: + apiVersion: "2024-01-01" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Compute/virtualMachines" + response: + properties: + osProfile: + linuxConfiguration: + patchSettings: + patchMode: "ImageDefault" + automaticByPlatformSettings: + bypassPlatformSafetyChecksOnUserSchedule: false + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass — Wrong resource type (not a VM) → if condition fails + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "storageacct1" + properties: {} + want_undefined: true + + # ========================================================================= + # Pass — VM with excluded publisher "microsoft-ads" and a gallery imageId + # → publisher exclusion gate (notEquals "microsoft-ads") fails + # ========================================================================= + + - note: pass_excluded_publisher + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-ads" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "microsoft-ads" + offer: "some-offer" + sku: "some-sku" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/galleries/myGallery/images/myImage" + osDisk: + osType: "Windows" + createOption: "FromImage" + osProfile: + computerName: "vm-ads" + windowsConfiguration: {} + request_context: + apiVersion: "2024-01-01" + want_undefined: true + + # ========================================================================= + # Pass — VM with createOption "Attach", computerName present, no recognized + # publisher or gallery imageId → no image inclusion path matches in + # the final publisher/image gate → if condition fails + # ========================================================================= + + - note: pass_osdisk_attach + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-attach" + location: "eastus" + properties: + storageProfile: + imageReference: {} + osDisk: + osType: "Windows" + createOption: "Attach" + osProfile: + computerName: "vm-attach" + windowsConfiguration: {} + request_context: + apiVersion: "2024-01-01" + want_undefined: true + + # ========================================================================= + # Pass — operatingSystemTypes set to ["Windows"] but VM is Linux + # → OS type filter does not match → if condition fails + # ========================================================================= + + - note: pass_wrong_os_param + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-linux-wrong-os" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-linux-wrong-os" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + osDisk: + osType: "Linux" + osProfile: + computerName: "vm-linux-wrong-os" + linuxConfiguration: + patchSettings: + patchMode: "ImageDefault" + parameters: + operatingSystemTypes: + - "Windows" + locations: [] + tagValues: [] + tagOperator: "Any" + resourceGroups: [] + request_context: + apiVersion: "2024-01-01" + want_undefined: true + + # ========================================================================= + # Pass — Old API version (< 2023-07-01) + # → requestContext().apiVersion guard fails + # ========================================================================= + + - note: pass_old_api_version + resource: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-old-api" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + osDisk: + osType: "Linux" + osProfile: + computerName: "vm-old-api" + linuxConfiguration: + patchSettings: + patchMode: "ImageDefault" + request_context: + apiVersion: "2020-06-01" + want_undefined: true + + # ========================================================================= + # Pass — Arc machine (Microsoft.HybridCompute/machines) + # → policy only applies to Microsoft.Compute/virtualMachines + # ========================================================================= + + - note: pass_arc_not_supported + resource: + type: "Microsoft.HybridCompute/machines" + name: "arc-server" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.HybridCompute/machines/arc-server" + location: "eastus" + properties: + osName: "Linux" + request_context: + apiVersion: "2024-01-01" + want_undefined: true + + # ========================================================================= + # Pass — VM in a location not included in the locations parameter + # → location filter does not match → if condition fails + # ========================================================================= + + - note: pass_non_matching_location + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-wrong-loc" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-wrong-loc" + location: "westeurope" + properties: + storageProfile: + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + osDisk: + osType: "Linux" + osProfile: + computerName: "vm-wrong-loc" + linuxConfiguration: + patchSettings: + patchMode: "ImageDefault" + parameters: + operatingSystemTypes: + - "Linux" + locations: + - "eastus" + - "westus2" + tagValues: [] + tagOperator: "Any" + resourceGroups: [] + request_context: + apiVersion: "2024-01-01" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_azupdate_scheduled_patching.yaml b/tests/azure_policy/cases/e2e_azupdate_scheduled_patching.yaml new file mode 100644 index 0000000..c6d8aca --- /dev/null +++ b/tests/azure_policy/cases/e2e_azupdate_scheduled_patching.yaml @@ -0,0 +1,1609 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Azure Update Manager/AzUpdateMgmtCenter_ScheduledPatching_DINE +# Real Azure Policy: "Schedule recurring updates using Azure Update Manager" +# Source: regolator/policyDefinitions/Azure Update Manager/AzUpdateMgmtCenter_ScheduledPatching_DINE.json +# +# Features exercised: +# - Largest policy in corpus (231 nodes, 1331 lines) — register overflow stress test +# - Value count with current() for tag matching +# - requestContext().apiVersion guard +# - resourceGroup().name, empty(), field('tags') +# - Deep allOf/anyOf nesting (depth 9) for OS image matching +# - DeployIfNotExists with deployment template + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Schedule recurring updates using Azure Update Manager", + "policyType": "BuiltIn", + "mode": "Indexed", + "description": "You can use Azure Update Manager in Azure to save recurring deployment schedules to install operating system updates for your Windows Server and Linux machines in Azure, in on-premises environments, and in other cloud environments connected using Azure Arc-enabled servers. This policy will also change the patch mode for the Azure Virtual Machine to 'AutomaticByPlatform'. See more: https://aka.ms/umc-scheduled-patching", + "parameters": { + "maintenanceConfigurationResourceId": { + "type": "String", + "metadata": { + "displayName": "Maintenance Configuration ARM ID", + "description": "ARM ID of Maintenance Configuration which will be used for scheduling.", + "assignPermissions": true + } + }, + "resourceGroups": { + "type": "Array", + "metadata": { + "displayName": "Resource groups", + "description": "The list of resource groups from which machines need to be targeted. Example: [\"rg1\", \"rg2\"]." + }, + "defaultValue": [] + }, + "operatingSystemTypes": { + "type": "Array", + "metadata": { + "displayName": "Operating System types", + "description": "The list of Operating System types from which machines need to be targeted." + }, + "defaultValue": [ + "Windows", + "Linux" + ], + "allowedValues": [ + "Windows", + "Linux" + ] + }, + "locations": { + "type": "Array", + "metadata": { + "displayName": "Machines locations", + "description": "The list of locations from which machines need to be targeted.", + "strongType": "location" + }, + "defaultValue": [] + }, + "tagValues": { + "type": "Array", + "metadata": { + "displayName": "Tags on machines", + "description": "The list of tags that need to matched for getting target machines (case sensitive). Example: [ {\"key\": \"tagKey1\", \"value\": \"value1\"}, {\"key\": \"tagKey2\", \"value\": \"value2\"}]." + }, + "defaultValue": [] + }, + "tagOperator": { + "type": "String", + "metadata": { + "displayName": "Tags operator", + "description": "Matching condition for resource tags" + }, + "allowedValues": [ + "All", + "Any" + ], + "defaultValue": "Any" + }, + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "in": [ + "Microsoft.HybridCompute/machines", + "Microsoft.Compute/virtualMachines" + ] + }, + { + "anyOf": [ + { + "value": "[empty(parameters('operatingSystemTypes'))]", + "equals": true + }, + { + "allOf": [ + { + "field": "type", + "in": [ + "Microsoft.HybridCompute/machines" + ] + }, + { + "field": "Microsoft.HybridCompute/machines/osName", + "in": "[parameters('operatingSystemTypes')]" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "in": [ + "Microsoft.Compute/virtualMachines" + ] + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "in": "[parameters('operatingSystemTypes')]" + }, + { + "anyOf": [ + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/galleries" + }, + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/images" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "true" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "in": "[parameters('operatingSystemTypes')]" + }, + { + "anyOf": [ + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/galleries" + }, + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/images" + }, + { + "value": "[field('Microsoft.Compute/virtualMachines/storageProfile.osDisk.createOption')]", + "equals": "Attach" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "false" + }, + { + "value": "[requestContext().apiVersion]", + "greaterOrEquals": "2023-07-01" + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "in": "[parameters('operatingSystemTypes')]" + }, + { + "allOf": [ + { + "value": "Linux", + "in": "[parameters('operatingSystemTypes')]" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-standard", + "sles-sapcal", + "sles-sap-byos", + "sles-sap", + "sles-byos", + "sles-15-sp4-sapcal", + "sles-15-sp4-basic", + "sles-15-sp4", + "sles-15-sp3-sapcal", + "sles-15-sp2-hpc", + "sles-15-sp2-basic", + "sles-15-sp1-sapcal", + "sles", + "sle-hpc-15-sp4", + "sles-12-sp5", + "sles-15-sp2", + "centos-hpc", + "centos-ci", + "centos-lvm", + "Centos", + "rhel-sap-ha", + "rhel-sap-apps", + "RHEL-RAW", + "RHEL", + "aml-workstation", + "aks", + "oracle-database-19-3", + "oracle-database", + "oracle-linux", + "cbl-mariner", + "0001-com-ubuntu-server-jammy", + "0001-com-ubuntu-pro-jammy", + "0001-com-ubuntu-pro-focal", + "0001-com-ubuntu-server-focal", + "0001-com-ubuntu-pro-bionic", + "UbuntuServer" + ] + } + ] + }, + { + "allOf": [ + { + "value": "Windows", + "in": "[parameters('operatingSystemTypes')]" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "WindowsServer", + "microsoftserveroperatingsystems-previews", + "windowsserverhotpatch-previews", + "sql2016sp1-ws2016", + "sql2016sp2-ws201", + "sql2017-ws2016", + "sql2019-ws2019", + "dynamics", + "process-server" + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "anyOf": [ + { + "value": "[empty(parameters('locations'))]", + "equals": true + }, + { + "field": "location", + "in": "[parameters('locations')]" + } + ] + }, + { + "anyOf": [ + { + "value": "[empty(parameters('resourceGroups'))]", + "equals": true + }, + { + "value": "[resourceGroup().name]", + "in": "[parameters('resourceGroups')]" + } + ] + }, + { + "anyOf": [ + { + "value": "[empty(parameters('tagValues'))]", + "equals": true + }, + { + "allOf": [ + { + "value": "[empty(field('tags'))]", + "equals": false + }, + { + "value": "[parameters('tagOperator')]", + "equals": "Any" + }, + { + "count": { + "value": "[parameters('tagValues')]", + "name": "tagKvp", + "where": { + "value": "[length(intersection(createObject(current('tagKvp').key, current('tagKvp').value), field('tags')))]", + "greater": 0 + } + }, + "greater": 0 + } + ] + }, + { + "allOf": [ + { + "value": "[empty(field('tags'))]", + "equals": false + }, + { + "value": "[parameters('tagOperator')]", + "equals": "All" + }, + { + "count": { + "value": "[parameters('tagValues')]", + "name": "tagKvp", + "where": { + "value": "[length(intersection(createObject(current('tagKvp').key, current('tagKvp').value), field('tags')))]", + "greater": 0 + } + }, + "equals": "[length(parameters('tagValues'))]" + } + ] + } + ] + }, + { + "anyOf": [ + { + "field": "type", + "in": [ + "Microsoft.HybridCompute/machines" + ] + }, + { + "allOf": [ + { + "field": "type", + "in": [ + "Microsoft.Compute/virtualMachines" + ] + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "exists": "true" + }, + { + "anyOf": [ + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/galleries" + }, + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/images" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "true" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "exists": "true" + }, + { + "anyOf": [ + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/galleries" + }, + { + "value": "[field('Microsoft.Compute/imageId')]", + "contains": "Microsoft.Compute/images" + }, + { + "value": "[field('Microsoft.Compute/virtualMachines/storageProfile.osDisk.createOption')]", + "equals": "Attach" + } + ] + }, + { + "field": "Microsoft.Compute/virtualMachines/osProfile.computerName", + "exists": "false" + }, + { + "value": "[requestContext().apiVersion]", + "greaterOrEquals": "2023-07-01" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Canonical" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftcblmariner" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "cbl-mariner" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "cbl-mariner-1", + "1-gen2", + "cbl-mariner-2", + "cbl-mariner-2-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "oracle" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "oracle-linux" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "8", + "8-ci", + "81", + "81-ci", + "81-gen2" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol9*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "ol9-lvm*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "oracle-database" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "oracle_db_21" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "oracle-database-*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "18.*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "oracle-database-19-3" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "oracle-database-19-0904" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-aks" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "aks" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "aks-engine-ubuntu-1804-202112" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-dsvm" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "aml-workstation" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "ubuntu-20", + "ubuntu-20-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Redhat" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "RHEL" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "9*" + } + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "notEquals": "74-gen2" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "RHEL-RAW" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "9*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "rhel-sap-ha" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "equals": "90sapha-gen2" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + } + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "notEquals": "7.5" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "rhel-sap-apps" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "equals": "90sapha-gen2" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "rhel-sap-*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "9_0" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "rhel-ha" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notIn": [ + "7.4", + "7.5", + "7.6", + "8.1", + "81_gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "rhel-sap" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notIn": [ + "7.4", + "7.5", + "7.7" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "OpenLogic" + }, + { + "allOf": [ + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "Centos" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "8*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "centos-lvm" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "7-lvm", + "8-lvm", + "7-lvm-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "centos-ci" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "7-ci" + } + ] + } + ] + }, + { + "field": "Microsoft.Compute/imageOffer", + "notEquals": "centos-hpc" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "SUSE" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-12-sp5", + "sles-15-sp2", + "sle-hpc-15-sp4", + "sles-15-sp1-sapcal", + "sles-15-sp3-sapcal", + "sles-15-sp4-basic", + "sles-15-sp4" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "gen1", + "gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles", + "sles-standard" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "12-sp4-gen2" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-15-sp2-basic", + "sles-15-sp2-hpc" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "gen2" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "sles-15-sp4-sapcal" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "gen1" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "sles-byos", + "sles-sap" + ] + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "12-sp4", + "12-sp4-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "sles-sap-byos" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "12-sp4", + "12-sp4-gen2", + "gen2-12-sp4" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "sles-sapcal" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "12-sp3" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "gen*" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "opensuse-leap-15-*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-12-sp5-*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-sap-12-sp5*" + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-sap-15-*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "sles-sap-15-*-byos" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftWindowsServer" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "windowsserver", + "windows-cvm", + "windowsserverdotnet", + "windowsserver-gen2preview", + "windowsserversemiannual", + "windowsserverupgrade" + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "microsoftserveroperatingsystems-previews" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "windows-server-vnext-azure-edition-core" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "windowsserverhotpatch-previews" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "windows-server-2022-azure-edition-hotpatch" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftSQLServer" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "sql2019-sles*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notIn": [ + "sql2019-rhel7", + "sql2017-rhel7" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftdynamicsax" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "dynamics" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftazuresiterecovery" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "process-server" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "windows-2012-r2-datacenter" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftbiztalkserver" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "biztalk-server" + } + ] + }, + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftpowerbi" + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftsharepoint" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "microsoftsharepointserver" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftwindowsserverhpcpack" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "windowsserverhpcpack" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftvisualstudio" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "visualstudio*" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2012r2" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2016" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2019" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "*-ws2022" + } + ] + } + ] + } + ] + }, + { + "field": "Microsoft.Compute/imagePublisher", + "notEquals": "microsoft-ads" + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + ], + "type": "Microsoft.Maintenance/configurationAssignments", + "evaluationDelay": "AfterProvisioningSuccess", + "existenceCondition": { + "field": "Microsoft.Maintenance/configurationAssignments/maintenanceConfigurationId", + "equals": "[parameters('maintenanceConfigurationResourceId')]" + }, + "deployment": { + "properties": { + "mode": "incremental", + "parameters": { + "machineResourceId": { + "value": "[field('id')]" + }, + "maintenanceConfigurationResourceId": { + "value": "[parameters('maintenanceConfigurationResourceId')]" + }, + "osType": { + "value": "[if(equals(toLower(field('type')), 'microsoft.compute/virtualmachines'), field('Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType'), field('Microsoft.HybridCompute/machines/osName'))]" + }, + "imagePublisher": { + "value": "[tolower(field('Microsoft.Compute/imagePublisher'))]" + }, + "imageOffer": { + "value": "[tolower(field('Microsoft.Compute/imageOffer'))]" + }, + "patchMode": { + "value": "[if(equals(toLower(field('type')), 'microsoft.compute/virtualmachines'), if(or(equals(toLower(field('Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType')), 'windows'), contains(createArray('microsoftwindowsserver', 'microsoftsqlserver', 'microsoftdynamicsax', 'microsoftazuresiterecovery', 'microsoftbiztalkserver', 'microsoftpowerbi', 'microsoftsharepoint', 'microsoftwindowsserverhpcpack', 'microsoftvisualstudio'),tolower(field('Microsoft.Compute/imagePublisher')))), field('Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration.patchSettings.patchMode'), field('Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration.patchSettings.patchMode')), 'NA')]" + }, + "bypassCheckValue": { + "value": "[if(equals(toLower(field('type')), 'microsoft.compute/virtualmachines'), if(or(equals(toLower(field('Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType')), 'windows'), contains(createArray('microsoftwindowsserver', 'microsoftsqlserver', 'microsoftdynamicsax', 'microsoftazuresiterecovery', 'microsoftbiztalkserver', 'microsoftpowerbi', 'microsoftsharepoint', 'microsoftwindowsserverhpcpack', 'microsoftvisualstudio'), toLower(field('Microsoft.Compute/imagePublisher')))), if(contains(field('Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration.patchSettings.automaticByPlatformSettings'), 'bypassPlatformSafetyChecksOnUserSchedule'), field('Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration.patchSettings.automaticByPlatformSettings.bypassPlatformSafetyChecksOnUserSchedule'), bool('false')), if(contains(field('Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration.patchSettings.automaticByPlatformSettings'), 'bypassPlatformSafetyChecksOnUserSchedule'), field('Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration.patchSettings.automaticByPlatformSettings.bypassPlatformSafetyChecksOnUserSchedule'), bool('false'))), bool('false'))]" + }, + "location": { + "value": "[field('location')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "machineResourceId": { + "type": "string" + }, + "maintenanceConfigurationResourceId": { + "type": "String" + }, + "osType": { + "type": "String" + }, + "patchMode": { + "type": "string" + }, + "imagePublisher": { + "type": "string" + }, + "imageOffer": { + "type": "string" + }, + "bypassCheckValue": { + "type": "bool" + }, + "location": { + "type": "string" + } + }, + "variables": { + "allowedWindowsPublishers": [ + "microsoftwindowsserver", + "microsoftsqlserver", + "microsoftdynamicsax", + "microsoftazuresiterecovery", + "microsoftbiztalkserver", + "microsoftpowerbi", + "microsoftsharepoint", + "microsoftwindowsserverhpcpack", + "microsoftvisualstudio" + ], + "imagePublisher": "[parameters('imagePublisher')]", + "imageOffer": "[parameters('imageOffer')]", + "osTypeFromAllowedListOfPublishers": "[if(contains(variables('allowedWindowsPublishers'), variables('imagePublisher')), 'windows', 'linux')]", + "osTypeFromPublisher": "[if(equals(variables('imagePublisher'), 'microsoftsqlserver'), if(contains(variables('imageOffer'), 'ws'), 'windows', 'linux'), variables('osTypeFromAllowedListOfPublishers'))]", + "osType": "[toLower(if(empty(parameters('osType')), variables('osTypeFromPublisher'), parameters('osType')))]", + "automaticByPlatformPatchMode": "AutomaticByPlatform", + "configAssignmentName": "[concat(uniqueString(tolower(parameters('maintenanceConfigurationResourceId'))), '-AzPolicy')]", + "isAzureMachine": "[contains(tolower(parameters('machineResourceId')), tolower('/providers/microsoft.compute/virtualmachines/'))]", + "isArcMachine": "[contains(tolower(parameters('machineResourceId')), tolower('/providers/Microsoft.HybridCompute/machines/'))]", + "linuxOSProfile": { + "linuxConfiguration": { + "patchSettings": { + "patchMode": "[variables('automaticByPlatformPatchMode')]", + "automaticByPlatformSettings": { + "bypassPlatformSafetyChecksOnUserSchedule": true + } + } + } + }, + "windowsOSProfile": { + "windowsConfiguration": { + "patchSettings": { + "patchMode": "[variables('automaticByPlatformPatchMode')]", + "automaticByPlatformSettings": { + "bypassPlatformSafetyChecksOnUserSchedule": true + } + } + } + }, + "patchModeShouldBeChanged": "[and(variables('isAzureMachine'), or(not(equals(parameters('patchMode'), variables('automaticByPlatformPatchMode'))), not(equals(parameters('bypassCheckValue'), bool('true')))))]", + "machineName": "[last(split(parameters('machineResourceId'), '/'))]", + "updatedOSProfile": "[if(equals(variables('osType'), 'windows'), variables('windowsOSProfile'), variables('linuxOSProfile'))]" + }, + "resources": [ + { + "condition": "[variables('patchModeShouldBeChanged')]", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[variables('machineName')]", + "location": "[parameters('location')]", + "properties": { + "osProfile": "[variables('updatedOSProfile')]" + } + }, + { + "type": "Microsoft.Compute/virtualMachines/providers/configurationAssignments", + "condition": "[variables('isAzureMachine')]", + "apiVersion": "2021-09-01-preview", + "name": "[concat(variables('machineName'), '/Microsoft.Maintenance/', variables('configAssignmentName'))]", + "location": "[parameters('location')]", + "properties": { + "maintenanceConfigurationId": "[parameters('maintenanceConfigurationResourceId')]" + }, + "dependsOn": [ + "[concat('Microsoft.Compute/virtualMachines/', variables('machineName'))]" + ] + }, + { + "type": "Microsoft.HybridCompute/machines/providers/configurationAssignments", + "condition": "[variables('isArcMachine')]", + "apiVersion": "2021-09-01-preview", + "name": "[concat(variables('machineName'), '/Microsoft.Maintenance/', variables('configAssignmentName'))]", + "location": "[parameters('location')]", + "properties": { + "maintenanceConfigurationId": "[parameters('maintenanceConfigurationResourceId')]" + } + } + ], + "outputs": { + "OSProfile": { + "type": "object", + "value": "[variables('updatedOSProfile')]" + }, + "configurationAssignmentName": { + "type": "string", + "value": "[variables('configAssignmentName')]" + } + } + } + } + } + } + } + } + } + } + +cases: + # ========================================================================= + # DINE — Linux VM with Canonical Ubuntu, matching tags, no maintenance + # assignment found → DeployIfNotExists + # ========================================================================= + + - note: dine_linux_vm_canonical_ubuntu + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-ubuntu-prod" + id: "/subscriptions/sub1/resourceGroups/rg-prod/providers/Microsoft.Compute/virtualMachines/vm-ubuntu-prod" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + osDisk: + osType: "Linux" + osProfile: + computerName: "vm-ubuntu-prod" + linuxConfiguration: + patchSettings: + patchMode: "ImageDefault" + tags: + env: "prod" + parameters: + maintenanceConfigurationResourceId: "/subscriptions/sub1/resourceGroups/rg-maint/providers/Microsoft.Maintenance/maintenanceConfigurations/config1" + operatingSystemTypes: + - "Linux" + tagValues: + - key: "env" + value: "prod" + tagOperator: "Any" + resourceGroups: [] + locations: [] + request_context: + apiVersion: "2024-01-01" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Maintenance/configurationAssignments" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass — Windows VM but operatingSystemTypes only has Linux + # ========================================================================= + + - note: pass_wrong_os_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-win-server" + id: "/subscriptions/sub1/resourceGroups/rg-prod/providers/Microsoft.Compute/virtualMachines/vm-win-server" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + osProfile: + computerName: "vm-win-server" + windowsConfiguration: + patchSettings: + patchMode: "AutomaticByOS" + parameters: + maintenanceConfigurationResourceId: "/subscriptions/sub1/resourceGroups/rg-maint/providers/Microsoft.Maintenance/maintenanceConfigurations/config1" + operatingSystemTypes: + - "Linux" + tagValues: [] + tagOperator: "Any" + resourceGroups: [] + locations: [] + request_context: + apiVersion: "2024-01-01" + want_undefined: true + + # ========================================================================= + # Pass — Correct Linux VM but old API version (less than 2023-07-01) + # ========================================================================= + + - note: pass_old_api_version + resource: + id: "/subscriptions/sub1/resourceGroups/rg-prod/providers/Microsoft.Compute/virtualMachines/vm-ubuntu-old-api" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + osDisk: + osType: "Linux" + osProfile: + computerName: "vm-ubuntu-old-api" + linuxConfiguration: + patchSettings: + patchMode: "ImageDefault" + tags: + env: "prod" + parameters: + maintenanceConfigurationResourceId: "/subscriptions/sub1/resourceGroups/rg-maint/providers/Microsoft.Maintenance/maintenanceConfigurations/config1" + operatingSystemTypes: + - "Linux" + tagValues: + - key: "env" + value: "prod" + tagOperator: "Any" + resourceGroups: [] + locations: [] + request_context: + apiVersion: "2022-01-01" + want_undefined: true + + # ========================================================================= + # Skip — Wrong resource type + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "storageacct1" + properties: {} + want_undefined: true + + # ========================================================================= + # DINE — HybridCompute/machines with matching osName → DeployIfNotExists + # ========================================================================= + + - note: dine_arc_machine + resource: + type: "Microsoft.HybridCompute/machines" + name: "arc-linux-server" + id: "/subscriptions/sub1/resourceGroups/rg-arc/providers/Microsoft.HybridCompute/machines/arc-linux-server" + location: "westus2" + properties: + osName: "linux" + tags: {} + parameters: + maintenanceConfigurationResourceId: "/subscriptions/sub1/resourceGroups/rg-maint/providers/Microsoft.Maintenance/maintenanceConfigurations/config1" + operatingSystemTypes: + - "Linux" + tagValues: [] + tagOperator: "Any" + resourceGroups: [] + locations: [] + request_context: + apiVersion: "2024-01-01" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Maintenance/configurationAssignments" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass — VM with excluded publisher "microsoft-ads" and a gallery imageId + # → publisher exclusion gate (notEquals "microsoft-ads") fails + # ========================================================================= + + - note: pass_excluded_publisher + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-ads" + id: "/subscriptions/sub1/resourceGroups/rg-prod/providers/Microsoft.Compute/virtualMachines/vm-ads" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "microsoft-ads" + offer: "windows-data-science-vm" + sku: "windows2016" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/galleries/myGallery/images/myImage" + osDisk: + osType: "Windows" + createOption: "FromImage" + osProfile: + computerName: "vm-ads" + windowsConfiguration: {} + parameters: + maintenanceConfigurationResourceId: "/subscriptions/sub1/resourceGroups/rg-maint/providers/Microsoft.Maintenance/maintenanceConfigurations/config1" + operatingSystemTypes: + - "Windows" + - "Linux" + tagValues: [] + tagOperator: "Any" + resourceGroups: [] + locations: [] + request_context: + apiVersion: "2024-01-01" + want_undefined: true + + # ========================================================================= + # Pass — Linux VM with maintenance config already assigned + # → existenceCondition satisfied → compliant + # ========================================================================= + + - note: pass_maintenance_config_exists + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-ubuntu-compliant" + id: "/subscriptions/sub1/resourceGroups/rg-prod/providers/Microsoft.Compute/virtualMachines/vm-ubuntu-compliant" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + osDisk: + osType: "Linux" + osProfile: + computerName: "vm-ubuntu-compliant" + linuxConfiguration: + patchSettings: + patchMode: "ImageDefault" + parameters: + maintenanceConfigurationResourceId: "/subscriptions/sub1/resourceGroups/rg-maint/providers/Microsoft.Maintenance/maintenanceConfigurations/config1" + operatingSystemTypes: + - "Linux" + tagValues: [] + tagOperator: "Any" + resourceGroups: [] + locations: [] + request_context: + apiVersion: "2024-01-01" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Maintenance/configurationAssignments" + response: + properties: + maintenanceConfigurationId: "/subscriptions/sub1/resourceGroups/rg-maint/providers/Microsoft.Maintenance/maintenanceConfigurations/config1" + want_undefined: true + + # ========================================================================= + # Pass — VM with unknown publisher, no gallery imageId + # → no publisher/image gate matches → if condition fails + # ========================================================================= + + - note: pass_unknown_publisher + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-unknown" + id: "/subscriptions/sub1/resourceGroups/rg-prod/providers/Microsoft.Compute/virtualMachines/vm-unknown" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "unknown-publisher" + offer: "unknown-offer" + sku: "unknown-sku" + osDisk: + osType: "Linux" + osProfile: + computerName: "vm-unknown" + linuxConfiguration: {} + parameters: + maintenanceConfigurationResourceId: "/subscriptions/sub1/resourceGroups/rg-maint/providers/Microsoft.Maintenance/maintenanceConfigurations/config1" + operatingSystemTypes: + - "Linux" + tagValues: [] + tagOperator: "Any" + resourceGroups: [] + locations: [] + request_context: + apiVersion: "2024-01-01" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_cmk_disk_encryption.yaml b/tests/azure_policy/cases/e2e_cmk_disk_encryption.yaml new file mode 100644 index 0000000..64ccc88 --- /dev/null +++ b/tests/azure_policy/cases/e2e_cmk_disk_encryption.yaml @@ -0,0 +1,572 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Compute/OSAndDataDiskCMKRequired_Deny +# Real Azure Policy: "OS and data disks should be encrypted with a customer-managed key" +# Features: anyOf, allOf nesting, field (type + alias), exists, equals, +# length(), count, not, current(), multiple resource types +# (VM, VMSS, disks, images, galleries/images/versions) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "OS and data disks should be encrypted with a customer-managed key", + "policyType": "BuiltIn", + "mode": "Indexed", + "description": "Use customer-managed keys to manage the encryption at rest of the contents of your managed disks. By default, the data is encrypted at rest with platform-managed keys, but customer-managed keys are commonly required to meet regulatory compliance standards. Customer-managed keys enable the data to be encrypted with an Azure Key Vault key created and owned by you. You have full control and responsibility for the key lifecycle, including rotation and management. Learn more at https://aka.ms/disks-cmk.", + "metadata": { + "category": "Compute", + "version": "3.0.0" + }, + "version": "3.0.0", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "Audit", + "allowedValues": [ + "Audit", + "Deny", + "Disabled" + ], + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + } + } + }, + "policyRule": { + "if": { + "anyOf": [ + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.managedDisk.diskEncryptionSet.id", + "exists": "False" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "value": "[length(field('Microsoft.Compute/virtualMachines/storageProfile.dataDisks'))]", + "greater": 0 + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.dataDisks[*].managedDisk.id", + "exists": "False" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.dataDisks[*].managedDisk.diskEncryptionSet.id", + "exists": "False" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachineScaleSets" + }, + { + "field": "Microsoft.Compute/virtualMachineScaleSets/virtualMachineProfile.storageProfile.osDisk.managedDisk.diskEncryptionSet.id", + "exists": "False" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachineScaleSets" + }, + { + "count": { + "field": "Microsoft.Compute/virtualMachineScaleSets/virtualMachineProfile.storageProfile.dataDisks[*]" + }, + "greater": 0 + }, + { + "not": { + "field": "Microsoft.Compute/virtualMachineScaleSets/virtualMachineProfile.storageProfile.dataDisks[*].managedDisk.diskEncryptionSet.id", + "exists": "true" + } + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/disks" + }, + { + "field": "Microsoft.Compute/disks/encryption.diskEncryptionSetId", + "exists": "False" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/galleries/images/versions" + }, + { + "value": "[length(field('Microsoft.Compute/galleries/images/versions/publishingProfile.targetRegions[*].encryption.osDiskImage.diskEncryptionSetId'))]", + "notEquals": "[length(field('Microsoft.Compute/galleries/images/versions/publishingProfile.targetRegions[*]'))]" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/galleries/images/versions" + }, + { + "value": "[length(field('Microsoft.Compute/galleries/images/versions/storageProfile.dataDiskImages[*]'))]", + "greater": 0 + }, + { + "anyOf": [ + { + "count": { + "field": "Microsoft.Compute/galleries/images/versions/publishingProfile.targetRegions[*]", + "where": { + "value": "[length(current('Microsoft.Compute/galleries/images/versions/publishingProfile.targetRegions[*].encryption.dataDiskImages[*].diskEncryptionSetId'))]", + "notEquals": "[length(field('Microsoft.Compute/galleries/images/versions/storageProfile.dataDiskImages[*]'))]" + } + }, + "greater": 0 + }, + { + "not": { + "field": "Microsoft.Compute/galleries/images/versions/publishingProfile.targetRegions[*].encryption.dataDiskImages[*].diskEncryptionSetId", + "exists": "true" + } + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/images" + }, + { + "field": "Microsoft.Compute/images/storageProfile.osDisk.diskEncryptionSet.id", + "exists": "False" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/images" + }, + { + "value": "[length(field('Microsoft.Compute/images/storageProfile.dataDisks[*]'))]", + "greater": 0 + }, + { + "not": { + "field": "Microsoft.Compute/images/storageProfile.dataDisks[*].diskEncryptionSet.id", + "exists": "true" + } + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + }, + "versions": [ + "3.0.0" + ] + }, + "id": "/providers/Microsoft.Authorization/policyDefinitions/702dd420-7fcc-42c5-afe8-4026edd20fe0", + "name": "702dd420-7fcc-42c5-afe8-4026edd20fe0" + } + +cases: + # ========================================================================= + # 1. VM with CMK on OS disk → pass + # ========================================================================= + + - note: pass_vm_osdisk_with_cmk + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-with-cmk" + properties: + storageProfile: + osDisk: + managedDisk: + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + want_undefined: true + + # ========================================================================= + # 2. VM without CMK on OS disk → Audit + # ========================================================================= + + - note: audit_vm_osdisk_no_cmk + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-no-cmk" + properties: + storageProfile: + osDisk: + managedDisk: + storageAccountType: "Premium_LRS" + want_effect: "Audit" + + # ========================================================================= + # 3. VM with CMK on OS disk and data disks having CMK → pass + # ========================================================================= + + - note: pass_vm_osdisk_and_datadisks_with_cmk + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-all-cmk" + properties: + storageProfile: + osDisk: + managedDisk: + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + dataDisks: + - lun: 0 + manageddisk: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/disks/datadisk0" + diskencryptionset: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + - lun: 1 + manageddisk: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/disks/datadisk1" + diskencryptionset: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + want_undefined: true + + # ========================================================================= + # 4. VM with data disks missing CMK (no managedDisk.id and no + # diskEncryptionSet.id) → Audit + # ========================================================================= + + - note: audit_vm_datadisks_no_cmk + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-datadisks-no-cmk" + properties: + storageProfile: + osDisk: + managedDisk: + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + dataDisks: + - lun: 0 + manageddisk: + storageaccounttype: "Premium_LRS" + - lun: 1 + manageddisk: + storageaccounttype: "Standard_LRS" + want_effect: "Audit" + + # ========================================================================= + # 5. VMSS without CMK on OS disk → Audit + # ========================================================================= + + - note: audit_vmss_osdisk_no_cmk + resource: + type: "Microsoft.Compute/virtualMachineScaleSets" + name: "vmss-no-cmk" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + managedDisk: + storageAccountType: "Premium_LRS" + want_effect: "Audit" + + # ========================================================================= + # 5b. VMSS with CMK on OS disk → pass + # ========================================================================= + + - note: pass_vmss_osdisk_with_cmk + resource: + type: "Microsoft.Compute/virtualMachineScaleSets" + name: "vmss-with-cmk" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + managedDisk: + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + want_undefined: true + + # ========================================================================= + # 5c. VMSS with data disks missing CMK → Audit + # ========================================================================= + + - note: audit_vmss_datadisks_no_cmk + resource: + type: "Microsoft.Compute/virtualMachineScaleSets" + name: "vmss-datadisks-no-cmk" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + managedDisk: + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + dataDisks: + - lun: 0 + managedDisk: + storageAccountType: "Premium_LRS" + - lun: 1 + managedDisk: + storageAccountType: "Standard_LRS" + want_effect: "Audit" + + # ========================================================================= + # 5d. VMSS with data disks having CMK → pass + # ========================================================================= + + - note: pass_vmss_datadisks_with_cmk + resource: + type: "Microsoft.Compute/virtualMachineScaleSets" + name: "vmss-datadisks-cmk" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + managedDisk: + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + dataDisks: + - lun: 0 + managedDisk: + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + - lun: 1 + managedDisk: + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + want_undefined: true + + # ========================================================================= + # 6. Disk without diskEncryptionSetId → Audit + # ========================================================================= + + - note: audit_disk_no_des + resource: + type: "Microsoft.Compute/disks" + name: "disk-no-encryption" + properties: + diskSizeGB: 128 + encryption: + type: "EncryptionAtRestWithPlatformKey" + want_effect: "Audit" + + # ========================================================================= + # 7. Disk with diskEncryptionSetId → pass + # ========================================================================= + + - note: pass_disk_with_des + resource: + type: "Microsoft.Compute/disks" + name: "disk-with-des" + properties: + diskSizeGB: 128 + encryption: + diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + type: "EncryptionAtRestWithCustomerKey" + want_undefined: true + + # ========================================================================= + # 8. Image without CMK on OS disk → Audit + # ========================================================================= + + - note: audit_image_osdisk_no_cmk + resource: + type: "Microsoft.Compute/images" + name: "image-no-cmk" + properties: + storageProfile: + osDisk: + osType: "Linux" + osState: "Generalized" + want_effect: "Audit" + + # ========================================================================= + # 8b. Image with CMK on OS disk → pass + # ========================================================================= + + - note: pass_image_osdisk_with_cmk + resource: + type: "Microsoft.Compute/images" + name: "image-with-cmk" + properties: + storageProfile: + osDisk: + osType: "Linux" + osState: "Generalized" + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + want_undefined: true + + # ========================================================================= + # 8c. Image with data disks missing CMK → Audit + # ========================================================================= + + - note: audit_image_datadisks_no_cmk + resource: + type: "Microsoft.Compute/images" + name: "image-datadisks-no-cmk" + properties: + storageProfile: + osDisk: + osType: "Linux" + osState: "Generalized" + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + dataDisks: + - lun: 0 + blobUri: "https://storage.blob.core.windows.net/vhds/datadisk.vhd" + - lun: 1 + blobUri: "https://storage.blob.core.windows.net/vhds/datadisk2.vhd" + want_effect: "Audit" + + # ========================================================================= + # 8d. Image with data disks having CMK → pass + # ========================================================================= + + - note: pass_image_datadisks_with_cmk + resource: + type: "Microsoft.Compute/images" + name: "image-datadisks-cmk" + properties: + storageProfile: + osDisk: + osType: "Linux" + osState: "Generalized" + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + dataDisks: + - lun: 0 + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + - lun: 1 + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + want_undefined: true + + # ========================================================================= + # Gallery Image Version: OS disk encryption (branch 6) + # ========================================================================= + + - note: audit_gallery_version_osdisk_no_cmk + resource: + type: "Microsoft.Compute/galleries/images/versions" + name: "gallery-version-osdisk-no-cmk" + properties: + publishingProfile: + targetRegions: + - name: "eastus" + - name: "westus" + want_effect: "Audit" + + - note: pass_gallery_version_osdisk_with_cmk + resource: + type: "Microsoft.Compute/galleries/images/versions" + name: "gallery-version-osdisk-cmk" + properties: + publishingProfile: + targetRegions: + - name: "eastus" + encryption: + osDiskImage: + diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + - name: "westus" + encryption: + osDiskImage: + diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + want_undefined: true + + # ========================================================================= + # Gallery Image Version: data disk encryption (branch 7) + # ========================================================================= + + - note: audit_gallery_version_datadisks_no_cmk + resource: + type: "Microsoft.Compute/galleries/images/versions" + name: "gallery-version-datadisks-no-cmk" + properties: + storageProfile: + dataDiskImages: + - lun: 0 + - lun: 1 + publishingProfile: + targetRegions: + - name: "eastus" + encryption: + osDiskImage: + diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + - name: "westus" + encryption: + osDiskImage: + diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + want_effect: "Audit" + + - note: pass_gallery_version_datadisks_with_cmk + resource: + type: "Microsoft.Compute/galleries/images/versions" + name: "gallery-version-datadisks-cmk" + properties: + storageProfile: + dataDiskImages: + - lun: 0 + - lun: 1 + publishingProfile: + targetRegions: + - name: "eastus" + encryption: + osDiskImage: + diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + dataDiskImages: + - diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + - diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + - name: "westus" + encryption: + osDiskImage: + diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + dataDiskImages: + - diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + - diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/myDES" + want_undefined: true + + # ========================================================================= + # 9. Wrong resource type → pass + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "myStorage" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_container_diagnostics_append.yaml b/tests/azure_policy/cases/e2e_container_diagnostics_append.yaml new file mode 100644 index 0000000..5d4e2c8 --- /dev/null +++ b/tests/azure_policy/cases/e2e_container_diagnostics_append.yaml @@ -0,0 +1,162 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Container Instances/ContainerInstance_LogAnalytics_Append +# Real Azure Policy: "Configure diagnostics for container group to log analytics workspace" +# Source: regolator/policyDefinitions/Container Instances/ContainerInstance_LogAnalytics_Append.json +# +# Features exercised: +# - Append effect with details array (two fields) +# - exists "false" operator (multiple conditions) +# - Parameterized effect (Append/Disabled) +# - Parameters injected into details value + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Configure diagnostics for container group to log analytics workspace", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "String", + "allowedValues": ["Append", "Disabled"], + "defaultValue": "Append" + }, + "workspaceId": { + "type": "String", + "metadata": { + "displayName": "Workspace ID" + } + }, + "workspaceKey": { + "type": "String", + "metadata": { + "displayName": "Workspace Key" + } + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.ContainerInstance/containerGroups" + }, + { + "field": "Microsoft.ContainerInstance/containerGroups/diagnostics.logAnalytics.workspaceId", + "exists": "false" + }, + { + "field": "Microsoft.ContainerInstance/containerGroups/diagnostics.logAnalytics.workspaceKey", + "exists": "false" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": [ + { + "field": "Microsoft.ContainerInstance/containerGroups/diagnostics.logAnalytics.workspaceId", + "value": "[parameters('workspaceId')]" + }, + { + "field": "Microsoft.ContainerInstance/containerGroups/diagnostics.logAnalytics.workspaceKey", + "value": "[parameters('workspaceKey')]" + } + ] + } + } + } + } + +cases: + # ========================================================================= + # Both diagnostics fields missing → append + # ========================================================================= + + - note: append_both_missing + resource: + type: "Microsoft.ContainerInstance/containerGroups" + name: "cg-no-diag" + properties: {} + parameters: + workspaceId: "workspace-guid-123" + workspaceKey: "workspace-key-abc" + want_effect: "Append" + want_details: + - field: "Microsoft.ContainerInstance/containerGroups/diagnostics.logAnalytics.workspaceId" + value: "workspace-guid-123" + - field: "Microsoft.ContainerInstance/containerGroups/diagnostics.logAnalytics.workspaceKey" + value: "workspace-key-abc" + + - note: append_diagnostics_empty + resource: + type: "Microsoft.ContainerInstance/containerGroups" + name: "cg-empty-diag" + properties: + diagnostics: {} + parameters: + workspaceId: "ws-id" + workspaceKey: "ws-key" + want_effect: "Append" + + # ========================================================================= + # One or both fields present → pass + # ========================================================================= + + - note: pass_workspace_id_present + resource: + type: "Microsoft.ContainerInstance/containerGroups" + name: "cg-has-id" + properties: + diagnostics: + logAnalytics: + workspaceId: "existing-id" + parameters: + workspaceId: "ws-id" + workspaceKey: "ws-key" + want_undefined: true + + - note: pass_workspace_key_present + resource: + type: "Microsoft.ContainerInstance/containerGroups" + name: "cg-has-key" + properties: + diagnostics: + logAnalytics: + workspaceKey: "existing-key" + parameters: + workspaceId: "ws-id" + workspaceKey: "ws-key" + want_undefined: true + + - note: pass_both_present + resource: + type: "Microsoft.ContainerInstance/containerGroups" + name: "cg-full-diag" + properties: + diagnostics: + logAnalytics: + workspaceId: "existing-id" + workspaceKey: "existing-key" + parameters: + workspaceId: "ws-id" + workspaceKey: "ws-key" + want_undefined: true + + # ========================================================================= + # Wrong type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "not-container" + properties: {} + parameters: + workspaceId: "ws-id" + workspaceKey: "ws-key" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_cosmos_firewall_audit.yaml b/tests/azure_policy/cases/e2e_cosmos_firewall_audit.yaml new file mode 100644 index 0000000..de1144b --- /dev/null +++ b/tests/azure_policy/cases/e2e_cosmos_firewall_audit.yaml @@ -0,0 +1,238 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Cosmos DB/Cosmos_NetworkRulesExist_Audit +# Real Azure Policy: "Azure Cosmos DB accounts should have firewall rules" +# Source: regolator/policyDefinitions/Cosmos DB/Cosmos_NetworkRulesExist_Audit.json +# +# Features exercised: +# - 4-level nesting: allOf → anyOf → allOf → anyOf +# - 3 separate count expressions (ipRules, privateEndpointConnections) +# - exists "false" checks +# - count field without where (plain count) +# - count with where clause (privateLinkServiceConnectionState.status) +# - Deeply nested sub-resource array alias + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Azure Cosmos DB accounts should have firewall rules", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "String", + "allowedValues": ["Audit", "Deny", "Disabled"], + "defaultValue": "Deny" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.DocumentDB/databaseAccounts" + }, + { + "anyOf": [ + { + "field": "Microsoft.DocumentDB/databaseAccounts/publicNetworkAccess", + "exists": "false" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/publicNetworkAccess", + "equals": "Enabled" + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.DocumentDB/databaseAccounts/isVirtualNetworkFilterEnabled", + "exists": "false" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/isVirtualNetworkFilterEnabled", + "equals": "false" + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.DocumentDB/databaseAccounts/ipRules", + "exists": "false" + }, + { + "count": { + "field": "Microsoft.DocumentDB/databaseAccounts/ipRules[*]" + }, + "equals": 0 + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.DocumentDB/databaseAccounts/ipRangeFilter", + "exists": "false" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/ipRangeFilter", + "equals": "" + } + ] + }, + { + "anyOf": [ + { + "count": { + "field": "Microsoft.DocumentDB/databaseAccounts/privateEndpointConnections[*]", + "where": { + "field": "Microsoft.DocumentDB/databaseAccounts/privateEndpointConnections[*].privateLinkServiceConnectionState.status", + "equals": "Approved" + } + }, + "less": 1 + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Fully unprotected (public, no vnet filter, no ip rules, no PE) → deny + # ========================================================================= + + - note: deny_completely_open + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-open" + properties: + publicNetworkAccess: "Enabled" + isVirtualNetworkFilterEnabled: false + ipRules: [] + ipRangeFilter: "" + privateEndpointConnections: [] + want_effect: "Deny" + + - note: deny_public_access_missing_fields + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-defaults" + properties: {} + want_effect: "Deny" + + # ========================================================================= + # Protected by disabling public access → pass + # ========================================================================= + + - note: pass_public_access_disabled + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-private" + properties: + publicNetworkAccess: "Disabled" + want_undefined: true + + # ========================================================================= + # Protected by vnet filter → pass + # ========================================================================= + + - note: pass_vnet_filter_enabled + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-vnet" + properties: + publicNetworkAccess: "Enabled" + isVirtualNetworkFilterEnabled: true + want_undefined: true + + # ========================================================================= + # Protected by IP rules → pass + # ========================================================================= + + - note: pass_has_ip_rules + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-ip" + properties: + publicNetworkAccess: "Enabled" + isVirtualNetworkFilterEnabled: false + ipRules: + - ipAddressOrRange: "10.0.0.1" + want_undefined: true + + - note: pass_has_ip_range_filter + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-iprange" + properties: + publicNetworkAccess: "Enabled" + isVirtualNetworkFilterEnabled: false + ipRules: [] + ipRangeFilter: "10.0.0.0/24" + want_undefined: true + + # ========================================================================= + # Protected by approved private endpoint → pass + # ========================================================================= + + - note: pass_approved_private_endpoint + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-pe" + properties: + publicNetworkAccess: "Enabled" + isVirtualNetworkFilterEnabled: false + ipRules: [] + ipRangeFilter: "" + privateEndpointConnections: + - properties: + privateLinkServiceConnectionState: + status: "Approved" + want_undefined: true + + # ========================================================================= + # Private endpoint exists but not approved → deny + # ========================================================================= + + - note: deny_pending_private_endpoint + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-pe-pending" + properties: + publicNetworkAccess: "Enabled" + isVirtualNetworkFilterEnabled: false + ipRules: [] + ipRangeFilter: "" + privateEndpointConnections: + - properties: + privateLinkServiceConnectionState: + status: "Pending" + want_effect: "Deny" + + # ========================================================================= + # Wrong type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "not-cosmos" + properties: + publicNetworkAccess: "Enabled" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_cosmos_locations_deny.yaml b/tests/azure_policy/cases/e2e_cosmos_locations_deny.yaml new file mode 100644 index 0000000..3bde3ad --- /dev/null +++ b/tests/azure_policy/cases/e2e_cosmos_locations_deny.yaml @@ -0,0 +1,170 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Cosmos DB/Cosmos_Locations_Deny +# Real Azure Policy: "Azure Cosmos DB allowed locations" +# Source: regolator/policyDefinitions/Cosmos DB/Cosmos_Locations_Deny.json +# +# Features exercised: +# - count with where clause +# - Chained template functions in where: replace(toLower(first(field(...))), ' ', '') +# - count result compared to length(field(...)) via notEquals +# - Parameterized effect with case-variant allowedValues + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Azure Cosmos DB allowed locations", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "listOfAllowedLocations": { + "type": "Array", + "metadata": { + "displayName": "Allowed locations", + "strongType": "location" + } + }, + "policyEffect": { + "type": "String", + "allowedValues": ["audit", "Audit", "deny", "Deny", "disabled", "Disabled"], + "defaultValue": "Deny" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.DocumentDB/databaseAccounts" + }, + { + "count": { + "field": "Microsoft.DocumentDB/databaseAccounts/Locations[*]", + "where": { + "value": "[replace(toLower(first(field('Microsoft.DocumentDB/databaseAccounts/Locations[*].locationName'))), ' ', '')]", + "in": "[parameters('listOfAllowedLocations')]" + } + }, + "notEquals": "[length(field('Microsoft.DocumentDB/databaseAccounts/Locations[*]'))]" + } + ] + }, + "then": { + "effect": "[parameters('policyEffect')]" + } + } + } + } + +cases: + # ========================================================================= + # All locations allowed → pass + # ========================================================================= + + - note: pass_all_locations_in_allowed_list + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-compliant" + properties: + Locations: + - locationName: "East US" + - locationName: "West US" + parameters: + listOfAllowedLocations: ["eastus", "westus"] + want_undefined: true + + - note: pass_single_location_allowed + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-single" + properties: + Locations: + - locationName: "East US" + parameters: + listOfAllowedLocations: ["eastus", "westus", "centralus"] + want_undefined: true + + # ========================================================================= + # Location not in allowed list → deny + # ========================================================================= + + - note: deny_location_not_allowed + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-bad-region" + properties: + Locations: + - locationName: "East US" + - locationName: "North Europe" + parameters: + listOfAllowedLocations: ["eastus", "westus"] + want_effect: "Deny" + + - note: deny_all_locations_disallowed + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-all-bad" + properties: + Locations: + - locationName: "South East Asia" + - locationName: "Japan East" + parameters: + listOfAllowedLocations: ["eastus", "westus"] + want_effect: "Deny" + + - note: deny_one_of_three_disallowed + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-one-bad" + properties: + Locations: + - locationName: "East US" + - locationName: "West US" + - locationName: "Brazil South" + parameters: + listOfAllowedLocations: ["eastus", "westus"] + want_effect: "Deny" + + # ========================================================================= + # Edge: location names with spaces normalized by replace+toLower + # ========================================================================= + + - note: pass_location_with_spaces_normalized + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-spaces" + properties: + Locations: + - locationName: "Central US" + parameters: + listOfAllowedLocations: ["centralus"] + want_undefined: true + + - note: pass_mixed_case_location + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-case" + properties: + Locations: + - locationName: "EAST US" + parameters: + listOfAllowedLocations: ["eastus"] + want_undefined: true + + # ========================================================================= + # Wrong type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "not-cosmos" + properties: + Locations: + - locationName: "East US" + parameters: + listOfAllowedLocations: ["westus"] + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_cosmos_max_throughput.yaml b/tests/azure_policy/cases/e2e_cosmos_max_throughput.yaml new file mode 100644 index 0000000..0e1d731 --- /dev/null +++ b/tests/azure_policy/cases/e2e_cosmos_max_throughput.yaml @@ -0,0 +1,445 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Cosmos DB/Cosmos_MaxThroughput_Deny +# Real Azure Policy: "Azure Cosmos DB throughput should be limited" +# Source: regolator/policyDefinitions/Cosmos DB/Cosmos_MaxThroughput_Deny.json +# +# Features exercised: +# - allOf with nested anyOf (type check + condition check) +# - type "like" pattern matching (*/throughputSettings) +# - type "in" with 9 resource types +# - Template expressions: if(), equals(), int(), field() +# - containsKey operator +# - exists operator +# - greater operator with parameterized threshold +# - Parameters: throughputMax (Integer), effect (String) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Azure Cosmos DB throughput should be limited", + "policyType": "BuiltIn", + "mode": "All", + "description": "This policy enables you to restrict the maximum throughput your organization can specify when creating Azure Cosmos DB databases and containers through the resource provider. It blocks the creation of autoscale resources.", + "metadata": { + "version": "1.1.0", + "category": "Cosmos DB" + }, + "version": "1.1.0", + "parameters": { + "throughputMax": { + "type": "Integer", + "metadata": { + "displayName": "Max RUs", + "description": "The maximum throughput (RU/s) that can be assigned to a container via the Resource Provider during create or update." + } + }, + "effect": { + "type": "String", + "metadata": { + "displayName": "Policy Effect", + "description": "The desired effect of the policy." + }, + "allowedValues": [ + "audit", + "Audit", + "deny", + "Deny", + "disabled", + "Disabled" + ], + "defaultValue": "Deny" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "anyOf": [ + { + "field": "type", + "like": "Microsoft.DocumentDB/databaseAccounts/*/throughputSettings" + }, + { + "field": "type", + "in": [ + "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", + "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", + "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases", + "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections", + "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases", + "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs", + "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces", + "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables", + "Microsoft.DocumentDB/databaseAccounts/tables" + ] + } + ] + }, + { + "anyOf": [ + { + "value": "[requestContext().apiVersion]", + "less": "2019-08-01" + }, + { + "value": "[if(equals(field('Microsoft.DocumentDB/databaseAccounts/sqlDatabases/options.throughput'), ''), 0, int(field('Microsoft.DocumentDB/databaseAccounts/sqlDatabases/options.throughput')))]", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/options", + "containsKey": "ProvisionedThroughputSettings" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/throughputSettings/default.resource.throughput", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/throughputSettings/default.resource.provisionedThroughputSettings", + "exists": "true" + }, + { + "value": "[if(equals(field('Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/options.throughput'), ''), 0, int(field('Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/options.throughput')))]", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/options", + "containsKey": "ProvisionedThroughputSettings" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/throughputSettings/default.resource.throughput", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/throughputSettings/default.resource.provisionedThroughputSettings", + "exists": "true" + }, + { + "value": "[if(equals(field('Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/options.throughput'), ''), 0, int(field('Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/options.throughput')))]", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/options", + "containsKey": "ProvisionedThroughputSettings" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/throughputSettings/default.resource.throughput", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/throughputSettings/default.resource.provisionedThroughputSettings", + "exists": "true" + }, + { + "value": "[if(equals(field('Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables/options.throughput'), ''), 0, int(field('Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables/options.throughput')))]", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables/options", + "containsKey": "ProvisionedThroughputSettings" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables/throughputSettings/default.resource.throughput", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables/throughputSettings/default.resource.provisionedThroughputSettings", + "exists": "true" + }, + { + "value": "[if(equals(field('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/options.throughput'), ''), 0, int(field('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/options.throughput')))]", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/options", + "containsKey": "ProvisionedThroughputSettings" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/throughputSettings/default.resource.throughput", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/throughputSettings/default.resource.provisionedThroughputSettings", + "exists": "true" + }, + { + "value": "[if(equals(field('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs/options.throughput'), ''), 0, int(field('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs/options.throughput')))]", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs/options", + "containsKey": "ProvisionedThroughputSettings" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs/throughputSettings/default.resource.throughput", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs/throughputSettings/default.resource.provisionedThroughputSettings", + "exists": "true" + }, + { + "value": "[if(equals(field('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/options.throughput'), ''), 0, int(field('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/options.throughput')))]", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/options", + "containsKey": "ProvisionedThroughputSettings" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/throughputSettings/default.resource.throughput", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/throughputSettings/default.resource.provisionedThroughputSettings", + "exists": "true" + }, + { + "value": "[if(equals(field('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections/options.throughput'), ''), 0, int(field('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections/options.throughput')))]", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections/options", + "containsKey": "ProvisionedThroughputSettings" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections/throughputSettings/default.resource.throughput", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections/throughputSettings/default.resource.provisionedThroughputSettings", + "exists": "true" + }, + { + "value": "[if(equals(field('Microsoft.DocumentDB/databaseAccounts/tables/options.throughput'), ''), 0, int(field('Microsoft.DocumentDB/databaseAccounts/tables/options.throughput')))]", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/tables/options", + "containsKey": "ProvisionedThroughputSettings" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/tables/throughputSettings/default.resource.throughput", + "greater": "[parameters('throughputMax')]" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/tables/throughputSettings/default.resource.provisionedThroughputSettings", + "exists": "true" + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + }, + "versions": [ + "1.1.0" + ] + }, + "id": "/providers/Microsoft.Authorization/policyDefinitions/0b7ef78e-a035-4f23-b9bd-aff122a1b1cf", + "name": "0b7ef78e-a035-4f23-b9bd-aff122a1b1cf" + } + +cases: + # ========================================================================= + # SQL Database: options.throughput exceeds max → Deny + # ========================================================================= + + - note: deny_sql_db_throughput_exceeds_max + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases" + name: "test-db" + properties: + options: + throughput: "600" + want_effect: "Deny" + + # ========================================================================= + # SQL Database: options.throughput within max → pass + # ========================================================================= + + - note: pass_sql_db_throughput_within_max + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases" + name: "test-db-ok" + properties: + options: + throughput: "200" + want_undefined: true + + # ========================================================================= + # SQL Database: autoscale (ProvisionedThroughputSettings key) → Deny + # ========================================================================= + + - note: deny_sql_db_autoscale + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases" + name: "test-db-autoscale" + properties: + options: + ProvisionedThroughputSettings: + maxThroughput: 4000 + want_effect: "Deny" + + # ========================================================================= + # Container: options.throughput exceeds max → Deny + # ========================================================================= + + - note: deny_container_throughput_exceeds_max + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" + name: "test-container" + properties: + options: + throughput: "600" + want_effect: "Deny" + + # ========================================================================= + # Wrong type (parent databaseAccounts, not a sub-resource) → pass + # ========================================================================= + + - note: pass_wrong_type + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "test-account" + properties: {} + want_undefined: true + + # ========================================================================= + # MongoDB: throughputSettings throughput exceeds max → Deny + # ========================================================================= + + - note: deny_mongodb_throughput_settings + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases" + name: "test-mongo" + properties: + "default": + resource: + throughput: 600 + want_effect: "Deny" + + # ========================================================================= + # Tables: throughputSettings autoscale (provisionedThroughputSettings) → Deny + # ========================================================================= + + - note: deny_table_autoscale + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/tables" + name: "test-table" + properties: + "default": + resource: + provisionedThroughputSettings: + maxThroughput: 4000 + want_effect: "Deny" + + # ========================================================================= + # Container: empty options.throughput → evaluates to 0 → pass + # ========================================================================= + + - note: pass_container_no_throughput + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers" + name: "test-container-empty" + properties: + options: + throughput: "" + want_undefined: true + + # ========================================================================= + # Cassandra Keyspace: options.throughput exceeds max → Deny + # ========================================================================= + + - note: deny_cassandra_keyspace_throughput + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces" + name: "test-cassandra-ks" + properties: + options: + throughput: "600" + want_effect: "Deny" + + # ========================================================================= + # Cassandra Table: options.throughput exceeds max → Deny + # ========================================================================= + + - note: deny_cassandra_table_throughput + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables" + name: "test-cassandra-table" + properties: + options: + throughput: "600" + want_effect: "Deny" + + # ========================================================================= + # Gremlin Database: options.throughput exceeds max → Deny + # ========================================================================= + + - note: deny_gremlin_database_throughput + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases" + name: "test-gremlin-db" + properties: + options: + throughput: "600" + want_effect: "Deny" + + # ========================================================================= + # Gremlin Graph: autoscale (ProvisionedThroughputSettings key) → Deny + # ========================================================================= + + - note: deny_gremlin_graph_autoscale + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs" + name: "test-gremlin-graph" + properties: + options: + ProvisionedThroughputSettings: + maxThroughput: 4000 + want_effect: "Deny" + + # ========================================================================= + # MongoDB Collection: options.throughput exceeds max → Deny + # ========================================================================= + + - note: deny_mongo_collection_throughput + parameters: + throughputMax: 400 + resource: + type: "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections" + name: "test-mongo-collection" + properties: + options: + throughput: "600" + want_effect: "Deny" diff --git a/tests/azure_policy/cases/e2e_cosmos_private_modify.yaml b/tests/azure_policy/cases/e2e_cosmos_private_modify.yaml new file mode 100644 index 0000000..6c2d769 --- /dev/null +++ b/tests/azure_policy/cases/e2e_cosmos_private_modify.yaml @@ -0,0 +1,118 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Cosmos DB/Cosmos_PrivateNetworkAccess_Modify +# Real Azure Policy: "Configure CosmosDB accounts to disable public network access" +# Source: regolator/policyDefinitions/Cosmos DB/Cosmos_PrivateNetworkAccess_Modify.json +# +# Features exercised: +# - Modify effect with requestContext().apiVersion condition on operation +# - greaterOrEquals on API version string +# - conflictEffect in details + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Configure CosmosDB accounts to disable public network access", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "String", + "allowedValues": ["Modify", "Disabled"], + "defaultValue": "Modify" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.DocumentDB/databaseAccounts" + }, + { + "field": "Microsoft.DocumentDB/databaseAccounts/publicNetworkAccess", + "notEquals": "Disabled" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c", + "/providers/Microsoft.Authorization/roleDefinitions/5bd9cd88-fe45-4216-938b-f97437e15450" + ], + "conflictEffect": "audit", + "operations": [ + { + "condition": "[greaterOrEquals(requestContext().apiVersion, '2021-01-15')]", + "operation": "addOrReplace", + "field": "Microsoft.DocumentDB/databaseAccounts/publicNetworkAccess", + "value": "Disabled" + } + ] + } + } + } + } + } + +cases: + # ========================================================================= + # Public access enabled → modify + # ========================================================================= + + - note: modify_public_access_enabled + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-public" + properties: + publicNetworkAccess: "Enabled" + api_version: "2023-04-15" + want_effect: "Modify" + want_details: + roleDefinitionIds: + - "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + - "/providers/Microsoft.Authorization/roleDefinitions/5bd9cd88-fe45-4216-938b-f97437e15450" + operations: + - condition: "[greaterOrEquals(requestContext().apiVersion, '2021-01-15')]" + operation: "addOrReplace" + field: "Microsoft.DocumentDB/databaseAccounts/publicNetworkAccess" + value: "Disabled" + + - note: modify_public_access_missing + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-no-field" + properties: {} + api_version: "2023-04-15" + want_effect: "Modify" + + # ========================================================================= + # Public access already disabled → pass + # ========================================================================= + + - note: pass_public_access_disabled + resource: + type: "Microsoft.DocumentDB/databaseAccounts" + name: "cosmos-disabled" + properties: + publicNetworkAccess: "Disabled" + api_version: "2023-04-15" + want_undefined: true + + # ========================================================================= + # Wrong type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "not-cosmos" + properties: + publicNetworkAccess: "Enabled" + api_version: "2023-04-15" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_custom_owner_role.yaml b/tests/azure_policy/cases/e2e_custom_owner_role.yaml new file mode 100644 index 0000000..48994b9 --- /dev/null +++ b/tests/azure_policy/cases/e2e_custom_owner_role.yaml @@ -0,0 +1,231 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: General/CustomSubscription_OwnerRole_Audit +# Real Azure Policy: "[Deprecated]: Custom subscription owner roles should not exist" +# Source: regolator/policyDefinitions/General/CustomSubscription_OwnerRole_Audit.json +# +# Features exercised: +# - 4 double-negation blocks: not { field notEquals }, not { field notIn }, not { field notLike } +# - Array wildcard aliases: permissions[*].actions[*], assignableScopes[*] +# - subscription().id and concat(subscription().id, '/') +# - notLike "/providers/Microsoft.Management/*" +# - Deeply nested sub-resource arrays (permissions[*].actions[*]) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "[Deprecated]: Custom subscription owner roles should not exist", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "Audit", + "allowedValues": ["Audit", "Disabled"] + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Authorization/roleDefinitions" + }, + { + "field": "Microsoft.Authorization/roleDefinitions/type", + "equals": "CustomRole" + }, + { + "anyOf": [ + { + "not": { + "field": "Microsoft.Authorization/roleDefinitions/permissions[*].actions[*]", + "notEquals": "*" + } + } + ] + }, + { + "anyOf": [ + { + "not": { + "field": "Microsoft.Authorization/roleDefinitions/assignableScopes[*]", + "notIn": [ + "[concat(subscription().id,'/')]", + "[subscription().id]", + "/" + ] + } + }, + { + "not": { + "field": "Microsoft.Authorization/roleDefinitions/assignableScopes[*]", + "notLike": "/providers/Microsoft.Management/*" + } + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Custom owner role with subscription scope → audit + # ========================================================================= + + - note: audit_custom_owner_subscription_scope + resource: + type: "Microsoft.Authorization/roleDefinitions" + name: "custom-owner" + properties: + type: "CustomRole" + permissions: + - actions: + - "*" + assignableScopes: + - "/subscriptions/sub-123" + context: + subscription: + subscriptionId: "sub-123" + id: "/subscriptions/sub-123" + want_effect: "Audit" + + - note: audit_custom_owner_subscription_trailing_slash + resource: + type: "Microsoft.Authorization/roleDefinitions" + name: "custom-owner-slash" + properties: + type: "CustomRole" + permissions: + - actions: + - "*" + assignableScopes: + - "/subscriptions/sub-123/" + context: + subscription: + subscriptionId: "sub-123" + id: "/subscriptions/sub-123" + want_effect: "Audit" + + - note: audit_custom_owner_root_scope + resource: + type: "Microsoft.Authorization/roleDefinitions" + name: "custom-owner-root" + properties: + type: "CustomRole" + permissions: + - actions: + - "*" + assignableScopes: + - "/" + context: + subscription: + subscriptionId: "sub-123" + id: "/subscriptions/sub-123" + want_effect: "Audit" + + - note: audit_custom_owner_management_group_scope + resource: + type: "Microsoft.Authorization/roleDefinitions" + name: "custom-owner-mg" + properties: + type: "CustomRole" + permissions: + - actions: + - "*" + assignableScopes: + - "/providers/Microsoft.Management/managementGroups/mg1" + context: + subscription: + subscriptionId: "sub-123" + id: "/subscriptions/sub-123" + want_effect: "Audit" + + # ========================================================================= + # Custom role without owner (*) actions → pass + # ========================================================================= + + - note: pass_custom_role_no_wildcard_actions + resource: + type: "Microsoft.Authorization/roleDefinitions" + name: "custom-reader" + properties: + type: "CustomRole" + permissions: + - actions: + - "Microsoft.Compute/virtualMachines/read" + - "Microsoft.Storage/storageAccounts/read" + assignableScopes: + - "/subscriptions/sub-123" + context: + subscription: + subscriptionId: "sub-123" + id: "/subscriptions/sub-123" + want_undefined: true + + # ========================================================================= + # Custom owner role but NOT scoped to subscription/root/MG → pass + # ========================================================================= + + - note: pass_custom_owner_resource_group_scope + resource: + type: "Microsoft.Authorization/roleDefinitions" + name: "custom-owner-rg" + properties: + type: "CustomRole" + permissions: + - actions: + - "*" + assignableScopes: + - "/subscriptions/sub-123/resourceGroups/rg1" + context: + subscription: + subscriptionId: "sub-123" + id: "/subscriptions/sub-123" + want_undefined: true + + # ========================================================================= + # BuiltIn role (not CustomRole) → pass + # ========================================================================= + + - note: pass_builtin_role + resource: + type: "Microsoft.Authorization/roleDefinitions" + name: "builtin-owner" + properties: + type: "BuiltInRole" + permissions: + - actions: + - "*" + assignableScopes: + - "/" + context: + subscription: + subscriptionId: "sub-123" + id: "/subscriptions/sub-123" + want_undefined: true + + # ========================================================================= + # Wrong resource type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "not-role-def" + properties: + type: "CustomRole" + context: + subscription: + subscriptionId: "sub-123" + id: "/subscriptions/sub-123" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_datafactory_linked_secrets.yaml b/tests/azure_policy/cases/e2e_datafactory_linked_secrets.yaml new file mode 100644 index 0000000..fb8596f --- /dev/null +++ b/tests/azure_policy/cases/e2e_datafactory_linked_secrets.yaml @@ -0,0 +1,458 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Data Factory/LinkedService_InlineSecrets_Audit +# Real Azure Policy: "Azure Data Factory linked services should use Key Vault for storing secrets" +# Source: regolator/policyDefinitions/Data Factory/LinkedService_InlineSecrets_Audit.json +# +# Features exercised: +# - Large anyOf with 20+ branches +# - "contains" operator for secret keywords in connectionString +# - "exists" checks for secret fields +# - "equals" / "notEquals" / "in" checks for .type field (SecureString vs AzureKeyVaultSecret) +# - Service-type-prefixed aliases (SqlServer., AzureStorage., etc.) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Azure Data Factory linked services should use Key Vault for storing secrets", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "String", + "allowedValues": ["Audit", "Deny", "Disabled"], + "defaultValue": "Audit" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.DataFactory/factories/linkedservices" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.DataFactory/factories/linkedservices/typeProperties.connectionString", + "exists": "true" + }, + { + "anyOf": [ + { + "field": "Microsoft.DataFactory/factories/linkedservices/typeProperties.connectionString", + "contains": "AccountKey=" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/typeProperties.connectionString", + "contains": "PWD=" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/typeProperties.connectionString", + "contains": "Password=" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/typeProperties.connectionString", + "contains": "CredString=" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/typeProperties.connectionString", + "contains": "pwd=" + } + ] + } + ] + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/SqlServer.typeProperties.password.type", + "equals": "SecureString" + }, + { + "allOf": [ + { + "field": "Microsoft.DataFactory/factories/linkedservices/SqlServer.typeProperties.password", + "exists": "true" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/SqlServer.typeProperties.password.type", + "exists": "false" + } + ] + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/AzureSqlDW.typeProperties.servicePrincipalKey.type", + "equals": "SecureString" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/AzureSearch.typeProperties.key.type", + "equals": "SecureString" + }, + { + "allOf": [ + { + "field": "Microsoft.DataFactory/factories/linkedservices/AzureStorage.typeProperties.sasUri", + "exists": "true" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/AzureStorage.typeProperties.sasUri.type", + "notEquals": "AzureKeyVaultSecret" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.DataFactory/factories/linkedservices/AzureBlobStorage.typeProperties.servicePrincipalKey", + "exists": "true" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/AzureBlobStorage.typeProperties.servicePrincipalKey.type", + "notEquals": "AzureKeyVaultSecret" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.DataFactory/factories/linkedservices/AzureStorage.typeProperties.accountKey", + "exists": "true" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/CosmosDb.typeProperties.accountKey.type", + "notEquals": "AzureKeyVaultSecret" + } + ] + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/typeProperties.encryptedCredential", + "exists": "true" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/AmazonMWS.typeProperties.mwsAuthToken.type", + "equals": "SecureString" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/AmazonMWS.typeProperties.secretKey.type", + "equals": "SecureString" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/AmazonS3.typeProperties.secretAccessKey.type", + "equals": "SecureString" + }, + { + "allOf": [ + { + "field": "Microsoft.DataFactory/factories/linkedservices/Dynamics.typeProperties.servicePrincipalCredential", + "exists": "true" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/Dynamics.typeProperties.servicePrincipalCredential.type", + "equals": "SecureString" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.DataFactory/factories/linkedservices/Hubspot.typeProperties.accessToken", + "exists": "true" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/Hubspot.typeProperties.accessToken.type", + "equals": "SecureString" + } + ] + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/Odbc.typeProperties.credential.type", + "equals": "SecureString" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/GoogleAdWords.typeProperties.developerToken.type", + "equals": "SecureString" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/GoogleBigQuery.typeProperties.clientSecret.type", + "equals": "SecureString" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/GoogleBigQuery.typeProperties.refreshToken.type", + "equals": "SecureString" + }, + { + "allOf": [ + { + "field": "Microsoft.DataFactory/factories/linkedservices/type", + "in": [ + "MongoDbAtlas", + "MongoDbV2" + ] + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/typeProperties.connectionString.type", + "notEquals": "AzureKeyVaultSecret" + } + ] + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/OData.typeProperties.servicePrincipalEmbeddedCert.type", + "equals": "SecureString" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/OData.typeProperties.servicePrincipalEmbeddedCertPassword.type", + "equals": "SecureString" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/Sftp.typeProperties.privateKeyContent.type", + "equals": "SecureString" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/Sftp.typeProperties.passPhrase.type", + "equals": "SecureString" + }, + { + "field": "Microsoft.DataFactory/factories/linkedservices/Salesforce.typeProperties.securityToken.type", + "equals": "SecureString" + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # connectionString contains "Password=" → Audit + # ========================================================================= + + - note: audit_connection_string_with_password + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-sql-inline-password" + properties: + type: SqlServer + typeProperties: + connectionString: "Server=myserver.database.windows.net;Database=mydb;User ID=admin;Password=secret123" + want_effect: "Audit" + + # ========================================================================= + # connectionString without secret keywords → pass + # ========================================================================= + + - note: pass_connection_string_no_secrets + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-sql-integrated" + properties: + type: SqlServer + typeProperties: + connectionString: "Server=myserver.database.windows.net;Database=mydb;Integrated Security=true" + want_undefined: true + + # ========================================================================= + # SqlServer password.type = SecureString → Audit + # ========================================================================= + + - note: audit_sql_server_secure_string + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-sql-securestring" + properties: + type: SqlServer + typeProperties: + connectionString: "Server=myserver.database.windows.net;Database=mydb;Integrated Security=true" + password: + type: SecureString + value: "my-password" + want_effect: "Audit" + + # ========================================================================= + # SqlServer password.type = AzureKeyVaultSecret → pass + # ========================================================================= + + - note: pass_sql_server_keyvault + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-sql-keyvault" + properties: + type: SqlServer + typeProperties: + connectionString: "Server=myserver.database.windows.net;Database=mydb;Integrated Security=true" + password: + type: AzureKeyVaultSecret + store: + referenceName: myKeyVault + type: LinkedServiceReference + secretName: mySecret + want_undefined: true + + # ========================================================================= + # encryptedCredential exists → Audit + # ========================================================================= + + - note: audit_encrypted_credential + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-encrypted" + properties: + type: AzureBlobStorage + typeProperties: + connectionString: "DefaultEndpointsProtocol=https;EndpointSuffix=core.windows.net" + encryptedCredential: "eyJWZXJzaW9uIj..." + want_effect: "Audit" + + # ========================================================================= + # No secrets at all → pass + # ========================================================================= + + - note: pass_no_secrets + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-clean" + properties: + type: AzureBlobFS + typeProperties: + url: "https://mydatalake.dfs.core.windows.net" + want_undefined: true + + # ========================================================================= + # Wrong resource type → pass + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "not-data-factory" + properties: + supportsHttpsTrafficOnly: true + want_undefined: true + + # ========================================================================= + # AzureStorage sasUri exists but type not AzureKeyVaultSecret → Audit + # ========================================================================= + + - note: audit_storage_sas_no_keyvault + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-storage-inline-sas" + properties: + type: AzureStorage + typeProperties: + sasUri: "https://mystorage.blob.core.windows.net/?sv=2020-08-04&ss=b&srt=sco&sp=rwdlacupx" + want_effect: "Audit" + + # ========================================================================= + # AzureSqlDW servicePrincipalKey.type = SecureString → Audit + # ========================================================================= + + - note: audit_azure_sql_dw_spkey_securestring + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-sqldw-spkey" + properties: + type: AzureSqlDW + typeProperties: + connectionString: "Server=myserver.database.windows.net;Database=mydb;Integrated Security=true" + servicePrincipalKey: + type: SecureString + value: "my-sp-key" + want_effect: "Audit" + + # ========================================================================= + # AzureStorage accountKey exists + type != AzureKeyVaultSecret → Audit + # ========================================================================= + + - note: audit_storage_accountkey_not_keyvault + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-storage-accountkey" + properties: + type: AzureStorage + typeProperties: + connectionString: "DefaultEndpointsProtocol=https;EndpointSuffix=core.windows.net" + accountKey: + type: SecureString + value: "base64accountkey==" + want_effect: "Audit" + + # ========================================================================= + # MongoDbAtlas connectionString.type != AzureKeyVaultSecret → Audit + # ========================================================================= + + - note: audit_mongodbatlas_connstr_not_keyvault + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-mongodbatlas-inline" + properties: + type: MongoDbAtlas + typeProperties: + connectionString: + type: SecureString + value: "mongodb+srv://user:pass@cluster0.mongodb.net/mydb" + database: mydb + want_effect: "Audit" + + # ========================================================================= + # AmazonS3 secretAccessKey.type = SecureString → Audit + # ========================================================================= + + - note: audit_amazon_s3_secret_securestring + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-amazons3-secret" + properties: + type: AmazonS3 + typeProperties: + accessKeyId: "AKIAIOSFODNN7EXAMPLE" + secretAccessKey: + type: SecureString + value: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + want_effect: "Audit" + + # ========================================================================= + # Sftp privateKeyContent.type = SecureString → Audit + # ========================================================================= + + - note: audit_sftp_privatekey_securestring + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-sftp-privatekey" + properties: + type: Sftp + typeProperties: + host: "sftp.example.com" + userName: "sftpuser" + privateKeyContent: + type: SecureString + value: "-----BEGIN RSA PRIVATE KEY-----..." + want_effect: "Audit" + + # ========================================================================= + # Odbc credential.type = AzureKeyVaultSecret → pass (not SecureString) + # ========================================================================= + + - note: pass_odbc_credential_keyvault + resource: + type: "Microsoft.DataFactory/factories/linkedservices" + name: "adf-odbc-keyvault" + properties: + type: Odbc + typeProperties: + connectionString: "Driver={SQL Server};Server=myserver;Database=mydb" + credential: + type: AzureKeyVaultSecret + store: + referenceName: myKeyVault + type: LinkedServiceReference + secretName: odbcCredential + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_dcra_vmss_linux_dine.yaml b/tests/azure_policy/cases/e2e_dcra_vmss_linux_dine.yaml new file mode 100644 index 0000000..2b2be11 --- /dev/null +++ b/tests/azure_policy/cases/e2e_dcra_vmss_linux_dine.yaml @@ -0,0 +1,757 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Monitoring/AzureMonitor_DCRA_VMSS_Linux_DINE +# Real Azure Policy: "Configure Linux Virtual Machine Scale Sets to be associated +# with a Data Collection Rule or a Data Collection Endpoint" +# Source: regolator/policyDefinitions/Monitoring/AzureMonitor_DCRA_VMSS_Linux_DINE.json +# +# Features exercised: +# - 78 condition nodes, 611 lines — representative of ~30 Monitoring/* policies +# - Boolean parameter (scopeToSupportedImages) +# - Polymorphic resourceType parameter (DCR vs DCE) +# - DeployIfNotExists with conditional deployment +# - existenceCondition with anyOf +# - Large hardcoded region list (60+ locations) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Configure Linux Virtual Machine Scale Sets to be associated with a Data Collection Rule or a Data Collection Endpoint", + "policyType": "BuiltIn", + "mode": "Indexed", + "description": "Deploy Association to link Linux virtual machine scale sets to the specified Data Collection Rule or the specified Data Collection Endpoint. The list of locations and OS images are updated over time as support is increased.", + "parameters": { + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy." + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + }, + "scopeToSupportedImages": { + "type": "Boolean", + "metadata": { + "displayName": "Scope Policy to Azure Monitor Agent-Supported Operating Systems", + "description": "If set to true, the policy will apply only to virtual machine scale sets with AMA-supported operating systems. Otherwise, the policy will apply to all virtual machine scale set resources in the assignment scope. For supported operating systems, see https://aka.ms/AMAOverview." + }, + "allowedValues": [ + true, + false + ], + "defaultValue": true + }, + "listOfLinuxImageIdToInclude": { + "type": "Array", + "metadata": { + "displayName": "Additional Linux Machine Images", + "description": "List of virtual machine scale set images that have supported Linux OS to add to scope. Example values: '/subscriptions//resourceGroups/YourResourceGroup/providers/Microsoft.Compute/images/ContosoStdImage'" + }, + "defaultValue": [] + }, + "dcrResourceId": { + "type": "String", + "metadata": { + "displayName": "Data Collection Rule Resource Id or Data Collection Endpoint Resource Id", + "description": "Resource Id of the Data Collection Rule or the Data Collection Endpoint to be applied on the Linux machines in scope.", + "portalReview": "true", + "assignPermissions": true + } + }, + "resourceType": { + "type": "String", + "metadata": { + "displayName": "Resource Type", + "description": "Either a Data Collection Rule (DCR) or a Data Collection Endpoint (DCE)", + "portalReview": "true" + }, + "allowedValues": [ + "Microsoft.Insights/dataCollectionRules", + "Microsoft.Insights/dataCollectionEndpoints" + ], + "defaultValue": "Microsoft.Insights/dataCollectionRules" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachineScaleSets" + }, + { + "field": "location", + "in": [ + "australiacentral", + "australiacentral2", + "australiaeast", + "australiasoutheast", + "brazilsouth", + "brazilsoutheast", + "canadacentral", + "canadaeast", + "centralindia", + "centralus", + "centraluseuap", + "eastasia", + "eastus", + "eastus2", + "eastus2euap", + "francecentral", + "francesouth", + "germanynorth", + "germanywestcentral", + "israelcentral", + "italynorth", + "japaneast", + "japanwest", + "jioindiacentral", + "jioindiawest", + "koreacentral", + "koreasouth", + "malaysiasouth", + "mexicocentral", + "northcentralus", + "northeurope", + "norwayeast", + "norwaywest", + "polandcentral", + "qatarcentral", + "southafricanorth", + "southafricawest", + "southcentralus", + "southeastasia", + "southindia", + "spaincentral", + "swedencentral", + "swedensouth", + "switzerlandnorth", + "switzerlandwest", + "taiwannorth", + "taiwannorthwest", + "uaecentral", + "uaenorth", + "uksouth", + "ukwest", + "westcentralus", + "westeurope", + "westindia", + "westus", + "westus2", + "westus3" + ] + }, + { + "anyOf": [ + { + "allOf": [ + { + "value": "[parameters('scopeToSupportedImages')]", + "equals": false + }, + { + "field": "Microsoft.Compute/virtualMachineScaleSets/virtualMachineProfile.storageProfile.osDisk.osType", + "like": "Linux*" + } + ] + }, + { + "field": "Microsoft.Compute/imageId", + "in": "[parameters('listOfLinuxImageIdToInclude')]" + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "RedHat" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "RHEL", + "RHEL-ARM64", + "RHEL-BYOS", + "RHEL-HA", + "RHEL-SAP", + "RHEL-SAP-APPS", + "RHEL-SAP-HA" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSku", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "9*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "rhel-lvm7*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "rhel-lvm8*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "rhel-lvm9*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "SUSE" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "SLES", + "SLES-HPC", + "SLES-HPC-Priority", + "SLES-SAP", + "SLES-SAP-BYOS", + "SLES-Priority", + "SLES-BYOS", + "SLES-SAPCAL", + "SLES-Standard" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSku", + "like": "12*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "15*" + } + ] + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-12*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "sles-15*" + } + ] + }, + { + "field": "Microsoft.Compute/imageSku", + "in": [ + "gen1", + "gen2" + ] + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Canonical" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "UbuntuServer" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "0001-com-ubuntu-server-*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "0001-com-ubuntu-pro-*" + } + ] + }, + { + "field": "Microsoft.Compute/imageSku", + "in": [ + "14.04.0-lts", + "14.04.1-lts", + "14.04.2-lts", + "14.04.3-lts", + "14.04.4-lts", + "14.04.5-lts", + "16_04_0-lts-gen2", + "16_04-lts-gen2", + "16.04-lts", + "16.04.0-lts", + "18_04-lts-arm64", + "18_04-lts-gen2", + "18.04-lts", + "20_04-lts-arm64", + "20_04-lts-gen2", + "20_04-lts", + "22_04-lts-gen2", + "22_04-lts", + "pro-16_04-lts-gen2", + "pro-16_04-lts", + "pro-18_04-lts-gen2", + "pro-18_04-lts", + "pro-20_04-lts-gen2", + "pro-20_04-lts", + "pro-22_04-lts-gen2", + "pro-22_04-lts" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Oracle" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "Oracle-Linux" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSku", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "ol7*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "ol8*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "ol9*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "OpenLogic" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "CentOS", + "Centos-LVM", + "CentOS-SRIOV" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSku", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "8*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "cloudera" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "cloudera-centos-os" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "7*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "almalinux" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "almalinux*" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSku", + "like": "8*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "9*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "ctrliqinc1648673227698" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "rocky-8*" + }, + { + "field": "Microsoft.Compute/imageSku", + "like": "rocky-8*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "credativ" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "Debian" + ] + }, + { + "field": "Microsoft.Compute/imageSku", + "equals": "9" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Debian" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "debian-10", + "debian-11" + ] + }, + { + "field": "Microsoft.Compute/imageSku", + "in": [ + "10", + "10-gen2", + "11", + "11-gen2" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoftcblmariner" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "cbl-mariner" + }, + { + "field": "Microsoft.Compute/imageSku", + "in": [ + "1-gen2", + "cbl-mariner-1", + "cbl-mariner-2", + "cbl-mariner-2-arm64", + "cbl-mariner-2-gen2" + ] + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Insights/dataCollectionRuleAssociations", + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa", + "/providers/microsoft.authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "evaluationDelay": "AfterProvisioning", + "existenceCondition": { + "anyOf": [ + { + "field": "Microsoft.Insights/dataCollectionRuleAssociations/dataCollectionRuleId", + "equals": "[parameters('dcrResourceId')]" + }, + { + "field": "Microsoft.Insights/dataCollectionRuleAssociations/dataCollectionEndpointId", + "equals": "[parameters('dcrResourceId')]" + } + ] + }, + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceName": { + "type": "string" + }, + "location": { + "type": "string" + }, + "dcrResourceId": { + "type": "string" + }, + "resourceType": { + "type": "string" + } + }, + "variables": { + "dcrAssociationName": "[concat('assoc-', uniqueString(concat(parameters('resourceName'), parameters('dcrResourceId'))))]", + "dceAssociationName": "configurationAccessEndpoint", + "dcrResourceType": "Microsoft.Insights/dataCollectionRules", + "dceResourceType": "Microsoft.Insights/dataCollectionEndpoints" + }, + "resources": [ + { + "condition": "[equals(parameters('resourceType'), variables('dcrResourceType'))]", + "name": "[variables('dcrAssociationName')]", + "type": "Microsoft.Insights/dataCollectionRuleAssociations", + "apiVersion": "2021-04-01", + "properties": { + "dataCollectionRuleId": "[parameters('dcrResourceId')]" + }, + "scope": "[concat('Microsoft.Compute/virtualMachineScaleSets/', parameters('resourceName'))]" + }, + { + "condition": "[equals(parameters('resourceType'), variables('dceResourceType'))]", + "name": "[variables('dceAssociationName')]", + "type": "Microsoft.Insights/dataCollectionRuleAssociations", + "apiVersion": "2021-04-01", + "properties": { + "dataCollectionEndpointId": "[parameters('dcrResourceId')]" + }, + "scope": "[concat('Microsoft.Compute/virtualMachineScaleSets/', parameters('resourceName'))]" + } + ] + }, + "parameters": { + "resourceName": { + "value": "[field('name')]" + }, + "location": { + "value": "[field('location')]" + }, + "dcrResourceId": { + "value": "[parameters('dcrResourceId')]" + }, + "resourceType": { + "value": "[parameters('resourceType')]" + } + } + } + } + } + } + } + } + } + +cases: + # ========================================================================= + # DINE - Linux VMSS with Canonical/UbuntuServer image, no DCRA + # ========================================================================= + + - note: dine_vmss_canonical_ubuntu + resource: + type: "Microsoft.Compute/virtualMachineScaleSets" + name: "vmss-ubuntu" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-ubuntu" + location: "eastus" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + osType: "Linux" + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-lts" + parameters: + dcrResourceId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Insights/dataCollectionRules/dcr1" + effect: "DeployIfNotExists" + scopeToSupportedImages: true + listOfLinuxImageIdToInclude: [] + resourceType: "Microsoft.Insights/dataCollectionRules" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Insights/dataCollectionRuleAssociations" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass - Windows VMSS (wrong OS type) + # ========================================================================= + + - note: pass_wrong_os_type + resource: + type: "Microsoft.Compute/virtualMachineScaleSets" + name: "vmss-windows" + location: "eastus" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + osType: "Windows" + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + parameters: + dcrResourceId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Insights/dataCollectionRules/dcr1" + effect: "DeployIfNotExists" + scopeToSupportedImages: true + listOfLinuxImageIdToInclude: [] + resourceType: "Microsoft.Insights/dataCollectionRules" + want_undefined: true + + # ========================================================================= + # DINE - VMSS with unsupported publisher but scopeToSupportedImages=false + # ========================================================================= + + - note: dine_vmss_scope_bypass + resource: + type: "Microsoft.Compute/virtualMachineScaleSets" + name: "vmss-custom" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss-custom" + location: "eastus" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + osType: "Linux" + imageReference: + publisher: "CustomPublisher" + offer: "CustomLinux" + sku: "1.0" + parameters: + dcrResourceId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Insights/dataCollectionRules/dcr1" + effect: "DeployIfNotExists" + scopeToSupportedImages: false + listOfLinuxImageIdToInclude: [] + resourceType: "Microsoft.Insights/dataCollectionRules" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Insights/dataCollectionRuleAssociations" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass - unsupported publisher with scopeToSupportedImages=true + # ========================================================================= + + - note: pass_unsupported_publisher_scoped + resource: + type: "Microsoft.Compute/virtualMachineScaleSets" + name: "vmss-custom-scoped" + location: "eastus" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + osType: "Linux" + imageReference: + publisher: "CustomPublisher" + offer: "CustomLinux" + sku: "1.0" + parameters: + dcrResourceId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Insights/dataCollectionRules/dcr1" + effect: "DeployIfNotExists" + scopeToSupportedImages: true + listOfLinuxImageIdToInclude: [] + resourceType: "Microsoft.Insights/dataCollectionRules" + want_undefined: true + + # ========================================================================= + # Skip - wrong resource type + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-linux" + location: "eastus" + properties: + storageProfile: + osDisk: + osType: "Linux" + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-lts" + parameters: + dcrResourceId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Insights/dataCollectionRules/dcr1" + effect: "DeployIfNotExists" + scopeToSupportedImages: true + listOfLinuxImageIdToInclude: [] + resourceType: "Microsoft.Insights/dataCollectionRules" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_double_encryption.yaml b/tests/azure_policy/cases/e2e_double_encryption.yaml new file mode 100644 index 0000000..1f6e216 --- /dev/null +++ b/tests/azure_policy/cases/e2e_double_encryption.yaml @@ -0,0 +1,130 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Compute/DoubleEncryptionRequired_Deny +# Real Azure Policy: "Managed disks should be double encrypted with both +# platform-managed and customer-managed keys" +# Features: allOf, field (type + alias), equals, notEquals, parameters() with +# defaultValue and allowedValues, parameterized effect + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Managed disks should be double encrypted", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "Audit", + "allowedValues": ["Audit", "Deny", "Disabled"], + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + } + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/diskEncryptionSets" + }, + { + "field": "Microsoft.Compute/diskEncryptionSets/encryptionType", + "notEquals": "EncryptionAtRestWithPlatformAndCustomerKeys" + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Audit (default effect) — wrong encryption type + # ========================================================================= + + - note: audit_single_key_encryption + resource: + type: "Microsoft.Compute/diskEncryptionSets" + name: "myDES" + location: "eastus" + properties: + encryptionType: "EncryptionAtRestWithCustomerKey" + want_effect: "Audit" + + # ========================================================================= + # No effect — correct double encryption + # ========================================================================= + + - note: pass_double_encryption + resource: + type: "Microsoft.Compute/diskEncryptionSets" + name: "myDES" + location: "eastus" + properties: + encryptionType: "EncryptionAtRestWithPlatformAndCustomerKeys" + want_undefined: true + + # ========================================================================= + # No effect — wrong resource type + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "myVM" + location: "eastus" + properties: + hardwareProfile: + vmSize: "Standard_D2s_v3" + want_undefined: true + + # ========================================================================= + # Deny — explicit effect parameter override + # ========================================================================= + + - note: deny_with_explicit_effect + resource: + type: "Microsoft.Compute/diskEncryptionSets" + name: "myDES" + location: "westus" + properties: + encryptionType: "EncryptionAtRestWithCustomerKey" + parameters: + effect: "Deny" + want_effect: "Deny" + + # ========================================================================= + # Audit — platform-only encryption (not double) + # ========================================================================= + + - note: audit_platform_only_encryption + resource: + type: "Microsoft.Compute/diskEncryptionSets" + name: "platformDES" + location: "eastus" + properties: + encryptionType: "EncryptionAtRestWithPlatformKey" + want_effect: "Audit" + + # ========================================================================= + # No effect — encryption type missing (field is undefined/null) + # notEquals with null LHS: Azure Policy treats missing field as null, + # and null notEquals "string" is true → should fire + # ========================================================================= + + - note: audit_missing_encryption_type + resource: + type: "Microsoft.Compute/diskEncryptionSets" + name: "noPropDES" + location: "eastus" + properties: {} + want_effect: "Audit" diff --git a/tests/azure_policy/cases/e2e_fic_aks_issuer.yaml b/tests/azure_policy/cases/e2e_fic_aks_issuer.yaml new file mode 100644 index 0000000..c24c4f3 --- /dev/null +++ b/tests/azure_policy/cases/e2e_fic_aks_issuer.yaml @@ -0,0 +1,247 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Managed Identity/FIC_LimitToAzureKubernetesIssuer +# Real Azure Policy: "Managed Identity Federated Credentials from Azure +# Kubernetes should be from trusted sources" +# Source: regolator/policyDefinitions/Managed Identity/FIC_LimitToAzureKubernetesIssuer.json +# +# Features exercised: +# - Value count (count over parameter arrays) +# - Complex nested if/split/length value expressions to parse issuer URL +# - Double negation: not { anyOf [...] } +# - Child resource type (sub-resource) +# - `like` operator with wildcard pattern + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "[Preview]: Managed Identity Federated Credentials from Azure Kubernetes should be from trusted sources", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "allowedTenants": { + "type": "Array", + "metadata": { + "displayName": "Allowed tenants", + "description": "The list of allowed Azure AD tenant ID's of AKS OIDC issuers. Empty to allow all tenants." + } + }, + "allowedLocations": { + "type": "Array", + "defaultValue": [], + "metadata": { + "displayName": "Allowed locations", + "description": "The list of allowed locations for AKS OIDC issuers. Empty to allow any location." + } + }, + "allowedClusterExceptions": { + "type": "Array", + "defaultValue": [], + "metadata": { + "displayName": "Allowed Exception Clusters", + "description": "The list of specific cluster ids that will be exceptions to the location and tenant rules." + } + }, + "effect": { + "type": "String", + "defaultValue": "Audit", + "allowedValues": ["Audit", "Disabled", "Deny"] + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + }, + { + "allOf": [ + { + "value": "[if(greaterOrEquals(length(split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer'),'/')),3),split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer'),'/')[2],'')]", + "like": "*.oic.prod-aks.azure.com" + }, + { + "not": { + "anyOf": [ + { + "allOf": [ + { + "anyOf": [ + { + "count": { + "value": "[parameters('allowedLocations')]" + }, + "equals": 0 + }, + { + "value": "[if(greaterOrEquals(length(split(if(greaterOrEquals(length(split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer'),'/')),3),split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer'),'/')[2],''), '.')),1),split(if(greaterOrEquals(length(split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer'),'/')),3),split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer'),'/')[2],''), '.')[0],'')]", + "in": "[parameters('allowedLocations')]" + } + ] + }, + { + "anyOf": [ + { + "count": { + "value": "[parameters('allowedTenants')]" + }, + "equals": 0 + }, + { + "value": "[if(greaterOrEquals(length(split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer'),'/')),4),split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer'),'/')[3],'')]", + "in": "[parameters('allowedTenants')]" + } + ] + } + ] + }, + { + "value": "[if(greaterOrEquals(length(split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer'),'/')),5),split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer'),'/')[4],'')]", + "in": "[parameters('allowedClusterExceptions')]" + } + ] + } + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Audit — untrusted AKS issuer (wrong tenant AND wrong location) + # ========================================================================= + + - note: audit_untrusted_tenant_and_location + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "aks-fic" + properties: + issuer: "https://eastus.oic.prod-aks.azure.com/bad-tenant-id/some-cluster-id" + subject: "system:serviceaccount:default:workload-identity-sa" + parameters: + allowedTenants: ["good-tenant-id"] + allowedLocations: ["westus"] + allowedClusterExceptions: [] + want_effect: "Audit" + + # ========================================================================= + # Audit — correct location but wrong tenant + # ========================================================================= + + - note: audit_wrong_tenant_correct_location + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "aks-fic" + properties: + issuer: "https://eastus.oic.prod-aks.azure.com/bad-tenant-id/some-cluster-id" + subject: "system:serviceaccount:default:workload-identity-sa" + parameters: + allowedTenants: ["good-tenant-id"] + allowedLocations: ["eastus"] + allowedClusterExceptions: [] + want_effect: "Audit" + + # ========================================================================= + # Pass — allowed tenant and allowed location + # ========================================================================= + + - note: pass_allowed_tenant_and_location + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "aks-fic" + properties: + issuer: "https://eastus.oic.prod-aks.azure.com/good-tenant-id/some-cluster-id" + subject: "system:serviceaccount:default:workload-identity-sa" + parameters: + allowedTenants: ["good-tenant-id", "other-tenant-id"] + allowedLocations: ["eastus", "westus"] + allowedClusterExceptions: [] + want_undefined: true + + # ========================================================================= + # Pass — allowed tenant, empty allowedLocations (any location allowed) + # ========================================================================= + + - note: pass_allowed_tenant_any_location + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "aks-fic" + properties: + issuer: "https://westeurope.oic.prod-aks.azure.com/good-tenant-id/some-cluster-id" + subject: "system:serviceaccount:default:workload-identity-sa" + parameters: + allowedTenants: ["good-tenant-id"] + allowedLocations: [] + allowedClusterExceptions: [] + want_undefined: true + + # ========================================================================= + # Pass — empty allowedTenants and empty allowedLocations (allow all) + # ========================================================================= + + - note: pass_empty_tenants_and_locations_allows_all + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "aks-fic" + properties: + issuer: "https://australiaeast.oic.prod-aks.azure.com/any-tenant/any-cluster" + subject: "system:serviceaccount:kube-system:my-sa" + parameters: + allowedTenants: [] + allowedLocations: [] + allowedClusterExceptions: [] + want_undefined: true + + # ========================================================================= + # Pass — cluster ID is in the exceptions list (bypasses tenant/location) + # ========================================================================= + + - note: pass_cluster_in_exceptions + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "aks-fic" + properties: + issuer: "https://eastus.oic.prod-aks.azure.com/untrusted-tenant/special-cluster-id" + subject: "system:serviceaccount:default:workload-identity-sa" + parameters: + allowedTenants: ["other-tenant"] + allowedLocations: ["westus"] + allowedClusterExceptions: ["special-cluster-id"] + want_undefined: true + + # ========================================================================= + # Pass — non-AKS issuer (GitHub Actions OIDC) + # ========================================================================= + + - note: pass_non_aks_issuer + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "github-fic" + properties: + issuer: "https://token.actions.githubusercontent.com" + subject: "repo:myorg/myrepo:ref:refs/heads/main" + parameters: + allowedTenants: ["some-tenant"] + want_undefined: true + + # ========================================================================= + # Skip — wrong resource type entirely + # ========================================================================= + + - note: skip_wrong_resource_type + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities" + name: "my-identity" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_fic_github_issuer.yaml b/tests/azure_policy/cases/e2e_fic_github_issuer.yaml new file mode 100644 index 0000000..5c8f008 --- /dev/null +++ b/tests/azure_policy/cases/e2e_fic_github_issuer.yaml @@ -0,0 +1,184 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Managed Identity/FIC_LimitToGitHubIssuer +# Real Azure Policy: "Managed Identity Federated Credentials from GitHub +# should be from trusted repository owners" +# Source: regolator/policyDefinitions/Managed Identity/FIC_LimitToGitHubIssuer.json +# +# Features exercised: +# - Value count (count over parameter array) +# - Complex nested if/split/length value expressions to parse subject field +# - Double negation: not { anyOf [...] } +# - Child resource type (sub-resource) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "[Preview]: Managed Identity Federated Credentials from GitHub should be from trusted repository owners", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "allowedRepoOwners": { + "type": "Array", + "metadata": { + "displayName": "Allowed Repo Owners" + } + }, + "allowedRepoExceptions": { + "type": "Array", + "defaultValue": [], + "metadata": { + "displayName": "Allowed Repo Exceptions" + } + }, + "effect": { + "type": "String", + "defaultValue": "Audit", + "allowedValues": ["Audit", "Disabled", "Deny"] + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + }, + { + "allOf": [ + { + "field": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer", + "equals": "https://token.actions.githubusercontent.com" + }, + { + "not": { + "anyOf": [ + { + "allOf": [ + { + "anyOf": [ + { + "count": { + "value": "[parameters('allowedRepoOwners')]" + }, + "equals": 0 + }, + { + "value": "[if(greaterOrEquals(length(split(if(greaterOrEquals(length(split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/subject'),':')),2),split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/subject'),':')[1],''), '/')),2),split(if(greaterOrEquals(length(split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/subject'),':')),2),split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/subject'),':')[1],''), '/')[0],'')]", + "in": "[parameters('allowedRepoOwners')]" + } + ] + } + ] + }, + { + "value": "[if(greaterOrEquals(length(split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/subject'),':')),2),split(field('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/subject'),':')[1],'')]", + "in": "[parameters('allowedRepoExceptions')]" + } + ] + } + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Audit — untrusted repo owner (not in allowedRepoOwners) + # ========================================================================= + + - note: audit_untrusted_repo_owner + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "github-fic" + properties: + issuer: "https://token.actions.githubusercontent.com" + subject: "repo:evil-org/malicious-repo:ref:refs/heads/main" + parameters: + allowedRepoOwners: ["trusted-org", "another-org"] + allowedRepoExceptions: [] + want_effect: "Audit" + + # ========================================================================= + # Pass — repo owner is in the allowed list + # ========================================================================= + + - note: pass_allowed_repo_owner + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "github-fic" + properties: + issuer: "https://token.actions.githubusercontent.com" + subject: "repo:trusted-org/my-repo:ref:refs/heads/main" + parameters: + allowedRepoOwners: ["trusted-org", "another-org"] + allowedRepoExceptions: [] + want_undefined: true + + # ========================================================================= + # Pass — repo is in exceptions list (even if owner not allowed) + # ========================================================================= + + - note: pass_repo_in_exceptions + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "github-fic" + properties: + issuer: "https://token.actions.githubusercontent.com" + subject: "repo:random-org/special-repo:ref:refs/heads/main" + parameters: + allowedRepoOwners: ["trusted-org"] + allowedRepoExceptions: ["random-org/special-repo"] + want_undefined: true + + # ========================================================================= + # Pass — empty allowedRepoOwners means allow all + # ========================================================================= + + - note: pass_empty_owners_allows_all + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "github-fic" + properties: + issuer: "https://token.actions.githubusercontent.com" + subject: "repo:any-org/any-repo:ref:refs/heads/main" + parameters: + allowedRepoOwners: [] + allowedRepoExceptions: [] + want_undefined: true + + # ========================================================================= + # Pass — not a GitHub issuer (different OIDC provider) + # ========================================================================= + + - note: pass_non_github_issuer + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials" + name: "aks-fic" + properties: + issuer: "https://oidc.prod-aks.azure.com/00000000-0000-0000-0000-000000000000" + subject: "system:serviceaccount:default:workload-identity-sa" + parameters: + allowedRepoOwners: ["trusted-org"] + want_undefined: true + + # ========================================================================= + # Skip — wrong resource type + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.ManagedIdentity/userAssignedIdentities" + name: "my-identity" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_functionapp_https_modify.yaml b/tests/azure_policy/cases/e2e_functionapp_https_modify.yaml new file mode 100644 index 0000000..eb1deee --- /dev/null +++ b/tests/azure_policy/cases/e2e_functionapp_https_modify.yaml @@ -0,0 +1,183 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: App Service/FunctionApp_AuditHTTP_Modify +# Real Azure Policy: "Configure Function apps to only be accessible over HTTPS" +# Source: regolator/policyDefinitions/App Service/FunctionApp_AuditHTTP_Modify.json +# +# Features exercised: +# - kind contains / notContains string operators +# - exists "false" — field doesn't exist or is null +# - Modify with greaterOrEquals(requestContext().apiVersion,...) condition +# - conflictEffect: audit + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Configure Function apps to only be accessible over HTTPS", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "Modify", + "allowedValues": ["Modify", "Disabled"] + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Web/sites" + }, + { + "field": "kind", + "contains": "functionapp" + }, + { + "field": "kind", + "notContains": "workflowapp" + }, + { + "anyOf": [ + { + "field": "Microsoft.Web/sites/httpsOnly", + "exists": "false" + }, + { + "field": "Microsoft.Web/sites/httpsOnly", + "equals": "false" + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/de139f84-1756-47ae-9be6-808fbbe84772" + ], + "conflictEffect": "audit", + "operations": [ + { + "condition": "[greaterOrEquals(requestContext().apiVersion, '2019-08-01')]", + "operation": "addOrReplace", + "field": "Microsoft.Web/sites/httpsOnly", + "value": true + } + ] + } + } + } + } + } + +cases: + # ========================================================================= + # Function app with httpsOnly missing → Modify + # ========================================================================= + + - note: modify_functionapp_httpsonly_missing + resource: + type: "Microsoft.Web/sites" + kind: "functionapp" + name: "func-no-https" + properties: {} + api_version: "2022-03-01" + want_effect: "Modify" + want_details: + roleDefinitionIds: + - "/providers/microsoft.authorization/roleDefinitions/de139f84-1756-47ae-9be6-808fbbe84772" + operations: + - condition: "[greaterOrEquals(requestContext().apiVersion, '2019-08-01')]" + operation: "addOrReplace" + field: "Microsoft.Web/sites/httpsOnly" + value: true + + # ========================================================================= + # Function app with httpsOnly = false → Modify + # ========================================================================= + + - note: modify_functionapp_httpsonly_false + resource: + type: "Microsoft.Web/sites" + kind: "functionapp,linux" + name: "func-linux-no-https" + properties: + httpsOnly: false + api_version: "2020-06-01" + want_effect: "Modify" + want_details: + roleDefinitionIds: + - "/providers/microsoft.authorization/roleDefinitions/de139f84-1756-47ae-9be6-808fbbe84772" + operations: + - condition: "[greaterOrEquals(requestContext().apiVersion, '2019-08-01')]" + operation: "addOrReplace" + field: "Microsoft.Web/sites/httpsOnly" + value: true + + # ========================================================================= + # Function app with httpsOnly = true → pass (condition not met) + # ========================================================================= + + - note: pass_functionapp_httpsonly_true + resource: + type: "Microsoft.Web/sites" + kind: "functionapp" + name: "func-https" + properties: + httpsOnly: true + api_version: "2022-03-01" + want_undefined: true + + # ========================================================================= + # Workflow app (Logic App) — notContains "workflowapp" fails → pass + # ========================================================================= + + - note: pass_workflowapp_excluded + resource: + type: "Microsoft.Web/sites" + kind: "functionapp,workflowapp" + name: "logic-app" + properties: {} + api_version: "2022-03-01" + want_undefined: true + + # ========================================================================= + # Web app (not function app) — contains "functionapp" fails → pass + # ========================================================================= + + - note: pass_webapp_not_functionapp + resource: + type: "Microsoft.Web/sites" + kind: "app" + name: "web-app" + properties: + httpsOnly: false + api_version: "2022-03-01" + want_undefined: true + + # ========================================================================= + # Old API version → operation condition not met, no operations emitted + # ========================================================================= + + - note: modify_old_api_no_operations + resource: + type: "Microsoft.Web/sites" + kind: "functionapp" + name: "func-old-api" + properties: {} + api_version: "2018-02-01" + want_effect: "Modify" + want_details: + roleDefinitionIds: + - "/providers/microsoft.authorization/roleDefinitions/de139f84-1756-47ae-9be6-808fbbe84772" + operations: + - condition: "[greaterOrEquals(requestContext().apiVersion, '2019-08-01')]" + operation: "addOrReplace" + field: "Microsoft.Web/sites/httpsOnly" + value: true diff --git a/tests/azure_policy/cases/e2e_guest_config_user_identity.yaml b/tests/azure_policy/cases/e2e_guest_config_user_identity.yaml new file mode 100644 index 0000000..692c5ad --- /dev/null +++ b/tests/azure_policy/cases/e2e_guest_config_user_identity.yaml @@ -0,0 +1,836 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Guest Configuration/AddUserIdentity_Prerequisite +# Real Azure Policy: "[Preview]: Add user-assigned managed identity to enable Guest Configuration assignments on virtual machines" +# Source: regolator/policyDefinitions/Guest Configuration/AddUserIdentity_Prerequisite.json +# +# Features exercised: +# - Self-referential DeployIfNotExists (details.type matches resource type) +# - identity.type / identity.userAssignedIdentities existenceCondition +# - containsKey with concat(subscription().subscriptionId, field('location')) +# - Deep allOf/anyOf nesting for OS image publisher matching (Windows + Linux) +# - requestContext().apiVersion guard (>= 2018-10-01) +# - deploymentScope: subscription +# - subscription-level ARM deployment template + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "[Preview]: Add user-assigned managed identity to enable Guest Configuration assignments on virtual machines", + "mode": "Indexed", + "policyType": "BuiltIn", + "description": "This policy adds a user-assigned managed identity to virtual machines hosted in Azure that are supported by Guest Configuration. A user-assigned managed identity is a prerequisite for all Guest Configuration assignments and must be added to machines before using any Guest Configuration policy definitions. For more information on Guest Configuration, visit https://aka.ms/gcpol.", + "metadata": { + "category": "Guest Configuration", + "version": "2.1.0-preview", + "preview": true + }, + "version": "2.1.0-preview", + "parameters": { + "effect": { + "type": "String", + "metadata": { + "displayName": "Policy Effect", + "description": "The effect determines what happens when the policy rule is evaluated to match." + }, + "allowedValues": [ + "AuditIfNotExists", + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "anyOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "in": [ + "esri", + "incredibuild", + "MicrosoftDynamicsAX", + "MicrosoftSharepoint", + "MicrosoftVisualStudio", + "MicrosoftWindowsDesktop", + "MicrosoftWindowsServerHPCPack" + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftWindowsServer" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "2008*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftSQLServer" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "SQL2008*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-dsvm" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "dsvm-win*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-ads" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "standard-data-science-vm", + "windows-data-science-vm" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "batch" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "rendering-windows2016" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "center-for-internet-security-inc" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "cis-windows-server-201*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "pivotal" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "bosh-windows-server*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "cloud-infrastructure-services" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "ad*" + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/virtualMachines/osProfile.windowsConfiguration", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "like": "Windows*" + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "exists": "false" + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "2008*" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "SQL2008*" + } + ] + } + ] + } + ] + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "in": [ + "microsoft-aks", + "qubole-inc", + "datastax", + "couchbase", + "scalegrid", + "checkpoint", + "paloaltonetworks", + "debian", + "credativ" + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "OpenLogic" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Oracle" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "RedHat" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "center-for-internet-security-inc" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "cis-win*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Suse" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "11*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Canonical" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "12*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-dsvm" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "dsvm-win*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "cloudera" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-ads" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "linux*" + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration", + "exists": "true" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "like": "Linux*" + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "exists": "false" + }, + { + "field": "Microsoft.Compute/imagePublisher", + "notIn": [ + "OpenLogic", + "RedHat", + "credativ", + "Suse", + "Canonical", + "microsoft-dsvm", + "cloudera", + "microsoft-ads", + "center-for-internet-security-inc", + "Oracle", + "AzureDatabricks", + "azureopenshift" + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "value": "[requestContext().apiVersion]", + "greaterOrEquals": "2018-10-01" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Compute/virtualMachines", + "name": "[field('name')]", + "evaluationDelay": "AfterProvisioning", + "deploymentScope": "subscription", + "existenceCondition": { + "allOf": [ + { + "field": "identity.type", + "contains": "UserAssigned" + }, + { + "field": "identity.userAssignedIdentities", + "containsKey": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/Built-In-Identity-RG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Built-In-Identity-', field('location'))]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c", + "/providers/Microsoft.Authorization/roleDefinitions/18d7d88d-d35e-4fb5-a5c3-7773c20a72d9" + ], + "deployment": { + "location": "eastus", + "properties": { + "mode": "incremental", + "parameters": { + "bringYourOwnUserAssignedManagedIdentity": { + "value": false + }, + "location": { + "value": "[field('location')]" + }, + "uaName": { + "value": "Built-In-Identity" + }, + "identityResourceGroup": { + "value": "Built-In-Identity-RG" + }, + "vmName": { + "value": "[field('name')]" + }, + "vmResourceGroup": { + "value": "[resourceGroup().name]" + }, + "resourceId": { + "value": "[field('id')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.1", + "parameters": { + "bringYourOwnUserAssignedManagedIdentity": { + "type": "bool" + }, + "location": { + "type": "string" + }, + "uaName": { + "type": "string" + }, + "identityResourceGroup": { + "type": "string" + }, + "vmName": { + "type": "string" + }, + "vmResourceGroup": { + "type": "string" + }, + "resourceId": { + "type": "string" + } + }, + "variables": { + "uaNameWithLocation": "[concat(parameters('uaName'),'-', parameters('location'))]", + "precreatedUaId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', trim(parameters('identityResourceGroup')), '/providers/Microsoft.ManagedIdentity/userAssignedIdentities/', trim(parameters('uaName')))]", + "autocreatedUaId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', trim(parameters('identityResourceGroup')), '/providers/Microsoft.ManagedIdentity/userAssignedIdentities/', trim(parameters('uaName')), '-', parameters('location'))]", + "deployUALockName": "[concat('deployUALock-', uniqueString(deployment().name))]", + "deployUAName": "[concat('deployUA-', uniqueString(deployment().name))]", + "deployGetResourceProperties": "[concat('deployGetResourceProperties-', uniqueString(deployment().name))]", + "deployAssignUAName": "[concat('deployAssignUA-', uniqueString(deployment().name))]" + }, + "resources": [ + { + "condition": "[not(parameters('bringYourOwnUserAssignedManagedIdentity'))]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2020-06-01", + "name": "[parameters('identityResourceGroup')]", + "location": "eastus" + }, + { + "condition": "[parameters('bringYourOwnUserAssignedManagedIdentity')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-06-01", + "name": "[variables('deployUALockName')]", + "resourceGroup": "[parameters('identityResourceGroup')]", + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "inner" + }, + "parameters": { + "uaName": { + "value": "[parameters('uaName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "uaName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Authorization/locks", + "apiVersion": "2016-09-01", + "name": "[concat('CanNotDeleteLock-', parameters('uaName'))]", + "scope": "[concat('Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('uaName'))]", + "properties": { + "level": "CanNotDelete", + "notes": "Please do not delete this User-Assigned Identity since extensions enabled by Azure Policy are relying on their existence." + } + } + ] + } + } + }, + { + "condition": "[not(parameters('bringYourOwnUserAssignedManagedIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-06-01", + "name": "[variables('deployUAName')]", + "resourceGroup": "[parameters('identityResourceGroup')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups', parameters('identityResourceGroup'))]" + ], + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "inner" + }, + "parameters": { + "uaName": { + "value": "[variables('uaNameWithLocation')]" + }, + "location": { + "value": "[parameters('location')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "uaName": { + "type": "string" + }, + "location": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "name": "[parameters('uaName')]", + "apiVersion": "2018-11-30", + "location": "[parameters('location')]" + }, + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities/providers/locks", + "apiVersion": "2016-09-01", + "name": "[concat(parameters('uaName'), '/Microsoft.Authorization/', 'CanNotDeleteLock-', parameters('uaName'))]", + "dependsOn": [ + "[parameters('uaName')]" + ], + "properties": { + "level": "CanNotDelete", + "notes": "Please do not delete this User-Assigned Identity since extensions enabled by Azure Policy are relying on their existence." + } + } + ] + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-06-01", + "name": "[variables('deployGetResourceProperties')]", + "location": "eastus", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups', parameters('identityResourceGroup'))]", + "[variables('deployUAName')]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "resource": { + "type": "object", + "value": "[reference(parameters('resourceId'), '2019-07-01', 'Full')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2020-06-01", + "name": "[concat(variables('deployAssignUAName'))]", + "resourceGroup": "[parameters('vmResourceGroup')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups', parameters('identityResourceGroup'))]", + "[variables('deployUAName')]", + "[variables('deployGetResourceProperties')]" + ], + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "inner" + }, + "parameters": { + "uaId": { + "value": "[if(parameters('bringYourOwnUserAssignedManagedIdentity'), variables('precreatedUaId'), variables('autocreatedUaId'))]" + }, + "vmName": { + "value": "[parameters('vmName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "identityType": { + "value": "[if(contains(reference(variables('deployGetResourceProperties')).outputs.resource.value, 'identity'), reference(variables('deployGetResourceProperties')).outputs.resource.value.identity.type, '')]" + }, + "userAssignedIdentities": { + "value": "[if(and(contains(reference(variables('deployGetResourceProperties')).outputs.resource.value, 'identity'), contains(reference(variables('deployGetResourceProperties')).outputs.resource.value.identity, 'userAssignedIdentities')), reference(variables('deployGetResourceProperties')).outputs.resource.value.identity.userAssignedIdentities, createObject())]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "uaId": { + "type": "string" + }, + "vmName": { + "type": "string" + }, + "location": { + "type": "string" + }, + "identityType": { + "type": "string" + }, + "userAssignedIdentities": { + "type": "object" + } + }, + "variables": { + "identityTypeValue": "[if(contains(parameters('identityType'), 'SystemAssigned'), 'SystemAssigned,UserAssigned', 'UserAssigned')]", + "userAssignedIdentitiesValue": "[union(parameters('userAssignedIdentities'), createObject(parameters('uaId'), createObject()))]" + }, + "resources": [ + { + "apiVersion": "2019-07-01", + "type": "Microsoft.Compute/virtualMachines", + "name": "[parameters('vmName')]", + "location": "[parameters('location')]", + "identity": { + "type": "[variables('identityTypeValue')]", + "userAssignedIdentities": "[variables('userAssignedIdentitiesValue')]" + } + } + ] + } + } + } + ] + } + } + } + } + } + } + }, + "versions": [ + "2.1.0-PREVIEW", + "2.0.1-PREVIEW" + ] + } + +cases: + # ========================================================================= + # DINE — Windows VM (MicrosoftWindowsServer), no user-assigned identity + # → existenceCondition fails → DeployIfNotExists + # ========================================================================= + + - note: dine_windows_vm_no_identity + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-win-noidentity" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-win-noidentity" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + osProfile: + windowsConfiguration: {} + request_context: + apiVersion: "2024-01-01" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Compute/virtualMachines" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass — Windows VM with proper user-assigned identity in existence result + # → existenceCondition passes → compliant + # ========================================================================= + + - note: pass_windows_vm_with_identity + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-win-identified" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-win-identified" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + osDisk: + osType: "Windows" + osProfile: + windowsConfiguration: {} + request_context: + apiVersion: "2024-01-01" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Compute/virtualMachines" + response: + identity: + type: "UserAssigned" + userAssignedIdentities: + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/Built-In-Identity-RG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Built-In-Identity-eastus": {} + want_undefined: true + + # ========================================================================= + # DINE — Linux VM (Canonical publisher), no user-assigned identity + # → existenceCondition fails → DeployIfNotExists + # ========================================================================= + + - note: dine_linux_vm_no_identity + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-linux-noidentity" + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-linux-noidentity" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + osDisk: + osType: "Linux" + osProfile: + linuxConfiguration: {} + request_context: + apiVersion: "2024-01-01" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Compute/virtualMachines" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass — Wrong resource type (not a VM) → if condition fails + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "storageacct1" + properties: {} + want_undefined: true + + # ========================================================================= + # Pass — Linux VM with microsoft-ads publisher and non-matching offer + # → microsoft-ads specific conditions require offer like "linux*" + # or in ["standard-data-science-vm","windows-data-science-vm"] + # → Linux catch-all excludes microsoft-ads (it's in the notIn list) + # → Windows catch-all fails (no windowsConfiguration, osType=Linux) + # → if condition fails + # ========================================================================= + + - note: pass_excluded_publisher + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-ads-excluded" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "microsoft-ads" + offer: "some-random-offer" + sku: "some-sku" + osDisk: + osType: "Linux" + osProfile: + linuxConfiguration: {} + request_context: + apiVersion: "2024-01-01" + want_undefined: true + + # ========================================================================= + # Pass — VM without osProfile and no matching publisher + # → Windows catch-all: no windowsConfiguration, no Windows osType + # → Linux catch-all: no linuxConfiguration, no Linux osType + # → No specific publisher match + # → if condition fails + # ========================================================================= + + - note: pass_no_os_config + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-no-os" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "unknown-publisher" + offer: "unknown-offer" + sku: "unknown-sku" + request_context: + apiVersion: "2024-01-01" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_keyvault_firewall_enabled.yaml b/tests/azure_policy/cases/e2e_keyvault_firewall_enabled.yaml new file mode 100644 index 0000000..887385f --- /dev/null +++ b/tests/azure_policy/cases/e2e_keyvault_firewall_enabled.yaml @@ -0,0 +1,127 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Key Vault/FirewallEnabled_Audit +# Features: nested count + current() + ipRangeContains + parameterized effect/defaults + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Azure Key Vault should have firewall enabled", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "String", + "allowedValues": ["Audit", "Deny", "Disabled"], + "defaultValue": "Audit" + }, + "restrictIPAddresses": { + "type": "String", + "defaultValue": "No", + "allowedValues": ["Yes", "No"] + }, + "allowedIPAddresses": { + "type": "Array", + "defaultValue": [] + } + }, + "policyRule": { + "if": { + "allOf": [ + {"field": "type", "equals": "Microsoft.KeyVault/vaults"}, + {"field": "Microsoft.KeyVault/vaults/createMode", "notEquals": "recover"}, + { + "anyOf": [ + { + "field": "Microsoft.KeyVault/vaults/networkAcls.defaultAction", + "notEquals": "Deny" + }, + { + "allOf": [ + {"value": "[parameters('restrictIPAddresses')]", "equals": "Yes"}, + { + "anyOf": [ + { + "allOf": [ + { + "count": { + "value": "[parameters('allowedIPAddresses')]", + "name": "allowedIPAddresses" + }, + "notEquals": 0 + }, + { + "not": { + "count": { + "field": "Microsoft.KeyVault/vaults/networkAcls.ipRules[*]", + "where": { + "count": { + "value": "[parameters('allowedIPAddresses')]", + "name": "allowedIpAddress", + "where": { + "value": "[ipRangeContains(current('allowedIpAddress'), current('Microsoft.KeyVault/vaults/networkAcls.ipRules[*].value'))]", + "equals": true + } + }, + "greater": 0 + } + }, + "equals": "[length(field('Microsoft.KeyVault/vaults/networkAcls.ipRules[*]'))]" + } + } + ] + } + ] + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + - note: audit_when_default_action_allow + resource: + type: "Microsoft.KeyVault/vaults" + name: "kv-open" + properties: + networkAcls: + defaultAction: "Allow" + ipRules: [] + want_effect: "Audit" + + - note: pass_when_default_action_deny_and_no_restriction_mode + resource: + type: "Microsoft.KeyVault/vaults" + name: "kv-deny" + properties: + networkAcls: + defaultAction: "Deny" + ipRules: [] + want_undefined: true + + - note: audit_when_restricted_allowed_ips_do_not_cover_all_rules + resource: + type: "Microsoft.KeyVault/vaults" + name: "kv-partial" + properties: + networkAcls: + defaultAction: "Deny" + ipRules: + - value: "10.0.0.5" + - value: "192.168.1.5" + parameters: + restrictIPAddresses: "Yes" + allowedIPAddresses: ["10.0.0.0/24"] + effect: "Audit" + want_effect: "Audit" diff --git a/tests/azure_policy/cases/e2e_managed_disk_encryption_sets.yaml b/tests/azure_policy/cases/e2e_managed_disk_encryption_sets.yaml new file mode 100644 index 0000000..d197240 --- /dev/null +++ b/tests/azure_policy/cases/e2e_managed_disk_encryption_sets.yaml @@ -0,0 +1,326 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Compute/ManagedDiskEncryptionSetsAllowed_Deny +# Real Azure Policy: "Managed disks should use a specific set of disk encryption sets for the customer-managed key encryption" +# Features: anyOf, allOf nesting, field (type + alias), exists, notIn, in, +# length(), count, not, multiple resource types +# (VM, VMSS, disks, images, galleries/images/versions) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Managed disks should use a specific set of disk encryption sets for the customer-managed key encryption", + "policyType": "BuiltIn", + "mode": "Indexed", + "description": "Requiring a specific set of disk encryption sets to be used with managed disks give you control over the keys used for encryption at rest. You are able to select the allowed encrypted sets and all others are rejected when attached to a disk. Learn more at https://aka.ms/disks-cmk.", + "metadata": { + "category": "Compute", + "version": "2.0.0" + }, + "version": "2.0.0", + "parameters": { + "allowedEncryptionSets": { + "type": "Array", + "metadata": { + "displayName": "Allowed disk encryption set", + "description": "The list of allowed disk encryption sets for managed disks.", + "strongType": "Microsoft.Compute/diskEncryptionSets" + } + }, + "effect": { + "type": "string", + "defaultValue": "Audit", + "allowedValues": [ + "Audit", + "Deny", + "Disabled" + ], + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + } + } + }, + "policyRule": { + "if": { + "anyOf": [ + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/disks" + }, + { + "field": "Microsoft.Compute/disks/managedBy", + "exists": "False" + }, + { + "field": "Microsoft.Compute/disks/encryption.diskEncryptionSetId", + "notIn": "[parameters('allowedEncryptionSets')]" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.managedDisk.diskEncryptionSet.id", + "notIn": "[parameters('allowedEncryptionSets')]" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachineScaleSets" + }, + { + "field": "Microsoft.Compute/virtualMachineScaleSets/virtualMachineProfile.storageProfile.osDisk.managedDisk.diskEncryptionSet.id", + "notIn": "[parameters('allowedEncryptionSets')]" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachineScaleSets" + }, + { + "count": { + "field": "Microsoft.Compute/virtualMachineScaleSets/virtualMachineProfile.storageProfile.dataDisks[*]" + }, + "greater": 0 + }, + { + "not": { + "field": "Microsoft.Compute/virtualMachineScaleSets/virtualMachineProfile.storageProfile.dataDisks[*].managedDisk.diskEncryptionSet.id", + "in": "[parameters('allowedEncryptionSets')]" + } + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/galleries/images/versions" + }, + { + "not": { + "field": "Microsoft.Compute/galleries/images/versions/publishingProfile.targetRegions[*].encryption.osDiskImage.diskEncryptionSetId", + "in": "[parameters('allowedEncryptionSets')]" + } + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/galleries/images/versions" + }, + { + "value": "[length(field('Microsoft.Compute/galleries/images/versions/storageProfile.dataDiskImages[*]'))]", + "greater": 0 + }, + { + "not": { + "field": "Microsoft.Compute/galleries/images/versions/publishingProfile.targetRegions[*].encryption.dataDiskImages[*].diskEncryptionSetId", + "in": "[parameters('allowedEncryptionSets')]" + } + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/images" + }, + { + "field": "Microsoft.Compute/images/storageProfile.osDisk.diskEncryptionSet.id", + "notIn": "[parameters('allowedEncryptionSets')]" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/images" + }, + { + "value": "[length(field('Microsoft.Compute/images/storageProfile.dataDisks[*]'))]", + "greater": 0 + }, + { + "field": "Microsoft.Compute/images/storageProfile.dataDisks[*].diskEncryptionSet.id", + "notIn": "[parameters('allowedEncryptionSets')]" + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + }, + "versions": [ + "2.0.0" + ] + }, + "id": "/providers/Microsoft.Authorization/policyDefinitions/d461a302-a187-421a-89ac-84acdb4edc04", + "name": "d461a302-a187-421a-89ac-84acdb4edc04" + } + +cases: + # ========================================================================= + # 1. VM with OS disk DES in allowed list → pass + # ========================================================================= + + - note: pass_vm_osdisk_in_allowed + parameters: + effect: "Deny" + allowedEncryptionSets: + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1" + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des2" + resource: + type: "Microsoft.Compute/virtualMachines" + name: "test-vm" + properties: + storageProfile: + osDisk: + managedDisk: + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1" + want_undefined: true + + # ========================================================================= + # 2. VM with OS disk DES NOT in allowed list → Deny + # ========================================================================= + + - note: deny_vm_osdisk_not_in_allowed + parameters: + effect: "Deny" + allowedEncryptionSets: + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1" + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des2" + resource: + type: "Microsoft.Compute/virtualMachines" + name: "test-vm-bad" + properties: + storageProfile: + osDisk: + managedDisk: + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des-other" + want_effect: "Deny" + + # ========================================================================= + # 3. Managed disk with allowed DES → pass + # ========================================================================= + + - note: pass_disk_in_allowed + parameters: + effect: "Deny" + allowedEncryptionSets: + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1" + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des2" + resource: + type: "Microsoft.Compute/disks" + name: "test-disk-allowed" + properties: + diskSizeGB: 128 + encryption: + diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1" + type: "EncryptionAtRestWithCustomerKey" + want_undefined: true + + # ========================================================================= + # 4. Managed disk with disallowed DES → Deny + # ========================================================================= + + - note: deny_disk_not_in_allowed + parameters: + effect: "Deny" + allowedEncryptionSets: + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1" + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des2" + resource: + type: "Microsoft.Compute/disks" + name: "test-disk-bad" + properties: + diskSizeGB: 128 + encryption: + diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des-other" + type: "EncryptionAtRestWithCustomerKey" + want_effect: "Deny" + + # ========================================================================= + # 5. Disk without managedBy (unmanaged) with disallowed DES → Deny + # (managedBy does not exist, so the disk clause fires) + # But if the DES is in the allowed list, it passes. + # Here we test: disk without managedBy but WITH managedBy present → pass + # (the disk clause requires managedBy exists=False) + # ========================================================================= + + - note: pass_disk_unmanaged + parameters: + effect: "Deny" + allowedEncryptionSets: + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1" + resource: + type: "Microsoft.Compute/disks" + name: "test-disk-managed-by-vm" + properties: + managedBy: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/myVM" + diskSizeGB: 128 + encryption: + diskEncryptionSetId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des-other" + type: "EncryptionAtRestWithCustomerKey" + want_undefined: true + + # ========================================================================= + # 6. Wrong resource type → pass + # ========================================================================= + + - note: pass_wrong_type + parameters: + effect: "Deny" + allowedEncryptionSets: + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1" + resource: + type: "Microsoft.Storage/storageAccounts" + name: "myStorage" + properties: {} + want_undefined: true + + # ========================================================================= + # 7. Image with OS disk DES not in allowed list → Deny + # ========================================================================= + + - note: deny_image_osdisk_not_allowed + parameters: + effect: "Deny" + allowedEncryptionSets: + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1" + - "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des2" + resource: + type: "Microsoft.Compute/images" + name: "test-image-bad" + properties: + storageProfile: + osDisk: + osType: "Linux" + osState: "Generalized" + diskEncryptionSet: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des-other" + want_effect: "Deny" diff --git a/tests/azure_policy/cases/e2e_monitoring_dine_existence.yaml b/tests/azure_policy/cases/e2e_monitoring_dine_existence.yaml new file mode 100644 index 0000000..bfbec7c --- /dev/null +++ b/tests/azure_policy/cases/e2e_monitoring_dine_existence.yaml @@ -0,0 +1,182 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Monitoring DINE existenceCondition — field + count + nested count +# Reproduces the failure pattern from ServiceHealthSubscriptionLevelAlertRules_DINE +# +# Uses bare field names (no FQ alias prefix, no alias catalog) to test +# existence logic independent of alias resolution. + +policy_definition: | + { + "properties": { + "displayName": "Test DINE existenceCondition", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "String", + "defaultValue": "DeployIfNotExists", + "allowedValues": ["DeployIfNotExists", "AuditIfNotExists", "Disabled"] + }, + "enableAlertRule": { + "type": "String", + "defaultValue": "true", + "allowedValues": ["true", "false"] + }, + "eventTypes": { + "type": "Array", + "defaultValue": ["Service Issues", "Planned Maintenance", "Health Advisories", "Security Advisories"] + }, + "actionGroups": { + "type": "Array", + "defaultValue": [] + }, + "createNewActionGroup": { + "type": "String", + "defaultValue": "true", + "allowedValues": ["true", "false"] + }, + "newActionGroupName": { + "type": "String", + "defaultValue": "ag-ServiceHealthAlertActionGroup" + }, + "resourceGroupName": { + "type": "String", + "defaultValue": "rg-serviceHealthAlert" + } + }, + "policyRule": { + "if": { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Insights/ActivityLogAlerts", + "existenceCondition": { + "allOf": [ + { + "field": "enabled", + "equals": "[parameters('enableAlertRule')]" + }, + { + "count": { + "field": "condition.allOf[*]", + "where": { + "allOf": [ + { + "field": "condition.allOf[*].field", + "equals": "category" + }, + { + "field": "condition.allOf[*].equals", + "equals": "ServiceHealth" + } + ] + } + }, + "greaterOrEquals": 1 + }, + { + "count": { + "field": "condition.allOf[*].anyOf[*]", + "where": { + "field": "condition.allOf[*].anyOf[*].field", + "equals": "properties.incidentType" + } + }, + "equals": "[if(contains(parameters('eventTypes'), 'Health Advisories'), add(length(parameters('eventTypes')), 2), length(parameters('eventTypes')))]" + }, + { + "count": { + "field": "actions.actionGroups[*]" + }, + "equals": "[add(length(parameters('actionGroups')), if(equals(parameters('createNewActionGroup'), 'true'), 1, 0))]" + } + ] + }, + "existenceScope": "resourceGroup", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + ] + } + } + } + } + } + +cases: + # ========================================================================= + # Related resource matches all conditions → compliant (Undefined) + # ========================================================================= + - note: compliant_all_conditions + resource: + type: "Microsoft.Resources/subscriptions" + id: "/subscriptions/00000000-0000-0000-0000-000000000000" + parameters: + effect: "AuditIfNotExists" + host_await: + - response: + enabled: "true" + scopes: + - "/subscriptions/00000000-0000-0000-0000-000000000000" + condition: + allof: + - field: "category" + equals: "ServiceHealth" + - anyof: + - field: "properties.incidentType" + equals: "Incident" + - field: "properties.incidentType" + equals: "Maintenance" + - field: "properties.incidentType" + equals: "Informational" + - field: "properties.incidentType" + equals: "ActionRequired" + - field: "properties.incidentType" + equals: "Security" + - field: "properties.incidentType" + equals: "Retirement" + actions: + actiongroups: + - actiongroupid: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-serviceHealthAlert/providers/Microsoft.Insights/actionGroups/ag-ServiceHealthAlertActionGroup" + want_undefined: true + + # ========================================================================= + # Related resource not found → effect fires + # ========================================================================= + - note: no_related_resource + resource: + type: "Microsoft.Resources/subscriptions" + id: "/subscriptions/00000000-0000-0000-0000-000000000000" + parameters: + effect: "AuditIfNotExists" + host_await: + - response: null + want_effect: "AuditIfNotExists" + + # ========================================================================= + # Related resource found but enabled is false → effect fires + # ========================================================================= + - note: enabled_false + resource: + type: "Microsoft.Resources/subscriptions" + id: "/subscriptions/00000000-0000-0000-0000-000000000000" + parameters: + effect: "AuditIfNotExists" + host_await: + - response: + enabled: "false" + condition: + allof: + - field: "category" + equals: "ServiceHealth" + - anyof: + - field: "properties.incidentType" + equals: "Incident" + actions: + actiongroups: + - actiongroupid: "something" + want_effect: "AuditIfNotExists" diff --git a/tests/azure_policy/cases/e2e_nic_public_ip_deny.yaml b/tests/azure_policy/cases/e2e_nic_public_ip_deny.yaml new file mode 100644 index 0000000..c114993 --- /dev/null +++ b/tests/azure_policy/cases/e2e_nic_public_ip_deny.yaml @@ -0,0 +1,138 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Network/NetworkPublicIPNic_Deny +# Real Azure Policy: "Network interfaces should not have public IPs" +# Source: regolator/policyDefinitions/Network/NetworkPublicIPNic_Deny.json +# +# Features exercised: +# - Double negation pattern: not { field notLike "*" } +# - Wildcard array alias: ipconfigurations[*].publicIpAddress.id +# - No parameters (hardcoded deny effect) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Network interfaces should not have public IPs", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": {}, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/networkInterfaces" + }, + { + "not": { + "field": "Microsoft.Network/networkInterfaces/ipconfigurations[*].publicIpAddress.id", + "notLike": "*" + } + } + ] + }, + "then": { + "effect": "deny" + } + } + } + } + +cases: + # ========================================================================= + # NIC with public IP → deny + # ========================================================================= + + - note: deny_single_ip_config_with_public_ip + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-public" + properties: + ipConfigurations: + - properties: + privateIPAddress: "10.0.0.4" + publicIpAddress: + id: "/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip1" + want_effect: "deny" + + - note: deny_multiple_ip_configs_all_public + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-multi-public" + properties: + ipConfigurations: + - properties: + privateIPAddress: "10.0.0.4" + publicIpAddress: + id: "/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip1" + - properties: + privateIPAddress: "10.0.0.5" + publicIpAddress: + id: "/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip2" + want_effect: "deny" + + - note: deny_one_of_many_has_public_ip + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-mixed" + properties: + ipConfigurations: + - properties: + privateIPAddress: "10.0.0.4" + - properties: + privateIPAddress: "10.0.0.5" + publicIpAddress: + id: "/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip1" + want_effect: "deny" + + # ========================================================================= + # NIC without public IP → pass + # ========================================================================= + + - note: pass_no_public_ip + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-private" + properties: + ipConfigurations: + - properties: + privateIPAddress: "10.0.0.4" + want_undefined: true + + - note: pass_multiple_configs_no_public_ip + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-multi-private" + properties: + ipConfigurations: + - properties: + privateIPAddress: "10.0.0.4" + - properties: + privateIPAddress: "10.0.0.5" + want_undefined: true + + - note: pass_empty_ip_configs + resource: + type: "Microsoft.Network/networkInterfaces" + name: "nic-empty" + properties: + ipConfigurations: [] + want_undefined: true + + # ========================================================================= + # Wrong resource type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "not-a-nic" + properties: + ipConfigurations: + - properties: + publicIpAddress: + id: "some-id" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_nsg_rdp_access.yaml b/tests/azure_policy/cases/e2e_nsg_rdp_access.yaml new file mode 100644 index 0000000..b4388ed --- /dev/null +++ b/tests/azure_policy/cases/e2e_nsg_rdp_access.yaml @@ -0,0 +1,370 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Network/NetworkSecurityGroup_RDPAccess_Audit +# Real Azure Policy: "[Deprecated]: RDP access from the Internet should be blocked" +# Features: and(), not(), lessOrEquals(), greaterOrEquals(), implicit allOf on [*] + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "[Deprecated]: RDP access from the Internet should be blocked", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "Audit", + "allowedValues": ["Audit", "Disabled"] + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/networkSecurityGroups/securityRules" + }, + { + "allOf": [ + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/access", + "equals": "Allow" + }, + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/direction", + "equals": "Inbound" + }, + { + "anyOf": [ + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange", + "equals": "*" + }, + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange", + "equals": "3389" + }, + { + "value": "[if(and(not(empty(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange'))), contains(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange'),'-')), and(lessOrEquals(int(first(split(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange'), '-'))),3389),greaterOrEquals(int(last(split(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange'), '-'))),3389)), 'false')]", + "equals": "true" + }, + { + "count": { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]", + "where": { + "value": "[if(and(not(empty(first(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]')))), contains(first(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]')),'-')), and(lessOrEquals(int(first(split(first(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]')), '-'))),3389),greaterOrEquals(int(last(split(first(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]')), '-'))),3389)) , 'false')]", + "equals": "true" + } + }, + "greater": 0 + }, + { + "not": { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]", + "notEquals": "*" + } + }, + { + "not": { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]", + "notEquals": "3389" + } + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", + "equals": "*" + }, + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", + "equals": "Internet" + }, + { + "not": { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefixes[*]", + "notEquals": "*" + } + }, + { + "not": { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefixes[*]", + "notEquals": "Internet" + } + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Exact port 3389 match + # ========================================================================= + + - note: audit_exact_port_3389_from_internet + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "allow-rdp" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "3389" + sourceAddressPrefix: "Internet" + want_effect: "Audit" + + - note: audit_exact_port_3389_from_wildcard + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "allow-rdp-any" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "3389" + sourceAddressPrefix: "*" + want_effect: "Audit" + + # ========================================================================= + # Wildcard port (*) + # ========================================================================= + + - note: audit_wildcard_port_from_internet + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "allow-all" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "*" + sourceAddressPrefix: "Internet" + want_effect: "Audit" + + # ========================================================================= + # Port range containing 3389 + # ========================================================================= + + - note: audit_port_range_includes_3389 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "allow-rdp-range" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "3380-3390" + sourceAddressPrefix: "*" + want_effect: "Audit" + + - note: audit_port_range_exact_3389_to_3389 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "exactly-3389" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "3389-3389" + sourceAddressPrefix: "Internet" + want_effect: "Audit" + + - note: audit_port_range_1_to_4000 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "low-ports" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "1-4000" + sourceAddressPrefix: "*" + want_effect: "Audit" + + # ========================================================================= + # Port range NOT containing 3389 + # ========================================================================= + + - note: pass_port_range_excludes_3389 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "http-only" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "80-443" + destinationPortRanges: [] + sourceAddressPrefix: "Internet" + sourceAddressPrefixes: [] + want_undefined: true + + - note: pass_port_3390_only + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "port-3390" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "3390" + destinationPortRanges: [] + sourceAddressPrefix: "*" + sourceAddressPrefixes: [] + want_undefined: true + + # ========================================================================= + # destinationPortRanges[*] array — double negation pattern + # ========================================================================= + + - note: audit_port_ranges_array_contains_3389 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "multi-port-rdp" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRanges: + - "80" + - "3389" + - "443" + sourceAddressPrefix: "Internet" + want_effect: "Audit" + + - note: audit_port_ranges_array_contains_wildcard + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "multi-port-wildcard" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRanges: + - "*" + sourceAddressPrefix: "*" + want_effect: "Audit" + + - note: audit_port_ranges_array_with_range_containing_3389 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "multi-port-range" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRanges: + - "80-443" + - "3380-3390" + sourceAddressPrefix: "Internet" + want_effect: "Audit" + + - note: pass_port_ranges_array_no_3389 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "non-rdp-ports" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRanges: + - "80" + - "443" + - "8080" + sourceAddressPrefix: "Internet" + want_undefined: true + + # ========================================================================= + # Source address variations + # ========================================================================= + + - note: audit_source_prefixes_array_wildcard + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "src-wild-array" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "3389" + sourceAddressPrefixes: + - "*" + want_effect: "Audit" + + - note: audit_source_prefixes_array_internet + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "src-inet-array" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "3389" + sourceAddressPrefixes: + - "Internet" + want_effect: "Audit" + + - note: pass_source_is_private_subnet + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "private-rdp" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "3389" + destinationPortRanges: [] + sourceAddressPrefix: "10.0.0.0/8" + sourceAddressPrefixes: [] + want_undefined: true + + - note: pass_source_prefixes_all_private + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "private-array" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "3389" + destinationPortRanges: [] + sourceAddressPrefixes: + - "10.0.0.0/8" + - "172.16.0.0/12" + want_undefined: true + + # ========================================================================= + # Non-matching access / direction + # ========================================================================= + + - note: pass_deny_rule + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "deny-rdp" + properties: + access: "Deny" + direction: "Inbound" + destinationPortRange: "3389" + sourceAddressPrefix: "*" + want_undefined: true + + - note: pass_outbound_rule + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "outbound-rdp" + properties: + access: "Allow" + direction: "Outbound" + destinationPortRange: "3389" + sourceAddressPrefix: "*" + want_undefined: true + + # ========================================================================= + # Wrong resource type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "not-nsg" + properties: + access: "Allow" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_nsg_ssh_access.yaml b/tests/azure_policy/cases/e2e_nsg_ssh_access.yaml new file mode 100644 index 0000000..55e974a --- /dev/null +++ b/tests/azure_policy/cases/e2e_nsg_ssh_access.yaml @@ -0,0 +1,379 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Network/NetworkSecurityGroup_SSHAccess_Audit +# Real Azure Policy: "[Deprecated]: SSH access from the Internet should be blocked" +# Source: regolator/policyDefinitions/Network/NetworkSecurityGroup_SSHAccess_Audit.json +# +# Features exercised: +# - Deeply nested template expressions: if(and(not(empty(...)), contains(...))) +# - Arithmetic in templates: int(), split(), first(), last() +# - Port range parsing: lessOrEquals/greaterOrEquals on split results +# - count with where clause + template expression +# - Double negation pattern: not { field notEquals "x" } +# - Parameterized effect with defaultValue +# - Multiple anyOf branches (destination port + source address) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "[Deprecated]: SSH access from the Internet should be blocked", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "Audit", + "allowedValues": ["Audit", "Disabled"] + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/networkSecurityGroups/securityRules" + }, + { + "allOf": [ + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/access", + "equals": "Allow" + }, + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/direction", + "equals": "Inbound" + }, + { + "anyOf": [ + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange", + "equals": "*" + }, + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange", + "equals": "22" + }, + { + "value": "[if(and(not(empty(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange'))), contains(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange'),'-')), and(lessOrEquals(int(first(split(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange'), '-'))),22),greaterOrEquals(int(last(split(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRange'), '-'))),22)), 'false')]", + "equals": "true" + }, + { + "count": { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]", + "where": { + "value": "[if(and(not(empty(first(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]')))), contains(first(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]')),'-')), and(lessOrEquals(int(first(split(first(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]')), '-'))),22),greaterOrEquals(int(last(split(first(field('Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]')), '-'))),22)) , 'false')]", + "equals": "true" + } + }, + "greater": 0 + }, + { + "not": { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]", + "notEquals": "*" + } + }, + { + "not": { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/destinationPortRanges[*]", + "notEquals": "22" + } + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", + "equals": "*" + }, + { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefix", + "equals": "Internet" + }, + { + "not": { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefixes[*]", + "notEquals": "*" + } + }, + { + "not": { + "field": "Microsoft.Network/networkSecurityGroups/securityRules/sourceAddressPrefixes[*]", + "notEquals": "Internet" + } + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Exact port 22 match + # ========================================================================= + + - note: audit_exact_port_22_from_internet + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "allow-ssh" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "22" + sourceAddressPrefix: "Internet" + want_effect: "Audit" + + - note: audit_exact_port_22_from_wildcard + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "allow-ssh-any" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "22" + sourceAddressPrefix: "*" + want_effect: "Audit" + + # ========================================================================= + # Wildcard port (*) + # ========================================================================= + + - note: audit_wildcard_port_from_internet + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "allow-all" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "*" + sourceAddressPrefix: "Internet" + want_effect: "Audit" + + # ========================================================================= + # Port range containing 22 + # ========================================================================= + + - note: audit_port_range_includes_22 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "allow-ssh-range" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "20-25" + sourceAddressPrefix: "*" + want_effect: "Audit" + + - note: audit_port_range_exact_22_to_22 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "exactly-22" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "22-22" + sourceAddressPrefix: "Internet" + want_effect: "Audit" + + - note: audit_port_range_1_to_1024 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "low-ports" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "1-1024" + sourceAddressPrefix: "*" + want_effect: "Audit" + + # ========================================================================= + # Port range NOT containing 22 + # ========================================================================= + + - note: pass_port_range_excludes_22 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "http-only" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "80-443" + destinationPortRanges: [] + sourceAddressPrefix: "Internet" + sourceAddressPrefixes: [] + want_undefined: true + + - note: pass_port_23_only + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "port-23" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "23" + destinationPortRanges: [] + sourceAddressPrefix: "*" + sourceAddressPrefixes: [] + want_undefined: true + + # ========================================================================= + # destinationPortRanges[*] array — double negation pattern + # ========================================================================= + + - note: audit_port_ranges_array_contains_22 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "multi-port-ssh" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRanges: + - "80" + - "22" + - "443" + sourceAddressPrefix: "Internet" + want_effect: "Audit" + + - note: audit_port_ranges_array_contains_wildcard + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "multi-port-wildcard" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRanges: + - "*" + sourceAddressPrefix: "*" + want_effect: "Audit" + + - note: audit_port_ranges_array_with_range_containing_22 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "multi-port-range" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRanges: + - "80-443" + - "10-30" + sourceAddressPrefix: "Internet" + want_effect: "Audit" + + - note: pass_port_ranges_array_no_22 + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "non-ssh-ports" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRanges: + - "80" + - "443" + - "8080" + sourceAddressPrefix: "Internet" + want_undefined: true + + # ========================================================================= + # Source address variations + # ========================================================================= + + - note: audit_source_prefixes_array_wildcard + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "src-wild-array" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "22" + sourceAddressPrefixes: + - "*" + want_effect: "Audit" + + - note: audit_source_prefixes_array_internet + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "src-inet-array" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "22" + sourceAddressPrefixes: + - "Internet" + want_effect: "Audit" + + - note: pass_source_is_private_subnet + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "private-ssh" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "22" + destinationPortRanges: [] + sourceAddressPrefix: "10.0.0.0/8" + sourceAddressPrefixes: [] + want_undefined: true + + - note: pass_source_prefixes_all_private + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "private-array" + properties: + access: "Allow" + direction: "Inbound" + destinationPortRange: "22" + destinationPortRanges: [] + sourceAddressPrefixes: + - "10.0.0.0/8" + - "172.16.0.0/12" + want_undefined: true + + # ========================================================================= + # Non-matching access / direction + # ========================================================================= + + - note: pass_deny_rule + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "deny-ssh" + properties: + access: "Deny" + direction: "Inbound" + destinationPortRange: "22" + sourceAddressPrefix: "*" + want_undefined: true + + - note: pass_outbound_rule + resource: + type: "Microsoft.Network/networkSecurityGroups/securityRules" + name: "outbound-ssh" + properties: + access: "Allow" + direction: "Outbound" + destinationPortRange: "22" + sourceAddressPrefix: "*" + want_undefined: true + + # ========================================================================= + # Wrong resource type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "not-nsg" + properties: + access: "Allow" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_pg_audit_aine.yaml b/tests/azure_policy/cases/e2e_pg_audit_aine.yaml new file mode 100644 index 0000000..f2d9910 --- /dev/null +++ b/tests/azure_policy/cases/e2e_pg_audit_aine.yaml @@ -0,0 +1,121 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: PostgreSQL/FlexibleServers_EnablePgAudit_AINE +# Real Azure Policy: "Auditing with PgAudit should be enabled for PostgreSQL +# flexible servers" +# Source: regolator/policyDefinitions/PostgreSQL/FlexibleServers_EnablePgAudit_AINE.json +# +# Features exercised: +# - AuditIfNotExists with simple existenceCondition (notEquals) +# - Sub-resource type: Microsoft.DBforPostgreSQL/flexibleServers/configurations +# - host_await for cross-resource lookup + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Auditing with PgAudit should be enabled for PostgreSQL flexible servers", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "AuditIfNotExists", + "allowedValues": ["AuditIfNotExists", "Disabled"] + } + }, + "policyRule": { + "if": { + "field": "type", + "equals": "Microsoft.DBforPostgreSQL/flexibleServers" + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.DBforPostgreSQL/flexibleServers/configurations", + "name": "pgaudit.log", + "existenceCondition": { + "field": "Microsoft.DBforPostgreSQL/flexibleServers/configurations/value", + "notEquals": "none" + } + } + } + } + } + } + +cases: + # ========================================================================= + # Related resource not found → AuditIfNotExists + # ========================================================================= + + - note: aine_config_not_found + resource: + type: "Microsoft.DBforPostgreSQL/flexibleServers" + name: "pg-no-config" + properties: {} + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.DBforPostgreSQL/flexibleServers/configurations" + name: "pgaudit.log" + response: null + want_effect: "AuditIfNotExists" + + # ========================================================================= + # pgaudit.log value is "all" → compliant + # ========================================================================= + + - note: compliant_pgaudit_enabled + resource: + type: "Microsoft.DBforPostgreSQL/flexibleServers" + name: "pg-audit-on" + properties: {} + host_await: + - response: + properties: + value: "all" + want_undefined: true + + # ========================================================================= + # pgaudit.log value is "none" → non-compliant (notEquals "none" fails) + # ========================================================================= + + - note: aine_pgaudit_none + resource: + type: "Microsoft.DBforPostgreSQL/flexibleServers" + name: "pg-audit-off" + properties: {} + host_await: + - response: + properties: + value: "none" + want_effect: "AuditIfNotExists" + + # ========================================================================= + # pgaudit.log value has specific categories → compliant + # ========================================================================= + + - note: compliant_pgaudit_specific + resource: + type: "Microsoft.DBforPostgreSQL/flexibleServers" + name: "pg-audit-specific" + properties: {} + host_await: + - response: + properties: + value: "read,write,ddl" + want_undefined: true + + # ========================================================================= + # Wrong type → pass + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Sql/servers" + name: "not-pg" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_portal_dashboard_deny.yaml b/tests/azure_policy/cases/e2e_portal_dashboard_deny.yaml new file mode 100644 index 0000000..04936e7 --- /dev/null +++ b/tests/azure_policy/cases/e2e_portal_dashboard_deny.yaml @@ -0,0 +1,240 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Portal/SharedDashboardInlineContent_Deny +# Real Azure Policy: "Shared dashboards should not have markdown tiles with inline content" +# Source: regolator/policyDefinitions/Portal/SharedDashboardInlineContent_Deny.json +# +# Features exercised: +# - Doubly-nested wildcard array: lenses[*].parts[*] +# - requestContext().apiVersion with greaterOrEquals version comparison +# - count with allOf+anyOf where clause +# - Very long alias names (Extension-HubsExtension-PartType-MarkdownPart...) +# - exists "false" inside count where +# - anyOf at top level selects old-API OR inline-content branches + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Shared dashboards should not have markdown tiles with inline content", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "Audit", + "allowedValues": ["Audit", "Deny", "Disabled"] + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Portal/dashboards" + }, + { + "anyof": [ + { + "not": { + "value": "[requestContext().apiVersion]", + "greaterOrEquals": "2020-09-01-alpha" + } + }, + { + "count": { + "field": "Microsoft.Portal/dashboards/lenses[*].parts[*]", + "where": { + "allOf": [ + { + "field": "Microsoft.Portal/dashboards/lenses[*].parts[*].metadata.type", + "equals": "Extension/HubsExtension/PartType/MarkdownPart" + }, + { + "anyOf": [ + { + "field": "Microsoft.Portal/dashboards/lenses[*].parts[*].metadata.Extension-HubsExtension-PartType-MarkdownPart.settings.content.settings.markdownUri", + "exists": "false" + }, + { + "field": "Microsoft.Portal/dashboards/lenses[*].parts[*].metadata.Extension-HubsExtension-PartType-MarkdownPart.settings.content.settings.markdownSource", + "exists": "false" + }, + { + "field": "Microsoft.Portal/dashboards/lenses[*].parts[*].metadata.Extension-HubsExtension-PartType-MarkdownPart.settings.content.settings.markdownSource", + "equals": "1" + } + ] + } + ] + } + }, + "greater": 0 + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Old API version (< 2020-09-01-alpha) → always audit + # ========================================================================= + + - note: audit_old_api_version + resource: + type: "Microsoft.Portal/dashboards" + name: "dash-old-api" + properties: + lenses: [] + api_version: "2019-01-01-preview" + want_effect: "Audit" + + # ========================================================================= + # New API, markdown part with inline content (markdownSource = "1") → audit + # ========================================================================= + + - note: audit_markdown_inline_content + resource: + type: "Microsoft.Portal/dashboards" + name: "dash-inline" + properties: + lenses: + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MarkdownPart" + Extension-HubsExtension-PartType-MarkdownPart: + settings: + content: + settings: + markdownUri: "https://example.com/content.md" + markdownSource: "1" + api_version: "2022-12-01-preview" + want_effect: "Audit" + + # ========================================================================= + # New API, markdown part missing markdownUri → audit + # ========================================================================= + + - note: audit_markdown_missing_uri + resource: + type: "Microsoft.Portal/dashboards" + name: "dash-no-uri" + properties: + lenses: + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MarkdownPart" + Extension-HubsExtension-PartType-MarkdownPart: + settings: + content: + settings: + markdownSource: "2" + api_version: "2022-12-01-preview" + want_effect: "Audit" + + # ========================================================================= + # New API, markdown part missing markdownSource → audit + # ========================================================================= + + - note: audit_markdown_missing_source + resource: + type: "Microsoft.Portal/dashboards" + name: "dash-no-source" + properties: + lenses: + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MarkdownPart" + Extension-HubsExtension-PartType-MarkdownPart: + settings: + content: + settings: + markdownUri: "https://example.com/content.md" + api_version: "2022-12-01-preview" + want_effect: "Audit" + + # ========================================================================= + # New API, markdown part with external source (markdownSource != "1") → pass + # ========================================================================= + + - note: pass_markdown_external_source + resource: + type: "Microsoft.Portal/dashboards" + name: "dash-external" + properties: + lenses: + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MarkdownPart" + Extension-HubsExtension-PartType-MarkdownPart: + settings: + content: + settings: + markdownUri: "https://example.com/content.md" + markdownSource: "2" + api_version: "2022-12-01-preview" + want_undefined: true + + # ========================================================================= + # New API, non-markdown parts only → pass + # ========================================================================= + + - note: pass_no_markdown_parts + resource: + type: "Microsoft.Portal/dashboards" + name: "dash-charts" + properties: + lenses: + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MonitorChartPart" + - metadata: + type: "Extension/HubsExtension/PartType/VideoPart" + api_version: "2022-12-01-preview" + want_undefined: true + + # ========================================================================= + # New API, multiple lenses with mixed parts → audit if any inline + # ========================================================================= + + - note: audit_multi_lens_one_inline + resource: + type: "Microsoft.Portal/dashboards" + name: "dash-multi" + properties: + lenses: + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MonitorChartPart" + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MarkdownPart" + Extension-HubsExtension-PartType-MarkdownPart: + settings: + content: + settings: + markdownSource: "1" + markdownUri: "https://example.com/a.md" + api_version: "2022-12-01-preview" + want_effect: "Audit" + + # ========================================================================= + # Wrong type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "not-dashboard" + properties: {} + api_version: "2019-01-01" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_servicebus_diagnostic_logs.yaml b/tests/azure_policy/cases/e2e_servicebus_diagnostic_logs.yaml new file mode 100644 index 0000000..a5c25f1 --- /dev/null +++ b/tests/azure_policy/cases/e2e_servicebus_diagnostic_logs.yaml @@ -0,0 +1,116 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Service Bus/AuditDiagnosticLog_Audit +# Features: AuditIfNotExists with inline existenceCondition evaluation, +# including count-with-where and ARM template expression padLeft(). +# +# NOTE: Without an alias catalog, fully-qualified field paths like +# "Microsoft.Insights/diagnosticSettings/logs[*]..." resolve as raw +# object keys. The test response structure mirrors this resolution. + +policy_definition: | + { + "properties": { + "displayName": "Resource logs in Service Bus should be enabled", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "AuditIfNotExists", + "allowedValues": ["AuditIfNotExists", "Disabled"] + }, + "requiredRetentionDays": { + "type": "String", + "defaultValue": "365" + } + }, + "policyRule": { + "if": { + "field": "type", + "equals": "Microsoft.ServiceBus/namespaces" + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "count": { + "field": "Microsoft.Insights/diagnosticSettings/logs[*]", + "where": { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs[*].retentionPolicy.enabled", + "equals": "true" + }, + { + "anyOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs[*].retentionPolicy.days", + "equals": "0" + }, + { + "value": "[padLeft(current('Microsoft.Insights/diagnosticSettings/logs[*].retentionPolicy.days'), 3, '0')]", + "greaterOrEquals": "[padLeft(parameters('requiredRetentionDays'), 3, '0')]" + } + ] + } + ] + } + ] + } + }, + "greaterOrEquals": 1 + } + } + } + } + } + } + +cases: + # Related resource not found → non-compliant + - note: non_compliant_resource_not_found + resource: + type: "Microsoft.ServiceBus/namespaces" + name: "sb-a" + properties: {} + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Insights/diagnosticSettings" + response: null + want_effect: "AuditIfNotExists" + + # Related resource found with compliant diagnostic log → compliant + - note: compliant_resource_found_and_condition_passes + resource: + type: "Microsoft.ServiceBus/namespaces" + name: "sb-b" + properties: {} + host_await: + - response: + Microsoft: + "Insights/diagnosticSettings/logs": + - retentionPolicy: + enabled: "true" + days: "365" + want_undefined: true + + # Related resource found but retention too short → non-compliant + - note: non_compliant_retention_too_short + resource: + type: "Microsoft.ServiceBus/namespaces" + name: "sb-c" + properties: {} + host_await: + - response: + Microsoft: + "Insights/diagnosticSettings/logs": + - retentionPolicy: + enabled: "true" + days: "10" + want_effect: "AuditIfNotExists" diff --git a/tests/azure_policy/cases/e2e_shared_dashboard_deny.yaml b/tests/azure_policy/cases/e2e_shared_dashboard_deny.yaml new file mode 100644 index 0000000..17667ea --- /dev/null +++ b/tests/azure_policy/cases/e2e_shared_dashboard_deny.yaml @@ -0,0 +1,204 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Portal/SharedDashboardInlineContent_Deny +# Real Azure Policy: "Shared dashboards should not have markdown tiles with inline content" +# Source: regolator/policyDefinitions/Portal/SharedDashboardInlineContent_Deny.json +# +# Features exercised: +# - Doubly-nested wildcard: lenses[*].parts[*] +# - Field count with where clause on deeply-nested metadata paths +# - requestContext().apiVersion guard with NOT (double negation pattern) +# - "anyof" (lowercase 'o') variant + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Shared dashboards should not have markdown tiles with inline content", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "Audit", + "allowedValues": ["Audit", "Deny", "Disabled"] + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Portal/dashboards" + }, + { + "anyof": [ + { + "not": { + "value": "[requestContext().apiVersion]", + "greaterOrEquals": "2020-09-01-alpha" + } + }, + { + "count": { + "field": "Microsoft.Portal/dashboards/lenses[*].parts[*]", + "where": { + "allOf": [ + { + "field": "Microsoft.Portal/dashboards/lenses[*].parts[*].metadata.type", + "equals": "Extension/HubsExtension/PartType/MarkdownPart" + }, + { + "anyOf": [ + { + "field": "Microsoft.Portal/dashboards/lenses[*].parts[*].metadata.Extension-HubsExtension-PartType-MarkdownPart.settings.content.settings.markdownUri", + "exists": "false" + }, + { + "field": "Microsoft.Portal/dashboards/lenses[*].parts[*].metadata.Extension-HubsExtension-PartType-MarkdownPart.settings.content.settings.markdownSource", + "exists": "false" + }, + { + "field": "Microsoft.Portal/dashboards/lenses[*].parts[*].metadata.Extension-HubsExtension-PartType-MarkdownPart.settings.content.settings.markdownSource", + "equals": "1" + } + ] + } + ] + } + }, + "greater": 0 + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Old API version → triggers (not greaterOrEquals "2020-09-01-alpha") + # ========================================================================= + + - note: audit_old_api_version + resource: + type: "Microsoft.Portal/dashboards" + name: "old-api-dashboard" + properties: + lenses: [] + request_context: + apiVersion: "2019-01-01" + want_effect: "Audit" + + # ========================================================================= + # New API version, markdown part with inline content (no markdownUri) + # ========================================================================= + + - note: audit_inline_markdown_no_uri + resource: + type: "Microsoft.Portal/dashboards" + name: "inline-md" + properties: + lenses: + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MarkdownPart" + Extension-HubsExtension-PartType-MarkdownPart: + settings: + content: + settings: + markdownSource: "1" + request_context: + apiVersion: "2022-12-01" + want_effect: "Audit" + + # ========================================================================= + # New API version, markdown part with markdownUri set → compliant + # ========================================================================= + + - note: pass_markdown_with_uri + resource: + type: "Microsoft.Portal/dashboards" + name: "external-md" + properties: + lenses: + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MarkdownPart" + Extension-HubsExtension-PartType-MarkdownPart: + settings: + content: + settings: + markdownUri: "https://raw.githubusercontent.com/org/repo/main/README.md" + markdownSource: "url" + request_context: + apiVersion: "2022-12-01" + want_undefined: true + + # ========================================================================= + # Non-markdown part → compliant + # ========================================================================= + + - note: pass_non_markdown_part + resource: + type: "Microsoft.Portal/dashboards" + name: "chart-dashboard" + properties: + lenses: + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MonitorChartPart" + request_context: + apiVersion: "2022-12-01" + want_undefined: true + + # ========================================================================= + # Multiple lenses/parts, mixed: one inline, one external → triggers + # ========================================================================= + + - note: audit_mixed_parts_one_inline + resource: + type: "Microsoft.Portal/dashboards" + name: "mixed-dashboard" + properties: + lenses: + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MonitorChartPart" + - metadata: + type: "Extension/HubsExtension/PartType/MarkdownPart" + Extension-HubsExtension-PartType-MarkdownPart: + settings: + content: + settings: + markdownSource: "1" + - parts: + - metadata: + type: "Extension/HubsExtension/PartType/MarkdownPart" + Extension-HubsExtension-PartType-MarkdownPart: + settings: + content: + settings: + markdownUri: "https://example.com/md.md" + markdownSource: "url" + request_context: + apiVersion: "2022-12-01" + want_effect: "Audit" + + # ========================================================================= + # Wrong type → skip + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "storage" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_signalr_public_network_modify.yaml b/tests/azure_policy/cases/e2e_signalr_public_network_modify.yaml new file mode 100644 index 0000000..912c375 --- /dev/null +++ b/tests/azure_policy/cases/e2e_signalr_public_network_modify.yaml @@ -0,0 +1,183 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: SignalR/PublicNetworkAccessDisabled_Modify +# Real Azure Policy: "Modify Azure SignalR Service resources to disable public network access" +# Source: regolator/policyDefinitions/SignalR/PublicNetworkAccessDisabled_Modify.json +# +# Features exercised: +# - Multi-modify (3 operations, one conditional) +# - Conditional modify gated on requestContext().apiVersion +# - conflictEffect: Audit +# - Bare field count (no where clause) +# - exists false + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Modify Azure SignalR Service resources to disable public network access", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "String", + "allowedValues": ["Modify", "Disabled"], + "defaultValue": "Modify" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.SignalRService/SignalR" + }, + { + "anyOf": [ + { + "field": "Microsoft.SignalRService/SignalR/networkACLs.defaultAction", + "equals": "Allow" + }, + { + "field": "Microsoft.SignalRService/SignalR/networkACLs.publicNetwork.allow", + "exists": false + }, + { + "count": { + "field": "Microsoft.SignalRService/SignalR/networkACLs.publicNetwork.allow[*]" + }, + "greater": 0 + } + ] + }, + { + "field": "Microsoft.SignalRService/SignalR/publicNetworkAccess", + "notEquals": "Disabled" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "conflictEffect": "Audit", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/8cf5e20a-e4b2-4e9d-b3a1-5ceb692c2761" + ], + "operations": [ + { + "operation": "addOrReplace", + "field": "Microsoft.SignalRService/SignalR/networkACLs.defaultAction", + "value": "Deny" + }, + { + "operation": "addOrReplace", + "field": "Microsoft.SignalRService/SignalR/networkACLs.publicNetwork.allow", + "value": [] + }, + { + "condition": "[greaterOrEquals(requestContext().apiVersion, '2021-06-01-preview')]", + "operation": "addOrReplace", + "field": "Microsoft.SignalRService/SignalR/publicNetworkAccess", + "value": "Disabled" + } + ] + } + } + } + } + } + +cases: + # ========================================================================= + # Modify — default action is Allow + # ========================================================================= + + - note: modify_default_action_allow + resource: + type: "Microsoft.SignalRService/SignalR" + name: "signalr-open" + properties: + networkACLs: + defaultAction: "Allow" + publicNetwork: + allow: + - "ServerConnection" + publicNetworkAccess: "Enabled" + want_effect: "Modify" + + # ========================================================================= + # Modify — publicNetwork.allow does not exist + # ========================================================================= + + - note: modify_public_allow_not_exists + resource: + type: "Microsoft.SignalRService/SignalR" + name: "signalr-no-allow" + properties: + networkACLs: + defaultAction: "Deny" + publicNetworkAccess: "Enabled" + want_effect: "Modify" + + # ========================================================================= + # Modify — publicNetwork.allow has items (count > 0) + # ========================================================================= + + - note: modify_public_allow_has_items + resource: + type: "Microsoft.SignalRService/SignalR" + name: "signalr-with-allow" + properties: + networkACLs: + defaultAction: "Deny" + publicNetwork: + allow: + - "ClientConnection" + - "ServerConnection" + publicNetworkAccess: "Enabled" + want_effect: "Modify" + + # ========================================================================= + # Compliant — already disabled + # ========================================================================= + + - note: pass_already_disabled + resource: + type: "Microsoft.SignalRService/SignalR" + name: "signalr-locked" + properties: + networkACLs: + defaultAction: "Deny" + publicNetwork: + allow: [] + publicNetworkAccess: "Disabled" + want_undefined: true + + # ========================================================================= + # Compliant — defaultAction=Deny, no allow field, already disabled + # ========================================================================= + + - note: pass_deny_no_allow_disabled + resource: + type: "Microsoft.SignalRService/SignalR" + name: "signalr-minimal" + properties: + networkACLs: + defaultAction: "Deny" + publicNetwork: + allow: [] + publicNetworkAccess: "Disabled" + want_undefined: true + + # ========================================================================= + # Wrong type → skip + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.SignalRService/WebPubSub" + name: "webpubsub" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_sql_audit_actions_aine.yaml b/tests/azure_policy/cases/e2e_sql_audit_actions_aine.yaml new file mode 100644 index 0000000..8e06d97 --- /dev/null +++ b/tests/azure_policy/cases/e2e_sql_audit_actions_aine.yaml @@ -0,0 +1,183 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: SQL/SqlServerAuditing_ActionsAndGroups_Audit +# Real Azure Policy: "SQL Auditing settings should have Action-Groups +# configured to capture critical activities" +# Source: regolator/policyDefinitions/SQL/SqlServerAuditing_ActionsAndGroups_Audit.json +# +# Features exercised: +# - AuditIfNotExists with existenceCondition +# - Triple double-negation: not { field[*] notEquals X } — asserts X is +# present in the array (aka "all elements are not-not-equal to X") +# - Wildcard array alias: auditActionsAndGroups[*] +# - host_await for cross-resource lookup + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "SQL Auditing settings should have Action-Groups configured to capture critical activities", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "AuditIfNotExists", + "allowedValues": ["AuditIfNotExists", "Disabled"] + } + }, + "policyRule": { + "if": { + "field": "type", + "equals": "Microsoft.Sql/servers" + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Sql/servers/auditingSettings", + "name": "default", + "existenceCondition": { + "allOf": [ + { + "not": { + "field": "Microsoft.Sql/servers/auditingSettings/auditActionsAndGroups[*]", + "notEquals": "SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP" + } + }, + { + "not": { + "field": "Microsoft.Sql/servers/auditingSettings/auditActionsAndGroups[*]", + "notEquals": "FAILED_DATABASE_AUTHENTICATION_GROUP" + } + }, + { + "not": { + "field": "Microsoft.Sql/servers/auditingSettings/auditActionsAndGroups[*]", + "notEquals": "BATCH_COMPLETED_GROUP" + } + } + ] + } + } + } + } + } + } + +cases: + # ========================================================================= + # Related resource not found → AuditIfNotExists + # ========================================================================= + + - note: aine_no_related_resource + resource: + type: "Microsoft.Sql/servers" + name: "sql-no-audit" + properties: {} + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Sql/servers/auditingSettings" + name: "default" + response: null + want_effect: "AuditIfNotExists" + + # ========================================================================= + # All three groups present → compliant (undefined) + # ========================================================================= + + - note: compliant_all_three_groups + resource: + type: "Microsoft.Sql/servers" + name: "sql-complete" + properties: {} + host_await: + - response: + properties: + auditactionsandgroups: + - "SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP" + - "FAILED_DATABASE_AUTHENTICATION_GROUP" + - "BATCH_COMPLETED_GROUP" + want_undefined: true + + # ========================================================================= + # All three present among extras → still compliant + # ========================================================================= + + - note: compliant_extras_present + resource: + type: "Microsoft.Sql/servers" + name: "sql-extras" + properties: {} + host_await: + - response: + properties: + auditactionsandgroups: + - "SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP" + - "FAILED_DATABASE_AUTHENTICATION_GROUP" + - "BATCH_COMPLETED_GROUP" + - "APPLICATION_ROLE_CHANGE_PASSWORD_GROUP" + - "DATABASE_OBJECT_ACCESS_GROUP" + want_undefined: true + + # ========================================================================= + # Missing BATCH_COMPLETED_GROUP → non-compliant + # ========================================================================= + + - note: aine_missing_batch_completed + resource: + type: "Microsoft.Sql/servers" + name: "sql-missing-batch" + properties: {} + host_await: + - response: + properties: + auditactionsandgroups: + - "SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP" + - "FAILED_DATABASE_AUTHENTICATION_GROUP" + want_effect: "AuditIfNotExists" + + # ========================================================================= + # Missing SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP → non-compliant + # ========================================================================= + + - note: aine_missing_successful_auth + resource: + type: "Microsoft.Sql/servers" + name: "sql-missing-success" + properties: {} + host_await: + - response: + properties: + auditactionsandgroups: + - "FAILED_DATABASE_AUTHENTICATION_GROUP" + - "BATCH_COMPLETED_GROUP" + want_effect: "AuditIfNotExists" + + # ========================================================================= + # Empty array → all three missing → non-compliant + # ========================================================================= + + - note: aine_empty_groups + resource: + type: "Microsoft.Sql/servers" + name: "sql-empty-groups" + properties: {} + host_await: + - response: + properties: + auditactionsandgroups: [] + want_effect: "AuditIfNotExists" + + # ========================================================================= + # Wrong type → pass (if-condition fails) + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "not-sql" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_sql_server_auditing.yaml b/tests/azure_policy/cases/e2e_sql_server_auditing.yaml new file mode 100644 index 0000000..960bacd --- /dev/null +++ b/tests/azure_policy/cases/e2e_sql_server_auditing.yaml @@ -0,0 +1,104 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: SQL/SqlServerAuditing_Audit +# Features: AuditIfNotExists with inline existenceCondition evaluation. +# The host returns the related resource (or null if not found), and the +# compiler evaluates the existenceCondition against it. +# +# NOTE: Without an alias catalog, fully-qualified field paths like +# "Microsoft.Sql/auditingSettings.state" resolve as raw object keys +# ("Microsoft" → "Sql/auditingSettings" → "state"). The test response +# structure mirrors this resolution. + +policy_definition: | + { + "properties": { + "displayName": "Auditing on SQL server should be enabled", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "string", + "defaultValue": "AuditIfNotExists", + "allowedValues": ["AuditIfNotExists", "Disabled"] + }, + "setting": { + "type": "String", + "defaultValue": "enabled", + "allowedValues": ["enabled", "disabled"] + } + }, + "policyRule": { + "if": { + "allOf": [ + {"field": "type", "equals": "Microsoft.Sql/servers"}, + {"field": "kind", "notContains": "analytics"} + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Sql/servers/auditingSettings", + "name": "default", + "existenceCondition": { + "field": "Microsoft.Sql/auditingSettings.state", + "equals": "[parameters('setting')]" + } + } + } + } + } + } + +cases: + # Related resource not found → non-compliant + - note: non_compliant_resource_not_found + resource: + type: "Microsoft.Sql/servers" + kind: "v12.0" + name: "sql-a" + properties: {} + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Sql/servers/auditingSettings" + name: "default" + response: null + want_effect: "AuditIfNotExists" + + # Related resource found and existenceCondition matches → compliant + - note: compliant_resource_found_and_condition_passes + resource: + type: "Microsoft.Sql/servers" + kind: "v12.0" + name: "sql-b" + properties: {} + host_await: + - response: + Microsoft: + "Sql/auditingSettings": + state: "enabled" + want_undefined: true + + # Related resource found but existenceCondition fails → non-compliant + - note: non_compliant_condition_fails + resource: + type: "Microsoft.Sql/servers" + kind: "v12.0" + name: "sql-c" + properties: {} + host_await: + - response: + Microsoft: + "Sql/auditingSettings": + state: "disabled" + want_effect: "AuditIfNotExists" + + # Primary resource type doesn't match → if-condition false → compliant + - note: pass_when_type_not_matching + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_ssh_security_baseline_dine.yaml b/tests/azure_policy/cases/e2e_ssh_security_baseline_dine.yaml new file mode 100644 index 0000000..ca90620 --- /dev/null +++ b/tests/azure_policy/cases/e2e_ssh_security_baseline_dine.yaml @@ -0,0 +1,1029 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Guest Configuration/LinuxSshServerSecurityBaseline_DINE +# Real Azure Policy: "[Preview]: Configure SSH Posture Control on Linux machines" +# Source: regolator/policyDefinitions/Guest Configuration/LinuxSshServerSecurityBaseline_DINE.json +# +# Features exercised: +# - 4th largest policy by line count (910 lines) +# - 19 SSH configuration parameters +# - base64(concat(...)) in existenceCondition for parameterHash +# - Tag-based AKS exclusion (tags['aks-managed-...']) +# - Multiple target types: VM, VMSS, HybridCompute, ConnectedVMwarevSphere +# - IncludeArcMachines as string parameter "true"/"false" +# - Conditional deployment for 3 different machine types +# - Guest configuration assignment with configurationParameters + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "[Preview]: Configure SSH Posture Control on Linux machines", + "policyType": "BuiltIn", + "mode": "Indexed", + "description": "This policy creates a Guest Configuration assignment to set SSH Posture Control on Linux machines.", + "version": "1.0.0-preview", + "parameters": { + "IncludeArcMachines": { + "type": "string", + "metadata": { + "displayName": "Include Arc connected servers", + "description": "By selecting this option, you agree to be charged monthly per Arc connected machine.", + "portalReview": "true" + }, + "allowedValues": [ + "true", + "false" + ], + "defaultValue": "false" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of this policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + }, + "accessPermissionsForSshdConfig": { + "type": "string", + "metadata": { + "displayName": "Access permissions for sshd_config", + "description": "File access permissions for /etc/ssh/sshd_config. Default is '600'" + }, + "defaultValue": "600" + }, + "ignoreHosts": { + "type": "string", + "metadata": { + "displayName": "Ignore rhosts and shosts", + "description": "Whether to ignore per-user .rhosts and .shosts files during HostbasedAuthentication. Default is 'yes'" + }, + "defaultValue": "yes" + }, + "logLevel": { + "type": "string", + "metadata": { + "displayName": "Log verbosity level", + "description": "The verbosity level for the sshd logging. Default is 'INFO'" + }, + "defaultValue": "INFO" + }, + "maxAuthTries": { + "type": "string", + "metadata": { + "displayName": "Maximum number of authentication attempts", + "description": "The maximum number of authentication attempts permitted per connection. Default is '6'" + }, + "defaultValue": "6" + }, + "allowUsers": { + "type": "string", + "metadata": { + "displayName": "Allowed users for SSH", + "description": "List of users to be allowed to connect with SSH. Default is all authenticated users ('*@*')" + }, + "defaultValue": "*@*" + }, + "denyUsers": { + "type": "string", + "metadata": { + "displayName": "Denied users for SSH", + "description": "List of users to be denied to connect with SSH. Default is 'root'" + }, + "defaultValue": "root" + }, + "allowGroups": { + "type": "string", + "metadata": { + "displayName": "Allowed groups for SSH", + "description": "List of user groups to be allowed to connect with SSH. Default is all groups ('*')" + }, + "defaultValue": "*" + }, + "denyGroups": { + "type": "string", + "metadata": { + "displayName": "Denied groups for SSH", + "description": "List of user groups to be denied to connect with SSH. Default is 'root'" + }, + "defaultValue": "root" + }, + "hostBasedAuthentication": { + "type": "string", + "metadata": { + "displayName": "Host-based authentication", + "description": "Host-based authentication. Default is 'no'" + }, + "defaultValue": "no" + }, + "permitRootLogin": { + "type": "string", + "metadata": { + "displayName": "Whether root can log in using ssh", + "description": "Whether root can log in using ssh. Default is 'no'" + }, + "defaultValue": "no" + }, + "permitEmptyPasswords": { + "type": "string", + "metadata": { + "displayName": "Whether the server allows login to accounts with empty password strings", + "description": "Whether the server allows login to accounts with empty password strings. Default is 'no'" + }, + "defaultValue": "no" + }, + "clientAliveCountMax": { + "type": "string", + "metadata": { + "displayName": "The number of client alive messages which may be sent without sshd receiving any messages back from the client", + "description": "The number of client alive messages which may be sent without sshd receiving any messages back from the client. Default is '0'" + }, + "defaultValue": "0" + }, + "clientAliveInterval": { + "type": "string", + "metadata": { + "displayName": "Timeout interval in seconds after which if no data has been received from the client, sshd will send a message to request a response", + "description": "Timeout interval in seconds after which if no data has been received from the client, sshd will send a message to request a response. Default is 1 hour ('3600')" + }, + "defaultValue": "3600" + }, + "loginGraceTime": { + "type": "string", + "metadata": { + "displayName": "The time in seconds after which the server disconnects if the user has not successfully logged in", + "description": "The time in seconds after which the server disconnects if the user has not successfully logged in. Default is 1 minute ('60')" + }, + "defaultValue": "60" + }, + "messageAuthenticationCodeAlgorithms": { + "type": "string", + "metadata": { + "displayName": "The list of available message authentication code (MAC) algorithms", + "description": "The list of available message authentication code (MAC) algorithms. Default is 'hmac-sha2-256,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-512-etm@openssh.com'" + }, + "defaultValue": "hmac-sha2-256,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-512-etm@openssh.com" + }, + "banner": { + "type": "string", + "metadata": { + "displayName": "The contents of the banner file that is sent to the remote user before authentication is allowed", + "description": "The contents of the banner file that is sent to the remote user before authentication is allowed. Default is '#######################################################################\n\nAuthorized access only!\n\nIf you are not authorized to access or use this system, disconnect now!\n\n#######################################################################\n'" + }, + "defaultValue": "#######################################################################\n\nAuthorized access only!\n\nIf you are not authorized to access or use this system, disconnect now!\n\n#######################################################################\n" + }, + "permitUserEnvironment": { + "type": "string", + "metadata": { + "displayName": "Whether ~/.ssh/environment and environment= options in ~/.ssh/authorized_keys are processed by sshd", + "description": "Whether ~/.ssh/environment and environment= options in ~/.ssh/authorized_keys are processed by sshd. Default is 'no'" + }, + "defaultValue": "no" + }, + "ciphers": { + "type": "string", + "metadata": { + "displayName": "The list of allowed ciphers", + "description": "The list of allowed ciphers. Default is 'aes128-ctr,aes192-ctr,aes256-ctr'" + }, + "defaultValue": "aes128-ctr,aes192-ctr,aes256-ctr" + }, + "port": { + "type": "string", + "metadata": { + "displayName": "The SSH port", + "description": "The SSH port. Default is '22'" + }, + "defaultValue": "22" + } + }, + "policyRule": { + "if": { + "anyOf": [ + { + "allOf": [ + { + "anyOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachineScaleSets" + } + ] + }, + { + "field": "tags['aks-managed-orchestrator']", + "exists": false + }, + { + "field": "tags['aks-managed-poolName']", + "exists": false + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "in": [ + "microsoft-aks", + "qubole-inc", + "datastax", + "couchbase", + "scalegrid", + "checkpoint", + "paloaltonetworks", + "debian", + "credativ" + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "OpenLogic" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Oracle" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "RedHat" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "center-for-internet-security-inc" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "cis-win*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Suse" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "11*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Canonical" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "12*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-dsvm" + }, + { + "field": "Microsoft.Compute/imageOffer", + "notLike": "dsvm-win*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "cloudera" + }, + { + "field": "Microsoft.Compute/imageSKU", + "notLike": "6*" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-ads" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "linux*" + } + ] + }, + { + "allOf": [ + { + "anyOf": [ + { + "field": "Microsoft.Compute/virtualMachines/osProfile.linuxConfiguration", + "exists": true + }, + { + "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType", + "like": "Linux*" + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "exists": false + }, + { + "field": "Microsoft.Compute/imagePublisher", + "notIn": [ + "OpenLogic", + "RedHat", + "credativ", + "Suse", + "Canonical", + "microsoft-dsvm", + "cloudera", + "microsoft-ads", + "center-for-internet-security-inc", + "Oracle", + "AzureDatabricks", + "azureopenshift" + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "value": "[parameters('IncludeArcMachines')]", + "equals": true + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.HybridCompute/machines" + }, + { + "field": "Microsoft.HybridCompute/imageOffer", + "like": "linux*" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.ConnectedVMwarevSphere/virtualMachines" + }, + { + "field": "Microsoft.ConnectedVMwarevSphere/virtualMachines/osProfile.osType", + "like": "linux*" + } + ] + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/088ab73d-1256-47ae-bea9-9de8e7131f31" + ], + "name": "SetLinuxSshServerSecurityBaseline", + "type": "Microsoft.GuestConfiguration/guestConfigurationAssignments", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.GuestConfiguration/guestConfigurationAssignments/parameterHash", + "equals": "[base64(concat('Ensure that permissions on /etc/ssh/sshd_config are configured;DesiredObjectValue', '=', parameters('accessPermissionsForSshdConfig'), ',', 'Ensure that the SSH IgnoreRhosts is configured;DesiredObjectValue', '=', parameters('ignoreHosts'), ',', 'Ensure that the SSH LogLevel is configured;DesiredObjectValue', '=', parameters('logLevel'), ',', 'Ensure that the SSH MaxAuthTries is configured;DesiredObjectValue', '=', parameters('maxAuthTries'), ',', 'Ensure that the allowed users for SSH access are configured;DesiredObjectValue', '=', parameters('allowUsers'), ',', 'Ensure that the denied users for SSH are configured;DesiredObjectValue', '=', parameters('denyUsers'), ',', 'Ensure that the allowed groups for SSH are configured;DesiredObjectValue', '=', parameters('allowGroups'), ',', 'Ensure that the denied groups for SSH are configured;DesiredObjectValue', '=', parameters('denyGroups'), ',', 'Ensure that the SSH HostBasedAuthentication is configured;DesiredObjectValue', '=', parameters('hostBasedAuthentication'), ',', 'Ensure that the SSH PermitRootLogin is configured;DesiredObjectValue', '=', parameters('permitRootLogin'), ',', 'Ensure that the SSH PermitEmptyPasswords is configured;DesiredObjectValue', '=', parameters('permitEmptyPasswords'), ',', 'Ensure that the SSH ClientAliveCountMax is configured;DesiredObjectValue', '=', parameters('clientAliveCountMax'), ',', 'Ensure that the SSH ClientAliveInterval is configured;DesiredObjectValue', '=', parameters('clientAliveInterval'), ',', 'Ensure that the SSH LoginGraceTime is configured;DesiredObjectValue', '=', parameters('loginGraceTime'), ',', 'Ensure that only approved MAC algorithms are used;DesiredObjectValue', '=', parameters('messageAuthenticationCodeAlgorithms'), ',', 'Ensure that the SSH warning banner is configured;DesiredObjectValue', '=', parameters('banner'), ',', 'Ensure that the SSH PermitUserEnvironment is configured;DesiredObjectValue', '=', parameters('permitUserEnvironment'), ',', 'Ensure that appropriate ciphers are used for SSH;DesiredObjectValue', '=', parameters('ciphers'), ',', 'Ensure that the SSH port is configured;DesiredObjectValue', '=', parameters('port')))]" + }, + { + "field": "Microsoft.GuestConfiguration/guestConfigurationAssignments/complianceStatus", + "equals": "Compliant" + } + ] + }, + "deployment": { + "properties": { + "mode": "incremental", + "parameters": { + "vmName": { + "value": "[field('name')]" + }, + "location": { + "value": "[field('location')]" + }, + "type": { + "value": "[field('type')]" + }, + "configurationName": { + "value": "SetLinuxSshServerSecurityBaseline" + }, + "accessPermissionsForSshdConfig": { + "value": "[parameters('accessPermissionsForSshdConfig')]" + }, + "ignoreHosts": { + "value": "[parameters('ignoreHosts')]" + }, + "logLevel": { + "value": "[parameters('logLevel')]" + }, + "maxAuthTries": { + "value": "[parameters('maxAuthTries')]" + }, + "allowUsers": { + "value": "[parameters('allowUsers')]" + }, + "denyUsers": { + "value": "[parameters('denyUsers')]" + }, + "allowGroups": { + "value": "[parameters('allowGroups')]" + }, + "denyGroups": { + "value": "[parameters('denyGroups')]" + }, + "hostBasedAuthentication": { + "value": "[parameters('hostBasedAuthentication')]" + }, + "permitRootLogin": { + "value": "[parameters('permitRootLogin')]" + }, + "permitEmptyPasswords": { + "value": "[parameters('permitEmptyPasswords')]" + }, + "clientAliveCountMax": { + "value": "[parameters('clientAliveCountMax')]" + }, + "clientAliveInterval": { + "value": "[parameters('clientAliveInterval')]" + }, + "loginGraceTime": { + "value": "[parameters('loginGraceTime')]" + }, + "messageAuthenticationCodeAlgorithms": { + "value": "[parameters('messageAuthenticationCodeAlgorithms')]" + }, + "banner": { + "value": "[parameters('banner')]" + }, + "permitUserEnvironment": { + "value": "[parameters('permitUserEnvironment')]" + }, + "ciphers": { + "value": "[parameters('ciphers')]" + }, + "port": { + "value": "[parameters('port')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "vmName": { + "type": "string" + }, + "location": { + "type": "string" + }, + "type": { + "type": "string" + }, + "configurationName": { + "type": "string" + }, + "accessPermissionsForSshdConfig": { + "type": "string" + }, + "ignoreHosts": { + "type": "string" + }, + "logLevel": { + "type": "string" + }, + "maxAuthTries": { + "type": "string" + }, + "allowUsers": { + "type": "string" + }, + "denyUsers": { + "type": "string" + }, + "allowGroups": { + "type": "string" + }, + "denyGroups": { + "type": "string" + }, + "hostBasedAuthentication": { + "type": "string" + }, + "permitRootLogin": { + "type": "string" + }, + "permitEmptyPasswords": { + "type": "string" + }, + "clientAliveCountMax": { + "type": "string" + }, + "clientAliveInterval": { + "type": "string" + }, + "loginGraceTime": { + "type": "string" + }, + "messageAuthenticationCodeAlgorithms": { + "type": "string" + }, + "banner": { + "type": "string" + }, + "permitUserEnvironment": { + "type": "string" + }, + "ciphers": { + "type": "string" + }, + "port": { + "type": "string" + } + }, + "resources": [ + { + "condition": "[equals(toLower(parameters('type')), toLower('microsoft.hybridcompute/machines'))]", + "apiVersion": "2018-11-20", + "type": "Microsoft.HybridCompute/machines/providers/guestConfigurationAssignments", + "name": "[concat(parameters('vmName'), '/Microsoft.GuestConfiguration/', parameters('configurationName'))]", + "location": "[parameters('location')]", + "properties": { + "guestConfiguration": { + "name": "[parameters('configurationName')]", + "version": "1.*", + "assignmentType": "ApplyAndAutoCorrect", + "configurationParameter": [ + { + "name": "Ensure that permissions on /etc/ssh/sshd_config are configured;DesiredObjectValue", + "value": "[parameters('accessPermissionsForSshdConfig')]" + }, + { + "name": "Ensure that the SSH IgnoreRhosts is configured;DesiredObjectValue", + "value": "[parameters('ignoreHosts')]" + }, + { + "name": "Ensure that the SSH LogLevel is configured;DesiredObjectValue", + "value": "[parameters('logLevel')]" + }, + { + "name": "Ensure that the SSH MaxAuthTries is configured;DesiredObjectValue", + "value": "[parameters('maxAuthTries')]" + }, + { + "name": "Ensure that the allowed users for SSH access are configured;DesiredObjectValue", + "value": "[parameters('allowUsers')]" + }, + { + "name": "Ensure that the denied users for SSH are configured;DesiredObjectValue", + "value": "[parameters('denyUsers')]" + }, + { + "name": "Ensure that the allowed groups for SSH are configured;DesiredObjectValue", + "value": "[parameters('allowGroups')]" + }, + { + "name": "Ensure that the denied groups for SSH are configured;DesiredObjectValue", + "value": "[parameters('denyGroups')]" + }, + { + "name": "Ensure that the SSH HostBasedAuthentication is configured;DesiredObjectValue", + "value": "[parameters('hostBasedAuthentication')]" + }, + { + "name": "Ensure that the SSH PermitRootLogin is configured;DesiredObjectValue", + "value": "[parameters('permitRootLogin')]" + }, + { + "name": "Ensure that the SSH PermitEmptyPasswords is configured;DesiredObjectValue", + "value": "[parameters('permitEmptyPasswords')]" + }, + { + "name": "Ensure that the SSH ClientAliveCountMax is configured;DesiredObjectValue", + "value": "[parameters('clientAliveCountMax')]" + }, + { + "name": "Ensure that the SSH ClientAliveInterval is configured;DesiredObjectValue", + "value": "[parameters('clientAliveInterval')]" + }, + { + "name": "Ensure that the SSH LoginGraceTime is configured;DesiredObjectValue", + "value": "[parameters('loginGraceTime')]" + }, + { + "name": "Ensure that only approved MAC algorithms are used;DesiredObjectValue", + "value": "[parameters('messageAuthenticationCodeAlgorithms')]" + }, + { + "name": "Ensure that the SSH warning banner is configured;DesiredObjectValue", + "value": "[parameters('banner')]" + }, + { + "name": "Ensure that the SSH PermitUserEnvironment is configured;DesiredObjectValue", + "value": "[parameters('permitUserEnvironment')]" + }, + { + "name": "Ensure that appropriate ciphers are used for SSH;DesiredObjectValue", + "value": "[parameters('ciphers')]" + }, + { + "name": "Ensure that the SSH port is configured;DesiredObjectValue", + "value": "[parameters('port')]" + } + ] + } + } + }, + { + "condition": "[equals(toLower(parameters('type')), toLower('Microsoft.Compute/virtualMachines'))]", + "apiVersion": "2018-11-20", + "type": "Microsoft.Compute/virtualMachines/providers/guestConfigurationAssignments", + "name": "[concat(parameters('vmName'), '/Microsoft.GuestConfiguration/', parameters('configurationName'))]", + "location": "[parameters('location')]", + "properties": { + "guestConfiguration": { + "name": "[parameters('configurationName')]", + "version": "1.*", + "assignmentType": "ApplyAndAutoCorrect", + "configurationParameter": [ + { + "name": "Ensure that permissions on /etc/ssh/sshd_config are configured;DesiredObjectValue", + "value": "[parameters('accessPermissionsForSshdConfig')]" + }, + { + "name": "Ensure that the SSH IgnoreRhosts is configured;DesiredObjectValue", + "value": "[parameters('ignoreHosts')]" + }, + { + "name": "Ensure that the SSH LogLevel is configured;DesiredObjectValue", + "value": "[parameters('logLevel')]" + }, + { + "name": "Ensure that the SSH MaxAuthTries is configured;DesiredObjectValue", + "value": "[parameters('maxAuthTries')]" + }, + { + "name": "Ensure that the allowed users for SSH access are configured;DesiredObjectValue", + "value": "[parameters('allowUsers')]" + }, + { + "name": "Ensure that the denied users for SSH are configured;DesiredObjectValue", + "value": "[parameters('denyUsers')]" + }, + { + "name": "Ensure that the allowed groups for SSH are configured;DesiredObjectValue", + "value": "[parameters('allowGroups')]" + }, + { + "name": "Ensure that the denied groups for SSH are configured;DesiredObjectValue", + "value": "[parameters('denyGroups')]" + }, + { + "name": "Ensure that the SSH HostBasedAuthentication is configured;DesiredObjectValue", + "value": "[parameters('hostBasedAuthentication')]" + }, + { + "name": "Ensure that the SSH PermitRootLogin is configured;DesiredObjectValue", + "value": "[parameters('permitRootLogin')]" + }, + { + "name": "Ensure that the SSH PermitEmptyPasswords is configured;DesiredObjectValue", + "value": "[parameters('permitEmptyPasswords')]" + }, + { + "name": "Ensure that the SSH ClientAliveCountMax is configured;DesiredObjectValue", + "value": "[parameters('clientAliveCountMax')]" + }, + { + "name": "Ensure that the SSH ClientAliveInterval is configured;DesiredObjectValue", + "value": "[parameters('clientAliveInterval')]" + }, + { + "name": "Ensure that the SSH LoginGraceTime is configured;DesiredObjectValue", + "value": "[parameters('loginGraceTime')]" + }, + { + "name": "Ensure that only approved MAC algorithms are used;DesiredObjectValue", + "value": "[parameters('messageAuthenticationCodeAlgorithms')]" + }, + { + "name": "Ensure that the SSH warning banner is configured;DesiredObjectValue", + "value": "[parameters('banner')]" + }, + { + "name": "Ensure that the SSH PermitUserEnvironment is configured;DesiredObjectValue", + "value": "[parameters('permitUserEnvironment')]" + }, + { + "name": "Ensure that appropriate ciphers are used for SSH;DesiredObjectValue", + "value": "[parameters('ciphers')]" + }, + { + "name": "Ensure that the SSH port is configured;DesiredObjectValue", + "value": "[parameters('port')]" + } + ] + } + } + }, + { + "condition": "[equals(toLower(parameters('type')), toLower('Microsoft.ConnectedVMwarevSphere/virtualMachines'))]", + "apiVersion": "2018-11-20", + "type": "Microsoft.ConnectedVMwarevSphere/virtualMachines/providers/guestConfigurationAssignments", + "name": "[concat(parameters('vmName'), '/Microsoft.GuestConfiguration/', parameters('configurationName'))]", + "location": "[parameters('location')]", + "properties": { + "guestConfiguration": { + "name": "[parameters('configurationName')]", + "version": "1.*", + "assignmentType": "ApplyAndAutoCorrect", + "configurationParameter": [ + { + "name": "Ensure that permissions on /etc/ssh/sshd_config are configured;DesiredObjectValue", + "value": "[parameters('accessPermissionsForSshdConfig')]" + }, + { + "name": "Ensure that the SSH IgnoreRhosts is configured;DesiredObjectValue", + "value": "[parameters('ignoreHosts')]" + }, + { + "name": "Ensure that the SSH LogLevel is configured;DesiredObjectValue", + "value": "[parameters('logLevel')]" + }, + { + "name": "Ensure that the SSH MaxAuthTries is configured;DesiredObjectValue", + "value": "[parameters('maxAuthTries')]" + }, + { + "name": "Ensure that the allowed users for SSH access are configured;DesiredObjectValue", + "value": "[parameters('allowUsers')]" + }, + { + "name": "Ensure that the denied users for SSH are configured;DesiredObjectValue", + "value": "[parameters('denyUsers')]" + }, + { + "name": "Ensure that the allowed groups for SSH are configured;DesiredObjectValue", + "value": "[parameters('allowGroups')]" + }, + { + "name": "Ensure that the denied groups for SSH are configured;DesiredObjectValue", + "value": "[parameters('denyGroups')]" + }, + { + "name": "Ensure that the SSH HostBasedAuthentication is configured;DesiredObjectValue", + "value": "[parameters('hostBasedAuthentication')]" + }, + { + "name": "Ensure that the SSH PermitRootLogin is configured;DesiredObjectValue", + "value": "[parameters('permitRootLogin')]" + }, + { + "name": "Ensure that the SSH PermitEmptyPasswords is configured;DesiredObjectValue", + "value": "[parameters('permitEmptyPasswords')]" + }, + { + "name": "Ensure that the SSH ClientAliveCountMax is configured;DesiredObjectValue", + "value": "[parameters('clientAliveCountMax')]" + }, + { + "name": "Ensure that the SSH ClientAliveInterval is configured;DesiredObjectValue", + "value": "[parameters('clientAliveInterval')]" + }, + { + "name": "Ensure that the SSH LoginGraceTime is configured;DesiredObjectValue", + "value": "[parameters('loginGraceTime')]" + }, + { + "name": "Ensure that only approved MAC algorithms are used;DesiredObjectValue", + "value": "[parameters('messageAuthenticationCodeAlgorithms')]" + }, + { + "name": "Ensure that the SSH warning banner is configured;DesiredObjectValue", + "value": "[parameters('banner')]" + }, + { + "name": "Ensure that the SSH PermitUserEnvironment is configured;DesiredObjectValue", + "value": "[parameters('permitUserEnvironment')]" + }, + { + "name": "Ensure that appropriate ciphers are used for SSH;DesiredObjectValue", + "value": "[parameters('ciphers')]" + }, + { + "name": "Ensure that the SSH port is configured;DesiredObjectValue", + "value": "[parameters('port')]" + } + ] + } + } + } + ] + } + } + } + } + } + } + } + } + +cases: + # ========================================================================= + # DINE — Linux VM with Canonical publisher, no guest config assignment + # ========================================================================= + + - note: dine_linux_vm_canonical + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-ubuntu-ssh" + location: "eastus" + tags: {} + properties: + osProfile: + linuxConfiguration: {} + storageProfile: + osDisk: + osType: "Linux" + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + parameters: + effect: "DeployIfNotExists" + IncludeArcMachines: "false" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.GuestConfiguration/guestConfigurationAssignments" + name: "SetLinuxSshServerSecurityBaseline" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass — VM with AKS-managed tags → excluded by tag filter + # ========================================================================= + + - note: pass_aks_managed_vm + resource: + type: "Microsoft.Compute/virtualMachines" + name: "aks-node-vm" + location: "eastus" + tags: + aks-managed-orchestrator: "1.27" + aks-managed-poolName: "default" + properties: + osProfile: + linuxConfiguration: {} + storageProfile: + osDisk: + osType: "Linux" + imageReference: + publisher: "Canonical" + offer: "UbuntuServer" + sku: "18.04-LTS" + parameters: + effect: "DeployIfNotExists" + IncludeArcMachines: "false" + want_undefined: true + + # ========================================================================= + # Pass — Windows VM (no linuxConfiguration) + # ========================================================================= + + - note: pass_windows_vm + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-windows" + location: "eastus" + tags: {} + properties: + osProfile: + windowsConfiguration: {} + storageProfile: + osDisk: + osType: "Windows" + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + parameters: + effect: "DeployIfNotExists" + IncludeArcMachines: "false" + want_undefined: true + + # ========================================================================= + # DINE — Arc machine (HybridCompute) when IncludeArcMachines=true + # ========================================================================= + + - note: dine_arc_machine_when_included + resource: + type: "Microsoft.HybridCompute/machines" + name: "arc-linux-ssh" + location: "eastus" + properties: + imageOffer: "linux" + parameters: + effect: "DeployIfNotExists" + IncludeArcMachines: "true" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.GuestConfiguration/guestConfigurationAssignments" + name: "SetLinuxSshServerSecurityBaseline" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Pass — Arc machine when IncludeArcMachines=false + # ========================================================================= + + - note: pass_arc_machine_when_excluded + resource: + type: "Microsoft.HybridCompute/machines" + name: "arc-linux-excluded" + location: "eastus" + properties: + imageOffer: "linux" + parameters: + effect: "DeployIfNotExists" + IncludeArcMachines: "false" + want_undefined: true + + # ========================================================================= + # Skip — wrong resource type + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "staccount1" + location: "eastus" + properties: {} + parameters: + effect: "DeployIfNotExists" + IncludeArcMachines: "false" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_storage_bypass_modify.yaml b/tests/azure_policy/cases/e2e_storage_bypass_modify.yaml new file mode 100644 index 0000000..44d8317 --- /dev/null +++ b/tests/azure_policy/cases/e2e_storage_bypass_modify.yaml @@ -0,0 +1,243 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: VirtualEnclaves/StorageNetworkAccessBypassOnly_Modify +# Real Azure Policy: "Configure Storage Accounts to restrict network access +# through network ACL bypass configuration only" +# Source: regolator/policyDefinitions/VirtualEnclaves/StorageNetworkAccessBypassOnly_Modify.json +# +# Features exercised: +# - Three plain field counts (ipRules[*], resourceAccessRules[*], virtualNetworkRules[*]) +# - greaterOrEquals 1 count comparison +# - Modify with 4 addOrReplace operations (no condition) +# - parameterised bypass configuration +# - notEquals on field + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Configure Storage Accounts to restrict network access through network ACL bypass configuration only", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "String", + "allowedValues": ["Modify", "Disabled"], + "defaultValue": "Modify" + }, + "bypassConfiguration": { + "type": "String", + "allowedValues": [ + "None", + "AzureServices", + "Logging", + "Metrics", + "Logging, Metrics", + "Logging, Metrics, AzureServices", + "Logging, AzureServices", + "Metrics, AzureServices" + ], + "defaultValue": "AzureServices" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Storage/storageAccounts" + }, + { + "anyOf": [ + { + "field": "Microsoft.Storage/storageAccounts/networkAcls.defaultAction", + "notEquals": "Deny" + }, + { + "count": { + "field": "Microsoft.Storage/storageAccounts/networkAcls.ipRules[*]" + }, + "greaterOrEquals": 1 + }, + { + "count": { + "field": "Microsoft.Storage/storageAccounts/networkAcls.resourceAccessRules[*]" + }, + "greaterOrEquals": 1 + }, + { + "count": { + "field": "Microsoft.Storage/storageAccounts/networkAcls.virtualNetworkRules[*]" + }, + "greaterOrEquals": 1 + }, + { + "field": "Microsoft.Storage/storageAccounts/networkAcls.bypass", + "notEquals": "[parameters('bypassConfiguration')]" + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "conflictEffect": "audit", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/17d1049b-9a84-46fb-8f53-869881c3d3ab" + ], + "operations": [ + { + "operation": "addOrReplace", + "field": "Microsoft.Storage/storageAccounts/networkAcls.defaultAction", + "value": "Deny" + }, + { + "operation": "addOrReplace", + "field": "Microsoft.Storage/storageAccounts/networkAcls.ipRules", + "value": [] + }, + { + "operation": "addOrReplace", + "field": "Microsoft.Storage/storageAccounts/networkAcls.virtualNetworkRules", + "value": [] + }, + { + "operation": "addOrReplace", + "field": "Microsoft.Storage/storageAccounts/networkAcls.bypass", + "value": "[parameters('bypassConfiguration')]" + } + ] + } + } + } + } + } + +cases: + # ========================================================================= + # Fully compliant — defaultAction=Deny, no rules, bypass matches + # ========================================================================= + + - note: pass_fully_compliant + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg-compliant" + properties: + networkAcls: + defaultAction: "Deny" + ipRules: [] + resourceAccessRules: [] + virtualNetworkRules: [] + bypass: "AzureServices" + want_undefined: true + + # ========================================================================= + # defaultAction != Deny → Modify + # ========================================================================= + + - note: modify_default_action_allow + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg-allow" + properties: + networkAcls: + defaultAction: "Allow" + ipRules: [] + resourceAccessRules: [] + virtualNetworkRules: [] + bypass: "AzureServices" + want_effect: "Modify" + want_details: + roleDefinitionIds: + - "/providers/Microsoft.Authorization/roleDefinitions/17d1049b-9a84-46fb-8f53-869881c3d3ab" + operations: + - operation: "addOrReplace" + field: "Microsoft.Storage/storageAccounts/networkAcls.defaultAction" + value: "Deny" + - operation: "addOrReplace" + field: "Microsoft.Storage/storageAccounts/networkAcls.ipRules" + value: [] + - operation: "addOrReplace" + field: "Microsoft.Storage/storageAccounts/networkAcls.virtualNetworkRules" + value: [] + - operation: "addOrReplace" + field: "Microsoft.Storage/storageAccounts/networkAcls.bypass" + value: "AzureServices" + + # ========================================================================= + # Has IP rules → count >= 1 triggers Modify + # ========================================================================= + + - note: modify_has_ip_rules + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg-ip-rules" + properties: + networkAcls: + defaultAction: "Deny" + ipRules: + - value: "10.0.0.1" + action: "Allow" + resourceAccessRules: [] + virtualNetworkRules: [] + bypass: "AzureServices" + want_effect: "Modify" + + # ========================================================================= + # Has vnet rules → count >= 1 triggers Modify + # ========================================================================= + + - note: modify_has_vnet_rules + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg-vnet-rules" + properties: + networkAcls: + defaultAction: "Deny" + ipRules: [] + resourceAccessRules: [] + virtualNetworkRules: + - id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1/subnets/subnet1" + bypass: "AzureServices" + want_effect: "Modify" + + # ========================================================================= + # bypass != parameter → Modify + # ========================================================================= + + - note: modify_bypass_mismatch + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg-bypass" + properties: + networkAcls: + defaultAction: "Deny" + ipRules: [] + resourceAccessRules: [] + virtualNetworkRules: [] + bypass: "None" + parameters: + bypassConfiguration: "AzureServices" + want_effect: "Modify" + + # ========================================================================= + # Custom bypass param, bypass matches → pass + # ========================================================================= + + - note: pass_custom_bypass_matches + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg-custom-bypass" + properties: + networkAcls: + defaultAction: "Deny" + ipRules: [] + resourceAccessRules: [] + virtualNetworkRules: [] + bypass: "Logging, Metrics" + parameters: + bypassConfiguration: "Logging, Metrics" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_storage_ip_allowlist.yaml b/tests/azure_policy/cases/e2e_storage_ip_allowlist.yaml new file mode 100644 index 0000000..bb273f8 --- /dev/null +++ b/tests/azure_policy/cases/e2e_storage_ip_allowlist.yaml @@ -0,0 +1,114 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Custom Storage IP allowlist +# Features: count.field + where(allOf) with action gate + notIn(parameters()) + +# parameter defaults from policy definition. + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Storage IP allowlist", + "policyType": "Custom", + "mode": "Indexed", + "description": "Only allow IP rules that match an approved allowlist.", + "metadata": { + "version": "1.0.0", + "category": "Network" + }, + "version": "1.0.0", + "parameters": { + "allowedIps": { + "type": "Array", + "metadata": { + "displayName": "Allowed IPs", + "description": "IP addresses permitted in IP rules" + }, + "defaultValue": ["10.0.0.0/24", "192.168.10.10"] + }, + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": ["deny", "audit", "disabled"], + "defaultValue": "deny" + } + }, + "policyRule": { + "if": { + "count": { + "field": "Microsoft.Storage/storageAccounts/networkAcls.ipRules[*]", + "where": { + "allOf": [ + { + "field": "Microsoft.Storage/storageAccounts/networkAcls.ipRules[*].action", + "equals": "Allow" + }, + { + "field": "Microsoft.Storage/storageAccounts/networkAcls.ipRules[*].value", + "notIn": "[parameters('allowedIps')]" + } + ] + } + }, + "greater": 0 + }, + "then": { + "effect": "[parameters('effect')]" + } + }, + "versions": ["1.0.0"] + }, + "id": "/providers/Microsoft.Authorization/policyDefinitions/storage-ip-allowlist", + "name": "storage-ip-allowlist", + "type": "Microsoft.Authorization/policyDefinitions" + } + +cases: + - note: provided_payload_allow1_does_not_match_action_filter + resource: + name: "storage-dev" + type: "Microsoft.Storage/storageAccounts" + location: "westus2" + properties: + networkAcls: + ipRules: + - value: "10.0.0.0/24" + action: "Allow1" + - value: "203.0.113.5" + action: "Allow1" + context: {} + parameters: {} + want_undefined: true + + - note: deny_when_allow_action_and_ip_not_in_allowlist + resource: + name: "storage-dev" + type: "Microsoft.Storage/storageAccounts" + location: "westus2" + properties: + networkAcls: + ipRules: + - value: "10.0.0.0/24" + action: "Allow" + - value: "203.0.113.5" + action: "Allow" + want_effect: "deny" + + - note: pass_when_all_allow_actions_are_in_allowlist + resource: + name: "storage-dev" + type: "Microsoft.Storage/storageAccounts" + location: "westus2" + properties: + networkAcls: + ipRules: + - value: "10.0.0.0/24" + action: "Allow" + - value: "192.168.10.10" + action: "Allow" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_storage_ip_allowlist_custom.yaml b/tests/azure_policy/cases/e2e_storage_ip_allowlist_custom.yaml new file mode 100644 index 0000000..9d39276 --- /dev/null +++ b/tests/azure_policy/cases/e2e_storage_ip_allowlist_custom.yaml @@ -0,0 +1,125 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Custom Storage IP allowlist policy +# Features: count.field + where(allOf) + notIn + parameterized effect/defaults + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Storage IP allowlist", + "policyType": "Custom", + "mode": "Indexed", + "description": "Only allow IP rules that match an approved allowlist.", + "metadata": { + "version": "1.0.0", + "category": "Network" + }, + "version": "1.0.0", + "parameters": { + "allowedIps": { + "type": "Array", + "metadata": { + "displayName": "Allowed IPs", + "description": "IP addresses permitted in IP rules" + }, + "defaultValue": ["10.0.0.0/24", "192.168.10.10"] + }, + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": ["deny", "audit", "disabled"], + "defaultValue": "deny" + } + }, + "policyRule": { + "if": { + "count": { + "field": "Microsoft.Storage/storageAccounts/networkAcls.ipRules[*]", + "where": { + "allOf": [ + { + "field": "Microsoft.Storage/storageAccounts/networkAcls.ipRules[*].action", + "equals": "Allow" + }, + { + "field": "Microsoft.Storage/storageAccounts/networkAcls.ipRules[*].value", + "notIn": "[parameters('allowedIps')]" + } + ] + } + }, + "greater": 0 + }, + "then": { + "effect": "[parameters('effect')]" + } + }, + "versions": ["1.0.0"] + }, + "id": "/providers/Microsoft.Authorization/policyDefinitions/storage-ip-allowlist", + "name": "storage-ip-allowlist", + "type": "Microsoft.Authorization/policyDefinitions" + } + +cases: + - note: pass_when_action_is_not_exact_allow + resource: + name: "storage-dev" + type: "Microsoft.Storage/storageAccounts" + location: "westus2" + properties: + networkAcls: + ipRules: + - value: "10.0.0.0/24" + action: "Allow1" + - value: "203.0.113.5" + action: "Allow1" + want_undefined: true + + - note: deny_when_disallowed_ip_has_allow_action + resource: + name: "storage-dev" + type: "Microsoft.Storage/storageAccounts" + location: "westus2" + properties: + networkAcls: + ipRules: + - value: "10.0.0.0/24" + action: "Allow" + - value: "203.0.113.5" + action: "Allow" + want_effect: "deny" + + - note: pass_when_all_allow_ips_are_in_allowlist + resource: + name: "storage-dev" + type: "Microsoft.Storage/storageAccounts" + location: "westus2" + properties: + networkAcls: + ipRules: + - value: "10.0.0.0/24" + action: "Allow" + - value: "192.168.10.10" + action: "Allow" + want_undefined: true + + - note: audit_when_effect_parameter_is_overridden + resource: + name: "storage-dev" + type: "Microsoft.Storage/storageAccounts" + location: "westus2" + properties: + networkAcls: + ipRules: + - value: "203.0.113.5" + action: "Allow" + parameters: + effect: "audit" + want_effect: "audit" diff --git a/tests/azure_policy/cases/e2e_storage_public_blob_modify.yaml b/tests/azure_policy/cases/e2e_storage_public_blob_modify.yaml new file mode 100644 index 0000000..be6cbc5 --- /dev/null +++ b/tests/azure_policy/cases/e2e_storage_public_blob_modify.yaml @@ -0,0 +1,158 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Storage/StorageAccountDisablePublicBlobAccess_Modify +# Real Azure Policy: "Configure your Storage account public access to be disallowed" +# Source: regolator/policyDefinitions/Storage/StorageAccountDisablePublicBlobAccess_Modify.json +# +# Features exercised: +# - requestContext().apiVersion less / greaterOrEquals comparisons +# - exists "true" combined with value condition in allOf +# - Modify with requestContext().apiVersion operation condition +# - conflictEffect: audit + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Configure your Storage account public access to be disallowed", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "String", + "allowedValues": ["Modify", "Disabled"], + "defaultValue": "Modify" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Storage/storageAccounts" + }, + { + "anyOf": [ + { + "allOf": [ + { + "value": "[requestContext().apiVersion]", + "less": "2019-04-01" + }, + { + "field": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess", + "exists": "true" + } + ] + }, + { + "field": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess", + "equals": "true" + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "conflictEffect": "audit", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/17d1049b-9a84-46fb-8f53-869881c3d3ab" + ], + "operations": [ + { + "condition": "[greaterOrEquals(requestContext().apiVersion, '2019-04-01')]", + "operation": "addOrReplace", + "field": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess", + "value": false + } + ] + } + } + } + } + } + +cases: + # ========================================================================= + # allowBlobPublicAccess = true, modern API → Modify + # ========================================================================= + + - note: modify_public_blob_enabled_modern_api + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg1" + properties: + allowBlobPublicAccess: true + api_version: "2021-02-01" + want_effect: "Modify" + want_details: + roleDefinitionIds: + - "/providers/Microsoft.Authorization/roleDefinitions/17d1049b-9a84-46fb-8f53-869881c3d3ab" + operations: + - condition: "[greaterOrEquals(requestContext().apiVersion, '2019-04-01')]" + operation: "addOrReplace" + field: "Microsoft.Storage/storageAccounts/allowBlobPublicAccess" + value: false + + # ========================================================================= + # Old API version + field exists → triggers via the less branch + # (requestContext().apiVersion < "2019-04-01" AND field exists → Modify) + # ========================================================================= + + - note: modify_old_api_field_exists + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg2" + properties: + allowBlobPublicAccess: false + api_version: "2018-11-01" + want_effect: "Modify" + want_details: + roleDefinitionIds: + - "/providers/Microsoft.Authorization/roleDefinitions/17d1049b-9a84-46fb-8f53-869881c3d3ab" + operations: + - condition: "[greaterOrEquals(requestContext().apiVersion, '2019-04-01')]" + operation: "addOrReplace" + field: "Microsoft.Storage/storageAccounts/allowBlobPublicAccess" + value: false + + # ========================================================================= + # Old API version but field doesn't exist → pass + # ========================================================================= + + - note: pass_old_api_field_missing + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg3" + properties: {} + api_version: "2018-11-01" + want_undefined: true + + # ========================================================================= + # allowBlobPublicAccess = false, modern API → pass + # ========================================================================= + + - note: pass_public_blob_disabled + resource: + type: "Microsoft.Storage/storageAccounts" + name: "stg4" + properties: + allowBlobPublicAccess: false + api_version: "2021-02-01" + want_undefined: true + + # ========================================================================= + # Wrong resource type → pass + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm1" + properties: {} + api_version: "2021-03-01" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_storage_vnet_rules.yaml b/tests/azure_policy/cases/e2e_storage_vnet_rules.yaml new file mode 100644 index 0000000..3727c35 --- /dev/null +++ b/tests/azure_policy/cases/e2e_storage_vnet_rules.yaml @@ -0,0 +1,180 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Storage/StorageAccountOnlyVnetRulesEnabled_Audit +# Real Azure Policy: "Storage accounts should restrict network access using +# virtual network rules" +# Features: allOf, anyOf, field (type + alias), equals, notEquals, field count +# (array [*] without where), greaterOrEquals, parameters() with defaultValue + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Storage accounts should restrict network access using virtual network rules", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the audit policy" + }, + "allowedValues": ["Audit", "Deny", "Disabled"], + "defaultValue": "Audit" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Storage/storageAccounts" + }, + { + "anyOf": [ + { + "field": "Microsoft.Storage/storageAccounts/networkAcls.defaultAction", + "notEquals": "Deny" + }, + { + "count": { + "field": "Microsoft.Storage/storageAccounts/networkAcls.ipRules[*]" + }, + "greaterOrEquals": 1 + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # Audit — default action is Allow (not Deny) + # ========================================================================= + + - note: audit_default_action_allow + resource: + type: "Microsoft.Storage/storageAccounts" + name: "openStorage" + location: "eastus" + properties: + networkAcls: + defaultAction: "Allow" + ipRules: [] + want_effect: "Audit" + + # ========================================================================= + # Audit — default action is Deny but has IP rules + # ========================================================================= + + - note: audit_deny_with_ip_rules + resource: + type: "Microsoft.Storage/storageAccounts" + name: "restrictedStorage" + location: "eastus" + properties: + networkAcls: + defaultAction: "Deny" + ipRules: + - value: "10.0.0.0/24" + action: "Allow" + want_effect: "Audit" + + # ========================================================================= + # No effect — default action is Deny and no IP rules + # ========================================================================= + + - note: pass_deny_no_ip_rules + resource: + type: "Microsoft.Storage/storageAccounts" + name: "vnetOnlyStorage" + location: "eastus" + properties: + networkAcls: + defaultAction: "Deny" + ipRules: [] + want_undefined: true + + # ========================================================================= + # Audit — both violations: default Allow AND has IP rules + # ========================================================================= + + - note: audit_allow_with_ip_rules + resource: + type: "Microsoft.Storage/storageAccounts" + name: "wideOpenStorage" + location: "westus" + properties: + networkAcls: + defaultAction: "Allow" + ipRules: + - value: "192.168.1.0/24" + action: "Allow" + - value: "10.0.0.1" + action: "Allow" + want_effect: "Audit" + + # ========================================================================= + # Audit — networkAcls missing entirely (defaultAction undefined → notEquals + # "Deny" is true for null/undefined) + # ========================================================================= + + - note: audit_no_network_acls + resource: + type: "Microsoft.Storage/storageAccounts" + name: "noAclStorage" + location: "eastus" + properties: {} + want_effect: "Audit" + + # ========================================================================= + # No effect — wrong resource type + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "myVM" + location: "eastus" + properties: {} + want_undefined: true + + # ========================================================================= + # Deny — explicit effect parameter override + # ========================================================================= + + - note: deny_with_explicit_effect + resource: + type: "Microsoft.Storage/storageAccounts" + name: "openStorage" + location: "eastus" + properties: + networkAcls: + defaultAction: "Allow" + ipRules: [] + parameters: + effect: "Deny" + want_effect: "Deny" + + # ========================================================================= + # No effect — Deny action, ipRules key missing (count over undefined = 0) + # ========================================================================= + + - note: pass_deny_no_ip_rules_key + resource: + type: "Microsoft.Storage/storageAccounts" + name: "minimalStorage" + location: "eastus" + properties: + networkAcls: + defaultAction: "Deny" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_stream_analytics_audit.yaml b/tests/azure_policy/cases/e2e_stream_analytics_audit.yaml new file mode 100644 index 0000000..971ec54 --- /dev/null +++ b/tests/azure_policy/cases/e2e_stream_analytics_audit.yaml @@ -0,0 +1,428 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Stream Analytics/DataExfiltration_Audit +# Real Azure Policy: "Stream Analytics job should connect to trusted inputs +# and outputs" +# Source: regolator/policyDefinitions/Stream Analytics/DataExfiltration_Audit.json +# +# Features exercised: +# - anyOf tree (2 top-level branches for outputs and streamingjobs) +# - Multiple datasource type checks with notIn on sub-resource types +# - Count with where clause on wildcard array (storageAccounts[*].accountName) +# - notIn operator with parameterised Array +# - Long alias paths with dots and hyphens (e.g. datasource.Microsoft-Storage-Blob.storageAccounts[*]) +# - exists "true" + notIn combined check + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Stream Analytics job should connect to trusted inputs and outputs", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "String", + "allowedValues": ["Deny", "Disabled", "Audit"], + "defaultValue": "Audit" + }, + "allowedEventHubNamespaces": { + "type": "Array", + "defaultValue": [] + }, + "allowedSQLServers": { + "type": "Array", + "defaultValue": [] + }, + "allowedStorageAccounts": { + "type": "Array", + "defaultValue": [] + }, + "allowedCosmosAccounts": { + "type": "Array", + "defaultValue": [] + }, + "allowedAzureFunctionAccounts": { + "type": "Array", + "defaultValue": [] + }, + "allowedIoTHubNamespaces": { + "type": "Array", + "defaultValue": [] + }, + "allowedMLWebServiceEndpoints": { + "type": "Array", + "defaultValue": [] + } + }, + "policyRule": { + "if": { + "anyOf": [ + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.StreamAnalytics/streamingjobs/outputs" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.type", + "equals": "Microsoft.EventHub/EventHub" + }, + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.Microsoft-ServiceBus-EventHub.serviceBusNamespace", + "notIn": "[parameters('allowedEventHubNamespaces')]" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.type", + "equals": "Microsoft.Sql/Server/Database" + }, + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.Microsoft-Sql-Server-Database.server", + "notIn": "[parameters('allowedSQLServers')]" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.type", + "equals": "Microsoft.Storage/Table" + }, + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.Microsoft-Storage-Table.accountName", + "notIn": "[parameters('allowedStorageAccounts')]" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.type", + "equals": "Microsoft.Storage/DocumentDB" + }, + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.Microsoft-Storage-DocumentDB.accountId", + "notIn": "[parameters('allowedCosmosAccounts')]" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.type", + "equals": "Microsoft.AzureFunction" + }, + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.Microsoft-AzureFunction.functionAppName", + "notIn": "[parameters('allowedAzureFunctionAccounts')]" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.type", + "equals": "Microsoft.Storage/Blob" + }, + { + "count": { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.Microsoft-Storage-Blob.storageAccounts[*]", + "where": { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.Microsoft-Storage-Blob.storageAccounts[*].accountName", + "notIn": "[parameters('allowedStorageAccounts')]" + } + }, + "greater": 0 + } + ] + }, + { + "field": "Microsoft.StreamAnalytics/streamingjobs/outputs/datasource.type", + "notIn": [ + "Microsoft.EventHub/EventHub", + "Microsoft.Sql/Server/Database", + "Microsoft.Storage/Table", + "Microsoft.Storage/DocumentDB", + "Microsoft.AzureFunction", + "Microsoft.Storage/Blob" + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.StreamAnalytics/streamingjobs" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.StreamAnalytics/streamingjobs/jobStorageAccount", + "exists": "true" + }, + { + "field": "Microsoft.StreamAnalytics/streamingjobs/jobStorageAccount.accountName", + "notIn": "[parameters('allowedStorageAccounts')]" + } + ] + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]" + } + } + } + } + +cases: + # ========================================================================= + # OUTPUT: EventHub output to allowed namespace → pass + # ========================================================================= + + - note: pass_output_eventhub_allowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs/outputs" + name: "output1" + properties: + datasource: + type: "Microsoft.EventHub/EventHub" + Microsoft-ServiceBus-EventHub: + serviceBusNamespace: "my-eh-namespace" + parameters: + allowedEventHubNamespaces: + - "my-eh-namespace" + want_undefined: true + + # ========================================================================= + # OUTPUT: EventHub output to disallowed namespace → Audit + # ========================================================================= + + - note: audit_output_eventhub_disallowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs/outputs" + name: "output2" + properties: + datasource: + type: "Microsoft.EventHub/EventHub" + Microsoft-ServiceBus-EventHub: + serviceBusNamespace: "rogue-namespace" + parameters: + allowedEventHubNamespaces: + - "trusted-namespace" + want_effect: "Audit" + + # ========================================================================= + # OUTPUT: SQL output to allowed server → pass + # ========================================================================= + + - note: pass_output_sql_allowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs/outputs" + name: "output-sql" + properties: + datasource: + type: "Microsoft.Sql/Server/Database" + Microsoft-Sql-Server-Database: + server: "sql-server-approved" + parameters: + allowedSQLServers: + - "sql-server-approved" + want_undefined: true + + # ========================================================================= + # OUTPUT: Blob storage with disallowed account in storageAccounts[*] → Audit + # ========================================================================= + + - note: audit_output_blob_disallowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs/outputs" + name: "output-blob" + properties: + datasource: + type: "Microsoft.Storage/Blob" + Microsoft-Storage-Blob: + storageAccounts: + - accountName: "rogue-storage" + - accountName: "trusted-storage" + parameters: + allowedStorageAccounts: + - "trusted-storage" + want_effect: "Audit" + + # ========================================================================= + # OUTPUT: Blob storage with all accounts allowed → pass + # ========================================================================= + + - note: pass_output_blob_all_allowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs/outputs" + name: "output-blob-ok" + properties: + datasource: + type: "Microsoft.Storage/Blob" + Microsoft-Storage-Blob: + storageAccounts: + - accountName: "trusted-storage" + parameters: + allowedStorageAccounts: + - "trusted-storage" + want_undefined: true + + # ========================================================================= + # OUTPUT: SQL output to disallowed server → Audit + # ========================================================================= + + - note: audit_output_sql_disallowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs/outputs" + name: "output-sql-bad" + properties: + datasource: + type: "Microsoft.Sql/Server/Database" + Microsoft-Sql-Server-Database: + server: "rogue-sql-server" + parameters: + allowedSQLServers: + - "trusted-sql-server" + want_effect: "Audit" + + # ========================================================================= + # OUTPUT: Table output to disallowed account → Audit + # ========================================================================= + + - note: audit_output_table_disallowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs/outputs" + name: "output-table" + properties: + datasource: + type: "Microsoft.Storage/Table" + Microsoft-Storage-Table: + accountName: "rogue-storage" + parameters: + allowedStorageAccounts: + - "trusted-storage" + want_effect: "Audit" + + # ========================================================================= + # OUTPUT: CosmosDB output to disallowed account → Audit + # ========================================================================= + + - note: audit_output_cosmosdb_disallowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs/outputs" + name: "output-cosmosdb" + properties: + datasource: + type: "Microsoft.Storage/DocumentDB" + Microsoft-Storage-DocumentDB: + accountId: "rogue-cosmos-account" + parameters: + allowedCosmosAccounts: + - "trusted-cosmos-account" + want_effect: "Audit" + + # ========================================================================= + # OUTPUT: AzureFunction output to disallowed function app → Audit + # ========================================================================= + + - note: audit_output_function_disallowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs/outputs" + name: "output-function" + properties: + datasource: + type: "Microsoft.AzureFunction" + Microsoft-AzureFunction: + functionAppName: "rogue-function-app" + parameters: + allowedAzureFunctionAccounts: + - "trusted-function-app" + want_effect: "Audit" + + # ========================================================================= + # OUTPUT: Unknown datasource type → Audit (not in known list) + # ========================================================================= + + - note: audit_output_unknown_type + resource: + type: "Microsoft.StreamAnalytics/streamingjobs/outputs" + name: "output-unknown" + properties: + datasource: + type: "Microsoft.SomeNewService/SomeType" + want_effect: "Audit" + + # ========================================================================= + # STREAMINGJOB: jobStorageAccount with disallowed account → Audit + # ========================================================================= + + - note: audit_job_storage_disallowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs" + name: "job1" + properties: + jobStorageAccount: + accountName: "rogue-storage" + parameters: + allowedStorageAccounts: + - "trusted-storage" + want_effect: "Audit" + + # ========================================================================= + # STREAMINGJOB: jobStorageAccount with allowed account → pass + # ========================================================================= + + - note: pass_job_storage_allowed + resource: + type: "Microsoft.StreamAnalytics/streamingjobs" + name: "job2" + properties: + jobStorageAccount: + accountName: "trusted-storage" + parameters: + allowedStorageAccounts: + - "trusted-storage" + want_undefined: true + + # ========================================================================= + # STREAMINGJOB: no jobStorageAccount → pass (exists "true" fails) + # ========================================================================= + + - note: pass_job_no_storage + resource: + type: "Microsoft.StreamAnalytics/streamingjobs" + name: "job3" + properties: {} + want_undefined: true + + # ========================================================================= + # Wrong type entirely → pass + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm1" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_tags_append.yaml b/tests/azure_policy/cases/e2e_tags_append.yaml new file mode 100644 index 0000000..99f8051 --- /dev/null +++ b/tests/azure_policy/cases/e2e_tags_append.yaml @@ -0,0 +1,115 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Tags/ApplyTag_Append +# Real Azure Policy: "Append a tag and its value to resources" +# Source: regolator/policyDefinitions/Tags/ApplyTag_Append.json +# +# Features exercised: +# - Append effect with details array +# - concat() for dynamic tag field +# - exists operator ("false") +# - No type filter (applies to all resource types) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Append a tag and its value to resources", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "tagName": { + "type": "String" + }, + "tagValue": { + "type": "String" + } + }, + "policyRule": { + "if": { + "field": "[concat('tags[', parameters('tagName'), ']')]", + "exists": "false" + }, + "then": { + "effect": "append", + "details": [ + { + "field": "[concat('tags[', parameters('tagName'), ']')]", + "value": "[parameters('tagValue')]" + } + ] + } + } + } + } + +cases: + # ========================================================================= + # Tag missing → append + # ========================================================================= + + - note: append_tag_missing + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-no-env" + properties: + tags: {} + parameters: + tagName: "environment" + tagValue: "production" + want_effect: "append" + want_details: + - field: "[concat('tags[', parameters('tagName'), ']')]" + value: "production" + + - note: append_no_tags_at_all + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-no-tags" + properties: {} + parameters: + tagName: "costCenter" + tagValue: "12345" + want_effect: "append" + + - note: append_other_tags_exist + resource: + type: "Microsoft.Storage/storageAccounts" + name: "sa-partial-tags" + properties: + tags: + owner: "alice" + parameters: + tagName: "environment" + tagValue: "staging" + want_effect: "append" + + # ========================================================================= + # Tag already exists → pass + # ========================================================================= + + - note: pass_tag_already_exists + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-has-env" + properties: + tags: + environment: "dev" + parameters: + tagName: "environment" + tagValue: "production" + want_undefined: true + + - note: pass_tag_matches_value + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-correct" + properties: + tags: + environment: "production" + parameters: + tagName: "environment" + tagValue: "production" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_tags_inherit_modify.yaml b/tests/azure_policy/cases/e2e_tags_inherit_modify.yaml new file mode 100644 index 0000000..d4094f6 --- /dev/null +++ b/tests/azure_policy/cases/e2e_tags_inherit_modify.yaml @@ -0,0 +1,147 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Tags/InheritTag_AddOrReplace_Modify +# Real Azure Policy: "Inherit a tag from the resource group" +# Source: regolator/policyDefinitions/Tags/InheritTag_AddOrReplace_Modify.json +# +# Features exercised: +# - resourceGroup() template function +# - concat() to build dynamic tag field path: tags[] +# - Parameter-derived field names +# - Modify effect with addOrReplace operation +# - value condition with resourceGroup().tags[parameters('tagName')] + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Inherit a tag from the resource group", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "tagName": { + "type": "String", + "metadata": { + "displayName": "Tag Name", + "description": "Name of the tag, such as 'environment'" + } + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "[concat('tags[', parameters('tagName'), ']')]", + "notEquals": "[resourceGroup().tags[parameters('tagName')]]" + }, + { + "value": "[resourceGroup().tags[parameters('tagName')]]", + "notEquals": "" + } + ] + }, + "then": { + "effect": "modify", + "details": { + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + ], + "operations": [ + { + "operation": "addOrReplace", + "field": "[concat('tags[', parameters('tagName'), ']')]", + "value": "[resourceGroup().tags[parameters('tagName')]]" + } + ] + } + } + } + } + } + +cases: + # ========================================================================= + # Resource tag differs from resource group tag → modify + # ========================================================================= + + - note: modify_tag_differs + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-wrong-env" + properties: + tags: + environment: "dev" + parameters: + tagName: "environment" + context: + resourceGroup: + tags: + environment: "production" + want_effect: "modify" + + - note: modify_tag_missing_on_resource + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-no-tag" + properties: {} + parameters: + tagName: "environment" + context: + resourceGroup: + tags: + environment: "production" + want_effect: "modify" + + # ========================================================================= + # Resource tag matches resource group tag → pass + # ========================================================================= + + - note: pass_tag_matches + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-correct-env" + properties: + tags: + environment: "production" + parameters: + tagName: "environment" + context: + resourceGroup: + tags: + environment: "production" + want_undefined: true + + # ========================================================================= + # Resource group tag is empty → pass (second condition fails) + # ========================================================================= + + - note: pass_rg_tag_empty + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-rg-empty" + properties: + tags: + environment: "dev" + parameters: + tagName: "environment" + context: + resourceGroup: + tags: + environment: "" + want_undefined: true + + - note: pass_rg_tag_missing + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-rg-no-tag" + properties: + tags: + environment: "dev" + parameters: + tagName: "environment" + context: + resourceGroup: + tags: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_vm_backup_dine.yaml b/tests/azure_policy/cases/e2e_vm_backup_dine.yaml new file mode 100644 index 0000000..e77d5dd --- /dev/null +++ b/tests/azure_policy/cases/e2e_vm_backup_dine.yaml @@ -0,0 +1,861 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Backup/VirtualMachineBackup_DINE +# Real Azure Policy: "Configure backup on virtual machines without a given tag +# to an existing recovery services vault in the same location" +# Source: regolator/policyDefinitions/Backup/VirtualMachineBackup_DINE.json +# +# Features exercised: +# - DeployIfNotExists effect +# - concat('tags[', parameters('exclusionTagName'), ']') — dynamic tag field access +# - empty() function for Boolean branching +# - notEquals for image publisher exclusion (azureopenshift, AzureDatabricks) +# - notContains on resource id (databricks-rg-) +# - Large anyOf/allOf for image publisher/offer/SKU matching +# - host_await for cross-resource DINE lookup (backupprotecteditems) +# - Deployment template with nested deployment + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Configure backup on virtual machines without a given tag to an existing recovery services vault in the same location", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "vaultLocation": { + "type": "String" + }, + "backupPolicyId": { + "type": "String" + }, + "exclusionTagName": { + "type": "String", + "defaultValue": "" + }, + "exclusionTagValue": { + "type": "Array", + "defaultValue": [] + }, + "effect": { + "type": "String", + "allowedValues": [ + "auditIfNotExists", + "AuditIfNotExists", + "deployIfNotExists", + "DeployIfNotExists", + "disabled", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "field": "location", + "equals": "[parameters('vaultLocation')]" + }, + { + "field": "id", + "notContains": "/resourceGroups/databricks-rg-" + }, + { + "field": "Microsoft.Compute/imagePublisher", + "notEquals": "azureopenshift" + }, + { + "field": "Microsoft.Compute/imagePublisher", + "notEquals": "AzureDatabricks" + }, + { + "anyOf": [ + { + "not": { + "field": "[concat('tags[', parameters('exclusionTagName'), ']')]", + "in": "[parameters('exclusionTagValue')]" + } + }, + { + "value": "[empty(parameters('exclusionTagValue'))]", + "equals": "true" + }, + { + "value": "[empty(parameters('exclusionTagName'))]", + "equals": "true" + } + ] + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftWindowsServer" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "WindowsServer" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "2008-R2-SP1", + "2008-R2-SP1-smalldisk", + "2012-Datacenter", + "2012-Datacenter-smalldisk", + "2012-R2-Datacenter", + "2012-R2-Datacenter-smalldisk", + "2016-Datacenter", + "2016-datacenter-gensecond", + "2016-Datacenter-Server-Core", + "2016-Datacenter-Server-Core-smalldisk", + "2016-Datacenter-smalldisk", + "2016-Datacenter-with-Containers", + "2016-Datacenter-with-RDSH", + "2019-Datacenter", + "2019-Datacenter-Core", + "2019-Datacenter-Core-smalldisk", + "2019-Datacenter-Core-with-Containers", + "2019-Datacenter-Core-with-Containers-smalldisk", + "2019-Datacenter-smalldisk", + "2019-Datacenter-with-Containers", + "2019-Datacenter-with-Containers-smalldisk", + "2019-Datacenter-zhcn", + "2019-datacenter-gensecond", + "2022-datacenter-g2", + "2022-datacenter", + "2022-datacenter-azure-edition", + "2022-datacenter-azure-edition-smalldisk", + "2022-datacenter-azure-edition-core", + "2022-datacenter-azure-edition-core-smalldisk", + "2022-datacenter-smalldisk-g2", + "2022-datacenter-smalldisk", + "2022-datacenter-core-g2", + "2022-datacenter-core", + "2022-datacenter-core-smalldisk-g2", + "2022-datacenter-core-smalldisk" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftWindowsServer" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "WindowsServerSemiAnnual" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "Datacenter-Core-1709-smalldisk", + "Datacenter-Core-1709-with-Containers-smalldisk", + "Datacenter-Core-1803-with-Containers-smalldisk" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftWindowsServerHPCPack" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "WindowsServerHPCPack" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftSQLServer" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "sql2019-ws2022" + }, + { + "field": "Microsoft.Compute/imageSKU", + "in": [ + "sqldev", + "sqldev-gen2", + "standard", + "standard-gen2" + ] + } + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "like": "*-WS2019" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "*-WS2016" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "*-WS2016-BYOL" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "*-WS2012R2" + }, + { + "field": "Microsoft.Compute/imageOffer", + "like": "*-WS2012R2-BYOL" + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftRServer" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "MLServer-WS2016" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftVisualStudio" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "VisualStudio", + "Windows" + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftDynamicsAX" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "Dynamics" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "Pre-Req-AX7-Onebox-U8" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "microsoft-ads" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "windows-data-science-vm" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftWindowsDesktop" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "Windows-10" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "MicrosoftWindowsDesktop" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "Windows-11" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "RedHat" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "RHEL-BYOS" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "rhel-lvm77" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "RHEL", + "RHEL-SAP-HANA" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "6.*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "8*" + } + ] + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "SUSE" + }, + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "SLES", + "SLES-HPC", + "SLES-HPC-Priority", + "SLES-SAP", + "SLES-SAP-BYOS", + "SLES-Priority", + "SLES-BYOS", + "SLES-SAPCAL", + "SLES-Standard" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "12*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Canonical" + }, + { + "field": "Microsoft.Compute/imageOffer", + "contains": "ubuntu" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "14.04*LTS" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "16.04*LTS" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "18.04*LTS" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "*20_04-lts" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "20_04-lts*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "22_04-lts-gen2" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "Oracle" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "Oracle-Linux" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "6.*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "OpenLogic" + }, + { + "anyOf": [ + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "equals": "CentOS-HPC" + }, + { + "field": "Microsoft.Compute/imageSKU", + "equals": "7_9-gen2" + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imageOffer", + "in": [ + "CentOS", + "Centos-LVM", + "CentOS-SRIOV" + ] + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/imageSKU", + "like": "6.*" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + } + ] + } + ] + } + ] + } + ] + }, + { + "allOf": [ + { + "field": "Microsoft.Compute/imagePublisher", + "equals": "cloudera" + }, + { + "field": "Microsoft.Compute/imageOffer", + "equals": "cloudera-centos-os" + }, + { + "field": "Microsoft.Compute/imageSKU", + "like": "7*" + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/9980e02c-c2be-4d73-94e8-173b1dc7cf3c", + "/providers/microsoft.authorization/roleDefinitions/5e467623-bb1f-42f4-a55d-6e525e11384b" + ], + "type": "Microsoft.RecoveryServices/backupprotecteditems", + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "backupPolicyId": { + "type": "String" + }, + "fabricName": { + "type": "String" + }, + "protectionContainers": { + "type": "String" + }, + "protectedItems": { + "type": "String" + }, + "sourceResourceId": { + "type": "String" + } + }, + "resources": [ + { + "apiVersion": "2017-05-10", + "name": "[concat('DeployProtection-',uniqueString(parameters('protectedItems')))]", + "type": "Microsoft.Resources/deployments", + "resourceGroup": "[first(skip(split(parameters('backupPolicyId'), '/'), 4))]", + "subscriptionId": "[first(skip(split(parameters('backupPolicyId'), '/'), 2))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "backupPolicyId": { + "type": "String" + }, + "fabricName": { + "type": "String" + }, + "protectionContainers": { + "type": "String" + }, + "protectedItems": { + "type": "String" + }, + "sourceResourceId": { + "type": "String" + } + }, + "resources": [ + { + "type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems", + "name": "[concat(first(skip(split(parameters('backupPolicyId'), '/'), 8)), '/', parameters('fabricName'), '/',parameters('protectionContainers'), '/', parameters('protectedItems'))]", + "apiVersion": "2016-06-01", + "properties": { + "protectedItemType": "Microsoft.Compute/virtualMachines", + "policyId": "[parameters('backupPolicyId')]", + "sourceResourceId": "[parameters('sourceResourceId')]" + } + } + ] + }, + "parameters": { + "backupPolicyId": { + "value": "[parameters('backupPolicyId')]" + }, + "fabricName": { + "value": "[parameters('fabricName')]" + }, + "protectionContainers": { + "value": "[parameters('protectionContainers')]" + }, + "protectedItems": { + "value": "[parameters('protectedItems')]" + }, + "sourceResourceId": { + "value": "[parameters('sourceResourceId')]" + } + } + } + } + ] + }, + "parameters": { + "backupPolicyId": { + "value": "[parameters('backupPolicyId')]" + }, + "fabricName": { + "value": "Azure" + }, + "protectionContainers": { + "value": "[concat('iaasvmcontainer;iaasvmcontainerv2;', resourceGroup().name, ';' ,field('name'))]" + }, + "protectedItems": { + "value": "[concat('vm;iaasvmcontainerv2;', resourceGroup().name, ';' ,field('name'))]" + }, + "sourceResourceId": { + "value": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/',field('name'))]" + } + } + } + } + } + } + } + } + } + +cases: + # ========================================================================= + # VM without backup → no related backupprotecteditems → DeployIfNotExists + # (default empty exclusion tag params → empty() branches pass the anyOf) + # ========================================================================= + + - note: dine_vm_no_backup + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-no-backup" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + parameters: + vaultLocation: "eastus" + backupPolicyId: "/subscriptions/sub1/resourceGroups/backup-rg/providers/Microsoft.RecoveryServices/vaults/vault1/backupPolicies/DefaultPolicy" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.RecoveryServices/backupprotecteditems" + response: null + want_effect: "DeployIfNotExists" + want_details: + roleDefinitionIds: + - "/providers/microsoft.authorization/roleDefinitions/9980e02c-c2be-4d73-94e8-173b1dc7cf3c" + - "/providers/microsoft.authorization/roleDefinitions/5e467623-bb1f-42f4-a55d-6e525e11384b" + type: "Microsoft.RecoveryServices/backupprotecteditems" + + # ========================================================================= + # VM with existing backup → backupprotecteditems found → compliant (pass) + # ========================================================================= + + - note: pass_vm_with_backup + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-has-backup" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + parameters: + vaultLocation: "eastus" + backupPolicyId: "/subscriptions/sub1/resourceGroups/backup-rg/providers/Microsoft.RecoveryServices/vaults/vault1/backupPolicies/DefaultPolicy" + host_await: + - response: + properties: + protectedItemType: "Microsoft.Compute/virtualMachines" + policyId: "/subscriptions/sub1/resourceGroups/backup-rg/providers/Microsoft.RecoveryServices/vaults/vault1/backupPolicies/DefaultPolicy" + sourceResourceId: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-has-backup" + want_undefined: true + + # ========================================================================= + # VM with exclusion tag matching parameter value → excluded from scope → pass + # Tag "backup-opt-out" = "true" is IN exclusionTagValue ["true", "yes"] + # → not(in) = false, empty(tagValue) = false, empty(tagName) = false + # → anyOf fails → allOf fails → if-condition false → pass + # ========================================================================= + + - note: pass_vm_excluded_by_tag + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-excluded-tag" + location: "eastus" + tags: + backup-opt-out: "true" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + parameters: + vaultLocation: "eastus" + backupPolicyId: "/subscriptions/sub1/resourceGroups/backup-rg/providers/Microsoft.RecoveryServices/vaults/vault1/backupPolicies/DefaultPolicy" + exclusionTagName: "backup-opt-out" + exclusionTagValue: + - "true" + - "yes" + want_undefined: true + + # ========================================================================= + # VM with excluded publisher "AzureDatabricks" → notEquals fails → pass + # ========================================================================= + + - note: pass_vm_excluded_publisher + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-databricks" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "AzureDatabricks" + offer: "databricks" + sku: "standard" + parameters: + vaultLocation: "eastus" + backupPolicyId: "/subscriptions/sub1/resourceGroups/backup-rg/providers/Microsoft.RecoveryServices/vaults/vault1/backupPolicies/DefaultPolicy" + want_undefined: true + + # ========================================================================= + # Wrong resource type → type check fails → pass + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "storage1" + location: "eastus" + properties: {} + parameters: + vaultLocation: "eastus" + backupPolicyId: "/subscriptions/sub1/resourceGroups/backup-rg/providers/Microsoft.RecoveryServices/vaults/vault1/backupPolicies/DefaultPolicy" + want_undefined: true + + # ========================================================================= + # VM has exclusion tag but value NOT in exclusion list → still in scope + # Tag "backup-opt-out" = "no" is NOT IN ["true", "yes"] + # → not(in) = true → anyOf passes → VM is in scope → DINE + # ========================================================================= + + - note: dine_vm_tag_not_in_exclusion + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-tag-no-match" + location: "eastus" + tags: + backup-opt-out: "no" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + parameters: + vaultLocation: "eastus" + backupPolicyId: "/subscriptions/sub1/resourceGroups/backup-rg/providers/Microsoft.RecoveryServices/vaults/vault1/backupPolicies/DefaultPolicy" + exclusionTagName: "backup-opt-out" + exclusionTagValue: + - "true" + - "yes" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.RecoveryServices/backupprotecteditems" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # VM in a different location than vaultLocation → location check fails → pass + # ========================================================================= + + - note: pass_vm_wrong_location + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-wrong-loc" + location: "westeurope" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + parameters: + vaultLocation: "eastus" + backupPolicyId: "/subscriptions/sub1/resourceGroups/backup-rg/providers/Microsoft.RecoveryServices/vaults/vault1/backupPolicies/DefaultPolicy" + want_undefined: true + + # ========================================================================= + # VM in a Databricks resource group → notContains "/resourceGroups/databricks-rg-" + # fails → allOf fails → pass + # ========================================================================= + + - note: pass_vm_databricks_rg + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-dbr" + id: "/subscriptions/sub1/resourceGroups/databricks-rg-myworkspace/providers/Microsoft.Compute/virtualMachines/vm-dbr" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "MicrosoftWindowsServer" + offer: "WindowsServer" + sku: "2019-Datacenter" + parameters: + vaultLocation: "eastus" + backupPolicyId: "/subscriptions/sub1/resourceGroups/backup-rg/providers/Microsoft.RecoveryServices/vaults/vault1/backupPolicies/DefaultPolicy" + want_undefined: true + + # ========================================================================= + # VM with publisher "azureopenshift" → notEquals fails → allOf fails → pass + # ========================================================================= + + - note: pass_vm_excluded_publisher_azureopenshift + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-openshift" + location: "eastus" + properties: + storageProfile: + imageReference: + publisher: "azureopenshift" + offer: "aro4" + sku: "aro_worker" + parameters: + vaultLocation: "eastus" + backupPolicyId: "/subscriptions/sub1/resourceGroups/backup-rg/providers/Microsoft.RecoveryServices/vaults/vault1/backupPolicies/DefaultPolicy" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_vm_managed_disk.yaml b/tests/azure_policy/cases/e2e_vm_managed_disk.yaml new file mode 100644 index 0000000..3f81d23 --- /dev/null +++ b/tests/azure_policy/cases/e2e_vm_managed_disk.yaml @@ -0,0 +1,156 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Compute/VMRequireManagedDisk_Audit +# Real Azure Policy: "Audit VMs that do not use managed disks" +# Features: anyOf, allOf nesting, field (type + alias), equals, exists, +# multiple resource types (VM + VMSS) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Audit VMs that do not use managed disks", + "policyType": "BuiltIn", + "mode": "All", + "parameters": {}, + "policyRule": { + "if": { + "anyOf": [ + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "field": "Microsoft.Compute/virtualMachines/osDisk.uri", + "exists": "True" + } + ] + }, + { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/VirtualMachineScaleSets" + }, + { + "anyOf": [ + { + "field": "Microsoft.Compute/VirtualMachineScaleSets/osDisk.vhdContainers", + "exists": "True" + }, + { + "field": "Microsoft.Compute/VirtualMachineScaleSets/osdisk.imageUrl", + "exists": "True" + } + ] + } + ] + } + ] + }, + "then": { + "effect": "audit" + } + } + } + } + +cases: + # ========================================================================= + # Audit — VM with unmanaged OS disk (vhd uri present) + # ========================================================================= + + - note: audit_vm_unmanaged_osdisk + resource: + type: "Microsoft.Compute/virtualMachines" + name: "legacyVM" + location: "eastus" + properties: + storageProfile: + osDisk: + vhd: + uri: "https://mystorage.blob.core.windows.net/vhds/osdisk.vhd" + want_effect: "audit" + + # ========================================================================= + # No effect — VM with managed disk (no vhd uri) + # ========================================================================= + + - note: pass_vm_managed_disk + resource: + type: "Microsoft.Compute/virtualMachines" + name: "modernVM" + location: "eastus" + properties: + storageProfile: + osDisk: + managedDisk: + storageAccountType: "Premium_LRS" + want_undefined: true + + # ========================================================================= + # Audit — VMSS with vhdContainers (unmanaged) + # ========================================================================= + + - note: audit_vmss_vhd_containers + resource: + type: "Microsoft.Compute/VirtualMachineScaleSets" + name: "legacyScaleSet" + location: "westus" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + vhdContainers: + - "https://stor1.blob.core.windows.net/vhds" + want_effect: "audit" + + # ========================================================================= + # Audit — VMSS with custom image URL (unmanaged) + # ========================================================================= + + - note: audit_vmss_image_url + resource: + type: "Microsoft.Compute/VirtualMachineScaleSets" + name: "customImgScaleSet" + location: "westus" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + image: + uri: "https://mystorage.blob.core.windows.net/images/custom.vhd" + want_effect: "audit" + + # ========================================================================= + # No effect — VMSS with managed disk (no vhdContainers, no imageUrl) + # ========================================================================= + + - note: pass_vmss_managed_disk + resource: + type: "Microsoft.Compute/VirtualMachineScaleSets" + name: "modernScaleSet" + location: "eastus" + properties: + virtualMachineProfile: + storageProfile: + osDisk: + managedDisk: + storageAccountType: "Standard_LRS" + want_undefined: true + + # ========================================================================= + # No effect — wrong resource type entirely + # ========================================================================= + + - note: skip_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "myStorage" + location: "eastus" + properties: {} + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_vm_replication_dine.yaml b/tests/azure_policy/cases/e2e_vm_replication_dine.yaml new file mode 100644 index 0000000..3b96ef4 --- /dev/null +++ b/tests/azure_policy/cases/e2e_vm_replication_dine.yaml @@ -0,0 +1,342 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Compute/VirtualMachineReplication_AzureSiteRecovery_DINE +# Real Azure Policy: "Configure disaster recovery on virtual machines by +# enabling replication via Azure Site Recovery" +# Source: regolator/policyDefinitions/Compute/VirtualMachineReplication_AzureSiteRecovery_DINE.json +# +# Features exercised: +# - DeployIfNotExists effect +# - concat('tags[', parameters('tagName'), ']') — dynamic tag field access +# - empty() function for Boolean branching +# - in / notIn operators on tag values +# - Multi-branch anyOf/allOf with parameterised control flow +# - existenceCondition with like + contains operators +# - host_await for cross-resource lookup + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Configure disaster recovery on virtual machines", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "sourceRegion": { + "type": "String" + }, + "targetRegion": { + "type": "String" + }, + "targetResourceGroupId": { + "type": "String" + }, + "vaultResourceGroupId": { + "type": "String" + }, + "vaultId": { + "type": "String" + }, + "recoveryNetworkId": { + "type": "String", + "defaultValue": "" + }, + "targetZone": { + "type": "String", + "defaultValue": "" + }, + "cacheStorageAccountId": { + "type": "String", + "defaultValue": "" + }, + "tagName": { + "type": "String", + "defaultValue": "" + }, + "tagValue": { + "type": "Array", + "defaultValue": [] + }, + "tagType": { + "type": "String", + "allowedValues": ["Inclusion", "Exclusion", ""], + "defaultValue": "" + }, + "effect": { + "type": "String", + "allowedValues": ["DeployIfNotExists", "Disabled"], + "defaultValue": "DeployIfNotExists" + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "field": "location", + "equals": "[parameters('sourceRegion')]" + }, + { + "anyOf": [ + { + "allOf": [ + { + "value": "[parameters('tagType')]", + "equals": "Inclusion" + }, + { + "field": "[concat('tags[', parameters('tagName'), ']')]", + "in": "[parameters('tagValue')]" + } + ] + }, + { + "allOf": [ + { + "value": "[parameters('tagType')]", + "equals": "Exclusion" + }, + { + "field": "[concat('tags[', parameters('tagName'), ']')]", + "notIn": "[parameters('tagValue')]" + } + ] + }, + { + "anyOf": [ + { + "value": "[empty(parameters('tagName'))]", + "equals": "true" + }, + { + "value": "[empty(parameters('tagValue'))]", + "equals": "true" + }, + { + "value": "[empty(parameters('tagType'))]", + "equals": "true" + } + ] + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Resources/links", + "existenceCondition": { + "allOf": [ + { + "field": "name", + "like": "ASR-Policy-Protect-*" + }, + { + "field": "Microsoft.Resources/links/targetId", + "contains": "/replicationProtectedItems/" + } + ] + } + } + } + } + } + } + +cases: + # ========================================================================= + # No tag filters (all params empty) → matches, related resource not found → DINE + # ========================================================================= + + - note: dine_no_tag_filter_no_related + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-no-dr" + location: "eastus" + properties: {} + parameters: + sourceRegion: "eastus" + targetRegion: "westus" + targetResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-dr" + vaultResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-vault" + vaultId: "/subscriptions/sub1/resourceGroups/rg-vault/providers/Microsoft.RecoveryServices/vaults/vault1" + host_await: + - key: + operation: "lookup_related_resources" + type: "Microsoft.Resources/links" + response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # No tag filter, related resource found with matching name/targetId → compliant + # ========================================================================= + + - note: compliant_dr_configured + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-dr-ok" + location: "eastus" + properties: {} + parameters: + sourceRegion: "eastus" + targetRegion: "westus" + targetResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-dr" + vaultResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-vault" + vaultId: "/subscriptions/sub1/resourceGroups/rg-vault/providers/Microsoft.RecoveryServices/vaults/vault1" + host_await: + - response: + name: "ASR-Policy-Protect-vm-dr-ok" + properties: + targetid: "/subscriptions/sub1/resourceGroups/rg-vault/providers/Microsoft.RecoveryServices/vaults/vault1/replicationProtectedItems/item1" + want_undefined: true + + # ========================================================================= + # Related resource found but name doesn't match like → DINE + # ========================================================================= + + - note: dine_wrong_link_name + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-wrong-link" + location: "eastus" + properties: {} + parameters: + sourceRegion: "eastus" + targetRegion: "westus" + targetResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-dr" + vaultResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-vault" + vaultId: "/subscriptions/sub1/resourceGroups/rg-vault/providers/Microsoft.RecoveryServices/vaults/vault1" + host_await: + - response: + name: "some-other-link" + properties: + targetid: "/subscriptions/sub1/resourceGroups/rg-vault/providers/Microsoft.RecoveryServices/vaults/vault1/replicationProtectedItems/item1" + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Inclusion tag type — VM has matching tag → if-condition matches + # ========================================================================= + + - note: dine_inclusion_tag_match + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-tagged" + location: "eastus" + tags: + Environment: "production" + properties: {} + parameters: + sourceRegion: "eastus" + targetRegion: "westus" + targetResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-dr" + vaultResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-vault" + vaultId: "/subscriptions/sub1/resourceGroups/rg-vault/providers/Microsoft.RecoveryServices/vaults/vault1" + tagName: "Environment" + tagValue: + - "production" + - "staging" + tagType: "Inclusion" + host_await: + - response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Inclusion tag type — VM tag doesn't match → if-condition fails → pass + # ========================================================================= + + - note: pass_inclusion_tag_no_match + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-dev" + location: "eastus" + tags: + Environment: "development" + properties: {} + parameters: + sourceRegion: "eastus" + targetRegion: "westus" + targetResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-dr" + vaultResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-vault" + vaultId: "/subscriptions/sub1/resourceGroups/rg-vault/providers/Microsoft.RecoveryServices/vaults/vault1" + tagName: "Environment" + tagValue: + - "production" + - "staging" + tagType: "Inclusion" + want_undefined: true + + # ========================================================================= + # Exclusion tag type — VM tag in exclusion list → pass (excluded from scope) + # ========================================================================= + + - note: pass_exclusion_tag_excluded + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-excluded" + location: "eastus" + tags: + Environment: "sandbox" + properties: {} + parameters: + sourceRegion: "eastus" + targetRegion: "westus" + targetResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-dr" + vaultResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-vault" + vaultId: "/subscriptions/sub1/resourceGroups/rg-vault/providers/Microsoft.RecoveryServices/vaults/vault1" + tagName: "Environment" + tagValue: + - "sandbox" + - "test" + tagType: "Exclusion" + want_undefined: true + + # ========================================================================= + # Exclusion tag type — VM tag NOT in exclusion list → matches → DINE + # ========================================================================= + + - note: dine_exclusion_tag_not_excluded + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-prod-excl" + location: "eastus" + tags: + Environment: "production" + properties: {} + parameters: + sourceRegion: "eastus" + targetRegion: "westus" + targetResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-dr" + vaultResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-vault" + vaultId: "/subscriptions/sub1/resourceGroups/rg-vault/providers/Microsoft.RecoveryServices/vaults/vault1" + tagName: "Environment" + tagValue: + - "sandbox" + - "test" + tagType: "Exclusion" + host_await: + - response: null + want_effect: "DeployIfNotExists" + + # ========================================================================= + # Wrong location → if-condition fails → pass + # ========================================================================= + + - note: pass_wrong_location + resource: + type: "Microsoft.Compute/virtualMachines" + name: "vm-wrong-loc" + location: "westus" + properties: {} + parameters: + sourceRegion: "eastus" + targetRegion: "westus" + targetResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-dr" + vaultResourceGroupId: "/subscriptions/sub1/resourceGroups/rg-vault" + vaultId: "/subscriptions/sub1/resourceGroups/rg-vault/providers/Microsoft.RecoveryServices/vaults/vault1" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_vm_skus_allowed.yaml b/tests/azure_policy/cases/e2e_vm_skus_allowed.yaml new file mode 100644 index 0000000..06447db --- /dev/null +++ b/tests/azure_policy/cases/e2e_vm_skus_allowed.yaml @@ -0,0 +1,150 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Compute/VMSkusAllowed_Deny +# Real Azure Policy: "Allowed virtual machine size SKUs" +# Features: allOf, not, field (type + alias), equals, in, parameters() + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Allowed virtual machine size SKUs", + "policyType": "BuiltIn", + "mode": "Indexed", + "parameters": { + "listOfAllowedSKUs": { + "type": "Array", + "metadata": { + "displayName": "Allowed Size SKUs" + } + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Compute/virtualMachines" + }, + { + "not": { + "field": "Microsoft.Compute/virtualMachines/sku.name", + "in": "[parameters('listOfAllowedSKUs')]" + } + } + ] + }, + "then": { + "effect": "Deny" + } + } + } + } + +cases: + # ========================================================================= + # Deny — VM SKU not in allowed list + # ========================================================================= + + - note: deny_disallowed_sku + resource: + type: "Microsoft.Compute/virtualMachines" + name: "myVM" + location: "eastus" + properties: + hardwareProfile: + vmSize: "Standard_E64i_v3" + parameters: + listOfAllowedSKUs: + - "Standard_D2s_v3" + - "Standard_D4s_v3" + - "Standard_D8s_v3" + want_effect: "Deny" + + # ========================================================================= + # No effect — VM SKU is in allowed list + # ========================================================================= + + - note: allow_permitted_sku + resource: + type: "Microsoft.Compute/virtualMachines" + name: "myVM" + location: "eastus" + properties: + hardwareProfile: + vmSize: "Standard_D4s_v3" + parameters: + listOfAllowedSKUs: + - "Standard_D2s_v3" + - "Standard_D4s_v3" + - "Standard_D8s_v3" + want_undefined: true + + # ========================================================================= + # No effect — wrong resource type (not a VM) + # ========================================================================= + + - note: skip_wrong_resource_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "myStorage" + location: "eastus" + properties: {} + parameters: + listOfAllowedSKUs: + - "Standard_D2s_v3" + want_undefined: true + + # ========================================================================= + # Deny — single allowed SKU, VM doesn't match + # ========================================================================= + + - note: deny_single_allowed_sku + resource: + type: "Microsoft.Compute/virtualMachines" + name: "bigVM" + location: "westus" + properties: + hardwareProfile: + vmSize: "Standard_M128s" + parameters: + listOfAllowedSKUs: + - "Standard_B1s" + want_effect: "Deny" + + # ========================================================================= + # No effect — empty allowed list means everything blocked? No: `in` with + # empty array is always false → not(false)=true → Deny. + # ========================================================================= + + - note: deny_empty_allowed_list + resource: + type: "Microsoft.Compute/virtualMachines" + name: "anyVM" + location: "eastus" + properties: + hardwareProfile: + vmSize: "Standard_D2s_v3" + parameters: + listOfAllowedSKUs: [] + want_effect: "Deny" + + # ========================================================================= + # Case sensitivity — SKU names are compared case-insensitively by the + # `in` operator (Azure Policy string comparison semantics) + # ========================================================================= + + - note: allow_case_insensitive_sku + resource: + type: "Microsoft.Compute/virtualMachines" + name: "myVM" + location: "eastus" + properties: + hardwareProfile: + vmSize: "standard_d4s_v3" + parameters: + listOfAllowedSKUs: + - "Standard_D4s_v3" + want_undefined: true diff --git a/tests/azure_policy/cases/e2e_vnet_ddos_modify.yaml b/tests/azure_policy/cases/e2e_vnet_ddos_modify.yaml new file mode 100644 index 0000000..d74fa42 --- /dev/null +++ b/tests/azure_policy/cases/e2e_vnet_ddos_modify.yaml @@ -0,0 +1,157 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# E2E Test: Network/VirtualNetworkDdosStandard_Audit +# Real Azure Policy: "Virtual networks should be protected by Azure DDoS Protection" +# Source: regolator/policyDefinitions/Network/VirtualNetworkDdosStandard_Audit.json +# +# Features exercised: +# - Modify effect with operations (addOrReplace) +# - anyOf within allOf (enableDdosProtection OR ddosProtectionPlan empty) +# - Parameterized effect with Modify/Audit/Disabled +# - conflictEffect in details +# - field equals "" (empty string check) + +aliases: test_aliases.json + +policy_definition: | + { + "properties": { + "displayName": "Virtual networks should be protected by Azure DDoS Protection", + "policyType": "BuiltIn", + "mode": "All", + "parameters": { + "effect": { + "type": "String", + "allowedValues": ["Modify", "Audit", "Disabled"], + "defaultValue": "Modify" + }, + "ddosPlan": { + "type": "String", + "metadata": { + "displayName": "DDoS Protection Plan", + "strongType": "Microsoft.Network/ddosProtectionPlans" + } + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/virtualNetworks" + }, + { + "anyOf": [ + { + "field": "Microsoft.Network/virtualNetworks/enableDdosProtection", + "notEquals": true + }, + { + "field": "Microsoft.Network/virtualNetworks/ddosProtectionPlan", + "equals": "" + } + ] + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "conflictEffect": "audit", + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7" + ], + "operations": [ + { + "operation": "addOrReplace", + "field": "Microsoft.Network/virtualNetworks/enableDdosProtection", + "value": true + }, + { + "operation": "addOrReplace", + "field": "Microsoft.Network/virtualNetworks/ddosProtectionPlan.id", + "value": "[parameters('ddosPlan')]" + } + ] + } + } + } + } + } + +cases: + # ========================================================================= + # Non-compliant: DDoS not enabled + # ========================================================================= + + - note: modify_ddos_not_enabled + resource: + type: "Microsoft.Network/virtualNetworks" + name: "vnet-no-ddos" + properties: + enableDdosProtection: false + ddosProtectionPlan: "" + parameters: + ddosPlan: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/ddosProtectionPlans/plan1" + want_effect: "Modify" + want_details: + roleDefinitionIds: + - "/providers/microsoft.authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7" + operations: + - operation: "addOrReplace" + field: "Microsoft.Network/virtualNetworks/enableDdosProtection" + value: true + - operation: "addOrReplace" + field: "Microsoft.Network/virtualNetworks/ddosProtectionPlan.id" + value: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/ddosProtectionPlans/plan1" + + - note: modify_ddos_enabled_but_no_plan + resource: + type: "Microsoft.Network/virtualNetworks" + name: "vnet-no-plan" + properties: + enableDdosProtection: true + ddosProtectionPlan: "" + parameters: + ddosPlan: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/ddosProtectionPlans/plan1" + want_effect: "Modify" + + - note: modify_ddos_missing_field + resource: + type: "Microsoft.Network/virtualNetworks" + name: "vnet-missing" + properties: {} + parameters: + ddosPlan: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/ddosProtectionPlans/plan1" + want_effect: "Modify" + + # ========================================================================= + # Compliant: DDoS enabled with plan + # ========================================================================= + + - note: pass_ddos_enabled_with_plan + resource: + type: "Microsoft.Network/virtualNetworks" + name: "vnet-protected" + properties: + enableDdosProtection: true + ddosProtectionPlan: + id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/ddosProtectionPlans/plan1" + parameters: + ddosPlan: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/ddosProtectionPlans/plan1" + want_undefined: true + + # ========================================================================= + # Wrong type + # ========================================================================= + + - note: pass_wrong_type + resource: + type: "Microsoft.Storage/storageAccounts" + name: "not-a-vnet" + properties: + enableDdosProtection: false + parameters: + ddosPlan: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Network/ddosProtectionPlans/plan1" + want_undefined: true