Problem
You have a policy defined in Azure to block certain items. Managing your resources within Azure itself there is no issue. However, when you run a Terraform apply using the AzureRM provider you are blocked by the policy.
Prerequisites
- Using AzureRM provider for Terraform runs.
- Defined a policy in Azure to block certain items.
Cause
The AzureRM provider is using the Azure API in the background, which is different from when you manage resources directly in the Azure Portal. This might lead to differences in how requests are send and what data is included. Sometimes the provider needs to make several API calls to achieve the goal defined in your Terraform code.
This might lead to unexpected values at certain stages in the Terraform apply leading to a block by your defined policy.
Solutions:
Adjust your Azure policy so that the API calls generated by the AzureRM Terraform provider are taken into account.
By running an apply with TRACE enabled, you can find the API calls that are made and check what is being sent. See the examples below.
Example 1
You are using the resource azurerm_monitor_action_group and have a policy defined that blocks the usage of smsReceivers
.
It would look something like:
"field": "Microsoft.Insights/actiongroups/smsReceivers[*]",
"exists": true
},
In your Terraform code you have not defined the sms_receiver
block, so you expect the policy to pass.
However your policy blocks with a message that might look like the following:
Error: creating or updating Action Group (Subscription: "****"
Resource Group Name: "myresourcegroup"
Action Group Name: "myactiongroup"): unexpected status 403 (403 Forbidden) with error: RequestDisallowedByPolicy: Resource 'myactiongroup' was disallowed by policy. Policy identifiers: '[{"policyAssignment":{"name":"Action Group allowed action types","id":"/subscriptions/****/resourceGroups/myresourcegroup/providers/Microsoft.Authorization/policyAssignments/bd56h0d5f6m8522fc7cyfhgh"},"policyDefinition":{"name":"Action Group allowed action types","id":"/subscriptions/****/providers/Microsoft.Authorization/policyDefinitions/9c513865-b4dc-4b42-ab1b-e8d5ad2364e6","version":"1.0.0"}},{"policyAssignment":{"name":"Action Group allowed notification types","id":"/subscriptions/****/providers/Microsoft.Authorization/policyAssignments/bc721c64cad7491f91153b7a"},"policyDefinition":{"name":"Action Group allowed notification types","id":"/subscriptions/****/providers/Microsoft.Authorization/policyDefinitions/134g949b-g222-5b75-9e2f-8cg23eg1cg62","version":"1.0.0"}}]'.
with azurerm_monitor_action_group.example,
on main.tf line 1, in resource "azurerm_monitor_action_group" "example":
1: resource "azurerm_monitor_action_group" "example" {
In the TRACE log file you can search for the actual API call that is being made.
Searching for RequestDisallowedByPolicy
you can find the complete response and error message (not fully shown) and the line with HTTP/2.0 403 Forbidden
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: [DEBUG] PUT https://management.azure.com/subscriptions/****/resourceGroups/myresourcegroup/providers/Microsoft.Insights/actionGroups/myactiongroup?api-version=2023-01-01
2025-09-24T14:19:38.732+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: [DEBUG] AzureRM Response for https://management.azure.com/subscriptions/****/resourceGroups/myresourcegroup/providers/Microsoft.Insights/actionGroups/myactiongroup?api-version=2023-01-01:
2025-09-24T14:19:38.732+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: HTTP/2.0 403 Forbidden
...
2025-09-24T14:19:38.733+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: {"error":{"code":"RequestDisallowedByPolicy","target":"myactiongroup","message":"Resource 'myactiongroup' was disallowed by policy. Policy identifiers: '[{\"policyAssignment\":{\"name\":\"Action Group allowed action types\",\"id\":\"/
...
Just above this message is the actual API call made by the provider:
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: [DEBUG] AzureRM Request:
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: PUT /subscriptions/****/resourceGroups/myresourcegroup/providers/Microsoft.Insights/actionGroups/myactiongroup?api-version=2023-01-01 HTTP/1.1
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: Host: management.azure.com
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: User-Agent: HashiCorp/go-azure-sdk (Go-http-Client/1.1 actiongroupsapis/2023-01-01) HashiCorp Terraform/1.3.6 (+https://www.terraform.io) terraform-provider-azurerm/dev pid-222c6c49-1b0a-5959-a213-6608f9eb8820
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: Content-Length: 683
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: Content-Type: application/json; charset=utf-8
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: X-Ms-Correlation-Request-Id: ****
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: Accept-Encoding: gzip
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe:
2025-09-24T14:19:38.275+0200 [DEBUG] provider.terraform-provider-azurerm_v4.45.1_x5.exe: {"location":"northeurope","properties":{"armRoleReceivers":[],"automationRunbookReceivers":[],"azureAppPushReceivers":[],"azureFunctionReceivers":[],"emailReceivers":[{"emailAddress":"example@example.com","name":"email***_-EmailAction-","useCommonAlertSchema":false}],"enabled":true,"eventHubReceivers":[{"eventHubName":"myeventhub","eventHubNameSpace":"myeventhub","name":"eventHUB","subscriptionId":"****","tenantId":"****","useCommonAlertSchema":true}],"groupShortName":"myactiongroup","itsmReceivers":[],"logicAppReceivers":[],"smsReceivers":[],"voiceReceivers":[],"webhookReceivers":[]},"tags":{}}
In the payload sent, last line of the output above, you can see that an empty "smsReceivers":[],
is being sent. This means that the provider sends an empty smsReceivers block even if you have not defined this in your code.
You will need to adjust your policy so that it allows for an empty smsReceivers
block.
Example 2
You are using the resource azurerm_kubernetes_cluster and have defined a policy to check that the azurePolicy.enabled
is set to true.
In your terraform code you have it added to your resource like so:
azure_policy_enabled = true
And your policy blocks if the field does not equals true
{
"field": "Microsoft.ContainerService/managedClusters/addonProfiles.azurePolicy.enabled",
"exists": false
},
{
"field": "Microsoft.ContainerService/managedClusters/addonProfiles.azurePolicy.enabled",
"notEquals": "true"
}
A Terraform apply fails with a message similar to:
Error: updating Kubernetes Cluster (Subscription: "***"
Resource Group Name: "myresourcegroup"
Kubernetes Cluster Name: "aks-cluster"): performing CreateOrUpdate: unexpected status 403 (403 Forbidden) with error: RequestDisallowedByPolicy: Resource 'aks-cluster' was disallowed by policy. Policy identifiers: '[{"policyAssignment":{"name":"Block azurePolicy missing or false","id":"/providers/Microsoft.Management/managementGroups/****/providers/Microsoft.Authorization/policyAssignments/mypolicyassignment"},"policyDefinition":{"name":"Block azurePolicy missing or false","id":"/providers/Microsoft.Management/managementgroups/****/providers/Microsoft.Authorization/policyDefinitions/mypolicydefinition","version":"1.0.0"},"policySetDefinition":{"name":"Block azurePolicy missing or false","id":"/providers/Microsoft.Management/managementGroups/****/providers/Microsoft.Authorization/policySetDefinitions/mypolicydefinition","version":"1.0.0"}}]'.
with azurerm_kubernetes_cluster.aks_cluster,
on main.tf line 98, in resource "azurerm_kubernetes_cluster" "aks_cluster":
98: resource "azurerm_kubernetes_cluster" "aks_cluster" {
In the TRACE log file we can you and search for the actual API call that is being made.
Searching for RequestDisallowedByPolicy
we can find the complete response and error message (not fully shown) and the line with HTTP/2.0 403 Forbidden
2025-09-08T09:07:28.1982063Z 2025-09-08T09:07:28.189Z [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: [DEBUG] PUT https://management.azure.com/subscriptions/***/resourceGroups/myresourcegroup/providers/Microsoft.ContainerService/managedClusters/aks-cluster?api-version=2025-05-01
2025-09-08T09:07:28.6882649Z 2025-09-08T09:07:28.687Z [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: [DEBUG] AzureRM Response for https://management.azure.com/subscriptions/***/resourceGroups/myresourcegroup/providers/Microsoft.ContainerService/managedClusters/aks-cluster?api-version=2025-05-01:
2025-09-08T09:07:28.6883451Z 2025-09-08T09:07:28.687Z [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: HTTP/2.0 403 Forbidden
...
2025-09-08T09:07:28.6898556Z 2025-09-08T09:07:28.687Z [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: {"error":{"code":"RequestDisallowedByPolicy","target":"aks-cluster","message":"Resource 'aks-cluster' was disallowed by policy. Policy identifiers: '[{\"policyAssignment\":{\"name\":\"Block azurePolicy missing or false\",
...
Above this response, we can find the API call being made:
2025-09-05T15:10:51.371+0200 [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: {"id":"/subscriptions/****/resourcegroups/myresourcegroup/providers/Microsoft.ContainerService/managedClusters/aks-cluster","identity":{"type":"SystemAssigned","userAssignedIdentities":null},"location":"northeurope","name":"aks-cluster","properties":{"addonProfiles":{"azureKeyvaultSecretsProvider":{"config":{"enableSecretRotation":"true","rotationPollInterval":"2m"},"enabled":true}},"agentPoolProfiles":[{"count":1,"currentOrchestratorVersion":"1.32.6","enableAutoScaling":false,"enableEncryptionAtHost":false,"enableFIPS":false,"enableNodePublicIP":false,"enableUltraSSD":false,"kubeletDiskType":"OS","maxPods":250,"mode":"System","name":"zd194451np","nodeImageVersion":"AKSUbuntu-2204containerd-202508.11.0","orchestratorVersion":"1.32","osDiskSizeGB":128,"osDiskType":"Managed","osSKU":"Ubuntu","osType":"Linux","powerState":{"code":"Running"},"provisioningState":"Succeeded","scaleDownMode":"Delete","securityProfile":{"enableSecureBoot":false,"enableVTPM":false},"type":"VirtualMachineScaleSets","upgradeSettings":{"maxSurge":"10%","maxUnavailable":"0","nodeSoakDurationInMinutes":0},"vmSize":"Standard_D2_v3"}],"apiServerAccessProfile":{"enablePrivateCluster":true,"enablePrivateClusterPublicFQDN":true,"privateDNSZone":"None"},"autoUpgradeProfile":{"nodeOSUpgradeChannel":"NodeImage","upgradeChannel":"none"},"azureMonitorProfile":{"metrics":{"enabled":false,"kubeStateMetrics":{}}},"azurePortalFQDN":"4b2660138723ab7ac0ddca715af2fb6d-priv.portal.hcp.northeurope.azmk8s.io","bootstrapProfile":{"artifactSource":"Direct"},"currentKubernetesVersion":"1.32.6","disableLocalAccounts":false,"dnsPrefix":"exampleaks1","enableRBAC":true,"fqdn":"exampleaks1-qmudxbn9.hcp.northeurope.azmk8s.io","identityProfile":{"kubeletidentity":{"clientId":"6b6f3c05-5e70-4dda-ae25-7a575af39cd1","objectId":"6ae396a3-c447-4237-8b46-7647a0aef54a","resourceId":"/subscriptions/****/resourcegroups/MC_myresourcegroup_aks-cluster_northeurope/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aks-cluster-agentpool"}},"kubernetesVersion":"1.32","maxAgentPools":100,"metricsProfile":{"costAnalysis":{"enabled":false}},"networkProfile":{"dnsServiceIP":"10.0.0.10","ipFamilies":["IPv4"],"loadBalancerProfile":{"backendPoolType":"NodeIPConfiguration","effectiveOutboundIPs":[{"id":"/subscriptions/****/resourceGroups/MC_myresourcegroup_aks-cluster_northeurope/providers/Microsoft.Network/publicIPAddresses/95a33390-6f9f-4136-8b9e-9717962ba039"}],"managedOutboundIPs":{"count":1}},"loadBalancerSku":"standard","networkDataplane":"azure","networkPlugin":"azure","networkPluginMode":"overlay","networkPolicy":"none","outboundType":"loadBalancer","podCidr":"10.244.0.0/16","podCidrs":["10.244.0.0/16"],"serviceCidr":"10.0.0.0/16","serviceCidrs":["10.0.0.0/16"]},"nodeProvisioningProfile":{"defaultNodePools":"Auto","mode":"Manual"},"nodeResourceGroup":"MC_myresourcegroup_aks-cluster_northeurope","oidcIssuerProfile":{"enabled":false},"powerState":{"code":"Running"},"privateFQDN":"exampleaks1-qmudxbn9.hcp.northeurope.azmk8s.io","privateLinkResources":[{"groupId":"management","id":"/subscriptions/****/resourcegroups/myresourcegroup/providers/Microsoft.ContainerService/managedClusters/aks-cluster/privateLinkResources/management","name":"management","requiredMembers":["management"],"type":"Microsoft.ContainerService/managedClusters/privateLinkResources"}],"provisioningState":"Succeeded","resourceUID":"68badf32b1bb3400015ce66c","securityProfile":{},"servicePrincipalProfile":{"clientId":"msi"},"storageProfile":{"diskCSIDriver":{"enabled":true},"fileCSIDriver":{"enabled":true},"snapshotController":{"enabled":true}},"supportPlan":"KubernetesOfficial","workloadAutoScalerProfile":{}},"sku":{"name":"Base","tier":"Free"},"tags":{"Environment":"Production"},"type":"Microsoft.ContainerService/ManagedClusters"}
In this output there is no azurePolicy
setting defined, meaning there is no value sent for azurePolicy with this request, even when you have defined this in your terraform code via azure_policy_enabled
.
If you run an apply without the policy, you can see that in this case the cluster creation made of multiple API calls.
Disabling the policy will make that the apply succeeds and the following additional API call can be seen:
2025-09-05T15:10:51.371+0200 [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: [DEBUG] PUT https://management.azure.com/subscriptions/****/resourceGroups/zd194451-paul/providers/Microsoft.ContainerService/managedClusters/aks-cluster?api-version=2025-05-01
2025-09-05T15:10:52.765+0200 [TRACE] dag/walk: vertex "output.client_certificate (expand)" is waiting for "azurerm_kubernetes_cluster.example"
...
azurerm_kubernetes_cluster.example: Still modifying... [id=/subscriptions/****-...rService/managedClusters/aks-cluster, 10s elapsed]
2025-09-05T15:11:01.961+0200 [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: [DEBUG] AzureRM Response for https://management.azure.com/subscriptions/****/resourceGroups/zd194451-paul/providers/Microsoft.ContainerService/managedClusters/aks-cluster?api-version=2025-05-01:
2025-09-05T15:11:01.961+0200 [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: HTTP/2.0 200 OK
...
2025-09-05T15:11:01.961+0200 [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: },
2025-09-05T15:11:01.961+0200 [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: "azurepolicy": {
2025-09-05T15:11:01.961+0200 [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: "enabled": true,
2025-09-05T15:11:01.961+0200 [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: "config": {
2025-09-05T15:11:01.961+0200 [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: "version": "v2"
2025-09-05T15:11:01.961+0200 [DEBUG] provider.terraform-provider-azurerm_v4.42.0_x5: },
...
In this second API call you can see that azurepolicy enabled
is set to true
, as configured in your code.
If you would not have the azure_policy_enabled
set in your code, the value for the azure policy enabled would be set to false. Meaning that the default value is always sent.
Here you can see that the cluster creation is made of multiple API calls, where the first API call does not have a value for azurePolicy.enabled
. The second API call does set the azurePolicy.enabled
to true.
You should adjust the Azure policy to match this behaviour.