Skip to content
Draft
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
35 changes: 33 additions & 2 deletions infra-as-code/bicep/ai-agent-blob-storage.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ resource agentStorageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' = {
isLocalUserEnabled: false
defaultToOAuthAuthentication: true
allowCrossTenantReplication: false
publicNetworkAccess: 'Disabled'
publicNetworkAccess: 'Enabled'
minimumTlsVersion: 'TLS1_2'
supportsHttpsTrafficOnly: true
isHnsEnabled: false
Expand All @@ -79,8 +79,39 @@ resource agentStorageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' = {
}
}

resource blob 'blobServices' existing = {
resource blob 'blobServices' = {
name: 'default'
properties: {
cors: {
corsRules: [
{
allowedOrigins: [
'*'
]
allowedHeaders: [
'*'
]
exposedHeaders: [
'*'
]
allowedMethods: [
'DELETE'
'GET'
'HEAD'
'MERGE'
'POST'
'OPTIONS'
'PUT'
'PATCH'
]
maxAgeInSeconds: 120
}
]
}
deleteRetentionPolicy: {
allowPermanentDelete: false
}
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions infra-as-code/bicep/ai-agent-service-dependencies.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,19 @@ module deployAzureAISearchService 'ai-search.bicep' = {
}
}

@description('Deploy Azure AI Search instance for the Azure AI Foundry Agent Service (dependency). This is used when a user uploads a file to the agent, and the agent needs to search for information in that file.')
module deployUserManagedIdentity 'ai-agent-user-managed-identity.bicep' = {
name: 'agentUserManagedIdentityDeploy'
scope: resourceGroup()
params: {
location: location
baseName: baseName
}
}

// ---- Outputs ----

output cosmosDbAccountName string = deployCosmosDbThreadStorageAccount.outputs.cosmosDbAccountName
output storageAccountName string = deployAgentStorageAccount.outputs.storageAccountName
output aiSearchName string = deployAzureAISearchService.outputs.aiSearchName
output agentUserManagedIdentityName string = deployUserManagedIdentity.outputs.agentUserManagedIdentityName
26 changes: 26 additions & 0 deletions infra-as-code/bicep/ai-agent-user-managed-identity.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
targetScope = 'resourceGroup'

@description('The region in which this architecture is deployed. Should match the region of the resource group.')
@minLength(1)
param location string = resourceGroup().location

@description('This is the base name for each Azure resource name (6-8 chars)')
@minLength(6)
@maxLength(8)
param baseName string

// ---- Existing resources ----

// ---- New resources ----

@description('The agent User Managed Identity for the AI Foundry Project.')
resource agentUserManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2025-01-31-preview' = {
name: 'mi-agent-${baseName}'
location: location
}

// Role assignments

// ---- Outputs ----

output agentUserManagedIdentityName string = agentUserManagedIdentity.name
35 changes: 26 additions & 9 deletions infra-as-code/bicep/ai-foundry-project.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,17 @@ param existingBingAccountName string
@minLength(1)
param existingWebApplicationInsightsResourceName string

@description('The existing User Managed Identity for the AI Foundry project.')
@minLength(1)
param existingAgentUserManagedIdentityName string

// ---- Existing resources ----

@description('Existing Agent User Managed Identity for the AI Foundry Project.')
resource agentUserManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2025-01-31-preview' existing = {
name: existingAgentUserManagedIdentityName
}

