From 3fc289b09f8bc9fea1154984f91f809418bd3009 Mon Sep 17 00:00:00 2001 From: Karthik Saligrama Date: Fri, 5 Jun 2026 05:23:50 +0000 Subject: [PATCH 1/2] fix: add outbound PE rules for managed VNet hosted agent connectivity Hosted agents running in Microsoft-managed VNet could not connect to Foundry project resources (Storage, Cosmos DB, AI Search) because no outbound private endpoint rules were configured on the managed network. Changes: - Add outbound PE rules for Storage (blob), Cosmos DB (Sql), and AI Search (searchService) in managed-network.bicep with sequential dependsOn to avoid API conflicting state errors - Add network-connection-approver-role.bicep module for cross-RG role assignments when BYO resources are in different resource groups - Update main.bicep to pass resource IDs to managed network module, add conditional cross-RG role assignments, and fix dependency chain - Change location parameter to default to resourceGroup().location instead of hardcoding eastus2 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../18-managed-virtual-network/main.bicep | 70 +++++++++++-------- .../main.bicepparam | 1 - .../managed-network.bicep | 55 +++++++++++++++ .../network-connection-approver-role.bicep | 18 +++++ 4 files changed, 114 insertions(+), 30 deletions(-) create mode 100644 infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/network-connection-approver-role.bicep diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicep b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicep index 5386c9ba9..14e51d249 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicep +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicep @@ -2,30 +2,8 @@ Standard Setup Managed Network Secured Steps for main.bicep ----------------------------------- */ -@description('Location for all resources.') -@allowed([ - 'spaincentral' - 'westus' - 'eastus' - 'japaneast' - 'francecentral' - 'eastus2' - 'uaenorth' - 'brazilsouth' - 'germanywestcentral' - 'italynorth' - 'southcentralus' - 'westcentralus' - 'australiaeast' - 'swedencentral' - 'canadaeast' - 'southafricanorth' - 'westeurope' - 'westus3' - 'southindia' - 'uksouth' -]) -param location string = 'eastus2' +@description('Location for all resources. Defaults to the resource group location.') +param location string = resourceGroup().location @description('The isolation mode for the managed network') @allowed([ @@ -237,19 +215,52 @@ resource cosmosDB 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' existing = scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) } +// Assign Network Connection Approver role on each target resource group +// This allows the AI account identity to approve managed PE connections to resources in other RGs +module networkApproverRoleStorage 'modules-network-secured/network-connection-approver-role.bicep' = if (storagePassedIn) { + name: 'network-approver-storage-${uniqueSuffix}-deployment' + scope: resourceGroup(azureStorageSubscriptionId, azureStorageResourceGroupName) + params: { + aiAccountPrincipalId: aiAccount.outputs.accountPrincipalId + aiAccountResourceId: aiAccount.outputs.accountID + } +} + +module networkApproverRoleCosmos 'modules-network-secured/network-connection-approver-role.bicep' = if (cosmosPassedIn) { + name: 'network-approver-cosmos-${uniqueSuffix}-deployment' + scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName) + params: { + aiAccountPrincipalId: aiAccount.outputs.accountPrincipalId + aiAccountResourceId: aiAccount.outputs.accountID + } +} + +module networkApproverRoleSearch 'modules-network-secured/network-connection-approver-role.bicep' = if (searchPassedIn) { + name: 'network-approver-search-${uniqueSuffix}-deployment' + scope: resourceGroup(aiSearchServiceSubscriptionId, aiSearchServiceResourceGroupName) + params: { + aiAccountPrincipalId: aiAccount.outputs.accountPrincipalId + aiAccountResourceId: aiAccount.outputs.accountID + } +} + // Configure Managed Network for AI Services Account -// This module sets up outbound rules for the managed virtual network to allow -// secure communication to customer resources (Storage, AI Search, Cosmos DB) +// This module sets up the managed virtual network and outbound PE rules to allow +// secure communication from hosted agents to customer resources (Storage, AI Search, Cosmos DB) module managedNetwork 'modules-network-secured/managed-network.bicep' = { name: 'managed-network-${uniqueSuffix}-deployment' params: { accountName: aiAccount.outputs.accountName isolationMode: isolationMode + storageAccountResourceId: storage.id + cosmosDBResourceId: cosmosDB.id + aiSearchResourceId: aiSearch.id } dependsOn: [ - aiSearch // Ensure AI Search exists - storage // Ensure Storage exists - cosmosDB // Ensure Cosmos DB exists + aiDependencies // Ensure dependent resources (Storage, CosmosDB, Search) are fully created + networkApproverRoleStorage + networkApproverRoleCosmos + networkApproverRoleSearch ] } @@ -394,6 +405,7 @@ module addProjectCapabilityHost 'modules-network-secured/add-project-capability- cosmosAccountRoleAssignments storageAccountRoleAssignment aiSearchRoleAssignments + managedNetwork // Ensure managed network outbound rules are provisioned ] } diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicepparam b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicepparam index 0df098e5f..66cf35949 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicepparam +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicepparam @@ -1,6 +1,5 @@ using './main.bicep' -param location = 'eastus2' param isolationMode = 'AllowOnlyApprovedOutbound' param aiServices = 'aiservices' param firstProjectName = 'project' diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep index 285948878..f9186a8d5 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep @@ -8,6 +8,15 @@ param accountName string ]) param isolationMode string = 'AllowOnlyApprovedOutbound' +@description('Resource ID of the Storage Account for outbound PE rule') +param storageAccountResourceId string + +@description('Resource ID of the Cosmos DB Account for outbound PE rule') +param cosmosDBResourceId string + +@description('Resource ID of the AI Search Service for outbound PE rule') +param aiSearchResourceId string + // Reference the existing AI Services account in the same resource group resource aiAccount 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { name: accountName @@ -28,4 +37,50 @@ resource managedNetwork 'Microsoft.CognitiveServices/accounts/managednetworks@20 } } +// Outbound PE rules allow the managed VNet (where hosted agents run) to reach dependent resources +// Rules must be created sequentially to avoid conflicting state errors on the managed network +#disable-next-line BCP081 +resource storageOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview' = { + parent: managedNetwork + name: 'storage-blob-rule' + properties: { + type: 'PrivateEndpoint' + destination: { + serviceResourceId: storageAccountResourceId + subresourceTarget: 'blob' + } + category: 'UserDefined' + } +} + +#disable-next-line BCP081 +resource cosmosDBOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview' = { + parent: managedNetwork + name: 'cosmos-sql-rule' + properties: { + type: 'PrivateEndpoint' + destination: { + serviceResourceId: cosmosDBResourceId + subresourceTarget: 'Sql' + } + category: 'UserDefined' + } + dependsOn: [storageOutboundRule] +} + +#disable-next-line BCP081 +resource aiSearchOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview' = { + parent: managedNetwork + name: 'aisearch-rule' + properties: { + type: 'PrivateEndpoint' + destination: { + serviceResourceId: aiSearchResourceId + subresourceTarget: 'searchService' + } + category: 'UserDefined' + } + dependsOn: [cosmosDBOutboundRule] +} + output managedNetworkSettingsName string = managedNetwork.name diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/network-connection-approver-role.bicep b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/network-connection-approver-role.bicep new file mode 100644 index 000000000..43baa426b --- /dev/null +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/network-connection-approver-role.bicep @@ -0,0 +1,18 @@ +@description('The principal ID of the AI Services account managed identity') +param aiAccountPrincipalId string + +@description('The AI Services account resource ID (used for deterministic GUID generation)') +param aiAccountResourceId string + +// Azure AI Enterprise Network Connection Approver role definition ID +var networkConnectionApproverRoleId = 'b556d68e-0be0-4f35-a333-ad7ee1ce17ea' + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(aiAccountResourceId, networkConnectionApproverRoleId, resourceGroup().id) + scope: resourceGroup() + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', networkConnectionApproverRoleId) + principalId: aiAccountPrincipalId + principalType: 'ServicePrincipal' + } +} From 0f9cdd64342b1607864c7b3d46cf2cd7090dfe4a Mon Sep 17 00:00:00 2001 From: Karthik Saligrama Date: Fri, 5 Jun 2026 17:43:11 +0000 Subject: [PATCH 2/2] fix: add outbound PE rule for AI Services account endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hosted agents in the managed VNet also need a private endpoint back to the Foundry/AI Services account itself, since publicNetworkAccess is disabled. Without this, agents cannot reach the Foundry API. Adds aiservices-account-rule as the first outbound PE rule in the sequential chain (aiservices → storage → cosmos → search). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../managed-network.bicep | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep index f9186a8d5..4d5e2d8a8 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep @@ -39,6 +39,22 @@ resource managedNetwork 'Microsoft.CognitiveServices/accounts/managednetworks@20 // Outbound PE rules allow the managed VNet (where hosted agents run) to reach dependent resources // Rules must be created sequentially to avoid conflicting state errors on the managed network + +// The agent needs a PE back to the Foundry/AI Services endpoint itself (public access is disabled) +#disable-next-line BCP081 +resource aiServicesOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview' = { + parent: managedNetwork + name: 'aiservices-account-rule' + properties: { + type: 'PrivateEndpoint' + destination: { + serviceResourceId: aiAccount.id + subresourceTarget: 'account' + } + category: 'UserDefined' + } +} + #disable-next-line BCP081 resource storageOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview' = { parent: managedNetwork @@ -51,6 +67,7 @@ resource storageOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetwor } category: 'UserDefined' } + dependsOn: [aiServicesOutboundRule] } #disable-next-line BCP081