cd /news/cloud-computing/zero-trust-rag-defeating-the-shared-… · home topics cloud-computing article
[ARTICLE · art-3935] src=dev.to ↗ pub= topic=cloud-computing verified=true sentiment=↓ negative

Zero-Trust RAG: Defeating the Shared Private Link Deadlock in Azure Terraform

The article addresses a critical gap in Azure Terraform pipelines for Enterprise RAG applications, where the `azurerm` provider can request a Shared Private Link from AI Search to OpenAI but cannot approve it, causing a deployment deadlock that requires manual Portal intervention. The solution uses the `azapi` provider to directly call the Azure Resource Manager REST API, dynamically reading the pending connection's GUID at runtime and auto-approving it, eliminating the need for manual "ClickOps" approval. Additionally, the article recommends disabling local authentication in favor of System Managed Identities and configuring private DNS zones to prevent opaque 403 errors from Azure Firewall.

read3 min views7 publishedMay 20, 2026

Your Terraform pipeline is green. The deployment completes without errors. You grab a coffee. Ten minutes later, you test your new Enterprise RAG application. It throws a 403 Forbidden . You open the Azure Portal, check the OpenAI Networking tab, and there it is: your Shared Private Link from AI Search is sitting in Pending. Nobody told Terraform to approve it. Nobody told you it even needed approving. This is the CI/CD killer of Azure AI infrastructure. AI Search must call OpenAI to vectorize data. The azurerm provider can successfully request this Shared Private Link — but it cannot approve its own request. The target resource (OpenAI) must explicitly accept the inbound connection. The standard provider has no method for this. Pipeline deadlocked. Someone has to click "Approve" in the Portal manually. ClickOps in 2026. We bypass the azurerm provider entirely and talk directly to the Azure Resource Manager REST API using the azapi provider. Because Azure generates a random GUID for the incoming connection, we can't hardcode the ID — we read it at runtime: data "azapi_resource_list" "pe_connections" { type = "Microsoft.CognitiveServices/accounts/privateEndpointConnections@2023-05-01" parent_id = azurerm_cognitive_account.openai.id response_export_values = ["value"] depends_on = [azurerm_search_shared_private_link_service.openai_link] } The depends_on is critical — without it, Terraform queries the connection list before the link is even requested, returns empty, and the approval silently fails. Then we filter for the Pending connection and approve it: resource "azapi_update_resource" "approve_shared_link" {

type = "Microsoft.CognitiveServices/accounts/privateEndpointConnections@2023-05-01"
resource_id = try(
[for conn in jsondecode(data.azapi_resource_list.pe_connections.output).value :

conn.id

if conn.properties.privateLinkServiceConnectionState.status == "Pending"
][0],

""

)
body = jsonencode({
properties = {
privateLinkServiceConnectionState = {

status = "Approved" description = "Approved via Terraform AzAPI Pipeline"

}
}
})
}
The try()

wrapper is not optional. On terraform destroy , the Shared Private Link is deleted before this resource is evaluated. Without try() , indexing [0] on an empty array crashes the destroy run and leaves orphaned resources in Azure. After terraform apply , the link goes from Pending to Approved in under 30 seconds. No Portal access required. Auto-approving the link lets AI Search reach OpenAI. But static API keys will fail your compliance audit. Keys leak. Keys get committed to Git. Disable local auth and use a System Managed Identity instead: resource "azurerm_search_service" "search" { name = var.search_service_name sku = "standard" public_network_access_enabled = false local_authentication_enabled = false identity {

type = "SystemAssigned"
}
}

resource "azurerm_role_assignment" "search_to_openai" { scope = azurerm_cognitive_account.openai.id role_definition_name = "Cognitive Services OpenAI User" principal_id = azurerm_search_service.search.identity[0].principal_id } Zero credential management. When the AI Search instance is deleted, its identity and permissions are destroyed automatically. If your-instance.openai.azure.com still resolves to a public IP, the Azure Firewall drops the traffic and you get another opaque 403 . Both services need their DNS zones linked to the VNet: resource "azurerm_private_dns_zone" "openai_dns" { name = "privatelink.openai.azure.com" resource_group_name = var.resource_group_name } resource "azurerm_private_dns_zone_virtual_network_link" "openai_vnet_link" { name = "link-openai-vnet" resource_group_name = var.resource_group_name private_dns_zone_name = azurerm_private_dns_zone.openai_dns.name virtual_network_id = azurerm_virtual_network.vnet.id registration_enabled = false } registration_enabled = false — always. Automatic registration conflicts with centralized Hub & Spoke DNS management. The free baseline (AzAPI auto-approval + basic networking) is on GitHub. The full article with complete VNet injection, Private DNS automation, and RBAC Identity Chaining is on my blog.

── more in #cloud-computing 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/zero-trust-rag-defea…] indexed:0 read:3min 2026-05-20 ·