@description('The internal ID of the project is used in the Azure Storage blob containers and in the Cosmos DB collections.')
#disable-next-line BCP053
var workspaceId = aiFoundry::project.properties.internalId
Expand All @@ -53,7 +62,7 @@ resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-previ
name: guid(aiFoundry::project.id, cosmosDbAccount::dataContributorRole.id, 'enterprise_memory', 'user')
properties: {
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
principalId: aiFoundry::project.identity.principalId
principalId: agentUserManagedIdentity.properties.principalId
scope: scopeUserContainerId
}
dependsOn: [
Expand All @@ -66,7 +75,7 @@ resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-previ
name: guid(aiFoundry::project.id, cosmosDbAccount::dataContributorRole.id, 'enterprise_memory', 'system')
properties: {
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
principalId: aiFoundry::project.identity.principalId
principalId: agentUserManagedIdentity.properties.principalId
scope: scopeSystemContainerId
}
dependsOn: [
Expand All @@ -80,7 +89,7 @@ resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-previ
name: guid(aiFoundry::project.id, cosmosDbAccount::dataContributorRole.id, 'enterprise_memory', 'entities')
properties: {
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
principalId: aiFoundry::project.identity.principalId
principalId: agentUserManagedIdentity.properties.principalId
scope: scopeEntityContainerId
}
dependsOn: [
Expand Down Expand Up @@ -144,7 +153,10 @@ resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-06-01' existing =
name: 'projchat'
location: location
identity: {
type: 'SystemAssigned'
type: 'UserAssigned'
userAssignedIdentities: {
'${agentUserManagedIdentity.id}': {}
}
}
properties: {
description: 'Chat using internet data in your Azure AI Foundry Agent.'
Expand Down Expand Up @@ -243,6 +255,11 @@ resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-06-01' existing =
}
dependsOn: [
applicationInsightsConnection // Single thread changes to the project, else conflict errors tend to happen
projectDbCosmosDbOperatorAssignment
projectBlobDataContributorAssignment
projectBlobDataOwnerConditionalAssignment
projectAISearchContributorAssignment
projectAISearchIndexDataContributorAssignment
]
}

Expand Down Expand Up @@ -278,7 +295,7 @@ resource projectDbCosmosDbOperatorAssignment 'Microsoft.Authorization/roleAssign
scope: cosmosDbAccount
properties: {
roleDefinitionId: cosmosDbOperatorRole.id
principalId: aiFoundry::project.identity.principalId
principalId: agentUserManagedIdentity.properties.principalId
principalType: 'ServicePrincipal'
}
}
Expand All @@ -288,7 +305,7 @@ resource projectBlobDataContributorAssignment 'Microsoft.Authorization/roleAssig
scope: agentStorageAccount
properties: {
roleDefinitionId: storageBlobDataContributorRole.id
principalId: aiFoundry::project.identity.principalId
principalId: agentUserManagedIdentity.properties.principalId
principalType: 'ServicePrincipal'
}
}
Expand All @@ -297,7 +314,7 @@ resource projectBlobDataOwnerConditionalAssignment 'Microsoft.Authorization/role
name: guid(aiFoundry::project.id, storageBlobDataOwnerRole.id, agentStorageAccount.id)
scope: agentStorageAccount
properties: {
principalId: aiFoundry::project.identity.principalId
principalId: agentUserManagedIdentity.properties.principalId
roleDefinitionId: storageBlobDataOwnerRole.id
principalType: 'ServicePrincipal'
conditionVersion: '2.0'
Expand All @@ -310,7 +327,7 @@ resource projectAISearchContributorAssignment 'Microsoft.Authorization/roleAssig
scope: azureAISearchService
properties: {
roleDefinitionId: azureAISearchServiceContributorRole.id
principalId: aiFoundry::project.identity.principalId
principalId: agentUserManagedIdentity.properties.principalId
principalType: 'ServicePrincipal'
}
}
Expand All @@ -320,7 +337,7 @@ resource projectAISearchIndexDataContributorAssignment 'Microsoft.Authorization/
scope: azureAISearchService
properties: {
roleDefinitionId: azureAISearchIndexDataContributorRole.id
principalId: aiFoundry::project.identity.principalId
principalId: agentUserManagedIdentity.properties.principalId
principalType: 'ServicePrincipal'
}
}
Expand Down
20 changes: 17 additions & 3 deletions infra-as-code/bicep/ai-foundry.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,21 @@ param privateEndpointSubnetResourceId string
@minLength(36)
param aiFoundryPortalUserPrincipalId string

@description('The existing User Managed Identity for the AI Foundry project.')
@minLength(1)
param existingAgentUserManagedIdentityName string

// ---- Variables ----

var aiFoundryName = 'aif${baseName}'

// ---- Existing resources ----

@description('Existing Agent User Managed Identity for the AI Foundry Project.')
resource agentUserManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2025-01-31-preview' existing = {
name: existingAgentUserManagedIdentityName
}

@description('Existing: Private DNS zone for Azure AI services using the cognitive services FQDN.')
resource cognitiveServicesLinkedPrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' existing = {
name: 'privatelink.cognitiveservices.azure.com'
Expand Down Expand Up @@ -67,16 +78,19 @@ resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-06-01' = {
name: 'S0'
}
identity: {
type: 'SystemAssigned'
type: 'UserAssigned'
userAssignedIdentities: {
'${agentUserManagedIdentity.id}': {}
}
}
properties: {
customSubDomainName: aiFoundryName
allowProjectManagement: true // Azure AI Foundry account
disableLocalAuth: true
disableLocalAuth: false
networkAcls: {
bypass: 'None'
ipRules: []
defaultAction: 'Deny'
defaultAction: 'Allow'
virtualNetworkRules: []
}
publicNetworkAccess: 'Disabled'
Expand Down
2 changes: 2 additions & 0 deletions infra-as-code/bicep/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ module deployAzureAIFoundry 'ai-foundry.bicep' = {
agentSubnetResourceId: deployVirtualNetwork.outputs.agentsEgressSubnetResourceId
privateEndpointSubnetResourceId: deployVirtualNetwork.outputs.privateEndpointsSubnetResourceId
aiFoundryPortalUserPrincipalId: yourPrincipalId
existingAgentUserManagedIdentityName: deployAIAgentServiceDependencies.outputs.agentUserManagedIdentityName
}
dependsOn: [
deployAzureFirewall // Makes sure that egress traffic is controlled before workload resources start being deployed
Expand Down Expand Up @@ -161,6 +162,7 @@ module deployAzureAiFoundryProject 'ai-foundry-project.bicep' = {
existingStorageAccountName: deployAIAgentServiceDependencies.outputs.storageAccountName
existingBingAccountName: deployBingAccount.outputs.bingAccountName
existingWebApplicationInsightsResourceName: deployApplicationInsights.outputs.applicationInsightsName
existingAgentUserManagedIdentityName: deployAIAgentServiceDependencies.outputs.agentUserManagedIdentityName
}
dependsOn: [
deployJumpBox
Expand Down