<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Terraform on Andrew Wilson's Blog</title><link>https://andrewilson.co.uk/tags/terraform/</link><description>Recent content in Terraform on Andrew Wilson's Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 14 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://andrewilson.co.uk/tags/terraform/index.xml" rel="self" type="application/rss+xml"/><item><title>Azure Key Vault, Terraform, and the Control Plane/Data Plane Difference</title><link>https://andrewilson.co.uk/post/2026/09/azure-key-vault-terraform-control-data-plane/</link><pubDate>Mon, 14 Sep 2026 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2026/09/azure-key-vault-terraform-control-data-plane/</guid><description>&lt;p&gt;The other day I ran into one of those Azure deployment problems that looks reasonable right up until you understand which service is actually making the call.&lt;/p&gt;
&lt;p&gt;I had provisioned an Azure Key Vault with &lt;code&gt;enabled_for_template_deployment&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;. I was familiar with this setting from working with Bicep and ARM templates, where it allows the vault to be used for secret retrieval during a deployment.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; The deployment identity still needs the required ARM deployment permission.&lt;/p&gt;</description><content:encoded><![CDATA[<p>The other day I ran into one of those Azure deployment problems that looks reasonable right up until you understand which service is actually making the call.</p>
<p>I had provisioned an Azure Key Vault with <code>enabled_for_template_deployment</code> set to <code>true</code>. I was familiar with this setting from working with Bicep and ARM templates, where it allows the vault to be used for secret retrieval during a deployment.</p>
<blockquote>
<p><strong>Note</strong> The deployment identity still needs the required ARM deployment permission.</p>
</blockquote>
<p>The Key Vault deployed successfully. I then used Terraform to create a secret in it and expected the setting to cover that deployment as well. It did not.</p>
<p>That was the point where I had to stop thinking about the setting and look at what was actually making each request.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-hcl" data-lang="hcl"><span style="display:flex;"><span><span style="color:#ff79c6">variable</span> <span style="color:#f1fa8c">&#34;secret_value&#34;</span> {
</span></span><span style="display:flex;"><span>	type      <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">string</span>
</span></span><span style="display:flex;"><span>	sensitive <span style="color:#ff79c6">=</span> <span style="color:#8be9fd">true</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">resource</span> <span style="color:#f1fa8c">&#34;azurerm_key_vault&#34; &#34;example&#34;</span> {
</span></span><span style="display:flex;"><span>	name                          <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">&#34;example-key-vault&#34;</span>
</span></span><span style="display:flex;"><span>	location                      <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">azurerm_resource_group</span>.<span style="color:#ff79c6">example</span>.<span style="color:#ff79c6">location</span>
</span></span><span style="display:flex;"><span>	resource_group_name           <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">azurerm_resource_group</span>.<span style="color:#ff79c6">example</span>.<span style="color:#ff79c6">name</span>
</span></span><span style="display:flex;"><span>	tenant_id                     <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">data</span>.<span style="color:#ff79c6">azurerm_client_config</span>.<span style="color:#ff79c6">current</span>.<span style="color:#ff79c6">tenant_id</span>
</span></span><span style="display:flex;"><span>	sku_name                      <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">&#34;standard&#34;</span>
</span></span><span style="display:flex;"><span>	enable_rbac_authorization     <span style="color:#ff79c6">=</span> <span style="color:#8be9fd">true</span>
</span></span><span style="display:flex;"><span>	enabled_for_template_deployment <span style="color:#ff79c6">=</span> <span style="color:#8be9fd">true</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">resource</span> <span style="color:#f1fa8c">&#34;azurerm_key_vault_secret&#34; &#34;example&#34;</span> {
</span></span><span style="display:flex;"><span>	name         <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">&#34;example-secret&#34;</span>
</span></span><span style="display:flex;"><span>	value        <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">var</span>.<span style="color:#ff79c6">secret_value</span>
</span></span><span style="display:flex;"><span>	key_vault_id <span style="color:#ff79c6">=</span> <span style="color:#ff79c6">azurerm_key_vault</span>.<span style="color:#ff79c6">example</span>.<span style="color:#ff79c6">id</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="what-i-had-missed">What I had missed</h2>
<p>The Key Vault itself is an Azure Resource Manager resource. Configuring it with <code>azurerm_key_vault</code> is a control plane operation. That part matched what I expected.</p>
<p>Creating a secret is different. <code>azurerm_key_vault_secret</code> uses the Key Vault service API, which is the data plane. The Terraform identity therefore needs an appropriate Azure RBAC role to set secrets, and the machine running Terraform must be able to reach the Key Vault endpoint.</p>
<p>That meant checking two separate things:</p>
<ul>
<li>Did the Terraform identity have permission to write secrets?</li>
<li>Could the Terraform runner reach the vault through its networking configuration?</li>
</ul>
<p>Having successfully deployed the vault through ARM did not answer either question. The vault request and the secret request were taking different paths.</p>
<h2 id="where-azapi-fits">Where AzAPI fits</h2>
<p>Looking at AzAPI helped clarify the difference between the two providers.</p>
<p>AzureRM gives us typed Terraform resources. Many of those resources manage Azure Resource Manager resources through the control plane, but some resources, such as Key Vault secrets, use the service&rsquo;s data-plane API.</p>
<p>AzAPI works directly with Azure Resource Manager REST APIs. It is useful when AzureRM has not yet exposed a control-plane resource, API version, or property that is available in Azure.</p>
<p>That makes AzAPI useful for an AzureRM capability gap, but it is not a general replacement for AzureRM data-plane resources. It is a different route to the Azure Resource Manager API, not a switch that makes every AzureRM resource use ARM.</p>
<p>There is a useful detail here. Azure also exposes secrets as the ARM resource <code>Microsoft.KeyVault/vaults/secrets</code>, which AzAPI can target. That provides an ARM-based way to create a secret and may avoid the Terraform runner needing direct access to the Key Vault data-plane endpoint. It still requires the appropriate ARM permissions, and the ARM resource is intended for deployment scenarios; for normal secret interaction, Azure recommends the data-plane API.</p>
<h2 id="the-thing-worth-remembering">The thing worth remembering</h2>
<p><code>enabled_for_template_deployment</code> enables secret retrieval during an ARM or Bicep template deployment (<em>The deployment identity still needs the required ARM deployment permission</em>). The setting does not grant the deployment identity permission to create/read secrets.</p>
<p>When a deployment behaves unexpectedly, it is worth asking which identity is making the request and which endpoint it is calling. In this case, the vault was deployed through ARM, but the AzureRM secret resource was created through the Key Vault data plane. AzAPI can use a different ARM-based resource path, but that comes with different permissions and lifecycle considerations.</p>
<p>That distinction is easy to miss, particularly when moving between Bicep, ARM, and Terraform, so I am noting it here for the next time I run into it.</p>
<p>Useful references:</p>
<ul>
<li><a href="https://learn.microsoft.com/azure/azure-resource-manager/management/control-plane-and-data-plane">Azure control plane and data plane</a></li>
<li><a href="https://learn.microsoft.com/azure/azure-resource-manager/templates/key-vault-parameter">Use Azure Key Vault in template deployments</a></li>
<li><a href="https://learn.microsoft.com/azure/key-vault/general/rbac-guide">Azure Key Vault RBAC guide</a></li>
<li><a href="https://learn.microsoft.com/rest/api/keyvault/secrets/set-secret">Key Vault Set Secret REST API</a></li>
<li><a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault_secret">AzureRM Key Vault secret resource</a></li>
<li><a href="https://learn.microsoft.com/azure/templates/microsoft.keyvault/vaults/secrets">Microsoft.KeyVault/vaults/secrets ARM resource</a></li>
<li><a href="https://learn.microsoft.com/azure/developer/terraform/azapi/overview-azapi-provider">AzAPI provider overview</a></li>
</ul>
]]></content:encoded></item></channel></rss>