This policy initiative automatically configures private DNS zones for Azure private endpoints across your management group, ensuring private connectivity is properly configured without manual intervention.
When you create a private endpoint in Azure, you need to create DNS records in private DNS zones so resources can resolve the private IP address. This policy set automates that process by:
- Detecting when private endpoints are created
- Automatically creating DNS zone group configurations
- Linking the private endpoint to the appropriate private DNS zones
- Monitors: All private endpoint resources created under the management group scope
- Evaluates: Whether each private endpoint has the required private DNS zone configurations
- Remediates: Automatically creates DNS zone groups with the correct DNS zones when missing
The policy set uses a hybrid approach:
Uses Microsoft's native Azure Policy definitions for common services like:
- Storage Accounts (blob, file, queue, table, dfs, web)
- Key Vault
- Azure SQL Database
- Container Registry
- Event Hub, Service Bus, Event Grid
- Web Apps
- Azure Cache for Redis
- Search Services
- Machine Learning workspaces
- And more...
Deploys custom policy definitions for services without built-in policies or requiring special configuration:
- Azure Automation (Webhook, DSC and Hybrid Worker)
- Azure Synapse (SQL, SQL On-Demand, Dev)
- Storage secondary endpoints
- Cosmos DB (SQL, MongoDB, MongoDB vCore, Cassandra, Gremlin, Table)
- Database services (PostgreSQL, MySQL, MariaDB)
- AKS clusters (region-specific zones)
- Backup and Site Recovery
- Azure AI Foundry (special multi-zone configuration)
- Redis Enterprise
- Healthcare APIs
Azure AI Foundry resources (Microsoft.CognitiveServices/accounts with kind AIServices) require special handling because a single private endpoint must create DNS records in three private DNS zones simultaneously:
privatelink.cognitiveservices.azure.comprivatelink.openai.azure.comprivatelink.services.ai.azure.com
The custom policy template includes logic to:
- Accept an array of DNS zones via
privateDnsZoneConfigsparameter - Create multiple DNS zone configurations within a single zone group
- Verify compliance by checking that all required zones exist (not just one)
This ensures Azure AI Foundry endpoints work correctly for all service scenarios (Cognitive Services, OpenAI, AI Services).
- Management group where policies will be deployed
- Subscription containing the central private DNS zones
- Resource group containing the private DNS zones
- Policy version number, used in custom definition and names, eg "v3", "v4", etc.
Be sure to edit the pubsecDNS.parameters.json file with the managementgroupID that should be used to contain the definition. The deployment should also refer to this same management group ID, as below.
# Deploy the policies and policy set
New-AzManagementGroupDeployment `
-ManagementGroupId "alz" `
-Location "canadacentral" `
-TemplateFile ".\pubsecDNS.bicep" `
-TemplateParameterFile ".\pubsecDNS.parameters.json" `
-policyVersion "vX"# Assign to management group with managed identity
New-AzPolicyAssignment `
-Name "dns-private-endpoints" `
-DisplayName "Central DNS for Private Endpoints" `
-PolicySetDefinition (Get-AzPolicySetDefinition -Name 'custom-central-dns-private-endpoints' -ManagementGroupName "SLZ") `
-Scope "/providers/Microsoft.Management/managementGroups/SLZ" `
-PolicyParameterObject @{
privateDNSZoneSubscriptionId="<subscription-id>"
privateDNSZoneResourceGroupName="<resource-group-name>"
} `
-Location "canadacentral" `
-IdentityType "SystemAssigned"The managed identity needs Private DNS Zone Contributor role on the resource group containing your private DNS zones:
# Get the assignment's principal ID
$assignment = Get-AzPolicyAssignment -Name "dns-private-endpoints" -Scope "/providers/Microsoft.Management/managementGroups/SLZ"
# Grant permissions
New-AzRoleAssignment `
-ObjectId $assignment.Identity.PrincipalId `
-RoleDefinitionName "Private DNS Zone Contributor" `
-Scope "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>"The pubsecDNS.parameters.json file defines which DNS zones to configure:
{
"privateLinkServiceNamespace": "Microsoft.CognitiveServices/accounts",
"zone": "privatelink.cognitiveservices.azure.com",
"filterLocationLike": "*",
"groupId": "account",
"privateDnsZoneConfigs": [
"privatelink.cognitiveservices.azure.com",
"privatelink.openai.azure.com",
"privatelink.services.ai.azure.com"
]
}Fields:
privateLinkServiceNamespace: Azure resource provider and typezone: Primary DNS zone name (used for built-in policies)filterLocationLike: Region filter (*for all regions, or specific region likecanadacentral)groupId: Private link group identifierprivateDnsZoneConfigs: Array of DNS zones to configure (supports multiple zones for single endpoint)
The custom policy template (templates/DNS-PrivateEndpoints/azurepolicy.json) uses:
-
Existence Condition: Checks if private endpoint has DNS configurations for ALL required zones
"existenceCondition": { "count": { "value": "[parameters('privateDnsZoneConfigs')]", "where": { /* check each zone exists */ } }, "equals": "[length(parameters('privateDnsZoneConfigs'))]" }
-
Deployment Template: Uses ARM template
copyfunction to create multiple zone configs"copy": [{ "name": "privateDnsZoneConfigs", "count": "[length(parameters('privateDnsZoneConfigs'))]", "input": { /* create zone config */ } }]
- Effect:
DeployIfNotExists - Trigger: Private endpoint creation or update
- Compliance Check: Verifies all required DNS zone configurations exist
- Remediation: Creates missing DNS zone group with all required zones
-
Check if built-in policy exists:
- If yes: Add to
builtInPolicyMapinpubsecDNS.bicep - If no: Add to
privateDNSZonesarray in parameters file (will use custom policy)
- If yes: Add to
-
For multi-zone services (like AI Foundry):
- Include all zones in
privateDnsZoneConfigsarray - The custom policy template handles multiple zones automatically
- Include all zones in
To update existing policies, you must delete them first (Azure doesn't allow removing parameters from policies):
# Delete policy set assignment
Remove-AzPolicyAssignment -Id "<assignment-id>"
# Delete policy set definition
Remove-AzPolicySetDefinition -Name 'custom-central-dns-private-endpoints' -ManagementGroupName "SLZ" -Force
# Delete custom policies
Get-AzPolicyDefinition -ManagementGroupName "SLZ" -Custom |
Where-Object { $_.Name -like 'dns-pe-*' } |
ForEach-Object { Remove-AzPolicyDefinition -Name $_.Name -ManagementGroupName "SLZ" -Force }
# Redeploy with new configuration
# (follow deployment steps above)- Check policy assignment scope includes the subscription where private endpoints are created
- Verify managed identity has permissions on DNS zones resource group
- Check compliance state:
Get-AzPolicyStatefor the specific resource
-
Error: "MoreThanOnePrivateDnsZoneGroupPerPrivateEndpointNotAllowed"
- Private endpoint already has a zone group
- Delete existing zone group first, or update it manually
- Only one zone group allowed per private endpoint (but can contain multiple zone configs)
-
Error: "UnusedPolicyParameters"
- Parameter defined but not used in policy rule
- Must delete and recreate policy definition (can't remove parameters from existing policies)
Check DNS zone group configuration:
# Using Azure CLI
az network private-endpoint dns-zone-group list `
--endpoint-name "<endpoint-name>" `
--resource-group "<resource-group>" `
--output table- pubsecDNS.bicep: Main Bicep template deploying policies and policy set
- pubsecDNS.parameters.json: Configuration of DNS zones (50 entries)
- templates/DNS-PrivateEndpoints/azurepolicy.json: Custom policy template supporting multi-zone configuration
After deployment, the template outputs:
builtInPolicyCount: Number of policies using Microsoft built-in definitions (20)customPolicyCount: Number of custom policies deployed (30)totalPolicyCount: Total DNS zone configurations (50)
Licensed under the MIT license.