-
Notifications
You must be signed in to change notification settings - Fork 981
ALZ Policies Testing
The ALZ Policy Testing Framework is a set of tools and scripts that can be used to test Azure Policies do what is expected and prevent breaking regressions. The framework is designed to be used with pipelines as part of CI/CD processes to test policies as they are developed and integrated to ultimately improve the quality and stability of policies going into production environments.
This framework is based on the work done by @fawohlsc in this repo azure-policy-testing, and is built on the well established PowerShell testing framework Pester.
For ALZ, the focus is on testing Azure Policy definitions that have a DENY effect, as these can be very disruptive to organizations if a regression is introduced, and helps us improve the quality of the policies we are developing and deploying to production environments. The framework can be extended to test other policy effects, but this is not the focus of this framework.
NOTE: The ALZ team are considering adding support for testing Azure Policy definitions that use other effects like Audit, DeployIfNotExists.
For authoring tests we standardized on using Az PowerShell native commands as much as possible as it is simpler to implement and read, however, there are circumstances where you will need to use REST APIs as not all features are exposed through Az PowerShell. To keep things simple, we have leveraged the Invoke-AzRestMethod
function that wraps the REST API calls and make it easier to use in the Pester tests.
- An empty (dedicated) Azure subscription
- If following the same process as outlined below, you will also need to ensure this subscription is added to the "Corp" management group in the Azure Landing Zone
- Pester
- Az PowerShell Module
- Invoke-AzRestMethod
The ALZ policy testing framework is designed to be used with GitHub Actions, but can be used with any CI/CD pipeline that supports PowerShell, or can be run directly on an ad hoc basis. The ALZ policy testing framework is designed to be used with the following workflow:
- A pull request is created to update a policy definition
- The pull request triggers a GitHub Action workflow
- The workflow runs the defined Pester tests against the policy definition
- The workflow reports the results of the tests back to the pull request checks
- The pull request is reviewed and handled based on the results of the tests
Create a new GitHub Action workflow in the .github/workflows
folder of your repository. The workflow should be triggered on pull request events and should run on the main
branch. The workflow should also allow being triggered manually to allow for testing of policies outside of pull requests.
Sample GitHub Action Workflow to run Policy tests
Create a new Pester test file in the tests/policy
folder of your repository. The test file should be named the same as the policy definition file it is testing, but with a .tests.ps1
extension. For example, if the policy definition file is named azurepolicy.json
, the test file should be named azurepolicy.tests.ps1
.
Write the Pester tests in the test file. The tests should cover the following scenarios:
- Conditions that should be true when the policy is evaluated, so it is compliant
- Conditions that should be false when the policy is evaluated, so it is non-compliant
It is important to test all the conditions evaluated in the policy. For example, if the policy is evaluating the location
of a resource, you should test the following scenarios:
- Resource is deployed in a location that is compliant with the policy
- Resource is deployed in a location that is non-compliant with the policy
See the How to write Pester tests for policies section for more details on how to write Pester tests for policies.
The testing framework is located in the ALZ repository in the tests
folder. The framework consists of the following folders:
-
policy
- Contains the Pester tests for the policies -
utils
- Contains the utility functions used by the Pester tests
For the purposes of this guide, we'll focus on the Policy test for Deny-MgmtPorts-Internet
policy as it demonstrates using both Az PowerShell and REST API calls in the Pester test. The policy definition file is located in the policy
folder of the ALZ repository in the policy
folder.
The policy tests are designed to run in an empty subscription(s) to ensure that the policy is evaluated in isolation and not impacted by other policies or resources in the subscription.
NOTE: Because we are testing Azure policies in the context of Azure Landing Zone, we are using a dedicated subscription in the "Corp" landing zone that is added under the Corp management group, where we retrieve the deployed policy definition ID and create a new policy assignment to test the policy (because we do not assign all policies by default, and some get assigned to different scopes). You can extend this methodology to test policies outside of Azure Landing Zone by deploying the policy you want to test and assigning it to the scope you want to test (e.g. subscription, resource group, etc.
The policy test has 4 main sections (aligned with how Pester works):
# Set the default context for Az commands.
Set-AzContext -SubscriptionId $env:SUBSCRIPTION_ID -TenantId $env:TENANT_ID -Force
if (-not [String]::IsNullOrEmpty($DeploymentConfigPath)) {
Write-Information "==> Loading deployment configuration from : $DeploymentConfigPath"
$deploymentObject = Get-Content -Path $DeploymentConfigPath | ConvertFrom-Json -AsHashTable
# Set the esCompanyPrefix from the deployment configuration if not specified
$esCompanyPrefix = $deploymentObject.TemplateParameterObject.enterpriseScaleCompanyPrefix
$mangementGroupScope = "/providers/Microsoft.Management/managementGroups/$esCompanyPrefix-corp"
}
$definition = Get-AzPolicyDefinition | Where-Object { $_.Name -eq 'Deny-MgmtPorts-From-Internet' }
New-AzPolicyAssignment -Name "TDeny-MgmtPorts-Internet" -Scope $mangementGroupScope -PolicyDefinition $definition -PolicyParameterObject @{
"ports" = @("3389", "22")
}
As part of the setup before running the test, we need to ensure we have the correct Azure context set, and that the policy is assigned to the correct scope. Because these steps are running as part of Azure Landing Zone pull request testing, the policies we want to test get deployed prior to running these test. In this case, we retrieve the policy definition and assign it to the management group scope, passing in the policy parameters to ensure the policy is evaluated correctly.
If you want to extend this methodology to test policies independent of deploying ALZ, you could extend this section to also deploy the policy you want to test, and then do the policy assignment.
DENY - group of tests to validate scenarios that where the policy effect is applied and deployment should fail.
As an example, using Az PowerShell:
It "Should deny non-compliant port '3389'" -Tag "deny-noncompliant-nsg-port" {
AzTest -ResourceGroup {
param($ResourceGroup)
$networkSecurityGroup = New-AzNetworkSecurityGroup `
-Name "nsg-test" `
-ResourceGroupName $ResourceGroup.ResourceGroupName `
-Location $ResourceGroup.Location
# Should be disallowed by policy, so exception should be thrown.
{
$networkSecurityGroup | Add-AzNetworkSecurityRuleConfig `
-Name RDP-rule `
-Description "Allow RDP" `
-Access Allow `
-Protocol Tcp `
-Direction Inbound `
-Priority 200 `
-SourceAddressPrefix * `
-SourcePortRange * `
-DestinationAddressPrefix * `
-DestinationPortRange 3389 # Incompliant.
| Set-AzNetworkSecurityGroup
} | Should -Throw "*disallowed by policy*"
}
}
In this example, we are creating a new Network Security Group (NSG) and adding a rule to allow RDP traffic on port 3389. The policy we're testing is configured to deny traffic on port 3389, so we expect this operation to fail. We use the Should -Throw
command to validate that the operation failed with the expected error message.
ALLOW - group of tests to validate scenarios that are compliant with the policy conditions and should succeed.
As an example, using REST API with Invoke-AzRestMethod
:
It "Should allow compliant port ranges* - API" -Tag "allow-compliant-nsg-port" {
AzTest -ResourceGroup {
param($ResourceGroup)
#Destination port ranges to test
$portRanges = @("23","3390-3392","8080")
# Create Payload for NSG
$securityRules = @(
@{
name = "Web-rule"
properties = @{
description = "Allow Web2"
protocol = "Tcp"
sourcePortRange = "*"
destinationPortRange = "443"
sourceAddressPrefix = "*"
destinationAddressPrefix = "*"
access = "Allow"
priority = 300
direction = "Inbound"
}
},
@{
name = "Multi-rule"
properties = @{
description = "Allow Mgmt3"
protocol = "Tcp"
sourcePortRange = "*"
destinationPortRanges = $portRanges
sourceAddressPrefix = "*"
destinationAddressPrefix = "*"
access = "Allow"
priority = 310
direction = "Inbound"
}
}
)
$object = @{
properties = @{
securityRules = $securityRules
}
location = "uksouth"
}
$payload = ConvertTo-Json -InputObject $object -Depth 100
# Should be disallowed by policy, so exception should be thrown.
{
$httpResponse = Invoke-AzRestMethod `
-ResourceGroupName $ResourceGroup.ResourceGroupName `
-ResourceProviderName "Microsoft.Network" `
-ResourceType "networkSecurityGroups" `
-Name "testNSG99" `
-ApiVersion "2022-11-01" `
-Method "PUT" `
-Payload $payload
if ($httpResponse.StatusCode -eq 200 -or $httpResponse.StatusCode -eq 201) {
# NSG created
}
# Error response describing why the operation failed.
else {
throw "Operation failed with message: '$($httpResponse.Content)'"
}
} | Should -Not -Throw
}
}
In this example, we are creating a new Network Security Group (NSG) and adding a rule to allow traffic on port 443. The policy we're testing is configured to deny traffic on port 3389, so we expect this operation to succeed. We use the Should -Not -Throw
command to validate that the operation succeeded.
Remove-AzPolicyAssignment -Name "TDeny-MgmtPorts-Internet" -Scope $mangementGroupScope -Confirm:$false
In this example, we are removing the policy assignment after the tests are completed (if you're testing outside of an ALZ deployment, you can also use this to remove the deployed policy).
- What's New?
- Community Calls
- Frequently Asked Questions (FAQ)
- Known issues
- What is Enterprise-Scale
- How it Works
- Deploying Enterprise-Scale
- Pre-requisites
- ALZ Resource Providers Guidance
- Configure Microsoft Entra permissions
- Configure Azure permissions
- Deploy landing zones
- Deploy reference implementations
- Telemetry Tracking Using Customer Usage Attribution (PID)
- Deploy without hybrid connectivity to on-premises
- Deploy with a hub and spoke based network topology
- Deploy with a hub and spoke based network topology with Zero Trust principles
- Deploy with an Azure Virtual WAN based network topology
- Deploy for Small Enterprises
- Operating the Azure platform using AzOps (Infrastructure as Code with GitHub Actions)
- Deploy workloads
- Create landing zones (subscriptions) via Subscription Vending
- Azure Landing Zones Deprecated Services
- Azure Landing Zone (ALZ) Policies
- Policies included in Azure landing zones reference implementations
- Policies included but not assigned by default and Workload Specific Compliance initiatives
- Policies FAQ & Tips
- Policies Testing Framework
- Migrate Azure landing zones custom policies to Azure built-in policies
- Updating Azure landing zones custom policies to latest
- MMA Deprecation Guidance
- Contributing