{"slug": "zero-trust-rag-defeating-the-shared-private-link-deadlock-in-azure-terraform", "title": "Zero-Trust RAG: Defeating the Shared Private Link Deadlock in Azure Terraform", "summary": "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.", "body_md": "Your Terraform pipeline is green. The deployment completes without errors. You grab a coffee.\nTen minutes later, you test your new Enterprise RAG application. It throws a 403 Forbidden\n. 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.\nNobody told Terraform to approve it. Nobody told you it even needed approving.\nThis is the CI/CD killer of Azure AI infrastructure.\nAI Search must call OpenAI to vectorize data. The azurerm\nprovider 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.\nWe bypass the azurerm\nprovider entirely and talk directly to the Azure Resource Manager REST API using the azapi\nprovider.\nBecause Azure generates a random GUID for the incoming connection, we can't hardcode the ID — we read it at runtime:\ndata \"azapi_resource_list\" \"pe_connections\" {\ntype = \"Microsoft.CognitiveServices/accounts/privateEndpointConnections@2023-05-01\"\nparent_id = azurerm_cognitive_account.openai.id\nresponse_export_values = [\"value\"]\ndepends_on = [azurerm_search_shared_private_link_service.openai_link]\n}\nThe depends_on\nis critical — without it, Terraform queries the connection list before the link is even requested, returns empty, and the approval silently fails.\nThen we filter for the Pending connection and approve it:\nresource \"azapi_update_resource\" \"approve_shared_link\" {\ntype = \"Microsoft.CognitiveServices/accounts/privateEndpointConnections@2023-05-01\"\nresource_id = try(\n[for conn in jsondecode(data.azapi_resource_list.pe_connections.output).value :\nconn.id\nif conn.properties.privateLinkServiceConnectionState.status == \"Pending\"\n][0],\n\"\"\n)\nbody = jsonencode({\nproperties = {\nprivateLinkServiceConnectionState = {\nstatus = \"Approved\"\ndescription = \"Approved via Terraform AzAPI Pipeline\"\n}\n}\n})\n}\nThe try()\nwrapper is not optional. On terraform destroy\n, the Shared Private Link is deleted before this resource is evaluated. Without try()\n, indexing [0]\non an empty array crashes the destroy run and leaves orphaned resources in Azure.\nAfter terraform apply\n, the link goes from Pending to Approved in under 30 seconds. No Portal access required.\nAuto-approving the link lets AI Search reach OpenAI. But static API keys will fail your compliance audit. Keys leak. Keys get committed to Git.\nDisable local auth and use a System Managed Identity instead:\nresource \"azurerm_search_service\" \"search\" {\nname = var.search_service_name\nsku = \"standard\"\npublic_network_access_enabled = false\nlocal_authentication_enabled = false\nidentity {\ntype = \"SystemAssigned\"\n}\n}\nresource \"azurerm_role_assignment\" \"search_to_openai\" {\nscope = azurerm_cognitive_account.openai.id\nrole_definition_name = \"Cognitive Services OpenAI User\"\nprincipal_id = azurerm_search_service.search.identity[0].principal_id\n}\nZero credential management. When the AI Search instance is deleted, its identity and permissions are destroyed automatically.\nIf your-instance.openai.azure.com\nstill resolves to a public IP, the Azure Firewall drops the traffic and you get another opaque 403\n. Both services need their DNS zones linked to the VNet:\nresource \"azurerm_private_dns_zone\" \"openai_dns\" {\nname = \"privatelink.openai.azure.com\"\nresource_group_name = var.resource_group_name\n}\nresource \"azurerm_private_dns_zone_virtual_network_link\" \"openai_vnet_link\" {\nname = \"link-openai-vnet\"\nresource_group_name = var.resource_group_name\nprivate_dns_zone_name = azurerm_private_dns_zone.openai_dns.name\nvirtual_network_id = azurerm_virtual_network.vnet.id\nregistration_enabled = false\n}\nregistration_enabled = false\n— always. Automatic registration conflicts with centralized Hub & Spoke DNS management.\nThe 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.", "url": "https://wpnews.pro/news/zero-trust-rag-defeating-the-shared-private-link-deadlock-in-azure-terraform", "canonical_source": "https://dev.to/dwoitzik/zero-trust-rag-defeating-the-shared-private-link-deadlock-in-azure-terraform-kdb", "published_at": "2026-05-20 22:00:00+00:00", "updated_at": "2026-05-20 22:02:08.603818+00:00", "lang": "en", "topics": ["cloud-computing", "developer-tools", "artificial-intelligence", "data"], "entities": ["Terraform", "Azure", "OpenAI", "AI Search", "Azure Resource Manager", "Azure Portal", "Shared Private Link", "azurerm"], "alternates": {"html": "https://wpnews.pro/news/zero-trust-rag-defeating-the-shared-private-link-deadlock-in-azure-terraform", "markdown": "https://wpnews.pro/news/zero-trust-rag-defeating-the-shared-private-link-deadlock-in-azure-terraform.md", "text": "https://wpnews.pro/news/zero-trust-rag-defeating-the-shared-private-link-deadlock-in-azure-terraform.txt", "jsonld": "https://wpnews.pro/news/zero-trust-rag-defeating-the-shared-private-link-deadlock-in-azure-terraform.jsonld"}}