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