Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down Expand Up @@ -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
]
}

Expand Down Expand Up @@ -394,6 +405,7 @@ module addProjectCapabilityHost 'modules-network-secured/add-project-capability-
cosmosAccountRoleAssignments
storageAccountRoleAssignment
aiSearchRoleAssignments
managedNetwork // Ensure managed network outbound rules are provisioned
]
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using './main.bicep'

param location = 'eastus2'
param isolationMode = 'AllowOnlyApprovedOutbound'
param aiServices = 'aiservices'
param firstProjectName = 'project'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,4 +37,67 @@ 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
name: 'storage-blob-rule'
properties: {
type: 'PrivateEndpoint'
destination: {
serviceResourceId: storageAccountResourceId
subresourceTarget: 'blob'
}
category: 'UserDefined'
}
dependsOn: [aiServicesOutboundRule]
}

#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
Original file line number Diff line number Diff line change
@@ -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'
}
}
Loading