From 524eda9890494763b7ceb23371c801ce9be924aa Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Tue, 23 Dec 2025 20:02:07 +0000 Subject: [PATCH 01/19] initial structure for private networking --- deployers/bicep/main.bicep | 27 +++++++++++++++++++ deployers/bicep/modules/privateDNS.bicep | 13 +++++++++ deployers/bicep/modules/privateEndpoint.bicep | 2 ++ deployers/bicep/modules/virtualNetwork.bicep | 26 ++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 deployers/bicep/modules/privateDNS.bicep create mode 100644 deployers/bicep/modules/privateEndpoint.bicep create mode 100644 deployers/bicep/modules/virtualNetwork.bicep diff --git a/deployers/bicep/main.bicep b/deployers/bicep/main.bicep index 86e0cfaa..751479bb 100644 --- a/deployers/bicep/main.bicep +++ b/deployers/bicep/main.bicep @@ -76,6 +76,10 @@ param specialTags object = {} - Default is false''') param enableDiagLogging bool +@description('''Enable private endpoints and virtual network integration for deployed resources. +- Default is false''') +param enablePrivateNetworking bool + @description('''Array of GPT model names to deploy to the OpenAI resource.''') param gptModels array = [ { @@ -136,6 +140,7 @@ var acrCloudSuffix = cloudEnvironment == 'AzureCloud' ? '.azurecr.io' : '.azurec var acrName = toLower('${appName}${environment}acr') var containerRegistry = '${acrName}${acrCloudSuffix}' var containerImageName = '${containerRegistry}/${imageName}' +var vNetName = '${appName}-${environment}-vnet' //========================================================= // Resource group deployment @@ -146,6 +151,28 @@ resource rg 'Microsoft.Resources/resourceGroups@2022-09-01' = { tags: tags } +//========================================================= +// Create Virtual Network if private networking is enabled +//========================================================= +module virtualNetwork 'modules/virtualNetwork.bicep' = if (enablePrivateNetworking) { + name: 'virtualNetwork' + scope: rg + params: { + location: location + vNetName: vNetName + addressSpaces: ['10.0.0.0/25'] + subnetConfigs: [ + { + name: 'privateEndpoints' + addressPrefix: '10.0.0.0/26' + enablePrivateEndpointNetworkPolicies: true + enablePrivateLinkServiceNetworkPolicies: true + } + ] + tags: tags + } +} + //========================================================= // Create log analytics workspace //========================================================= diff --git a/deployers/bicep/modules/privateDNS.bicep b/deployers/bicep/modules/privateDNS.bicep new file mode 100644 index 00000000..79d2e058 --- /dev/null +++ b/deployers/bicep/modules/privateDNS.bicep @@ -0,0 +1,13 @@ +targetScope = 'resourceGroup' + +param zoneName string +param tags object + +resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { + name: zoneName + location: 'global' + tags: tags +} + +output privateDnsZoneId string = privateDnsZone.id +output privateDnsZoneName string = privateDnsZone.name diff --git a/deployers/bicep/modules/privateEndpoint.bicep b/deployers/bicep/modules/privateEndpoint.bicep new file mode 100644 index 00000000..3ac2d4ff --- /dev/null +++ b/deployers/bicep/modules/privateEndpoint.bicep @@ -0,0 +1,2 @@ +targetScope = 'resourceGroup' + diff --git a/deployers/bicep/modules/virtualNetwork.bicep b/deployers/bicep/modules/virtualNetwork.bicep new file mode 100644 index 00000000..a0cede54 --- /dev/null +++ b/deployers/bicep/modules/virtualNetwork.bicep @@ -0,0 +1,26 @@ +targetScope = 'resourceGroup' + +param location string +param vNetName string +param addressSpaces array +param subnetConfigs array +param tags object + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' = { + name: vNetName + location: location + properties: { + addressSpace: { + addressPrefixes: addressSpaces + } + subnets: [for subnet in subnetConfigs: { + name: subnet.name + properties: { + addressPrefix: subnet.addressPrefix + privateEndpointNetworkPolicies: subnet.enablePrivateEndpointNetworkPolicies ? 'Enabled' : 'Disabled' + privateLinkServiceNetworkPolicies: subnet.enablePrivateLinkServiceNetworkPolicies ? 'Enabled' : 'Disabled' + } + }] + } + tags: tags +} From 940ab59a827a1be1c428ebb1992a3a8928461a27 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Tue, 6 Jan 2026 21:11:28 +0000 Subject: [PATCH 02/19] Update to support private networking for core services --- deployers/bicep/main.bicep | 70 +++++++++++++++++-- deployers/bicep/modules/appService.bicep | 31 ++++++++ deployers/bicep/modules/appServicePlan.bicep | 1 + .../modules/azureContainerRegistry.bicep | 31 +++++++- deployers/bicep/modules/cosmosDb.bicep | 26 +++++++ .../bicep/modules/documentIntelligence.bicep | 27 ++++++- deployers/bicep/modules/keyVault.bicep | 27 ++++++- deployers/bicep/modules/openAI.bicep | 27 ++++++- deployers/bicep/modules/privateEndpoint.bicep | 58 +++++++++++++++ deployers/bicep/modules/search.bicep | 27 ++++++- deployers/bicep/modules/storageAccount.bicep | 27 +++++++ deployers/bicep/modules/virtualNetwork.bicep | 19 +++++ 12 files changed, 361 insertions(+), 10 deletions(-) diff --git a/deployers/bicep/main.bicep b/deployers/bicep/main.bicep index 751479bb..03322df8 100644 --- a/deployers/bicep/main.bicep +++ b/deployers/bicep/main.bicep @@ -160,18 +160,30 @@ module virtualNetwork 'modules/virtualNetwork.bicep' = if (enablePrivateNetworki params: { location: location vNetName: vNetName - addressSpaces: ['10.0.0.0/25'] + addressSpaces: ['10.0.0.0/21'] subnetConfigs: [ { - name: 'privateEndpoints' - addressPrefix: '10.0.0.0/26' + name: 'AppServiceIntegration' + addressPrefix: '10.0.0.0/24' + enablePrivateEndpointNetworkPolicies: true + enablePrivateLinkServiceNetworkPolicies: true + } + { + name: 'PrivateEndpoints' // this subnet name must be present if private endpoints are to be used + addressPrefix: '10.0.2.0/24' + enablePrivateEndpointNetworkPolicies: true + enablePrivateLinkServiceNetworkPolicies: true + } + { + name: 'Management' + addressPrefix: '10.0.4.0/25' enablePrivateEndpointNetworkPolicies: true enablePrivateLinkServiceNetworkPolicies: true } ] tags: tags } -} +} //========================================================= // Create log analytics workspace @@ -215,6 +227,10 @@ module keyVault 'modules/keyVault.bicep' = { tags: tags enableDiagLogging: enableDiagLogging logAnalyticsId: logAnalytics.outputs.logAnalyticsId + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' } } @@ -248,6 +264,12 @@ module cosmosDB 'modules/cosmosDb.bicep' = { keyVault: keyVault.outputs.keyVaultName authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions + + // todo: do not enable cosmosdb until validation has been completed. + // #disable-next-line BCP318 // expect one value to be null if private networking is disabled + // vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' + // #disable-next-line BCP318 // expect one value to be null if private networking is disabled + // privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' } } @@ -267,6 +289,13 @@ module acr 'modules/azureContainerRegistry.bicep' = { keyVault: keyVault.outputs.keyVaultName authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions + + appName: appName + environment: environment + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' } } @@ -287,6 +316,11 @@ module searchService 'modules/search.bicep' = { keyVault: keyVault.outputs.keyVaultName authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions + + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' } } @@ -307,6 +341,11 @@ module docIntel 'modules/documentIntelligence.bicep' = { keyVault: keyVault.outputs.keyVaultName authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions + + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' } } @@ -327,6 +366,11 @@ module storageAccount 'modules/storageAccount.bicep' = { keyVault: keyVault.outputs.keyVaultName authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions + + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' } } @@ -350,6 +394,11 @@ module openAI 'modules/openAI.bicep' = { gptModels: gptModels embeddingModels: embeddingModels + + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' } } @@ -396,6 +445,13 @@ module appService 'modules/appService.bicep' = { enterpriseAppClientSecret: enterpriseAppClientSecret authenticationType: authenticationType keyVaultUri: keyVault.outputs.keyVaultUri + + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' + #disable-next-line BCP318 // expect one value to be null if private networking is disabled + appServiceSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.appServiceSubnetId : '' } } @@ -548,3 +604,9 @@ output var_videoIndexerAccountId string = deployVideoIndexerService : '' #disable-next-line BCP318 // expect one value to be null output var_speechServiceEndpoint string = deploySpeechService ? speechService.outputs.speechServiceEndpoint : '' + +//-------------------------------------------- +#disable-next-line BCP318 // may not be configured if private networking is disabled +output var_vNetId string = virtualNetwork.outputs.vNetId +#disable-next-line BCP318 // may not be configured if private networking is disabled +output var_privateNetworkSubnetId string = virtualNetwork.outputs.privateNetworkSubnetId diff --git a/deployers/bicep/modules/appService.bicep b/deployers/bicep/modules/appService.bicep index 4f70f03a..d3ffca4b 100644 --- a/deployers/bicep/modules/appService.bicep +++ b/deployers/bicep/modules/appService.bicep @@ -55,6 +55,31 @@ resource appInsights 'Microsoft.Insights/components@2020-02-02' existing = { var acrDomain = azurePlatform == 'AzureUSGovernment' ? '.azurecr.us' : '.azurecr.io' +param vNetId string = '' +param privateEndpointSubnetId string = '' +param appServiceSubnetId string = '' + + +// create private endpoint for azure website if private endpoint subnet id is provided +module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { + name: toLower('${appName}-${environment}-app-pe') + + params: { + name: 'app' + location: location + appName: appName + environment: environment + privateDNSZoneName: 'privatelink.azurewebsites.net' + vNetId: vNetId + subnetId: privateEndpointSubnetId + serviceResourceID: webApp.id + groupIDs: [ + 'sites' + ] + tags: tags + } +} + // add web app resource webApp 'Microsoft.Web/sites@2022-03-01' = { name: toLower('${appName}-${environment}-app') @@ -62,6 +87,12 @@ resource webApp 'Microsoft.Web/sites@2022-03-01' = { kind: 'app,linux,container' properties: { serverFarmId: appServicePlanId + + virtualNetworkSubnetId: appServiceSubnetId != '' ? appServiceSubnetId : null + publicNetworkAccess: appServiceSubnetId != '' ? 'Disabled' : 'Enabled' + vnetImagePullEnabled: appServiceSubnetId != '' ? true : false + + siteConfig: { linuxFxVersion: 'DOCKER|${containerImageName}' acrUseManagedIdentityCreds: true diff --git a/deployers/bicep/modules/appServicePlan.bicep b/deployers/bicep/modules/appServicePlan.bicep index 15e5a1ce..e6be76e3 100644 --- a/deployers/bicep/modules/appServicePlan.bicep +++ b/deployers/bicep/modules/appServicePlan.bicep @@ -32,6 +32,7 @@ resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { targetWorkerCount: 0 targetWorkerSizeId: 0 } + tags: tags } diff --git a/deployers/bicep/modules/azureContainerRegistry.bicep b/deployers/bicep/modules/azureContainerRegistry.bicep index 4023447e..a71bea9e 100644 --- a/deployers/bicep/modules/azureContainerRegistry.bicep +++ b/deployers/bicep/modules/azureContainerRegistry.bicep @@ -11,22 +11,49 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool +param appName string +param environment string +param vNetId string = '' +param privateEndpointSubnetId string = '' + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } +// create private endpoint for azure container registry if private endpoint subnet id is provided +module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { + name: toLower('${appName}-${environment}-acr-pe') + dependsOn:[ + acr + ] + params: { + name: 'acr' + location: location + appName: appName + environment: environment + privateDNSZoneName: 'privatelink.azurecr.io' + vNetId: vNetId + subnetId: privateEndpointSubnetId + serviceResourceID: acr.id + groupIDs: [ + 'registry' + ] + tags: tags + } +} + // azure container registry resource acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = { name: acrName location: location sku: { - name: 'Standard' + name: privateEndpointSubnetId != '' ? 'Premium' : 'Standard' } properties: { adminUserEnabled: true - publicNetworkAccess: 'Enabled' + publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' } tags: tags } diff --git a/deployers/bicep/modules/cosmosDb.bicep b/deployers/bicep/modules/cosmosDb.bicep index 67ca669a..bbbd5f88 100644 --- a/deployers/bicep/modules/cosmosDb.bicep +++ b/deployers/bicep/modules/cosmosDb.bicep @@ -12,17 +12,43 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool +param vNetId string = '' +param privateEndpointSubnetId string = '' + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } +//create private endpoint for cosmosdb if private endpoint subnet id is provided +module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { + name: toLower('${appName}-${environment}-cosmos-pe') + dependsOn: [ + cosmosDb + ] + params: { + name: 'cosmos' + location: location + appName: appName + environment: environment + privateDNSZoneName: 'privatelink.documents.azure.com' + vNetId: vNetId + subnetId: privateEndpointSubnetId + serviceResourceID: cosmosDb.id + groupIDs: [ + 'sql' + ] + tags: tags + } +} + // cosmos db resource cosmosDb 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { name: toLower('${appName}-${environment}-cosmos') location: location kind: 'GlobalDocumentDB' properties: { + publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' databaseAccountOfferType: 'Standard' capabilities: [ { diff --git a/deployers/bicep/modules/documentIntelligence.bicep b/deployers/bicep/modules/documentIntelligence.bicep index 70b343e3..81e0a758 100644 --- a/deployers/bicep/modules/documentIntelligence.bicep +++ b/deployers/bicep/modules/documentIntelligence.bicep @@ -12,11 +12,36 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool +param vNetId string = '' +param privateEndpointSubnetId string = '' + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } +// create private endpoint for azure document intelligence if private endpoint subnet id is provided +module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { + name: toLower('${appName}-${environment}-docintel-pe') + dependsOn:[ + docIntel + ] + params: { + name: 'docintel' + location: location + appName: appName + environment: environment + privateDNSZoneName: 'privatelink.cognitiveservices.azure.com' + vNetId: vNetId + subnetId: privateEndpointSubnetId + serviceResourceID: docIntel.id + groupIDs: [ + 'account' + ] + tags: tags + } +} + // document intelligence resource resource docIntel 'Microsoft.CognitiveServices/accounts@2025-06-01' = { name: toLower('${appName}-${environment}-docintel') @@ -26,7 +51,7 @@ resource docIntel 'Microsoft.CognitiveServices/accounts@2025-06-01' = { name: 'S0' } properties: { - publicNetworkAccess: 'Enabled' + publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' customSubDomainName: toLower('${appName}-${environment}-docintel') } tags: tags diff --git a/deployers/bicep/modules/keyVault.bicep b/deployers/bicep/modules/keyVault.bicep index 6b2786de..8eeee102 100644 --- a/deployers/bicep/modules/keyVault.bicep +++ b/deployers/bicep/modules/keyVault.bicep @@ -8,11 +8,36 @@ param tags object param enableDiagLogging bool param logAnalyticsId string +param vNetId string = '' +param privateEndpointSubnetId string = '' + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } +// create private endpoint for key vault if private endpoint subnet id is provided +module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { + name: toLower('${appName}-${environment}-kv-pe') + dependsOn:[ + kv + ] + params: { + name: 'kv' + location: location + appName: appName + environment: environment + privateDNSZoneName: 'privatelink.vaultcore.azure.net' + vNetId: vNetId + subnetId: privateEndpointSubnetId + serviceResourceID: kv.id + groupIDs: [ + 'vault' + ] + tags: tags + } +} + // key vault resource resource kv 'Microsoft.KeyVault/vaults@2024-11-01' = { name: toLower('${appName}-${environment}-kv') @@ -27,7 +52,7 @@ resource kv 'Microsoft.KeyVault/vaults@2024-11-01' = { enabledForDeployment: false enabledForDiskEncryption: false enabledForTemplateDeployment: false - publicNetworkAccess: 'Enabled' + publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' enableRbacAuthorization: true } tags: tags diff --git a/deployers/bicep/modules/openAI.bicep b/deployers/bicep/modules/openAI.bicep index 9a04b79d..4c4e86bd 100644 --- a/deployers/bicep/modules/openAI.bicep +++ b/deployers/bicep/modules/openAI.bicep @@ -15,11 +15,36 @@ param configureApplicationPermissions bool param gptModels array param embeddingModels array +param vNetId string = '' +param privateEndpointSubnetId string = '' + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } +// create private endpoint for azure openAI if private endpoint subnet id is provided +module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { + name: toLower('${appName}-${environment}-openai-pe') + dependsOn:[ + openAI + ] + params: { + name: 'openai' + location: location + appName: appName + environment: environment + privateDNSZoneName: 'privatelink.openai.azure.com' + vNetId: vNetId + subnetId: privateEndpointSubnetId + serviceResourceID: openAI.id + groupIDs: [ + 'account' + ] + tags: tags + } +} + // deploy new Azure OpenAI Resource resource openAI 'Microsoft.CognitiveServices/accounts@2024-10-01' = { name: toLower('${appName}-${environment}-openai') @@ -32,7 +57,7 @@ resource openAI 'Microsoft.CognitiveServices/accounts@2024-10-01' = { type: 'SystemAssigned' } properties: { - publicNetworkAccess: 'Enabled' + publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' customSubDomainName: toLower('${appName}-${environment}-openai') } tags: tags diff --git a/deployers/bicep/modules/privateEndpoint.bicep b/deployers/bicep/modules/privateEndpoint.bicep index 3ac2d4ff..e720ec4c 100644 --- a/deployers/bicep/modules/privateEndpoint.bicep +++ b/deployers/bicep/modules/privateEndpoint.bicep @@ -1,2 +1,60 @@ targetScope = 'resourceGroup' +param name string +param location string +param appName string +param environment string +param serviceResourceID string +param subnetId string +param groupIDs array +param vNetId string = '' +param privateDNSZoneName string = '' +param tags object + +resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = if (privateDNSZoneName != '') { + name: privateDNSZoneName + location: 'global' + tags: tags +} + +resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = if (privateDNSZoneName != '') { + name: toLower('${appName}-${environment}-${name}-pe-dnszonelink') + parent: privateDnsZone + location: 'global' + properties: { + registrationEnabled: false + virtualNetwork: { + id: vNetId + } + } +} + +resource privateEndpoint 'Microsoft.Network/privateEndpoints@2021-05-01' = { + name: toLower('${appName}-${environment}-${name}-pe') + location: location + properties: { + subnet: { + id: subnetId + } + privateLinkServiceConnections: [ + { + name: toLower('${appName}-${environment}-${name}-pe') + properties: { + privateLinkServiceId: serviceResourceID + groupIds: groupIDs + } + } + ] + customNetworkInterfaceName: toLower('${appName}-${environment}-${name}-nic') + } + tags: tags +} + +@description('Private endpoint resource ID') +output privateEndpointId string = privateEndpoint.id + +@description('Private endpoint name') +output privateEndpointName string = privateEndpoint.name + +@description('Private IP address assigned to the private endpoint') +output privateIpAddress string = privateEndpoint.properties.customDnsConfigs[0].ipAddresses[0] diff --git a/deployers/bicep/modules/search.bicep b/deployers/bicep/modules/search.bicep index 2786b91e..7fb38201 100644 --- a/deployers/bicep/modules/search.bicep +++ b/deployers/bicep/modules/search.bicep @@ -12,11 +12,36 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool +param vNetId string = '' +param privateEndpointSubnetId string = '' + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } +// create private endpoint for azure search service if private endpoint subnet id is provided +module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { + name: toLower('${appName}-${environment}-search-pe') + dependsOn:[ + searchService + ] + params: { + name: 'search' + location: location + appName: appName + environment: environment + privateDNSZoneName: 'privatelink.search.windows.net' + vNetId: vNetId + subnetId: privateEndpointSubnetId + serviceResourceID: searchService.id + groupIDs: [ + 'searchService' + ] + tags: tags + } +} + // search service resource resource searchService 'Microsoft.Search/searchServices@2025-05-01' = { name: toLower('${appName}-${environment}-search') @@ -27,7 +52,7 @@ resource searchService 'Microsoft.Search/searchServices@2025-05-01' = { properties: { #disable-next-line BCP036 // template is incorrect hostingMode: 'default' - publicNetworkAccess: 'Enabled' + publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' replicaCount: 1 partitionCount: 1 authOptions: { diff --git a/deployers/bicep/modules/storageAccount.bicep b/deployers/bicep/modules/storageAccount.bicep index 7e10ccae..6d621438 100644 --- a/deployers/bicep/modules/storageAccount.bicep +++ b/deployers/bicep/modules/storageAccount.bicep @@ -12,11 +12,36 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool +param vNetId string = '' +param privateEndpointSubnetId string = '' + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } +// create private endpoint for azure storage if private endpoint subnet id is provided +module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { + name: toLower('${appName}-${environment}-storage-pe') + dependsOn:[ + storageAccount + ] + params: { + name: 'storage' + location: location + appName: appName + environment: environment + privateDNSZoneName: 'privatelink.blob.core.windows.net' + vNetId: vNetId + subnetId: privateEndpointSubnetId + serviceResourceID: storageAccount.id + groupIDs: [ + 'blob' + ] + tags: tags + } +} + // storage account resource resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { #disable-next-line BCP334 //Name length managed by Bicep parameters. @@ -26,7 +51,9 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { name: 'Standard_LRS' } kind: 'StorageV2' + properties: { + publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' accessTier: 'Hot' allowBlobPublicAccess: false allowSharedKeyAccess: true diff --git a/deployers/bicep/modules/virtualNetwork.bicep b/deployers/bicep/modules/virtualNetwork.bicep index a0cede54..33c9a83a 100644 --- a/deployers/bicep/modules/virtualNetwork.bicep +++ b/deployers/bicep/modules/virtualNetwork.bicep @@ -19,8 +19,27 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' = { addressPrefix: subnet.addressPrefix privateEndpointNetworkPolicies: subnet.enablePrivateEndpointNetworkPolicies ? 'Enabled' : 'Disabled' privateLinkServiceNetworkPolicies: subnet.enablePrivateLinkServiceNetworkPolicies ? 'Enabled' : 'Disabled' + delegations: subnet.name == 'AppServiceIntegration' ? [ + { + name: 'delegation' + properties: { + serviceName: 'Microsoft.Web/serverFarms' + } + } + ] : [] } }] } tags: tags } + + + +var subnetIds = [for subnet in subnetConfigs: resourceId('Microsoft.Network/virtualNetworks/subnets', vNetName, subnet.name)] +var subnetNames = [for subnet in subnetConfigs: subnet.name] +var appServiceIntegrationSubnetIndex = indexOf(subnetNames, 'AppServiceIntegration') +var privateEndpointIndex = indexOf(subnetNames, 'PrivateEndpoints') + +output vNetId string = virtualNetwork.id +output privateNetworkSubnetId string = privateEndpointIndex == -1 ? '' : subnetIds[privateEndpointIndex] +output appServiceSubnetId string = appServiceIntegrationSubnetIndex == -1 ? '' : subnetIds[appServiceIntegrationSubnetIndex] From 2ae9cc389b576f3aa91f7dab5c852ef004baaa73 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Wed, 14 Jan 2026 14:40:29 +0000 Subject: [PATCH 03/19] refactor of private networking --- deployers/azure.yaml | 37 +- deployers/bicep/main.bicep | 153 ++++-- deployers/bicep/modules/appService.bicep | 32 +- .../modules/azureContainerRegistry.bicep | 31 +- deployers/bicep/modules/contentSafety.bicep | 4 +- deployers/bicep/modules/cosmosDb.bicep | 31 +- .../bicep/modules/documentIntelligence.bicep | 27 +- deployers/bicep/modules/keyVault.bicep | 27 +- deployers/bicep/modules/openAI.bicep | 27 +- deployers/bicep/modules/privateDNS.bicep | 16 + deployers/bicep/modules/privateEndpoint.bicep | 40 +- .../bicep/modules/privateNetworking.bicep | 467 ++++++++++++++++++ deployers/bicep/modules/search.bicep | 27 +- deployers/bicep/modules/speechService.bicep | 4 +- deployers/bicep/modules/storageAccount.bicep | 27 +- deployers/bicep/modules/videoIndexer.bicep | 4 +- deployers/bicep/modules/virtualNetwork.bicep | 3 +- deployers/bicep/postconfig.py | 8 +- 18 files changed, 674 insertions(+), 291 deletions(-) create mode 100644 deployers/bicep/modules/privateNetworking.bicep diff --git a/deployers/azure.yaml b/deployers/azure.yaml index 90a40cc9..0832cf6b 100644 --- a/deployers/azure.yaml +++ b/deployers/azure.yaml @@ -10,15 +10,15 @@ hooks: shell: sh run: | # Set up variables - + export var_acrName=${var_acrname} export var_configureApplication=${var_configureApplication} export var_cosmosDb_uri=${var_cosmosDb_uri} + export var_cosmosDb_accountName=${var_cosmosDb_accountName} export var_subscriptionId=${AZURE_SUBSCRIPTION_ID} export var_rgName=${var_rgName} export var_keyVaultUri=${var_keyVaultUri} - + export var_keyVaultName=${var_keyVaultName} export var_authenticationType=${var_authenticationType} - export var_openAIEndpoint=${var_openAIEndpoint} export var_openAIResourceGroup=${var_openAIResourceGroup} export var_openAIGPTModel=${var_openAIGPTModel} @@ -31,6 +31,7 @@ hooks: export var_deploymentLocation=${var_deploymentLocation} export var_videoIndexerAccountId=${var_videoIndexerAccountId} export var_speechServiceEndpoint=${var_speechServiceEndpoint} + export var_vNetId=${var_vNetId} # Execute post-configuration script if enabled if [ "${var_configureApplication}" = "true" ]; then @@ -66,6 +67,36 @@ hooks: docker push ${var_containerRegistry}/${var_imageName}:${timestamp} echo "Restarting web service..." az webapp start --name ${var_webService} --resource-group ${var_rgName} + + postup: + posix: + shell: sh + run: | + # If using private endpoints, disable cosmos and key vault public interface + if [ "${var_enablePrivateNetworking}" = "true" ]; then + echo "Disabling public network access for CosmosDB..." + + echo "Disabling public network access for CosmosDB..." + az cosmosdb update --name ${var_cosmosDb_accountName} --resource-group ${var_rgName} --public-network-access Disabled + echo "Public network access for CosmosDB disabled." + + echo "Disabling public network access for Key Vault..." + az keyvault update --name ${var_keyVaultName} --resource-group ${var_rgName} --public-network-access Disabled + echo "Public network access for Key Vault disabled." + + echo "Disabling public network access for Azure Container Registry..." + az acr update --name ${var_acrName} --resource-group ${var_rgName} --public-network-enabled false + echo "Public network access for Azure Container Registry disabled." + + # echo "Disabling public network access for Web Application..." + # az webapp update --name ${var_webService} --resource-group ${var_rgName} --public-network-access Disabled + # echo "Public network access for Web Application disabled." + else + echo "Skipping disabling public network access for CosmosDB, Key Vault, and Azure Container Registry (var_enablePrivateNetworking is not true)" + fi + + echo "Deployment completed successfully." + services: web: project: ../application/single_app diff --git a/deployers/bicep/main.bicep b/deployers/bicep/main.bicep index 03322df8..82b1d3af 100644 --- a/deployers/bicep/main.bicep +++ b/deployers/bicep/main.bicep @@ -82,6 +82,24 @@ param enablePrivateNetworking bool @description('''Array of GPT model names to deploy to the OpenAI resource.''') param gptModels array = [ + { + modelName: 'gpt-5-chat' + modelVersion: '2025-10-03' + skuName: 'GlobalStandard' + skuCapacity: 150 + } + { + modelName: 'gpt-5-nano' + modelVersion: '2025-08-07' + skuName: 'GlobalStandard' + skuCapacity: 250 + } + { + modelName: 'o4-mini' + modelVersion: '2025-04-16' + skuName: 'GlobalStandard' + skuCapacity: 150 + } { modelName: 'gpt-4.1' modelVersion: '2025-04-14' @@ -111,6 +129,20 @@ param embeddingModels array = [ skuCapacity: 150 } ] + +//---------------- +// cosmos db firewall +// this is needed to allow access during deployment. Should be the ipAddress of the machine running the deployment +// may be a single ip address or a full range in cidr format. EG. 123.45.67.125/32 or 123.45.67.125 or 10.10.1.0/24 +// strongly encourage makking sure 0.0.0.0 is included to allow Azure services to access cosmos db +param allowedIpAddresses array = [ + { + ipAddressOrRange: '0.0.0.0' //--- required to allow Azure services to access cosmos db + } + { + ipAddressOrRange: '173.66.57.199' //--- replace with your own IP address + } +] //---------------- // optional services @@ -155,15 +187,15 @@ resource rg 'Microsoft.Resources/resourceGroups@2022-09-01' = { // Create Virtual Network if private networking is enabled //========================================================= module virtualNetwork 'modules/virtualNetwork.bicep' = if (enablePrivateNetworking) { - name: 'virtualNetwork' scope: rg + name: 'virtualNetwork' params: { location: location vNetName: vNetName addressSpaces: ['10.0.0.0/21'] subnetConfigs: [ { - name: 'AppServiceIntegration' + name: 'AppServiceIntegration' // this subnet name must be present for app service vnet integration addressPrefix: '10.0.0.0/24' enablePrivateEndpointNetworkPolicies: true enablePrivateLinkServiceNetworkPolicies: true @@ -174,12 +206,6 @@ module virtualNetwork 'modules/virtualNetwork.bicep' = if (enablePrivateNetworki enablePrivateEndpointNetworkPolicies: true enablePrivateLinkServiceNetworkPolicies: true } - { - name: 'Management' - addressPrefix: '10.0.4.0/25' - enablePrivateEndpointNetworkPolicies: true - enablePrivateLinkServiceNetworkPolicies: true - } ] tags: tags } @@ -227,10 +253,6 @@ module keyVault 'modules/keyVault.bicep' = { tags: tags enableDiagLogging: enableDiagLogging logAnalyticsId: logAnalytics.outputs.logAnalyticsId - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' } } @@ -264,12 +286,8 @@ module cosmosDB 'modules/cosmosDb.bicep' = { keyVault: keyVault.outputs.keyVaultName authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions - - // todo: do not enable cosmosdb until validation has been completed. - // #disable-next-line BCP318 // expect one value to be null if private networking is disabled - // vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' - // #disable-next-line BCP318 // expect one value to be null if private networking is disabled - // privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' + enablePrivateNetworking: enablePrivateNetworking + allowedIpAddresses: allowedIpAddresses } } @@ -289,13 +307,7 @@ module acr 'modules/azureContainerRegistry.bicep' = { keyVault: keyVault.outputs.keyVaultName authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions - - appName: appName - environment: environment - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' + enablePrivateNetworking: enablePrivateNetworking } } @@ -317,10 +329,7 @@ module searchService 'modules/search.bicep' = { authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' + enablePrivateNetworking: enablePrivateNetworking } } @@ -342,10 +351,7 @@ module docIntel 'modules/documentIntelligence.bicep' = { authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' + enablePrivateNetworking: enablePrivateNetworking } } @@ -367,10 +373,7 @@ module storageAccount 'modules/storageAccount.bicep' = { authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' + enablePrivateNetworking: enablePrivateNetworking } } @@ -395,10 +398,7 @@ module openAI 'modules/openAI.bicep' = { gptModels: gptModels embeddingModels: embeddingModels - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' + enablePrivateNetworking: enablePrivateNetworking } } @@ -446,12 +446,9 @@ module appService 'modules/appService.bicep' = { authenticationType: authenticationType keyVaultUri: keyVault.outputs.keyVaultUri + enablePrivateNetworking: enablePrivateNetworking #disable-next-line BCP318 // expect one value to be null if private networking is disabled - vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' - #disable-next-line BCP318 // expect one value to be null if private networking is disabled - appServiceSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.appServiceSubnetId : '' + appServiceSubnetId: enablePrivateNetworking? virtualNetwork.outputs.appServiceSubnetId : '' } } @@ -476,6 +473,8 @@ module contentSafety 'modules/contentSafety.bicep' = if (deployContentSafety) { keyVault: keyVault.outputs.keyVaultName authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions + + enablePrivateNetworking: enablePrivateNetworking } } @@ -496,6 +495,9 @@ module redisCache 'modules/redisCache.bicep' = if (deployRedisCache) { keyVault: keyVault.outputs.keyVaultName authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions + + //enablePrivateNetworking: enablePrivateNetworking + } } @@ -504,6 +506,7 @@ module redisCache 'modules/redisCache.bicep' = if (deployRedisCache) { //========================================================= module speechService 'modules/speechService.bicep' = if (deploySpeechService) { name: 'speechService' + dependsOn:[] scope: rg params: { location: location @@ -516,6 +519,8 @@ module speechService 'modules/speechService.bicep' = if (deploySpeechService) { keyVault: keyVault.outputs.keyVaultName authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions + + enablePrivateNetworking: enablePrivateNetworking } } @@ -535,6 +540,13 @@ module videoIndexerService 'modules/videoIndexer.bicep' = if (deployVideoIndexer storageAccount: storageAccount.outputs.name openAiServiceName: openAI.outputs.openAIName + + enablePrivateNetworking: enablePrivateNetworking + + // #disable-next-line BCP318 // expect one value to be null if private networking is disabled + // vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' + // #disable-next-line BCP318 // expect one value to be null if private networking is disabled + // privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' } } @@ -555,16 +567,54 @@ module setPermissions 'modules/setPermissions.bicep' = if (configureApplicationP openAIName: openAI.outputs.openAIName docIntelName: docIntel.outputs.documentIntelligenceServiceName storageAccountName: storageAccount.outputs.name + searchServiceName: searchService.outputs.searchServiceName + #disable-next-line BCP318 // expect one value to be null speechServiceName: deploySpeechService ? speechService.outputs.speechServiceName : '' + #disable-next-line BCP318 // expect one value to be null + contentSafetyName: deployContentSafety ? contentSafety.outputs.contentSafetyName : '' + #disable-next-line BCP318 // expect one value to be null + videoIndexerName: deployVideoIndexerService ? videoIndexerService.outputs.videoIndexerServiceName : '' + } +} + +//========================================================= +// configure private networking +//========================================================= +module privateNetworking 'modules/privateNetworking.bicep' = if (enablePrivateNetworking) { + name: 'privateNetworking' + scope: rg + params: { + + #disable-next-line BCP318 // value can't be null based on enablePrivateNetworking condition + virtualNetworkId: virtualNetwork.outputs.vNetId + #disable-next-line BCP318 // value can't be null based on enablePrivateNetworking condition + privateEndpointSubnetId: virtualNetwork.outputs.privateNetworkSubnetId + + location: location + appName: appName + environment: environment + tags: tags + + keyVaultName: keyVault.outputs.keyVaultName + cosmosDBName: cosmosDB.outputs.cosmosDbName + acrName: acr.outputs.acrName searchServiceName: searchService.outputs.searchServiceName + docIntelName: docIntel.outputs.documentIntelligenceServiceName + storageAccountName: storageAccount.outputs.name + openAIName: openAI.outputs.openAIName + webAppName: appService.outputs.name + #disable-next-line BCP318 // expect one value to be null contentSafetyName: deployContentSafety ? contentSafety.outputs.contentSafetyName : '' #disable-next-line BCP318 // expect one value to be null + speechServiceName: deploySpeechService ? speechService.outputs.speechServiceName : '' + #disable-next-line BCP318 // expect one value to be null videoIndexerName: deployVideoIndexerService ? videoIndexerService.outputs.videoIndexerServiceName : '' } } + //========================================================= // output values //========================================================= @@ -581,7 +631,9 @@ output var_acrName string = toLower('${appName}${environment}acr') // output values required for postprovision script in azure.yaml output var_configureApplication bool = configureApplicationPermissions output var_keyVaultUri string = keyVault.outputs.keyVaultUri +output var_keyVaultName string = keyVault.outputs.keyVaultName output var_cosmosDb_uri string = cosmosDB.outputs.cosmosDbUri +output var_cosmosDb_accountName string = cosmosDB.outputs.cosmosDbName output var_subscriptionId string = subscription().subscriptionId output var_authenticationType string = toLower(authenticationType) output var_openAIEndpoint string = openAI.outputs.openAIEndpoint @@ -606,7 +658,8 @@ output var_videoIndexerAccountId string = deployVideoIndexerService output var_speechServiceEndpoint string = deploySpeechService ? speechService.outputs.speechServiceEndpoint : '' //-------------------------------------------- -#disable-next-line BCP318 // may not be configured if private networking is disabled -output var_vNetId string = virtualNetwork.outputs.vNetId -#disable-next-line BCP318 // may not be configured if private networking is disabled -output var_privateNetworkSubnetId string = virtualNetwork.outputs.privateNetworkSubnetId +output var_enablePrivateNetworking bool = enablePrivateNetworking +// #disable-next-line BCP318 // may not be configured if private networking is disabled +// output var_vNetId string = enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' +// #disable-next-line BCP318 // may not be configured if private networking is disabled +// output var_privateNetworkSubnetId string = enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' diff --git a/deployers/bicep/modules/appService.bicep b/deployers/bicep/modules/appService.bicep index d3ffca4b..ca7601c9 100644 --- a/deployers/bicep/modules/appService.bicep +++ b/deployers/bicep/modules/appService.bicep @@ -24,6 +24,8 @@ param authenticationType string @secure() param enterpriseAppClientSecret string = '' param keyVaultUri string +param enablePrivateNetworking bool +param appServiceSubnetId string = '' // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { @@ -55,31 +57,6 @@ resource appInsights 'Microsoft.Insights/components@2020-02-02' existing = { var acrDomain = azurePlatform == 'AzureUSGovernment' ? '.azurecr.us' : '.azurecr.io' -param vNetId string = '' -param privateEndpointSubnetId string = '' -param appServiceSubnetId string = '' - - -// create private endpoint for azure website if private endpoint subnet id is provided -module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { - name: toLower('${appName}-${environment}-app-pe') - - params: { - name: 'app' - location: location - appName: appName - environment: environment - privateDNSZoneName: 'privatelink.azurewebsites.net' - vNetId: vNetId - subnetId: privateEndpointSubnetId - serviceResourceID: webApp.id - groupIDs: [ - 'sites' - ] - tags: tags - } -} - // add web app resource webApp 'Microsoft.Web/sites@2022-03-01' = { name: toLower('${appName}-${environment}-app') @@ -89,10 +66,9 @@ resource webApp 'Microsoft.Web/sites@2022-03-01' = { serverFarmId: appServicePlanId virtualNetworkSubnetId: appServiceSubnetId != '' ? appServiceSubnetId : null - publicNetworkAccess: appServiceSubnetId != '' ? 'Disabled' : 'Enabled' - vnetImagePullEnabled: appServiceSubnetId != '' ? true : false + publicNetworkAccess: 'Enabled' // configuration is set in post provision step in azure.yaml with post deployment script + vnetImagePullEnabled: enablePrivateNetworking ? true : false - siteConfig: { linuxFxVersion: 'DOCKER|${containerImageName}' acrUseManagedIdentityCreds: true diff --git a/deployers/bicep/modules/azureContainerRegistry.bicep b/deployers/bicep/modules/azureContainerRegistry.bicep index a71bea9e..43edc07b 100644 --- a/deployers/bicep/modules/azureContainerRegistry.bicep +++ b/deployers/bicep/modules/azureContainerRegistry.bicep @@ -11,49 +11,24 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool -param appName string -param environment string -param vNetId string = '' -param privateEndpointSubnetId string = '' +param enablePrivateNetworking bool // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } -// create private endpoint for azure container registry if private endpoint subnet id is provided -module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { - name: toLower('${appName}-${environment}-acr-pe') - dependsOn:[ - acr - ] - params: { - name: 'acr' - location: location - appName: appName - environment: environment - privateDNSZoneName: 'privatelink.azurecr.io' - vNetId: vNetId - subnetId: privateEndpointSubnetId - serviceResourceID: acr.id - groupIDs: [ - 'registry' - ] - tags: tags - } -} - // azure container registry resource acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = { name: acrName location: location sku: { - name: privateEndpointSubnetId != '' ? 'Premium' : 'Standard' + name: enablePrivateNetworking ? 'Premium' : 'Standard' } properties: { adminUserEnabled: true - publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' + publicNetworkAccess: 'Enabled' // configuration is set in post provision step in azure.yaml with post deployment script } tags: tags } diff --git a/deployers/bicep/modules/contentSafety.bicep b/deployers/bicep/modules/contentSafety.bicep index 59c40125..02ba06c7 100644 --- a/deployers/bicep/modules/contentSafety.bicep +++ b/deployers/bicep/modules/contentSafety.bicep @@ -12,6 +12,8 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool +param enablePrivateNetworking bool + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' @@ -26,7 +28,7 @@ resource contentSafety 'Microsoft.CognitiveServices/accounts@2025-06-01' = { name: 'S0' } properties: { - publicNetworkAccess: 'Enabled' + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' customSubDomainName: toLower('${appName}-${environment}-contentsafety') } tags: tags diff --git a/deployers/bicep/modules/cosmosDb.bicep b/deployers/bicep/modules/cosmosDb.bicep index bbbd5f88..6aa89d50 100644 --- a/deployers/bicep/modules/cosmosDb.bicep +++ b/deployers/bicep/modules/cosmosDb.bicep @@ -12,49 +12,30 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool -param vNetId string = '' -param privateEndpointSubnetId string = '' +param enablePrivateNetworking bool +param allowedIpAddresses array = [] // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } -//create private endpoint for cosmosdb if private endpoint subnet id is provided -module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { - name: toLower('${appName}-${environment}-cosmos-pe') - dependsOn: [ - cosmosDb - ] - params: { - name: 'cosmos' - location: location - appName: appName - environment: environment - privateDNSZoneName: 'privatelink.documents.azure.com' - vNetId: vNetId - subnetId: privateEndpointSubnetId - serviceResourceID: cosmosDb.id - groupIDs: [ - 'sql' - ] - tags: tags - } -} - // cosmos db resource cosmosDb 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { name: toLower('${appName}-${environment}-cosmos') location: location kind: 'GlobalDocumentDB' properties: { - publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' + publicNetworkAccess: 'Enabled' // configuration is set in post provision step in azure.yaml with post deployment script databaseAccountOfferType: 'Standard' capabilities: [ { name: 'EnableServerless' } ] + isVirtualNetworkFilterEnabled: enablePrivateNetworking ? true : false + ipRules: enablePrivateNetworking ? allowedIpAddresses : [] + locations: [ { locationName: location diff --git a/deployers/bicep/modules/documentIntelligence.bicep b/deployers/bicep/modules/documentIntelligence.bicep index 81e0a758..b340c795 100644 --- a/deployers/bicep/modules/documentIntelligence.bicep +++ b/deployers/bicep/modules/documentIntelligence.bicep @@ -12,36 +12,13 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool -param vNetId string = '' -param privateEndpointSubnetId string = '' +param enablePrivateNetworking bool // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } -// create private endpoint for azure document intelligence if private endpoint subnet id is provided -module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { - name: toLower('${appName}-${environment}-docintel-pe') - dependsOn:[ - docIntel - ] - params: { - name: 'docintel' - location: location - appName: appName - environment: environment - privateDNSZoneName: 'privatelink.cognitiveservices.azure.com' - vNetId: vNetId - subnetId: privateEndpointSubnetId - serviceResourceID: docIntel.id - groupIDs: [ - 'account' - ] - tags: tags - } -} - // document intelligence resource resource docIntel 'Microsoft.CognitiveServices/accounts@2025-06-01' = { name: toLower('${appName}-${environment}-docintel') @@ -51,7 +28,7 @@ resource docIntel 'Microsoft.CognitiveServices/accounts@2025-06-01' = { name: 'S0' } properties: { - publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' customSubDomainName: toLower('${appName}-${environment}-docintel') } tags: tags diff --git a/deployers/bicep/modules/keyVault.bicep b/deployers/bicep/modules/keyVault.bicep index 8eeee102..384fe398 100644 --- a/deployers/bicep/modules/keyVault.bicep +++ b/deployers/bicep/modules/keyVault.bicep @@ -8,36 +8,11 @@ param tags object param enableDiagLogging bool param logAnalyticsId string -param vNetId string = '' -param privateEndpointSubnetId string = '' - // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } -// create private endpoint for key vault if private endpoint subnet id is provided -module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { - name: toLower('${appName}-${environment}-kv-pe') - dependsOn:[ - kv - ] - params: { - name: 'kv' - location: location - appName: appName - environment: environment - privateDNSZoneName: 'privatelink.vaultcore.azure.net' - vNetId: vNetId - subnetId: privateEndpointSubnetId - serviceResourceID: kv.id - groupIDs: [ - 'vault' - ] - tags: tags - } -} - // key vault resource resource kv 'Microsoft.KeyVault/vaults@2024-11-01' = { name: toLower('${appName}-${environment}-kv') @@ -52,7 +27,7 @@ resource kv 'Microsoft.KeyVault/vaults@2024-11-01' = { enabledForDeployment: false enabledForDiskEncryption: false enabledForTemplateDeployment: false - publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' + publicNetworkAccess: 'Enabled' // configuration is set in post provision step in azure.yaml with post deployment script enableRbacAuthorization: true } tags: tags diff --git a/deployers/bicep/modules/openAI.bicep b/deployers/bicep/modules/openAI.bicep index 4c4e86bd..a5d2e1b6 100644 --- a/deployers/bicep/modules/openAI.bicep +++ b/deployers/bicep/modules/openAI.bicep @@ -15,36 +15,13 @@ param configureApplicationPermissions bool param gptModels array param embeddingModels array -param vNetId string = '' -param privateEndpointSubnetId string = '' +param enablePrivateNetworking bool // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } -// create private endpoint for azure openAI if private endpoint subnet id is provided -module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { - name: toLower('${appName}-${environment}-openai-pe') - dependsOn:[ - openAI - ] - params: { - name: 'openai' - location: location - appName: appName - environment: environment - privateDNSZoneName: 'privatelink.openai.azure.com' - vNetId: vNetId - subnetId: privateEndpointSubnetId - serviceResourceID: openAI.id - groupIDs: [ - 'account' - ] - tags: tags - } -} - // deploy new Azure OpenAI Resource resource openAI 'Microsoft.CognitiveServices/accounts@2024-10-01' = { name: toLower('${appName}-${environment}-openai') @@ -57,7 +34,7 @@ resource openAI 'Microsoft.CognitiveServices/accounts@2024-10-01' = { type: 'SystemAssigned' } properties: { - publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' customSubDomainName: toLower('${appName}-${environment}-openai') } tags: tags diff --git a/deployers/bicep/modules/privateDNS.bicep b/deployers/bicep/modules/privateDNS.bicep index 79d2e058..9306b6cc 100644 --- a/deployers/bicep/modules/privateDNS.bicep +++ b/deployers/bicep/modules/privateDNS.bicep @@ -1,6 +1,10 @@ targetScope = 'resourceGroup' param zoneName string +param appName string +param environment string +param name string +param vNetId string param tags object resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { @@ -9,5 +13,17 @@ resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { tags: tags } +resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { + name: toLower('${appName}-${environment}-${name}-pe-dnszonelink') + parent: privateDnsZone + location: 'global' + properties: { + registrationEnabled: false + virtualNetwork: { + id: vNetId + } + } +} + output privateDnsZoneId string = privateDnsZone.id output privateDnsZoneName string = privateDnsZone.name diff --git a/deployers/bicep/modules/privateEndpoint.bicep b/deployers/bicep/modules/privateEndpoint.bicep index e720ec4c..aaa52da0 100644 --- a/deployers/bicep/modules/privateEndpoint.bicep +++ b/deployers/bicep/modules/privateEndpoint.bicep @@ -7,25 +7,21 @@ param environment string param serviceResourceID string param subnetId string param groupIDs array -param vNetId string = '' -param privateDNSZoneName string = '' +param privateDnsZoneIds array = [] param tags object -resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = if (privateDNSZoneName != '') { - name: privateDNSZoneName - location: 'global' - tags: tags -} - -resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = if (privateDNSZoneName != '') { - name: toLower('${appName}-${environment}-${name}-pe-dnszonelink') - parent: privateDnsZone - location: 'global' +resource dnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2021-05-01' = if (length(privateDnsZoneIds) > 0) { + name: 'default' + parent: privateEndpoint properties: { - registrationEnabled: false - virtualNetwork: { - id: vNetId - } + privateDnsZoneConfigs: [ + for zoneId in privateDnsZoneIds: { + name: last(split(zoneId, '/')) + properties: { + privateDnsZoneId: zoneId + } + } + ] } } @@ -50,11 +46,11 @@ resource privateEndpoint 'Microsoft.Network/privateEndpoints@2021-05-01' = { tags: tags } -@description('Private endpoint resource ID') -output privateEndpointId string = privateEndpoint.id +// @description('Private endpoint resource ID') +// output privateEndpointId string = privateEndpoint.id -@description('Private endpoint name') -output privateEndpointName string = privateEndpoint.name +// @description('Private endpoint name') +// output privateEndpointName string = privateEndpoint.name -@description('Private IP address assigned to the private endpoint') -output privateIpAddress string = privateEndpoint.properties.customDnsConfigs[0].ipAddresses[0] +// @description('Private IP address assigned to the private endpoint') +// output privateIpAddress string = privateEndpoint.properties.customDnsConfigs[0].ipAddresses[0] diff --git a/deployers/bicep/modules/privateNetworking.bicep b/deployers/bicep/modules/privateNetworking.bicep new file mode 100644 index 00000000..1c640a90 --- /dev/null +++ b/deployers/bicep/modules/privateNetworking.bicep @@ -0,0 +1,467 @@ +targetScope = 'resourceGroup' + +param virtualNetworkId string +param privateEndpointSubnetId string + +param location string +param appName string +param environment string +param tags object + +param keyVaultName string +param cosmosDBName string +param acrName string +param searchServiceName string +param docIntelName string +param storageAccountName string +param openAIName string +param webAppName string + + +// // redis cache +param contentSafetyName string +param speechServiceName string +param videoIndexerName string + +//var vNetName = '${appName}-${environment}-vnet' + +//========================================================= +// Create Virtual Network if private networking is enabled +//========================================================= +// module virtualNetwork 'virtualNetwork.bicep' = { +// name: 'virtualNetwork' +// params: { +// location: location +// vNetName: vNetName +// addressSpaces: ['10.0.0.0/21'] +// subnetConfigs: [ +// { +// name: 'AppServiceIntegration' +// addressPrefix: '10.0.0.0/24' +// enablePrivateEndpointNetworkPolicies: true +// enablePrivateLinkServiceNetworkPolicies: true +// } +// { +// name: 'PrivateEndpoints' // this subnet name must be present if private endpoints are to be used +// addressPrefix: '10.0.2.0/24' +// enablePrivateEndpointNetworkPolicies: true +// enablePrivateLinkServiceNetworkPolicies: true +// } +// ] +// tags: tags +// } +// } +// resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' existing = { +// name: vNetName +// } + +//========================================================= +// key vault +//========================================================= +resource kv 'Microsoft.KeyVault/vaults@2025-05-01' existing = { + name: keyVaultName +} + +module keyVaultDNSZone 'privateDNS.bicep' = { + name: 'keyVaultDNSZone' + params: { + zoneName: 'privatelink.vaultcore.azure.net' + appName: appName + environment: environment + name: 'kv' + vNetId: virtualNetworkId + tags: tags + } +} + +module keyVaultPE 'privateEndpoint.bicep' = { + name: 'keyVaultPE' + dependsOn: [ + kv + keyVaultDNSZone + ] + params: { + name: 'kv' + location: location + appName: appName + environment: environment + serviceResourceID: kv.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'vault' + ] + privateDnsZoneIds: [ + keyVaultDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} +//========================================================= +// cosmos db +//========================================================= +resource cosmosDb 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' existing = { + name: cosmosDBName +} + +module cosmosDbDNSZone 'privateDNS.bicep' = { + name: 'cosmosDbDNSZone' + params: { + zoneName: 'privatelink.documents.azure.com' + appName: appName + environment: environment + name: 'cosmosDb' + vNetId: virtualNetworkId + tags: tags + } +} + +module cosmosDbPE 'privateEndpoint.bicep' = { + name: 'cosmosDbPE' + dependsOn: [ + cosmosDb + cosmosDbDNSZone + ] + params: { + name: 'cosmosDb' + location: location + appName: appName + environment: environment + serviceResourceID: cosmosDb.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'sql' + ] + privateDnsZoneIds: [ + cosmosDbDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} +//========================================================= +// azure container registry +//========================================================= +resource acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = { + name: acrName +} + +module acrDNSZone 'privateDNS.bicep' = { + name: 'acrDNSZone' + params: { + zoneName: 'privatelink.azurecr.io' + appName: appName + environment: environment + name: 'acr' + vNetId: virtualNetworkId + tags: tags + } +} + +module acrPE 'privateEndpoint.bicep' = { + name: 'acrPE' + dependsOn: [ + acr + acrDNSZone + ] + params: { + name: 'acr' + location: location + appName: appName + environment: environment + serviceResourceID: acr.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'registry' + ] + privateDnsZoneIds: [ + acrDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} +//========================================================= +// search service +//========================================================= +resource searchService 'Microsoft.Search/searchServices@2025-05-01' existing = { + name: searchServiceName +} + +module searchServiceDNSZone 'privateDNS.bicep' = { + name: 'searchServiceDNSZone' + params: { + zoneName: 'privatelink.search.windows.net' + appName: appName + environment: environment + name: 'searchService' + vNetId: virtualNetworkId + tags: tags + } +} + +module searchServicePE 'privateEndpoint.bicep' = { + name: 'searchServicePE' + dependsOn: [ + searchService + searchServiceDNSZone + ] + params: { + name: 'searchService' + location: location + appName: appName + environment: environment + serviceResourceID: searchService.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'searchService' + ] + privateDnsZoneIds: [ + searchServiceDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} +//========================================================= +// document intelligence service +//========================================================= +resource docIntelService 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = if (docIntelName != '') { + name: docIntelName +} + +module docIntelDNSZone 'privateDNS.bicep' = { + name: 'docIntelDNSZone' + params: { + zoneName: 'privatelink.cognitiveservices.azure.com' + appName: appName + environment: environment + name: 'docIntelService' + vNetId: virtualNetworkId + tags: tags + } +} + +module docIntelPE 'privateEndpoint.bicep' = { + name: 'docIntelPE' + dependsOn: [ + docIntelService + docIntelDNSZone + ] + params: { + name: 'docIntelService' + location: location + appName: appName + environment: environment + serviceResourceID: docIntelService.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'account' + ] + privateDnsZoneIds: [ + docIntelDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} +//========================================================= +// storage account +//========================================================= +resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing = { + name: storageAccountName +} + +module storageAccountDNSZone 'privateDNS.bicep' = { + name: 'storageAccountDNSZone' + params: { + zoneName: 'privatelink.blob.core.windows.net' + appName: appName + environment: environment + name: 'storage' + vNetId: virtualNetworkId + tags: tags + } +} + +module storageAccountPE 'privateEndpoint.bicep' = { + name: 'storageAccountPE' + dependsOn: [ + storageAccount + storageAccountDNSZone + ] + params: { + name: 'storageAccount' + location: location + appName: appName + environment: environment + serviceResourceID: storageAccount.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'blob' + ] + privateDnsZoneIds: [ + storageAccountDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} +//========================================================= +resource openAiService 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = { + name: openAIName +} + +module openAiDNSZone 'privateDNS.bicep' = { + name: 'openAiDNSZone' + params: { + zoneName: 'privatelink.openai.azure.com' + appName: appName + environment: environment + name: 'openAiService' + vNetId: virtualNetworkId + tags: tags + } +} + +module openAiPE 'privateEndpoint.bicep' = { + name: 'openAiPE' + dependsOn: [ + openAiService + openAiDNSZone + ] + params: { + name: 'openAiService' + location: location + appName: appName + environment: environment + serviceResourceID: openAiService.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'account' + ] + privateDnsZoneIds: [ + openAiDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} +//========================================================= +// web app +//========================================================= +resource webApp 'Microsoft.Web/sites@2022-03-01' existing = { + name: webAppName +} + +module webAppDNSZone 'privateDNS.bicep' = { + name: 'webAppDNSZone' + params: { + zoneName: 'privatelink.azurewebsites.net' + appName: appName + environment: environment + name: 'webApp' + vNetId: virtualNetworkId + tags: tags + } +} + +module webAppPE 'privateEndpoint.bicep' = { + name: 'webAppPE' + dependsOn: [ + webApp + webAppDNSZone + ] + params: { + name: 'webApp' + location: location + appName: appName + environment: environment + serviceResourceID: webApp.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'sites' + ] + privateDnsZoneIds: [ + docIntelDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} +//========================================================= +// content safety service - Optional +//========================================================= +resource contentSafety 'Microsoft.CognitiveServices/accounts@2025-06-01' existing = if (contentSafetyName != '') { + name: contentSafetyName +} + +module contentSafetyPE 'privateEndpoint.bicep' = if (contentSafetyName != '') { + name: 'contentSafetyPE' + dependsOn: [ + contentSafety + ] + params: { + name: 'contentSafety' + location: location + appName: appName + environment: environment + serviceResourceID: contentSafety.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'account' + ] + privateDnsZoneIds: [ + docIntelDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} +//========================================================= +// speech service - Optional +//========================================================= +resource speechService 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = if (speechServiceName != '') { + name: speechServiceName +} + +module speechServicePE 'privateEndpoint.bicep' = if (speechServiceName != '') { + name: 'speechServicePE' + dependsOn: [ + speechService + ] + params: { + name: 'speechService' + location: location + appName: appName + environment: environment + serviceResourceID: speechService.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'account' + ] + privateDnsZoneIds: [ + docIntelDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} +//========================================================= +// video indexer service - Optional +//========================================================= +resource videoIndexerService 'Microsoft.VideoIndexer/accounts@2025-04-01' existing = if (videoIndexerName != '') { + name: videoIndexerName +} + +module videoIndexerPE 'privateEndpoint.bicep' = if (videoIndexerName != '') { + name: 'videoIndexerPE' + dependsOn: [ + videoIndexerService + ] + params: { + name: 'videoIndexerService' + location: location + appName: appName + environment: environment + serviceResourceID: videoIndexerService.id + subnetId: privateEndpointSubnetId + groupIDs: [ + 'account' + ] + privateDnsZoneIds: [ + docIntelDNSZone.outputs.privateDnsZoneId + ] + tags: tags + } +} diff --git a/deployers/bicep/modules/search.bicep b/deployers/bicep/modules/search.bicep index 7fb38201..4f4a5efd 100644 --- a/deployers/bicep/modules/search.bicep +++ b/deployers/bicep/modules/search.bicep @@ -12,36 +12,13 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool -param vNetId string = '' -param privateEndpointSubnetId string = '' +param enablePrivateNetworking bool // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } -// create private endpoint for azure search service if private endpoint subnet id is provided -module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { - name: toLower('${appName}-${environment}-search-pe') - dependsOn:[ - searchService - ] - params: { - name: 'search' - location: location - appName: appName - environment: environment - privateDNSZoneName: 'privatelink.search.windows.net' - vNetId: vNetId - subnetId: privateEndpointSubnetId - serviceResourceID: searchService.id - groupIDs: [ - 'searchService' - ] - tags: tags - } -} - // search service resource resource searchService 'Microsoft.Search/searchServices@2025-05-01' = { name: toLower('${appName}-${environment}-search') @@ -52,7 +29,7 @@ resource searchService 'Microsoft.Search/searchServices@2025-05-01' = { properties: { #disable-next-line BCP036 // template is incorrect hostingMode: 'default' - publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' replicaCount: 1 partitionCount: 1 authOptions: { diff --git a/deployers/bicep/modules/speechService.bicep b/deployers/bicep/modules/speechService.bicep index 17391ac7..d7f1de1f 100644 --- a/deployers/bicep/modules/speechService.bicep +++ b/deployers/bicep/modules/speechService.bicep @@ -12,6 +12,8 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool +param enablePrivateNetworking bool + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' @@ -29,7 +31,7 @@ resource speechService 'Microsoft.CognitiveServices/accounts@2024-10-01' = { type: 'SystemAssigned' } properties: { - publicNetworkAccess: 'Enabled' + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' customSubDomainName: toLower('${appName}-${environment}-speech') } tags: tags diff --git a/deployers/bicep/modules/storageAccount.bicep b/deployers/bicep/modules/storageAccount.bicep index 6d621438..cf38ac52 100644 --- a/deployers/bicep/modules/storageAccount.bicep +++ b/deployers/bicep/modules/storageAccount.bicep @@ -12,36 +12,13 @@ param keyVault string param authenticationType string param configureApplicationPermissions bool -param vNetId string = '' -param privateEndpointSubnetId string = '' +param enablePrivateNetworking bool // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' } -// create private endpoint for azure storage if private endpoint subnet id is provided -module privateEndpoint 'privateEndpoint.bicep' = if (privateEndpointSubnetId != '') { - name: toLower('${appName}-${environment}-storage-pe') - dependsOn:[ - storageAccount - ] - params: { - name: 'storage' - location: location - appName: appName - environment: environment - privateDNSZoneName: 'privatelink.blob.core.windows.net' - vNetId: vNetId - subnetId: privateEndpointSubnetId - serviceResourceID: storageAccount.id - groupIDs: [ - 'blob' - ] - tags: tags - } -} - // storage account resource resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { #disable-next-line BCP334 //Name length managed by Bicep parameters. @@ -53,7 +30,7 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { kind: 'StorageV2' properties: { - publicNetworkAccess: privateEndpointSubnetId != '' ? 'Disabled' : 'Enabled' + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' accessTier: 'Hot' allowBlobPublicAccess: false allowSharedKeyAccess: true diff --git a/deployers/bicep/modules/videoIndexer.bicep b/deployers/bicep/modules/videoIndexer.bicep index 8f41544a..786d7108 100644 --- a/deployers/bicep/modules/videoIndexer.bicep +++ b/deployers/bicep/modules/videoIndexer.bicep @@ -11,6 +11,8 @@ param logAnalyticsId string param storageAccount string param openAiServiceName string +param enablePrivateNetworking bool + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' @@ -33,7 +35,7 @@ resource videoIndexerService 'Microsoft.VideoIndexer/accounts@2025-04-01' = { type: 'SystemAssigned' } properties: { - publicNetworkAccess: 'Enabled' + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' storageServices: { resourceId: storage.id } diff --git a/deployers/bicep/modules/virtualNetwork.bicep b/deployers/bicep/modules/virtualNetwork.bicep index 33c9a83a..03952410 100644 --- a/deployers/bicep/modules/virtualNetwork.bicep +++ b/deployers/bicep/modules/virtualNetwork.bicep @@ -33,13 +33,12 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' = { tags: tags } - - var subnetIds = [for subnet in subnetConfigs: resourceId('Microsoft.Network/virtualNetworks/subnets', vNetName, subnet.name)] var subnetNames = [for subnet in subnetConfigs: subnet.name] var appServiceIntegrationSubnetIndex = indexOf(subnetNames, 'AppServiceIntegration') var privateEndpointIndex = indexOf(subnetNames, 'PrivateEndpoints') +// output results output vNetId string = virtualNetwork.id output privateNetworkSubnetId string = privateEndpointIndex == -1 ? '' : subnetIds[privateEndpointIndex] output appServiceSubnetId string = appServiceIntegrationSubnetIndex == -1 ? '' : subnetIds[appServiceIntegrationSubnetIndex] diff --git a/deployers/bicep/postconfig.py b/deployers/bicep/postconfig.py index 44406da2..216b5efd 100644 --- a/deployers/bicep/postconfig.py +++ b/deployers/bicep/postconfig.py @@ -133,7 +133,7 @@ item["enable_content_safety"] = True item["content_safety_endpoint"] = var_contentSafetyEndpoint item["content_safety_authentication_type"] = var_authenticationType -if keyvault_client: +if keyvault_client and var_authenticationType == "key": try: contentSafety_key_secret = keyvault_client.get_secret( "content-safety-key") @@ -149,7 +149,7 @@ # Search and Extract > Azure AI Search item["azure_ai_search_endpoint"] = var_searchServiceEndpoint item["azure_ai_search_authentication_type"] = var_authenticationType -if keyvault_client: +if keyvault_client and var_authenticationType == "key": try: search_key_secret = keyvault_client.get_secret("search-service-key") item["azure_ai_search_key"] = search_key_secret.value @@ -161,7 +161,7 @@ # Search and Extract > Azure Document Intelligence item["azure_document_intelligence_endpoint"] = var_documentIntelligenceServiceEndpoint item["azure_document_intelligence_authentication_type"] = var_authenticationType -if keyvault_client: +if keyvault_client and var_authenticationType == "key": try: documentIntelligence_key_secret = keyvault_client.get_secret( "document-intelligence-key") @@ -186,7 +186,7 @@ item["enable_audio_file_support"] = True item["speech_service_endpoint"] = var_speechServiceEndpoint item["speech_service_location"] = var_speechServiceLocation -if keyvault_client: +if keyvault_client and var_authenticationType == "key": try: speech_key_secret = keyvault_client.get_secret("speech-service-key") item["speech_service_key"] = speech_key_secret.value From 30c34afaf973a6e46ea44ae3d3d8059837c31299 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Wed, 14 Jan 2026 18:11:13 +0000 Subject: [PATCH 04/19] reformat content for ease of reading / maintenance --- deployers/azure.yaml | 28 +++++++++++-------- deployers/bicep/main.bicep | 57 ++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/deployers/azure.yaml b/deployers/azure.yaml index 0832cf6b..7e2ad824 100644 --- a/deployers/azure.yaml +++ b/deployers/azure.yaml @@ -4,8 +4,18 @@ metadata: infra: provider: bicep path: bicep +services: + web: + project: ../application/single_app + language: python + host: appservice + docker: + context: ../../ + dockerfile: application/single_app/Dockerfile hooks: postprovision: + # this is run after the infrastructure has been provisioned but before services are deployed. + # primary use is to configure application settings and permissions posix: shell: sh run: | @@ -31,7 +41,7 @@ hooks: export var_deploymentLocation=${var_deploymentLocation} export var_videoIndexerAccountId=${var_videoIndexerAccountId} export var_speechServiceEndpoint=${var_speechServiceEndpoint} - export var_vNetId=${var_vNetId} + # export var_vNetId=${var_vNetId} # Execute post-configuration script if enabled if [ "${var_configureApplication}" = "true" ]; then @@ -47,8 +57,9 @@ hooks: else echo "Skipping post-deployment configuration (var_configureApplication is not true)" fi - predeploy: + # this is run after infrastructure and postprovisioning but before service deployment + # primary use is to build and push container images posix: shell: sh run: | @@ -67,8 +78,10 @@ hooks: docker push ${var_containerRegistry}/${var_imageName}:${timestamp} echo "Restarting web service..." az webapp start --name ${var_webService} --resource-group ${var_rgName} - + postup: + # this is the final step to run after everything else is done + # primary use is disable public network access if private endpoints are used posix: shell: sh run: | @@ -96,12 +109,3 @@ hooks: fi echo "Deployment completed successfully." - -services: - web: - project: ../application/single_app - language: python - host: appservice - docker: - context: ../../ - dockerfile: application/single_app/Dockerfile \ No newline at end of file diff --git a/deployers/bicep/main.bicep b/deployers/bicep/main.bicep index 82b1d3af..b3db6ace 100644 --- a/deployers/bicep/main.bicep +++ b/deployers/bicep/main.bicep @@ -618,48 +618,45 @@ module privateNetworking 'modules/privateNetworking.bicep' = if (enablePrivateNe //========================================================= // output values //========================================================= -// output required for both predeploy and postprovision scripts in azure.yaml -output var_rgName string = rgName -// output values required for predeploy script in azure.yaml -output var_webService string = appService.outputs.name -output var_imageName string = contains(imageName, ':') ? split(imageName, ':')[0] : imageName -output var_imageTag string = split(imageName, ':')[1] -output var_containerRegistry string = containerRegistry -output var_acrName string = toLower('${appName}${environment}acr') // output values required for postprovision script in azure.yaml +output var_acrName string = toLower('${appName}${environment}acr') +output var_authenticationType string = toLower(authenticationType) +output var_blobStorageEndpoint string = storageAccount.outputs.endpoint output var_configureApplication bool = configureApplicationPermissions -output var_keyVaultUri string = keyVault.outputs.keyVaultUri -output var_keyVaultName string = keyVault.outputs.keyVaultName -output var_cosmosDb_uri string = cosmosDB.outputs.cosmosDbUri +#disable-next-line BCP318 // expect one value to be null +output var_contentSafetyEndpoint string = deployContentSafety ? contentSafety.outputs.contentSafetyEndpoint : '' output var_cosmosDb_accountName string = cosmosDB.outputs.cosmosDbName -output var_subscriptionId string = subscription().subscriptionId -output var_authenticationType string = toLower(authenticationType) +output var_cosmosDb_uri string = cosmosDB.outputs.cosmosDbUri +output var_deploymentLocation string = rg.location +output var_documentIntelligenceServiceEndpoint string = docIntel.outputs.documentIntelligenceServiceEndpoint +output var_keyVaultName string = keyVault.outputs.keyVaultName +output var_keyVaultUri string = keyVault.outputs.keyVaultUri output var_openAIEndpoint string = openAI.outputs.openAIEndpoint -output var_openAIResourceGroup string = openAI.outputs.openAIResourceGroup //may be able to remove output var_openAIGPTModels array = gptModels +output var_openAIResourceGroup string = openAI.outputs.openAIResourceGroup //may be able to remove output var_openAIEmbeddingModels array = embeddingModels -output var_blobStorageEndpoint string = storageAccount.outputs.endpoint #disable-next-line BCP318 // expect one value to be null -output var_contentSafetyEndpoint string = deployContentSafety ? contentSafety.outputs.contentSafetyEndpoint : '' -output var_deploymentLocation string = rg.location +output var_redisCacheHostName string = deployRedisCache ? redisCache.outputs.redisCacheHostName : '' +output var_rgName string = rgName output var_searchServiceEndpoint string = searchService.outputs.searchServiceEndpoint -output var_documentIntelligenceServiceEndpoint string = docIntel.outputs.documentIntelligenceServiceEndpoint -output var_videoIndexerName string = deployVideoIndexerService #disable-next-line BCP318 // expect one value to be null - ? videoIndexerService.outputs.videoIndexerServiceName - : '' -output var_videoIndexerAccountId string = deployVideoIndexerService +output var_speechServiceEndpoint string = deploySpeechService ? speechService.outputs.speechServiceEndpoint : '' +output var_subscriptionId string = subscription().subscriptionId #disable-next-line BCP318 // expect one value to be null - ? videoIndexerService.outputs.videoIndexerAccountId - : '' +output var_videoIndexerAccountId string = deployVideoIndexerService ? videoIndexerService.outputs.videoIndexerAccountId : '' #disable-next-line BCP318 // expect one value to be null -output var_speechServiceEndpoint string = deploySpeechService ? speechService.outputs.speechServiceEndpoint : '' +output var_videoIndexerName string = deployVideoIndexerService ? videoIndexerService.outputs.videoIndexerServiceName : '' +// #disable-next-line BCP318 // expect one value to be null +// output var_vNetId string = enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' + +// output values required for predeploy script in azure.yaml +output var_containerRegistry string = containerRegistry +output var_imageName string = contains(imageName, ':') ? split(imageName, ':')[0] : imageName +output var_imageTag string = split(imageName, ':')[1] +output var_webService string = appService.outputs.name -//-------------------------------------------- +// output values required for postup script in azure.yaml output var_enablePrivateNetworking bool = enablePrivateNetworking -// #disable-next-line BCP318 // may not be configured if private networking is disabled -// output var_vNetId string = enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' -// #disable-next-line BCP318 // may not be configured if private networking is disabled -// output var_privateNetworkSubnetId string = enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' + From 0b7221a8abf3be5456abe2aa621974d5c74992da Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Wed, 14 Jan 2026 18:11:36 +0000 Subject: [PATCH 05/19] found redis configs were missing --- deployers/bicep/modules/redisCache.bicep | 1 + 1 file changed, 1 insertion(+) diff --git a/deployers/bicep/modules/redisCache.bicep b/deployers/bicep/modules/redisCache.bicep index faab2e23..e782318a 100644 --- a/deployers/bicep/modules/redisCache.bicep +++ b/deployers/bicep/modules/redisCache.bicep @@ -62,3 +62,4 @@ module redisCacheSecret 'keyVault-Secrets.bicep' = if (authenticationType == 'ke } output redisCacheName string = redisCache.name +output redisCacheHostName string = redisCache.properties.hostName From 830aebd4dc0614a1f0a00566403d3a0126d41510 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 12:23:00 +0000 Subject: [PATCH 06/19] updates to add in missing configurations for redis cache --- deployers/azure.yaml | 1 + deployers/bicep/main.bicep | 8 ++------ deployers/bicep/modules/setPermissions.bicep | 19 +++++++++++++++++++ deployers/bicep/postconfig.py | 18 ++++++++++++++++-- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/deployers/azure.yaml b/deployers/azure.yaml index 7e2ad824..b2fe01bd 100644 --- a/deployers/azure.yaml +++ b/deployers/azure.yaml @@ -37,6 +37,7 @@ hooks: export var_contentSafetyEndpoint=${var_contentSafetyEndpoint} export var_searchServiceEndpoint=${var_searchServiceEndpoint} export var_documentIntelligenceServiceEndpoint=${var_documentIntelligenceServiceEndpoint} + export var_redisCacheHostName=${var_redisCacheHostName} export var_videoIndexerName=${var_videoIndexerName} export var_deploymentLocation=${var_deploymentLocation} export var_videoIndexerAccountId=${var_videoIndexerAccountId} diff --git a/deployers/bicep/main.bicep b/deployers/bicep/main.bicep index b3db6ace..b93554ba 100644 --- a/deployers/bicep/main.bicep +++ b/deployers/bicep/main.bicep @@ -497,7 +497,6 @@ module redisCache 'modules/redisCache.bicep' = if (deployRedisCache) { configureApplicationPermissions: configureApplicationPermissions //enablePrivateNetworking: enablePrivateNetworking - } } @@ -542,11 +541,6 @@ module videoIndexerService 'modules/videoIndexer.bicep' = if (deployVideoIndexer openAiServiceName: openAI.outputs.openAIName enablePrivateNetworking: enablePrivateNetworking - - // #disable-next-line BCP318 // expect one value to be null if private networking is disabled - // vNetId: enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' - // #disable-next-line BCP318 // expect one value to be null if private networking is disabled - // privateEndpointSubnetId: enablePrivateNetworking ? virtualNetwork.outputs.privateNetworkSubnetId : '' } } @@ -572,6 +566,8 @@ module setPermissions 'modules/setPermissions.bicep' = if (configureApplicationP #disable-next-line BCP318 // expect one value to be null speechServiceName: deploySpeechService ? speechService.outputs.speechServiceName : '' #disable-next-line BCP318 // expect one value to be null + redisCacheName: deployRedisCache ? redisCache.outputs.redisCacheName : '' + #disable-next-line BCP318 // expect one value to be null contentSafetyName: deployContentSafety ? contentSafety.outputs.contentSafetyName : '' #disable-next-line BCP318 // expect one value to be null videoIndexerName: deployVideoIndexerService ? videoIndexerService.outputs.videoIndexerServiceName : '' diff --git a/deployers/bicep/modules/setPermissions.bicep b/deployers/bicep/modules/setPermissions.bicep index 33564d8e..675c12b9 100644 --- a/deployers/bicep/modules/setPermissions.bicep +++ b/deployers/bicep/modules/setPermissions.bicep @@ -11,6 +11,7 @@ param docIntelName string param storageAccountName string param speechServiceName string param searchServiceName string +param redisCacheName string param contentSafetyName string param videoIndexerName string @@ -50,6 +51,10 @@ resource searchService 'Microsoft.Search/searchServices@2025-05-01' existing = { name: searchServiceName } +resource redisCache 'Microsoft.Cache/Redis@2024-11-01' existing = if (redisCacheName != '') { + name: redisCacheName +} + resource contentSafety 'Microsoft.CognitiveServices/accounts@2025-06-01' existing = if (contentSafetyName != '') { name: contentSafetyName } @@ -269,3 +274,17 @@ resource videoIndexerStorageCogServicesUserRole 'Microsoft.Authorization/roleAss principalType: 'ServicePrincipal' } } + +// grant the managed identity access to redis cache as a Redis Cache Contributor +resource redisCacheContributorRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (redisCacheName != '') { + name: guid(redisCache.id, webApp.id, 'redis-cache-contributor') + scope: redisCache + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'e0f68234-74aa-48ed-b826-c38b57376e17' + ) + principalId: webApp.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/deployers/bicep/postconfig.py b/deployers/bicep/postconfig.py index 216b5efd..5a12b56f 100644 --- a/deployers/bicep/postconfig.py +++ b/deployers/bicep/postconfig.py @@ -45,8 +45,8 @@ var_blobStorageEndpoint = os.getenv("var_blobStorageEndpoint") var_contentSafetyEndpoint = os.getenv("var_contentSafetyEndpoint") var_searchServiceEndpoint = os.getenv("var_searchServiceEndpoint") -var_documentIntelligenceServiceEndpoint = os.getenv( - "var_documentIntelligenceServiceEndpoint") +var_documentIntelligenceServiceEndpoint = os.getenv("var_documentIntelligenceServiceEndpoint") +var_redisCacheHostName = os.getenv("var_redisCacheHostName") var_videoIndexerName = os.getenv("var_videoIndexerName") var_videoIndexerLocation = os.getenv("var_deploymentLocation") var_videoIndexerAccountId = os.getenv("var_videoIndexerAccountId") @@ -143,6 +143,20 @@ print( f"Warning: Could not retrieve content-safety-key from Key Vault: {e}") +# Redis Cache Configuration +if var_redisCacheHostName and var_redisCacheHostName.strip(): + item["enable_redis_cache"] = True +item["redis_url"] = var_redisCacheHostName +item["redis_auth_type"] = var_authenticationType +if keyvault_client and var_authenticationType == "key": + try: + redis_key_secret = keyvault_client.get_secret("redis-cache-key") + item["redis_key"] = redis_key_secret.value + print("Retrieved redis cache key from Key Vault") + except Exception as e: + print( + f"Warning: Could not retrieve redis-cache-key from Key Vault: {e}") + # Safety > Conversation Archiving item["enable_conversation_archiving"] = True From 3a370c475d6c8d4c353dc93da5942112277955d5 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 12:23:47 +0000 Subject: [PATCH 07/19] handle zone names for gov cloud. Externalize zone names --- deployers/bicep/modules/privateDNSZones.json | 22 +++++++ .../bicep/modules/privateNetworking.bicep | 60 +++++++------------ 2 files changed, 43 insertions(+), 39 deletions(-) create mode 100644 deployers/bicep/modules/privateDNSZones.json diff --git a/deployers/bicep/modules/privateDNSZones.json b/deployers/bicep/modules/privateDNSZones.json new file mode 100644 index 00000000..66d22738 --- /dev/null +++ b/deployers/bicep/modules/privateDNSZones.json @@ -0,0 +1,22 @@ +{ + "azurecloud": { + "aisearch": "privatelink.search.windows.net", + "blobStorage": "privatelink.blob.core.windows.net", + "cognitiveServices": "privatelink.cognitiveservices.azure.com", + "containerRegistry": "privatelink.azurecr.io", + "cosmosDb": "privatelink.documents.azure.com", + "keyVault": "privatelink.vaultcore.azure.net", + "openAi": "privatelink.openai.azure.com", + "webSites": "privatelink.azurewebsites.net" + }, + "azureusgovernment": { + "aisearch": "privatelink.search.azure.us", + "blobStorage": "privatelink.blob.core.usgovcloudapi.net", + "cognitiveServices": "privatelink.cognitiveservices.azure.us", + "containerRegistry": "privatelink.azurecr.us", + "cosmosDb": "privatelink.documents.azure.us", + "keyVault": "privatelink.vaultcore.azure.us", + "openAi": "privatelink.openai.azure.us", + "webSites": "privatelink.azurewebsites.us" + } +} \ No newline at end of file diff --git a/deployers/bicep/modules/privateNetworking.bicep b/deployers/bicep/modules/privateNetworking.bicep index 1c640a90..abf5ac39 100644 --- a/deployers/bicep/modules/privateNetworking.bicep +++ b/deployers/bicep/modules/privateNetworking.bicep @@ -23,37 +23,19 @@ param contentSafetyName string param speechServiceName string param videoIndexerName string -//var vNetName = '${appName}-${environment}-vnet' - -//========================================================= -// Create Virtual Network if private networking is enabled //========================================================= -// module virtualNetwork 'virtualNetwork.bicep' = { -// name: 'virtualNetwork' -// params: { -// location: location -// vNetName: vNetName -// addressSpaces: ['10.0.0.0/21'] -// subnetConfigs: [ -// { -// name: 'AppServiceIntegration' -// addressPrefix: '10.0.0.0/24' -// enablePrivateEndpointNetworkPolicies: true -// enablePrivateLinkServiceNetworkPolicies: true -// } -// { -// name: 'PrivateEndpoints' // this subnet name must be present if private endpoints are to be used -// addressPrefix: '10.0.2.0/24' -// enablePrivateEndpointNetworkPolicies: true -// enablePrivateLinkServiceNetworkPolicies: true -// } -// ] -// tags: tags -// } -// } -// resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' existing = { -// name: vNetName -// } +// privateDNSZoneNames +var cloudName = toLower(az.environment().name) +var privateDnsZoneData = loadJsonContent('privateDNSZones.json') + +var aiSearchDnsZoneName = privateDnsZoneData[cloudName].aisearch +var blobStorageDnsZoneName = privateDnsZoneData[cloudName].blobStorage +var cognitiveServicesDnsZoneName = privateDnsZoneData[cloudName].cognitiveServices +var containerRegistryDnsZoneName = privateDnsZoneData[cloudName].containerRegistry +var cosmosDbDnsZoneName = privateDnsZoneData[cloudName].cosmosDb +var keyVaultDnsZoneName = privateDnsZoneData[cloudName].keyVault +var openAiDnsZoneName = privateDnsZoneData[cloudName].openAi +var webSitesDnsZoneName = privateDnsZoneData[cloudName].webSites //========================================================= // key vault @@ -65,7 +47,7 @@ resource kv 'Microsoft.KeyVault/vaults@2025-05-01' existing = { module keyVaultDNSZone 'privateDNS.bicep' = { name: 'keyVaultDNSZone' params: { - zoneName: 'privatelink.vaultcore.azure.net' + zoneName: keyVaultDnsZoneName appName: appName environment: environment name: 'kv' @@ -106,7 +88,7 @@ resource cosmosDb 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' existing = module cosmosDbDNSZone 'privateDNS.bicep' = { name: 'cosmosDbDNSZone' params: { - zoneName: 'privatelink.documents.azure.com' + zoneName: cosmosDbDnsZoneName appName: appName environment: environment name: 'cosmosDb' @@ -147,7 +129,7 @@ resource acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = { module acrDNSZone 'privateDNS.bicep' = { name: 'acrDNSZone' params: { - zoneName: 'privatelink.azurecr.io' + zoneName: containerRegistryDnsZoneName appName: appName environment: environment name: 'acr' @@ -188,7 +170,7 @@ resource searchService 'Microsoft.Search/searchServices@2025-05-01' existing = { module searchServiceDNSZone 'privateDNS.bicep' = { name: 'searchServiceDNSZone' params: { - zoneName: 'privatelink.search.windows.net' + zoneName: aiSearchDnsZoneName appName: appName environment: environment name: 'searchService' @@ -229,7 +211,7 @@ resource docIntelService 'Microsoft.CognitiveServices/accounts@2024-10-01' exist module docIntelDNSZone 'privateDNS.bicep' = { name: 'docIntelDNSZone' params: { - zoneName: 'privatelink.cognitiveservices.azure.com' + zoneName: cognitiveServicesDnsZoneName appName: appName environment: environment name: 'docIntelService' @@ -270,7 +252,7 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing module storageAccountDNSZone 'privateDNS.bicep' = { name: 'storageAccountDNSZone' params: { - zoneName: 'privatelink.blob.core.windows.net' + zoneName: blobStorageDnsZoneName appName: appName environment: environment name: 'storage' @@ -309,7 +291,7 @@ resource openAiService 'Microsoft.CognitiveServices/accounts@2024-10-01' existin module openAiDNSZone 'privateDNS.bicep' = { name: 'openAiDNSZone' params: { - zoneName: 'privatelink.openai.azure.com' + zoneName: openAiDnsZoneName appName: appName environment: environment name: 'openAiService' @@ -350,7 +332,7 @@ resource webApp 'Microsoft.Web/sites@2022-03-01' existing = { module webAppDNSZone 'privateDNS.bicep' = { name: 'webAppDNSZone' params: { - zoneName: 'privatelink.azurewebsites.net' + zoneName: webSitesDnsZoneName appName: appName environment: environment name: 'webApp' @@ -376,7 +358,7 @@ module webAppPE 'privateEndpoint.bicep' = { 'sites' ] privateDnsZoneIds: [ - docIntelDNSZone.outputs.privateDnsZoneId + webAppDNSZone.outputs.privateDnsZoneId ] tags: tags } From 2fbb5db7bed634ad9c7cc3b543f3a4bab9640707 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 15:40:30 +0000 Subject: [PATCH 08/19] fix: rename module for storing Cosmos DB secrets to improve clarity --- deployers/bicep/modules/cosmosDb.bicep | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployers/bicep/modules/cosmosDb.bicep b/deployers/bicep/modules/cosmosDb.bicep index 6aa89d50..abb6f674 100644 --- a/deployers/bicep/modules/cosmosDb.bicep +++ b/deployers/bicep/modules/cosmosDb.bicep @@ -92,8 +92,8 @@ resource cosmosDiagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-pre //========================================================= // store cosmos db keys in key vault if using key authentication and configure app permissions = true //========================================================= -module storeEnterpriseAppSecret 'keyVault-Secrets.bicep' = if (authenticationType == 'key' && configureApplicationPermissions) { - name: 'storeEnterpriseAppSecret' +module storeCosmosDbSecret 'keyVault-Secrets.bicep' = if (authenticationType == 'key' && configureApplicationPermissions) { + name: 'storeCosmosDbSecret' params: { keyVaultName: keyVault secretName: 'cosmos-db-key' From 30831f5af2aa36d51d7bf673627c35ba30fd7f46 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 15:40:54 +0000 Subject: [PATCH 09/19] fix: update OpenID issuer for Azure US Government environment --- deployers/bicep/modules/appService.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployers/bicep/modules/appService.bicep b/deployers/bicep/modules/appService.bicep index ca7601c9..5d9aa471 100644 --- a/deployers/bicep/modules/appService.bicep +++ b/deployers/bicep/modules/appService.bicep @@ -205,7 +205,7 @@ resource authSettings 'Microsoft.Web/sites/config@2022-03-01' = { azureActiveDirectory: { enabled: true registration: { - openIdIssuer: 'https://sts.windows.net/${tenant().tenantId}/' + openIdIssuer: azurePlatform == 'AzureUSGovernment' ? 'https://login.microsoftonline.us/${tenant().tenantId}/' : 'https://sts.windows.net/${tenant().tenantId}/' clientId: enterpriseAppClientId clientSecretSettingName: 'MICROSOFT_PROVIDER_AUTHENTICATION_SECRET' } From 97cea68acfaef1718267e68d5a1cf306cbe38d20 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 15:42:00 +0000 Subject: [PATCH 10/19] fix: remove deprecated Bicep parameter file --- deployers/bicep/params/scjulie1.bicepparam | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 deployers/bicep/params/scjulie1.bicepparam diff --git a/deployers/bicep/params/scjulie1.bicepparam b/deployers/bicep/params/scjulie1.bicepparam deleted file mode 100644 index 6b115dc3..00000000 --- a/deployers/bicep/params/scjulie1.bicepparam +++ /dev/null @@ -1,18 +0,0 @@ -using '../main.bicep' - -param azurePlatform = 'AzureUSGovernment' // or 'AzureCloud' -param tenantId = '' // Provide via --parameters or a secure way -param location = 'usgovvirginia' // or your preferred region -param resourceOwnerId = 'johndoe@domain.com' -param environment = 'sbx' -param baseName = 'julie1' -param acrName = 'acr8000' -param acrResourceGroupName = 'sc-emma1-sbx1-rg' // RG of your ACR -param imageName = 'simple-chat:2025-05-15_7' // Be specific with tags -param useExistingOpenAiInstance = true -param existingAzureOpenAiResourceName = 'gregazureopenai1' // if useExistingOpenAiInstance is true -param existingAzureOpenAiResourceGroupName = 'azureopenairg' // if useExistingOpenAiInstance is true -param appRegistrationClientId = 'a9acf8e2-441d-4aca-84f6-a83b3e820644' // scbingo1-ar -param appRegistrationClientSecret = '' // Provide via --parameters or a secure way -param appRegistrationSpObjectId = '364c5131-27b3-4ac1-bf95-0bd55106a109' -// Other SKUs can be overridden if needed From 7b5895ef17877fa7be84eef05a51f763bfde5506 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 17:04:49 +0000 Subject: [PATCH 11/19] feat: add roles for Control Center administration and dashboard access --- deployers/azurecli/appRegistrationRoles.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/deployers/azurecli/appRegistrationRoles.json b/deployers/azurecli/appRegistrationRoles.json index 5fb198cd..233a5b98 100644 --- a/deployers/azurecli/appRegistrationRoles.json +++ b/deployers/azurecli/appRegistrationRoles.json @@ -46,5 +46,22 @@ "id": "b0288440-5195-4264-9a33-cf9a4635d634", "isEnabled": true, "value": "ExternalApi" + }, + { + "allowedMemberTypes": [ "User" ], + "description": "Full administrative access to Control Center features", + "displayName": "Control Center Admin", + "id": "fad9b386-9392-4f15-b6df-6b47d8f1e75c", + "isEnabled": true, + "value": "ControlCenterAdmin" + }, + { + "allowedMemberTypes": [ "User" ], + "description": "Read-only access to Control Center dashboard and metrics", + "displayName": "Control Center Dashboard Reader", + "id": "6399b062-9114-49ec-a291-c445a0b2b33e", + "isEnabled": true, + "value": "ControlCenterDashboardReader" } + ] From 41d7ff9bd26c9fbda1bdcb62d6e12082f5ab7398 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 21:06:16 +0000 Subject: [PATCH 12/19] refactor: enhance post-provisioning and pre-deployment scripts with detailed logging and error handling --- deployers/azure.yaml | 241 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 199 insertions(+), 42 deletions(-) diff --git a/deployers/azure.yaml b/deployers/azure.yaml index b2fe01bd..2fb2c025 100644 --- a/deployers/azure.yaml +++ b/deployers/azure.yaml @@ -19,6 +19,12 @@ hooks: posix: shell: sh run: | + set -e + + echo "========================================" + echo "POST-PROVISION: Starting configuration" + echo "========================================" + # Set up variables export var_acrName=${var_acrname} export var_configureApplication=${var_configureApplication} @@ -42,71 +48,222 @@ hooks: export var_deploymentLocation=${var_deploymentLocation} export var_videoIndexerAccountId=${var_videoIndexerAccountId} export var_speechServiceEndpoint=${var_speechServiceEndpoint} - # export var_vNetId=${var_vNetId} # Execute post-configuration script if enabled if [ "${var_configureApplication}" = "true" ]; then - echo "Grant permissions to CosmosDB for post deployment steps..." - bash ./bicep/cosmosDb-postDeployPerms.sh - echo "Running post-deployment configuration..." - python3 -m pip install --user -r ./bicep/requirements.txt - python3 ./bicep/postconfig.py - echo "Post-deployment configuration completed." - echo "Restarting web service to apply new settings..." - az webapp restart --name ${var_webService} --resource-group ${var_rgName} - echo "Web service restarted." + echo "" + echo "[1/4] Granting permissions to CosmosDB..." + if bash ./bicep/cosmosDb-postDeployPerms.sh; then + echo "✓ CosmosDB permissions granted successfully" + else + echo "✗ ERROR: Failed to grant CosmosDB permissions" >&2 + exit 1 + fi + + echo "" + echo "[2/4] Installing Python dependencies..." + if python3 -m pip install --user -r ./bicep/requirements.txt > /dev/null 2>&1; then + echo "✓ Dependencies installed successfully" + else + echo "✗ ERROR: Failed to install Python dependencies" >&2 + exit 1 + fi + + echo "" + echo "[3/4] Running post-deployment configuration..." + if python3 ./bicep/postconfig.py; then + echo "✓ Post-deployment configuration completed" + else + echo "✗ ERROR: Post-deployment configuration failed" >&2 + exit 1 + fi + + echo "" + echo "[4/4] Restarting web service to apply settings..." + if az webapp restart --name ${var_webService} --resource-group ${var_rgName}; then + echo "✓ Web service restarted successfully" + else + echo "✗ ERROR: Failed to restart web service" >&2 + exit 1 + fi + + echo "" + echo "========================================" + echo "POST-PROVISION: Completed successfully" + echo "========================================" else - echo "Skipping post-deployment configuration (var_configureApplication is not true)" + echo "" + echo "ℹ Skipping post-deployment configuration (var_configureApplication is not true)" + echo "" + echo "========================================" + echo "POST-PROVISION: Completed (skipped)" + echo "========================================" fi + predeploy: # this is run after infrastructure and postprovisioning but before service deployment # primary use is to build and push container images posix: shell: sh run: | - # Build and push Docker image to ACR + set -e + + # Error handling function + cleanup_on_error() { + local exit_code=$? + echo "" + echo "✗ ERROR: Deployment failed at step: $1" >&2 + echo "Attempting to restart web service..." >&2 + az webapp start --name ${var_webService} --resource-group ${var_rgName} 2>/dev/null || true + exit ${exit_code} + } + + echo "========================================" + echo "PRE-DEPLOY: Building and pushing image" + echo "========================================" + cd .. timestamp="$(date +"%Y%m%d-%H%M%S")" - echo "Stopping web service prior to deployment..." - az webapp stop --name ${var_webService} --resource-group ${var_rgName} - echo "Building Docker image..." - docker build -f application/single_app/Dockerfile -t ${var_containerRegistry}/${var_imageName}:${timestamp} . - docker tag ${var_containerRegistry}/${var_imageName}:${timestamp} ${var_containerRegistry}/${var_imageName}:latest - echo "Logging in to ACR..." - az acr login --name ${var_acrName} - echo "Pushing image to ACR..." - docker push ${var_containerRegistry}/${var_imageName}:latest - docker push ${var_containerRegistry}/${var_imageName}:${timestamp} - echo "Restarting web service..." - az webapp start --name ${var_webService} --resource-group ${var_rgName} - + echo "" + echo "Deployment timestamp: ${timestamp}" + echo "Image: ${var_containerRegistry}/${var_imageName}:${timestamp}" + + echo "" + echo "[1/6] Stopping web service..." + if az webapp stop --name ${var_webService} --resource-group ${var_rgName}; then + echo "✓ Web service stopped successfully" + else + echo "✗ ERROR: Failed to stop web service" >&2 + exit 1 + fi + + echo "" + echo "[2/6] Building Docker image..." + echo "Context: $(pwd)" + echo "Dockerfile: application/single_app/Dockerfile" + if docker build -f application/single_app/Dockerfile \ + -t ${var_containerRegistry}/${var_imageName}:${timestamp} . ; then + echo "✓ Docker image built successfully" + else + cleanup_on_error "Docker build" + fi + + echo "" + echo "[3/6] Tagging image as latest..." + if docker tag ${var_containerRegistry}/${var_imageName}:${timestamp} \ + ${var_containerRegistry}/${var_imageName}:latest ; then + echo "✓ Image tagged successfully" + else + cleanup_on_error "Docker tag" + fi + + echo "" + echo "[4/6] Logging in to ACR (${var_acrName})..." + if az acr login --name ${var_acrName}; then + echo "✓ ACR login successful" + else + cleanup_on_error "ACR login" + fi + + echo "" + echo "[5/6] Pushing images to ACR..." + echo " → Pushing latest tag..." + if docker push ${var_containerRegistry}/${var_imageName}:latest; then + echo " ✓ Latest tag pushed successfully" + else + cleanup_on_error "Docker push (latest)" + fi + + echo " → Pushing timestamped tag..." + if docker push ${var_containerRegistry}/${var_imageName}:${timestamp}; then + echo " ✓ Timestamped tag pushed successfully" + else + cleanup_on_error "Docker push (timestamp)" + fi + + echo "" + echo "[6/6] Restarting web service..." + if az webapp start --name ${var_webService} --resource-group ${var_rgName}; then + echo "✓ Web service restarted successfully" + else + echo "✗ ERROR: Failed to restart web service" >&2 + exit 1 + fi + + echo "" + echo "========================================" + echo "PRE-DEPLOY: Completed successfully" + echo "========================================" + postup: # this is the final step to run after everything else is done # primary use is disable public network access if private endpoints are used posix: shell: sh run: | - # If using private endpoints, disable cosmos and key vault public interface + set -e + + echo "========================================" + echo "POST-UP: Final configuration" + echo "========================================" + if [ "${var_enablePrivateNetworking}" = "true" ]; then - echo "Disabling public network access for CosmosDB..." - - echo "Disabling public network access for CosmosDB..." - az cosmosdb update --name ${var_cosmosDb_accountName} --resource-group ${var_rgName} --public-network-access Disabled - echo "Public network access for CosmosDB disabled." + echo "" + echo "Configuring private networking..." - echo "Disabling public network access for Key Vault..." - az keyvault update --name ${var_keyVaultName} --resource-group ${var_rgName} --public-network-access Disabled - echo "Public network access for Key Vault disabled." + echo "" + echo "[1/3] Disabling public network access for CosmosDB..." + if az cosmosdb update --name ${var_cosmosDb_accountName} \ + --resource-group ${var_rgName} \ + --public-network-access Disabled > /dev/null; then + echo "✓ CosmosDB public access disabled" + else + echo "✗ ERROR: Failed to disable CosmosDB public access" >&2 + exit 1 + fi + + echo "" + echo "[2/3] Disabling public network access for Key Vault..." + if az keyvault update --name ${var_keyVaultName} \ + --resource-group ${var_rgName} \ + --public-network-access Disabled > /dev/null; then + echo "✓ Key Vault public access disabled" + else + echo "✗ ERROR: Failed to disable Key Vault public access" >&2 + exit 1 + fi - echo "Disabling public network access for Azure Container Registry..." - az acr update --name ${var_acrName} --resource-group ${var_rgName} --public-network-enabled false - echo "Public network access for Azure Container Registry disabled." + echo "" + echo "[3/3] Disabling public network access for Azure Container Registry..." + if az acr update --name ${var_acrName} \ + --resource-group ${var_rgName} \ + --public-network-enabled false > /dev/null; then + echo "✓ ACR public access disabled" + else + echo "✗ ERROR: Failed to disable ACR public access" >&2 + exit 1 + fi - # echo "Disabling public network access for Web Application..." - # az webapp update --name ${var_webService} --resource-group ${var_rgName} --public-network-access Disabled - # echo "Public network access for Web Application disabled." + # Uncomment when ready to disable web app public access + # echo "" + # echo "[4/4] Disabling public network access for Web Application..." + # if az webapp update --name ${var_webService} \ + # --resource-group ${var_rgName} \ + # --public-network-access Disabled; then + # echo "✓ Web Application public access disabled" + # else + # echo "✗ ERROR: Failed to disable Web Application public access" >&2 + # exit 1 + # fi + + echo "" + echo "✓ Private networking configured successfully" else - echo "Skipping disabling public network access for CosmosDB, Key Vault, and Azure Container Registry (var_enablePrivateNetworking is not true)" + echo "" + echo "ℹ Skipping private networking configuration (var_enablePrivateNetworking is not true)" fi - echo "Deployment completed successfully." + echo "" + echo "========================================" + echo "✓ DEPLOYMENT COMPLETED SUCCESSFULLY" + echo "========================================" \ No newline at end of file From 602b58332e486a205e6fe4ca1c03f085ea7c1c87 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 21:07:26 +0000 Subject: [PATCH 13/19] removed models that may cause issues in usgov, removed ipaddress from cosmos fw script, minor cleanup of unused values --- deployers/bicep/main.bicep | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/deployers/bicep/main.bicep b/deployers/bicep/main.bicep index b93554ba..b0fa9b80 100644 --- a/deployers/bicep/main.bicep +++ b/deployers/bicep/main.bicep @@ -82,24 +82,6 @@ param enablePrivateNetworking bool @description('''Array of GPT model names to deploy to the OpenAI resource.''') param gptModels array = [ - { - modelName: 'gpt-5-chat' - modelVersion: '2025-10-03' - skuName: 'GlobalStandard' - skuCapacity: 150 - } - { - modelName: 'gpt-5-nano' - modelVersion: '2025-08-07' - skuName: 'GlobalStandard' - skuCapacity: 250 - } - { - modelName: 'o4-mini' - modelVersion: '2025-04-16' - skuName: 'GlobalStandard' - skuCapacity: 150 - } { modelName: 'gpt-4.1' modelVersion: '2025-04-14' @@ -139,9 +121,7 @@ param allowedIpAddresses array = [ { ipAddressOrRange: '0.0.0.0' //--- required to allow Azure services to access cosmos db } - { - ipAddressOrRange: '173.66.57.199' //--- replace with your own IP address - } + //--- Add your IP address here during development ] //---------------- // optional services @@ -505,7 +485,6 @@ module redisCache 'modules/redisCache.bicep' = if (deployRedisCache) { //========================================================= module speechService 'modules/speechService.bicep' = if (deploySpeechService) { name: 'speechService' - dependsOn:[] scope: rg params: { location: location @@ -644,8 +623,6 @@ output var_subscriptionId string = subscription().subscriptionId output var_videoIndexerAccountId string = deployVideoIndexerService ? videoIndexerService.outputs.videoIndexerAccountId : '' #disable-next-line BCP318 // expect one value to be null output var_videoIndexerName string = deployVideoIndexerService ? videoIndexerService.outputs.videoIndexerServiceName : '' -// #disable-next-line BCP318 // expect one value to be null -// output var_vNetId string = enablePrivateNetworking ? virtualNetwork.outputs.vNetId : '' // output values required for predeploy script in azure.yaml output var_containerRegistry string = containerRegistry From 620eb8765cce281dd7da393c0fc11844db5ec784 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 21:08:04 +0000 Subject: [PATCH 14/19] fix: update condition for storing search service keys in key vault --- deployers/bicep/modules/search.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployers/bicep/modules/search.bicep b/deployers/bicep/modules/search.bicep index 4f4a5efd..c474ade5 100644 --- a/deployers/bicep/modules/search.bicep +++ b/deployers/bicep/modules/search.bicep @@ -56,7 +56,7 @@ resource searchDiagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-pre //========================================================= // store search Service keys in key vault if using key authentication and configure app permissions = true //========================================================= -module searchServiceSecret 'keyVault-Secrets.bicep' = if (configureApplicationPermissions) { +module searchServiceSecret 'keyVault-Secrets.bicep' = if (authenticationType == 'key' && configureApplicationPermissions) { name: 'storeSearchServiceSecret' params: { keyVaultName: keyVault From 1c15f8f34e3551a157f512beff409d782b00a4cb Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 21:08:21 +0000 Subject: [PATCH 15/19] feat: add environment and application permission parameters to deployment configuration --- deployers/bicep/main.parameters.json | 52 +++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/deployers/bicep/main.parameters.json b/deployers/bicep/main.parameters.json index e819bca9..dbd83d22 100644 --- a/deployers/bicep/main.parameters.json +++ b/deployers/bicep/main.parameters.json @@ -2,6 +2,7 @@ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { + "location": { "value": "${AZURE_LOCATION}" }, @@ -11,26 +12,61 @@ "appName": { "value": "${env_DEPLOYMENT_APPNAME}" }, + "environment": { + "value": "${environment}" + }, "azdEnvironmentName": { "value": "${AZURE_ENV_NAME}" }, - "specialTags": { - "value": { - "Project": "SimpleChat", - "SystemOwner": "Steve Carroll" - } + "imageName": { + "value": "${CONTAINER_IMAGE_NAME}" }, "enterpriseAppClientId": { "value": "${ENTERPRISE_APP_CLIENT_ID}" }, + "enterpriseAppServicePrincipalId": { + "value": "${ENTERPRISE_APP_SERVICE_PRINCIPAL_ID}" + }, "enterpriseAppClientSecret": { "value": "${ENTERPRISE_APP_CLIENT_SECRET}" }, - "imageName": { - "value": "${CONTAINER_IMAGE_NAME}" - }, "authenticationType": { "value": "${AUTHENTICATION_TYPE}" + }, + "configureApplicationPermissions": { + "value": "${CONFIGURE_APPLICATION_PERMISSIONS}" + }, + "specialTags": { + "value": { + "Project": "SimpleChat" + } + }, + "enableDiagLogging": { + "value": "${ENABLE_DIAG_LOGGING}" + }, + "enablePrivateNetworking": { + "value": "${ENABLE_PRIVATE_NETWORKING}" + }, + "gptModels": { + "value": "${GPT_MODELS}" + }, + "embeddingModels": { + "value": "${EMBEDDING_MODELS}" + }, + "allowedIpAddresses": { + "value": "${ALLOWED_IP_RANGES}" + }, + "deployContentSafety": { + "value": "${DEPLOY_CONTENT_SAFETY}" + }, + "deployRedisCache": { + "value": "${DEPLOY_REDIS_CACHE}" + }, + "deploySpeechService": { + "value": "${DEPLOY_SPEECH_SERVICE}" + }, + "deployVideoIndexerService": { + "value": "${DEPLOY_VIDEO_INDEXER_SERVICE}" } } } \ No newline at end of file From dee7b4b147348973435b70e1787cd5397c178d3c Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 21:11:29 +0000 Subject: [PATCH 16/19] update to oneclickdeploy.md to correct for updated bicep structures. --- deployers/bicep/OneClickDeploy.md | 4 +- deployers/bicep/main.json | 7879 +++++++++++++++++++++++++++++ 2 files changed, 7881 insertions(+), 2 deletions(-) create mode 100644 deployers/bicep/main.json diff --git a/deployers/bicep/OneClickDeploy.md b/deployers/bicep/OneClickDeploy.md index 0c5c931b..d89c7dd6 100644 --- a/deployers/bicep/OneClickDeploy.md +++ b/deployers/bicep/OneClickDeploy.md @@ -8,9 +8,9 @@ There are pre-deploy manual steps that must be completed first. After you have deployed, there are additional manual steps that will need to be completed as well. -[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2Fsimplechat%2Frefs%2Fheads%2Finfra-deployer-gunger%2Fdeployers%2Fbicep%2Fmain.json) +[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2Fsimplechat%2Frefs%2Fheads%2Fmain%2Fdeployers%2Fbicep%2Fmain.json) -[![Deploy to Azure](https://aka.ms/deploytoazuregovbutton)](https://portal.azure.us/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2Fsimplechat%2Frefs%2Fheads%2Finfra-deployer-gunger%2Fdeployers%2Fbicep%2Fmain.json) +[![Deploy to Azure](https://aka.ms/deploytoazuregovbutton)](https://portal.azure.us/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2Fsimplechat%2Frefs%2Fheads%2Fmain%2Fdeployers%2Fbicep%2Fmain.json) ## How to Use diff --git a/deployers/bicep/main.json b/deployers/bicep/main.json new file mode 100644 index 00000000..a3e17b22 --- /dev/null +++ b/deployers/bicep/main.json @@ -0,0 +1,7879 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "12894563845993036077" + } + }, + "parameters": { + "location": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "The Azure region where resources will be deployed. \n- Region must align to the target cloud environment" + } + }, + "cloudEnvironment": { + "type": "string", + "allowedValues": [ + "AzureCloud", + "AzureUSGovernment" + ], + "metadata": { + "description": "The target Azure Cloud environment.\n- Accepted values are: AzureCloud, AzureUSGovernment\n- Default is AzureCloud" + } + }, + "appName": { + "type": "string", + "minLength": 3, + "maxLength": 12, + "metadata": { + "description": "The name of the application to be deployed. \n- Name may only contain letters and numbers\n- Between 3 and 12 characters in length \n- No spaces or special characters" + } + }, + "environment": { + "type": "string", + "minLength": 2, + "maxLength": 10, + "metadata": { + "description": "The dev/qa/prod environment or as named in your environment. This will be used to create resource group names and tags.\n- Must be between 2 and 10 characters in length\n- No spaces or special characters" + } + }, + "azdEnvironmentName": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "metadata": { + "description": "Name of the AZD environment" + } + }, + "imageName": { + "type": "string", + "metadata": { + "description": "The name of the container image to deploy to the web app.\n- should be in the format :" + } + }, + "enterpriseAppClientId": { + "type": "string", + "metadata": { + "description": "Azure AD Application Client ID for enterprise authentication.\n- Should be the client ID of the registered Azure AD application" + } + }, + "enterpriseAppServicePrincipalId": { + "type": "string", + "metadata": { + "description": "Azure AD Application Service Principal Id for the enterprise application.\n- Should be the Service Principal ID of the registered Azure AD application" + } + }, + "enterpriseAppClientSecret": { + "type": "securestring", + "metadata": { + "description": "Azure AD Application Client Secret for enterprise authentication.\n- Required if enableEnterpriseApp is true\n- Should be created in Azure AD App Registration and passed via environment variable\n- Will be stored securely in Azure Key Vault during deployment" + } + }, + "authenticationType": { + "type": "string", + "allowedValues": [ + "key", + "managed_identity" + ], + "metadata": { + "description": "Authentication type for resources that support Managed Identity or Key authentication.\n- Key: Use access keys for authentication (application keys will be stored in Key Vault)\n- managed_identity: Use Managed Identity for authentication" + } + }, + "configureApplicationPermissions": { + "type": "bool", + "metadata": { + "description": "Configure permissions (based on authenticationType) for the deployed web application to access required resources.\n" + } + }, + "specialTags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional object containing additional tags to apply to all resources." + } + }, + "enableDiagLogging": { + "type": "bool", + "metadata": { + "description": "Enable diagnostic logging for resources deployed in the resource group. \n- All content will be sent to the deployed Log Analytics workspace\n- Default is false" + } + }, + "enablePrivateNetworking": { + "type": "bool", + "metadata": { + "description": "Enable private endpoints and virtual network integration for deployed resources. \n- Default is false" + } + }, + "gptModels": { + "type": "array", + "defaultValue": [ + { + "modelName": "gpt-4.1", + "modelVersion": "2025-04-14", + "skuName": "GlobalStandard", + "skuCapacity": 150 + }, + { + "modelName": "gpt-4o", + "modelVersion": "2024-11-20", + "skuName": "GlobalStandard", + "skuCapacity": 100 + } + ], + "metadata": { + "description": "Array of GPT model names to deploy to the OpenAI resource." + } + }, + "embeddingModels": { + "type": "array", + "defaultValue": [ + { + "modelName": "text-embedding-3-small", + "modelVersion": "1", + "skuName": "GlobalStandard", + "skuCapacity": 150 + }, + { + "modelName": "text-embedding-3-large", + "modelVersion": "1", + "skuName": "GlobalStandard", + "skuCapacity": 150 + } + ], + "metadata": { + "description": "Array of embedding model names to deploy to the OpenAI resource." + } + }, + "allowedIpAddresses": { + "type": "array", + "defaultValue": [ + { + "ipAddressOrRange": "0.0.0.0" + } + ] + }, + "deployContentSafety": { + "type": "bool", + "metadata": { + "description": "Enable deployment of Content Safety service and related resources.\n- Default is false" + } + }, + "deployRedisCache": { + "type": "bool", + "metadata": { + "description": "Enable deployment of Azure Cache for Redis and related resources.\n- Default is false" + } + }, + "deploySpeechService": { + "type": "bool", + "metadata": { + "description": "Enable deployment of Azure Speech service and related resources.\n- Default is false" + } + }, + "deployVideoIndexerService": { + "type": "bool", + "metadata": { + "description": "Enable deployment of Azure Video Indexer service and related resources.\n- Default is false" + } + } + }, + "variables": { + "rgName": "[format('{0}-{1}-rg', parameters('appName'), parameters('environment'))]", + "requiredTags": { + "application": "[parameters('appName')]", + "environment": "[parameters('environment')]", + "azd-env-name": "[parameters('azdEnvironmentName')]" + }, + "tags": "[union(variables('requiredTags'), parameters('specialTags'))]", + "acrCloudSuffix": "[if(equals(parameters('cloudEnvironment'), 'AzureCloud'), '.azurecr.io', '.azurecr.us')]", + "acrName": "[toLower(format('{0}{1}acr', parameters('appName'), parameters('environment')))]", + "containerRegistry": "[format('{0}{1}', variables('acrName'), variables('acrCloudSuffix'))]", + "containerImageName": "[format('{0}/{1}', variables('containerRegistry'), parameters('imageName'))]", + "vNetName": "[format('{0}-{1}-vnet', parameters('appName'), parameters('environment'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2022-09-01", + "name": "[variables('rgName')]", + "location": "[parameters('location')]", + "tags": "[variables('tags')]" + }, + { + "condition": "[parameters('enablePrivateNetworking')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "virtualNetwork", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "vNetName": { + "value": "[variables('vNetName')]" + }, + "addressSpaces": { + "value": [ + "10.0.0.0/21" + ] + }, + "subnetConfigs": { + "value": [ + { + "name": "AppServiceIntegration", + "addressPrefix": "10.0.0.0/24", + "enablePrivateEndpointNetworkPolicies": true, + "enablePrivateLinkServiceNetworkPolicies": true + }, + { + "name": "PrivateEndpoints", + "addressPrefix": "10.0.2.0/24", + "enablePrivateEndpointNetworkPolicies": true, + "enablePrivateLinkServiceNetworkPolicies": true + } + ] + }, + "tags": { + "value": "[variables('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "10221795613826494860" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "vNetName": { + "type": "string" + }, + "addressSpaces": { + "type": "array" + }, + "subnetConfigs": { + "type": "array" + }, + "tags": { + "type": "object" + } + }, + "variables": { + "copy": [ + { + "name": "subnetIds", + "count": "[length(parameters('subnetConfigs'))]", + "input": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vNetName'), parameters('subnetConfigs')[copyIndex('subnetIds')].name)]" + }, + { + "name": "subnetNames", + "count": "[length(parameters('subnetConfigs'))]", + "input": "[parameters('subnetConfigs')[copyIndex('subnetNames')].name]" + } + ], + "appServiceIntegrationSubnetIndex": "[indexOf(variables('subnetNames'), 'AppServiceIntegration')]", + "privateEndpointIndex": "[indexOf(variables('subnetNames'), 'PrivateEndpoints')]" + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2021-05-01", + "name": "[parameters('vNetName')]", + "location": "[parameters('location')]", + "properties": { + "copy": [ + { + "name": "subnets", + "count": "[length(parameters('subnetConfigs'))]", + "input": { + "name": "[parameters('subnetConfigs')[copyIndex('subnets')].name]", + "properties": { + "addressPrefix": "[parameters('subnetConfigs')[copyIndex('subnets')].addressPrefix]", + "privateEndpointNetworkPolicies": "[if(parameters('subnetConfigs')[copyIndex('subnets')].enablePrivateEndpointNetworkPolicies, 'Enabled', 'Disabled')]", + "privateLinkServiceNetworkPolicies": "[if(parameters('subnetConfigs')[copyIndex('subnets')].enablePrivateLinkServiceNetworkPolicies, 'Enabled', 'Disabled')]", + "delegations": "[if(equals(parameters('subnetConfigs')[copyIndex('subnets')].name, 'AppServiceIntegration'), createArray(createObject('name', 'delegation', 'properties', createObject('serviceName', 'Microsoft.Web/serverFarms'))), createArray())]" + } + } + } + ], + "addressSpace": { + "addressPrefixes": "[parameters('addressSpaces')]" + } + }, + "tags": "[parameters('tags')]" + } + ], + "outputs": { + "vNetId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vNetName'))]" + }, + "privateNetworkSubnetId": { + "type": "string", + "value": "[if(equals(variables('privateEndpointIndex'), -1), '', variables('subnetIds')[variables('privateEndpointIndex')])]" + }, + "appServiceSubnetId": { + "type": "string", + "value": "[if(equals(variables('appServiceIntegrationSubnetIndex'), -1), '', variables('subnetIds')[variables('appServiceIntegrationSubnetIndex')])]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "logAnalytics", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "16638534490731333297" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2022-10-01", + "name": "[toLower(format('{0}-{1}-la', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "properties": { + "sku": { + "name": "PerGB2018" + } + }, + "tags": "[parameters('tags')]" + } + ], + "outputs": { + "logAnalyticsId": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', toLower(format('{0}-{1}-la', parameters('appName'), parameters('environment'))))]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "applicationInsights", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "16543999957549068652" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "logAnalyticsId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[toLower(format('{0}-{1}-ai', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "WorkspaceResourceId": "[parameters('logAnalyticsId')]" + }, + "tags": "[parameters('tags')]" + } + ], + "outputs": { + "appInsightsName": { + "type": "string", + "value": "[toLower(format('{0}-{1}-ai', parameters('appName'), parameters('environment')))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "keyVault", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "3503710577838836741" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2024-11-01", + "name": "[toLower(format('{0}-{1}-kv', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "properties": { + "tenantId": "[subscription().tenantId]", + "sku": { + "family": "A", + "name": "standard" + }, + "accessPolicies": [], + "enabledForDeployment": false, + "enabledForDiskEncryption": false, + "enabledForTemplateDeployment": false, + "publicNetworkAccess": "Enabled", + "enableRbacAuthorization": true + }, + "tags": "[parameters('tags')]" + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', toLower(format('{0}-{1}-kv', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-kv', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardLogCategories.value]", + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]", + "[resourceId('Microsoft.KeyVault/vaults', toLower(format('{0}-{1}-kv', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + } + ], + "outputs": { + "keyVaultId": { + "type": "string", + "value": "[resourceId('Microsoft.KeyVault/vaults', toLower(format('{0}-{1}-kv', parameters('appName'), parameters('environment'))))]" + }, + "keyVaultName": { + "type": "string", + "value": "[toLower(format('{0}-{1}-kv', parameters('appName'), parameters('environment')))]" + }, + "keyVaultUri": { + "type": "string", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', toLower(format('{0}-{1}-kv', parameters('appName'), parameters('environment')))), '2024-11-01').vaultUri]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "condition": "[not(empty(parameters('enterpriseAppClientSecret')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storeEnterpriseAppSecret", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "secretName": { + "value": "enterprise-app-client-secret" + }, + "secretValue": { + "value": "[parameters('enterpriseAppClientSecret')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "18077870757859157550" + } + }, + "parameters": { + "keyVaultName": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretValue": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "SecretUri": { + "type": "string", + "value": "[format('{0}secrets/{1}', reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2025-05-01').vaultUri, parameters('secretName'))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "cosmosDB", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "keyVault": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "configureApplicationPermissions": { + "value": "[parameters('configureApplicationPermissions')]" + }, + "enablePrivateNetworking": { + "value": "[parameters('enablePrivateNetworking')]" + }, + "allowedIpAddresses": { + "value": "[parameters('allowedIpAddresses')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "15046922718277168026" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "authenticationType": { + "type": "string" + }, + "configureApplicationPermissions": { + "type": "bool" + }, + "enablePrivateNetworking": { + "type": "bool" + }, + "allowedIpAddresses": { + "type": "array", + "defaultValue": [] + } + }, + "resources": [ + { + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2023-04-15", + "name": "[toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "kind": "GlobalDocumentDB", + "properties": { + "publicNetworkAccess": "Enabled", + "databaseAccountOfferType": "Standard", + "capabilities": [ + { + "name": "EnableServerless" + } + ], + "isVirtualNetworkFilterEnabled": "[if(parameters('enablePrivateNetworking'), true(), false())]", + "ipRules": "[if(parameters('enablePrivateNetworking'), parameters('allowedIpAddresses'), createArray())]", + "locations": [ + { + "locationName": "[parameters('location')]", + "failoverPriority": 0, + "isZoneRedundant": false + } + ], + "consistencyPolicy": { + "defaultConsistencyLevel": "Session" + } + }, + "tags": "[parameters('tags')]" + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", + "apiVersion": "2023-04-15", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment'))), 'SimpleChat')]", + "properties": { + "resource": { + "id": "SimpleChat" + }, + "options": {} + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", + "apiVersion": "2023-04-15", + "name": "[format('{0}/{1}/{2}', toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment'))), 'SimpleChat', 'settings')]", + "properties": { + "resource": { + "id": "settings", + "partitionKey": { + "paths": [ + "/id" + ] + } + }, + "options": {} + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment'))), 'SimpleChat')]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardLogCategories.value]", + "metrics": [] + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment'))))]", + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + }, + { + "condition": "[and(equals(parameters('authenticationType'), 'key'), parameters('configureApplicationPermissions'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storeCosmosDbSecret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVault')]" + }, + "secretName": { + "value": "cosmos-db-key" + }, + "secretValue": { + "value": "[listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment')))), '2023-04-15').primaryMasterKey]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "18077870757859157550" + } + }, + "parameters": { + "keyVaultName": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretValue": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "SecretUri": { + "type": "string", + "value": "[format('{0}secrets/{1}', reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2025-05-01').vaultUri, parameters('secretName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment'))))]" + ] + } + ], + "outputs": { + "cosmosDbName": { + "type": "string", + "value": "[toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment')))]" + }, + "cosmosDbUri": { + "type": "string", + "value": "[reference(resourceId('Microsoft.DocumentDB/databaseAccounts', toLower(format('{0}-{1}-cosmos', parameters('appName'), parameters('environment')))), '2023-04-15').documentEndpoint]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "azureContainerRegistry", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "acrName": { + "value": "[variables('acrName')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "keyVault": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "configureApplicationPermissions": { + "value": "[parameters('configureApplicationPermissions')]" + }, + "enablePrivateNetworking": { + "value": "[parameters('enablePrivateNetworking')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "7200761421625936333" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "acrName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "authenticationType": { + "type": "string" + }, + "configureApplicationPermissions": { + "type": "bool" + }, + "enablePrivateNetworking": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2025-04-01", + "name": "[parameters('acrName')]", + "location": "[parameters('location')]", + "sku": { + "name": "[if(parameters('enablePrivateNetworking'), 'Premium', 'Standard')]" + }, + "properties": { + "adminUserEnabled": true, + "publicNetworkAccess": "Enabled" + }, + "tags": "[parameters('tags')]" + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('acrName'))]", + "name": "[toLower(format('{0}-diagnostics', parameters('acrName')))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardLogCategories.value]", + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName'))]", + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + }, + { + "condition": "[and(equals(parameters('authenticationType'), 'key'), parameters('configureApplicationPermissions'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storeContainerRegistrySecret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVault')]" + }, + "secretName": { + "value": "container-registry-key" + }, + "secretValue": { + "value": "[listCredentials(resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName')), '2025-04-01').passwords[0].value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "18077870757859157550" + } + }, + "parameters": { + "keyVaultName": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretValue": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "SecretUri": { + "type": "string", + "value": "[format('{0}secrets/{1}', reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2025-05-01').vaultUri, parameters('secretName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName'))]" + ] + } + ], + "outputs": { + "acrName": { + "type": "string", + "value": "[parameters('acrName')]" + }, + "acrResourceGroup": { + "type": "string", + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "searchService", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "keyVault": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "configureApplicationPermissions": { + "value": "[parameters('configureApplicationPermissions')]" + }, + "enablePrivateNetworking": { + "value": "[parameters('enablePrivateNetworking')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "12409981559885193891" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "authenticationType": { + "type": "string" + }, + "configureApplicationPermissions": { + "type": "bool" + }, + "enablePrivateNetworking": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.Search/searchServices", + "apiVersion": "2025-05-01", + "name": "[toLower(format('{0}-{1}-search', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "sku": { + "name": "basic" + }, + "properties": { + "hostingMode": "default", + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), 'Disabled', 'Enabled')]", + "replicaCount": 1, + "partitionCount": 1, + "authOptions": { + "aadOrApiKey": { + "aadAuthFailureMode": "http403" + } + }, + "disableLocalAuth": false + }, + "tags": "[parameters('tags')]" + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Search/searchServices/{0}', toLower(format('{0}-{1}-search', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-search', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardLogCategories.value]", + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]", + "[resourceId('Microsoft.Search/searchServices', toLower(format('{0}-{1}-search', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + }, + { + "condition": "[and(equals(parameters('authenticationType'), 'key'), parameters('configureApplicationPermissions'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storeSearchServiceSecret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVault')]" + }, + "secretName": { + "value": "search-service-key" + }, + "secretValue": { + "value": "[listAdminKeys(resourceId('Microsoft.Search/searchServices', toLower(format('{0}-{1}-search', parameters('appName'), parameters('environment')))), '2025-05-01').primaryKey]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "18077870757859157550" + } + }, + "parameters": { + "keyVaultName": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretValue": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "SecretUri": { + "type": "string", + "value": "[format('{0}secrets/{1}', reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2025-05-01').vaultUri, parameters('secretName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Search/searchServices', toLower(format('{0}-{1}-search', parameters('appName'), parameters('environment'))))]" + ] + } + ], + "outputs": { + "searchServiceName": { + "type": "string", + "value": "[toLower(format('{0}-{1}-search', parameters('appName'), parameters('environment')))]" + }, + "searchServiceEndpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Search/searchServices', toLower(format('{0}-{1}-search', parameters('appName'), parameters('environment')))), '2025-05-01').endpoint]" + }, + "searchServiceAuthencationType": { + "type": "string", + "value": "[parameters('authenticationType')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "docIntel", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "keyVault": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "configureApplicationPermissions": { + "value": "[parameters('configureApplicationPermissions')]" + }, + "enablePrivateNetworking": { + "value": "[parameters('enablePrivateNetworking')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1403194626393613544" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "authenticationType": { + "type": "string" + }, + "configureApplicationPermissions": { + "type": "bool" + }, + "enablePrivateNetworking": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-06-01", + "name": "[toLower(format('{0}-{1}-docintel', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "kind": "FormRecognizer", + "sku": { + "name": "S0" + }, + "properties": { + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), 'Disabled', 'Enabled')]", + "customSubDomainName": "[toLower(format('{0}-{1}-docintel', parameters('appName'), parameters('environment')))]" + }, + "tags": "[parameters('tags')]" + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', toLower(format('{0}-{1}-docintel', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-docintel', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardLogCategories.value]", + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]", + "[resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-docintel', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + }, + { + "condition": "[and(equals(parameters('authenticationType'), 'key'), parameters('configureApplicationPermissions'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storeDocumentIntelligenceSecret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVault')]" + }, + "secretName": { + "value": "document-intelligence-key" + }, + "secretValue": { + "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-docintel', parameters('appName'), parameters('environment')))), '2025-06-01').key1]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "18077870757859157550" + } + }, + "parameters": { + "keyVaultName": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretValue": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "SecretUri": { + "type": "string", + "value": "[format('{0}secrets/{1}', reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2025-05-01').vaultUri, parameters('secretName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-docintel', parameters('appName'), parameters('environment'))))]" + ] + } + ], + "outputs": { + "documentIntelligenceServiceName": { + "type": "string", + "value": "[toLower(format('{0}-{1}-docintel', parameters('appName'), parameters('environment')))]" + }, + "diagnosticLoggingEnabled": { + "type": "bool", + "value": "[parameters('enableDiagLogging')]" + }, + "documentIntelligenceServiceEndpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-docintel', parameters('appName'), parameters('environment')))), '2025-06-01').endpoint]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storageAccount", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "keyVault": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "configureApplicationPermissions": { + "value": "[parameters('configureApplicationPermissions')]" + }, + "enablePrivateNetworking": { + "value": "[parameters('enablePrivateNetworking')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1065428217831578633" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "authenticationType": { + "type": "string" + }, + "configureApplicationPermissions": { + "type": "bool" + }, + "enablePrivateNetworking": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[toLower(format('{0}{1}sa', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "properties": { + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), 'Disabled', 'Enabled')]", + "accessTier": "Hot", + "allowBlobPublicAccess": false, + "allowSharedKeyAccess": true, + "isHnsEnabled": true + }, + "tags": "[parameters('tags')]" + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2023-01-01", + "name": "[format('{0}/{1}', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))), 'default')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2023-01-01", + "name": "[format('{0}/{1}/{2}', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))), 'default', 'user-documents')]", + "properties": { + "publicAccess": "None" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))), 'default')]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2023-01-01", + "name": "[format('{0}/{1}/{2}', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))), 'default', 'group-documents')]", + "properties": { + "publicAccess": "None" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))), 'default')]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": [], + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.transactionMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]", + "[resourceId('Microsoft.Storage/storageAccounts', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))), 'default')]", + "name": "[toLower(format('{0}-blob-diagnostics', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardLogCategories.value]", + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.transactionMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))), 'default')]", + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]", + "[resourceId('Microsoft.Storage/storageAccounts', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + }, + { + "condition": "[and(equals(parameters('authenticationType'), 'key'), parameters('configureApplicationPermissions'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storeStorageAccountSecret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVault')]" + }, + "secretName": { + "value": "storage-account-key" + }, + "secretValue": { + "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment')))), '2022-09-01').keys[0].value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "18077870757859157550" + } + }, + "parameters": { + "keyVaultName": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretValue": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "SecretUri": { + "type": "string", + "value": "[format('{0}secrets/{1}', reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2025-05-01').vaultUri, parameters('secretName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment'))))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[toLower(format('{0}{1}sa', parameters('appName'), parameters('environment')))]" + }, + "endpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', toLower(format('{0}{1}sa', parameters('appName'), parameters('environment')))), '2022-09-01').primaryEndpoints.blob]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "openAI", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "keyVault": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "configureApplicationPermissions": { + "value": "[parameters('configureApplicationPermissions')]" + }, + "gptModels": { + "value": "[parameters('gptModels')]" + }, + "embeddingModels": { + "value": "[parameters('embeddingModels')]" + }, + "enablePrivateNetworking": { + "value": "[parameters('enablePrivateNetworking')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "17726947983911725769" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "authenticationType": { + "type": "string" + }, + "configureApplicationPermissions": { + "type": "bool" + }, + "gptModels": { + "type": "array" + }, + "embeddingModels": { + "type": "array" + }, + "enablePrivateNetworking": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2024-10-01", + "name": "[toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "kind": "OpenAI", + "sku": { + "name": "S0" + }, + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), 'Disabled', 'Enabled')]", + "customSubDomainName": "[toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment')))]" + }, + "tags": "[parameters('tags')]" + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardLogCategories.value]", + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]", + "[resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + }, + { + "copy": { + "name": "aiModel", + "count": "[length(concat(parameters('gptModels'), parameters('embeddingModels')))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('model-{0}-{1}', replace(concat(parameters('gptModels'), parameters('embeddingModels'))[copyIndex()].modelName, '.', '-'), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "parent": { + "value": "[toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment')))]" + }, + "modelName": { + "value": "[concat(parameters('gptModels'), parameters('embeddingModels'))[copyIndex()].modelName]" + }, + "modelVersion": { + "value": "[concat(parameters('gptModels'), parameters('embeddingModels'))[copyIndex()].modelVersion]" + }, + "skuName": { + "value": "[concat(parameters('gptModels'), parameters('embeddingModels'))[copyIndex()].skuName]" + }, + "skuCapacity": { + "value": "[concat(parameters('gptModels'), parameters('embeddingModels'))[copyIndex()].skuCapacity]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "3668428745262748302" + } + }, + "parameters": { + "parent": { + "type": "string" + }, + "modelName": { + "type": "string" + }, + "modelVersion": { + "type": "string" + }, + "skuName": { + "type": "string" + }, + "skuCapacity": { + "type": "int" + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('parent'), parameters('modelName'))]", + "properties": { + "model": { + "format": "OpenAI", + "name": "[parameters('modelName')]", + "version": "[parameters('modelVersion')]" + } + }, + "sku": { + "name": "[parameters('skuName')]", + "capacity": "[parameters('skuCapacity')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[and(equals(parameters('authenticationType'), 'key'), parameters('configureApplicationPermissions'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storeOpenAISecret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVault')]" + }, + "secretName": { + "value": "openAi-key" + }, + "secretValue": { + "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment')))), '2024-10-01').key1]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "18077870757859157550" + } + }, + "parameters": { + "keyVaultName": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretValue": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "SecretUri": { + "type": "string", + "value": "[format('{0}secrets/{1}', reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2025-05-01').vaultUri, parameters('secretName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment'))))]" + ] + } + ], + "outputs": { + "openAIName": { + "type": "string", + "value": "[toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment')))]" + }, + "openAIResourceGroup": { + "type": "string", + "value": "[resourceGroup().name]" + }, + "openAIEndpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-openai', parameters('appName'), parameters('environment')))), '2024-10-01').endpoint]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "appServicePlan", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "16108907418935593505" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2022-03-01", + "name": "[toLower(format('{0}-{1}-asp', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "sku": { + "name": "P1v3", + "tier": "PremiumV3", + "size": "P1v3", + "capacity": 1 + }, + "kind": "app,linux,container", + "properties": { + "reserved": true, + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + }, + "tags": "[parameters('tags')]" + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Web/serverfarms/{0}', toLower(format('{0}-{1}-asp', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-asp', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": [], + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', toLower(format('{0}-{1}-asp', parameters('appName'), parameters('environment'))))]", + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + } + ], + "outputs": { + "appServicePlanId": { + "type": "string", + "value": "[resourceId('Microsoft.Web/serverfarms', toLower(format('{0}-{1}-asp', parameters('appName'), parameters('environment'))))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "appService", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "acrName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'azureContainerRegistry'), '2025-04-01').outputs.acrName.value]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "appServicePlanId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'appServicePlan'), '2025-04-01').outputs.appServicePlanId.value]" + }, + "containerImageName": { + "value": "[variables('containerImageName')]" + }, + "azurePlatform": { + "value": "[parameters('cloudEnvironment')]" + }, + "cosmosDbName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'cosmosDB'), '2025-04-01').outputs.cosmosDbName.value]" + }, + "searchServiceName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'searchService'), '2025-04-01').outputs.searchServiceName.value]" + }, + "openAiServiceName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI'), '2025-04-01').outputs.openAIName.value]" + }, + "openAiResourceGroupName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI'), '2025-04-01').outputs.openAIResourceGroup.value]" + }, + "documentIntelligenceServiceName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'docIntel'), '2025-04-01').outputs.documentIntelligenceServiceName.value]" + }, + "appInsightsName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'applicationInsights'), '2025-04-01').outputs.appInsightsName.value]" + }, + "enterpriseAppClientId": { + "value": "[parameters('enterpriseAppClientId')]" + }, + "enterpriseAppClientSecret": { + "value": "[parameters('enterpriseAppClientSecret')]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "keyVaultUri": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultUri.value]" + }, + "enablePrivateNetworking": { + "value": "[parameters('enablePrivateNetworking')]" + }, + "appServiceSubnetId": "[if(parameters('enablePrivateNetworking'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'virtualNetwork'), '2025-04-01').outputs.appServiceSubnetId.value), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "5779075922193615045" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "acrName": { + "type": "string" + }, + "appServicePlanId": { + "type": "string" + }, + "containerImageName": { + "type": "string" + }, + "azurePlatform": { + "type": "string" + }, + "cosmosDbName": { + "type": "string" + }, + "searchServiceName": { + "type": "string" + }, + "openAiServiceName": { + "type": "string" + }, + "openAiResourceGroupName": { + "type": "string" + }, + "documentIntelligenceServiceName": { + "type": "string" + }, + "appInsightsName": { + "type": "string" + }, + "enterpriseAppClientId": { + "type": "string", + "defaultValue": "" + }, + "authenticationType": { + "type": "string" + }, + "enterpriseAppClientSecret": { + "type": "securestring", + "defaultValue": "" + }, + "keyVaultUri": { + "type": "string" + }, + "enablePrivateNetworking": { + "type": "bool" + }, + "appServiceSubnetId": { + "type": "string", + "defaultValue": "" + } + }, + "variables": { + "acrDomain": "[if(equals(parameters('azurePlatform'), 'AzureUSGovernment'), '.azurecr.us', '.azurecr.io')]" + }, + "resources": [ + { + "type": "Microsoft.Web/sites", + "apiVersion": "2022-03-01", + "name": "[toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "kind": "app,linux,container", + "properties": { + "serverFarmId": "[parameters('appServicePlanId')]", + "virtualNetworkSubnetId": "[if(not(equals(parameters('appServiceSubnetId'), '')), parameters('appServiceSubnetId'), null())]", + "publicNetworkAccess": "Enabled", + "vnetImagePullEnabled": "[if(parameters('enablePrivateNetworking'), true(), false())]", + "siteConfig": { + "linuxFxVersion": "[format('DOCKER|{0}', parameters('containerImageName'))]", + "acrUseManagedIdentityCreds": true, + "acrUserManagedIdentityID": "", + "alwaysOn": true, + "ftpsState": "Disabled", + "healthCheckPath": "/external/healthcheck", + "appSettings": "[flatten(createArray(createArray(createObject('name', 'AZURE_ENDPOINT', 'value', if(equals(parameters('azurePlatform'), 'AzureUSGovernment'), 'usgovernment', 'public')), createObject('name', 'SCM_DO_BUILD_DURING_DEPLOYMENT', 'value', 'false'), createObject('name', 'AZURE_COSMOS_ENDPOINT', 'value', reference(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDbName')), '2023-04-15').documentEndpoint), createObject('name', 'AZURE_COSMOS_AUTHENTICATION_TYPE', 'value', toLower(parameters('authenticationType')))), if(equals(parameters('authenticationType'), 'key'), createArray(createObject('name', 'AZURE_COSMOS_KEY', 'value', format('@Microsoft.KeyVault(SecretUri={0}secrets/cosmos-db-key)', parameters('keyVaultUri')))), createArray()), createArray(createObject('name', 'TENANT_ID', 'value', tenant().tenantId), createObject('name', 'CLIENT_ID', 'value', parameters('enterpriseAppClientId')), createObject('name', 'SECRET_KEY', 'value', if(not(empty(parameters('enterpriseAppClientSecret'))), parameters('enterpriseAppClientSecret'), format('@Microsoft.KeyVault(SecretUri={0}secrets/enterprise-app-client-secret)', parameters('keyVaultUri')))), createObject('name', 'MICROSOFT_PROVIDER_AUTHENTICATION_SECRET', 'value', format('@Microsoft.KeyVault(SecretUri={0}secrets/enterprise-app-client-secret)', parameters('keyVaultUri'))), createObject('name', 'DOCKER_REGISTRY_SERVER_URL', 'value', format('https://{0}{1}', parameters('acrName'), variables('acrDomain')))), if(equals(parameters('authenticationType'), 'key'), createArray(createObject('name', 'DOCKER_REGISTRY_SERVER_USERNAME', 'value', listCredentials(resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName')), '2025-04-01').username)), createArray()), if(equals(parameters('authenticationType'), 'key'), createArray(createObject('name', 'DOCKER_REGISTRY_SERVER_PASSWORD', 'value', format('@Microsoft.KeyVault(SecretUri={0}secrets/container-registry-key)', parameters('keyVaultUri')))), createArray()), createArray(createObject('name', 'WEBSITE_AUTH_AAD_ALLOWED_TENANTS', 'value', tenant().tenantId), createObject('name', 'AZURE_OPENAI_RESOURCE_NAME', 'value', parameters('openAiServiceName')), createObject('name', 'AZURE_OPENAI_RESOURCE_GROUP_NAME', 'value', parameters('openAiResourceGroupName')), createObject('name', 'AZURE_OPENAI_URL', 'value', reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAiServiceName')), '2024-10-01').endpoint), createObject('name', 'AZURE_SEARCH_SERVICE_NAME', 'value', parameters('searchServiceName'))), if(equals(parameters('authenticationType'), 'key'), createArray(createObject('name', 'AZURE_SEARCH_API_KEY', 'value', format('@Microsoft.KeyVault(SecretUri={0}secrets/search-service-key)', parameters('keyVaultUri')))), createArray()), createArray(createObject('name', 'AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT', 'value', reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('documentIntelligenceServiceName')), '2025-06-01').endpoint)), if(equals(parameters('authenticationType'), 'key'), createArray(createObject('name', 'AZURE_DOCUMENT_INTELLIGENCE_API_KEY', 'value', format('@Microsoft.KeyVault(SecretUri={0}secrets/document-intelligence-key)', parameters('keyVaultUri')))), createArray()), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName')), '2020-02-02').InstrumentationKey), createObject('name', 'APPLICATIONINSIGHTS_CONNECTION_STRING', 'value', reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName')), '2020-02-02').ConnectionString), createObject('name', 'APPINSIGHTS_PROFILERFEATURE_VERSION', 'value', '1.0.0'), createObject('name', 'APPINSIGHTS_SNAPSHOTFEATURE_VERSION', 'value', '1.0.0'), createObject('name', 'APPLICATIONINSIGHTS_CONFIGURATION_CONTENT', 'value', ''), createObject('name', 'ApplicationInsightsAgent_EXTENSION_VERSION', 'value', '~3'), createObject('name', 'DiagnosticServices_EXTENSION_VERSION', 'value', '~3'), createObject('name', 'InstrumentationEngine_EXTENSION_VERSION', 'value', 'disabled'), createObject('name', 'SnapshotDebugger_EXTENSION_VERSION', 'value', 'disabled'), createObject('name', 'XDT_MicrosoftApplicationInsights_BaseExtensions', 'value', 'disabled'), createObject('name', 'XDT_MicrosoftApplicationInsights_Mode', 'value', 'recommended'), createObject('name', 'XDT_MicrosoftApplicationInsights_PreemptSdk', 'value', 'disabled'))))]" + }, + "clientAffinityEnabled": false, + "httpsOnly": true + }, + "identity": { + "type": "SystemAssigned" + }, + "tags": "[union(parameters('tags'), createObject('azd-service-name', 'web'))]" + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment'))), 'logs')]", + "properties": { + "httpLogs": { + "fileSystem": { + "enabled": true, + "retentionInDays": 7, + "retentionInMb": 35 + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Web/sites/{0}', toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.webAppLogCategories.value]", + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]", + "[resourceId('Microsoft.Web/sites', toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2022-03-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment'))), 'authsettingsV2')]", + "properties": { + "globalValidation": { + "requireAuthentication": true, + "unauthenticatedClientAction": "RedirectToLoginPage", + "redirectToProvider": "azureActiveDirectory" + }, + "identityProviders": { + "azureActiveDirectory": { + "enabled": true, + "registration": { + "openIdIssuer": "[if(equals(parameters('azurePlatform'), 'AzureUSGovernment'), format('https://login.microsoftonline.us/{0}/', tenant().tenantId), format('https://sts.windows.net/{0}/', tenant().tenantId))]", + "clientId": "[parameters('enterpriseAppClientId')]", + "clientSecretSettingName": "MICROSOFT_PROVIDER_AUTHENTICATION_SECRET" + }, + "validation": { + "jwtClaimChecks": {}, + "allowedAudiences": [ + "[format('api://{0}', parameters('enterpriseAppClientId'))]", + "[parameters('enterpriseAppClientId')]" + ] + }, + "isAutoProvisioned": false + } + }, + "login": { + "routes": { + "logoutEndpoint": "/.auth/logout" + }, + "tokenStore": { + "enabled": true, + "tokenRefreshExtensionHours": 72, + "fileSystem": { + "directory": "/home/data/.auth" + } + }, + "preserveUrlFragmentsForLogins": false, + "allowedExternalRedirectUrls": [], + "cookieExpiration": { + "convention": "FixedTime", + "timeToExpiration": "08:00:00" + }, + "nonce": { + "validateNonce": true, + "nonceExpirationInterval": "00:05:00" + } + }, + "httpSettings": { + "requireHttps": true, + "routes": { + "apiPrefix": "/.auth" + }, + "forwardProxy": { + "convention": "NoProxy" + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment')))]" + }, + "defaultHostName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Web/sites', toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment')))), '2022-03-01').defaultHostName]" + }, + "resourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Web/sites', toLower(format('{0}-{1}-app', parameters('appName'), parameters('environment'))))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'azureContainerRegistry')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'applicationInsights')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'appServicePlan')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'cosmosDB')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'docIntel')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'searchService')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'virtualNetwork')]" + ] + }, + { + "condition": "[parameters('deployContentSafety')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "contentSafety", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "keyVault": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "configureApplicationPermissions": { + "value": "[parameters('configureApplicationPermissions')]" + }, + "enablePrivateNetworking": { + "value": "[parameters('enablePrivateNetworking')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "15792786587982505631" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "authenticationType": { + "type": "string" + }, + "configureApplicationPermissions": { + "type": "bool" + }, + "enablePrivateNetworking": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-06-01", + "name": "[toLower(format('{0}-{1}-contentsafety', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "kind": "ContentSafety", + "sku": { + "name": "S0" + }, + "properties": { + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), 'Disabled', 'Enabled')]", + "customSubDomainName": "[toLower(format('{0}-{1}-contentsafety', parameters('appName'), parameters('environment')))]" + }, + "tags": "[parameters('tags')]" + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', toLower(format('{0}-{1}-contentsafety', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-contentsafety', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardLogCategories.value]", + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-contentsafety', parameters('appName'), parameters('environment'))))]", + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + }, + { + "condition": "[and(equals(parameters('authenticationType'), 'key'), parameters('configureApplicationPermissions'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storeContentSafetySecret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVault')]" + }, + "secretName": { + "value": "content-safety-key" + }, + "secretValue": { + "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-contentsafety', parameters('appName'), parameters('environment')))), '2025-06-01').key1]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "18077870757859157550" + } + }, + "parameters": { + "keyVaultName": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretValue": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "SecretUri": { + "type": "string", + "value": "[format('{0}secrets/{1}', reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2025-05-01').vaultUri, parameters('secretName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-contentsafety', parameters('appName'), parameters('environment'))))]" + ] + } + ], + "outputs": { + "contentSafetyName": { + "type": "string", + "value": "[toLower(format('{0}-{1}-contentsafety', parameters('appName'), parameters('environment')))]" + }, + "contentSafetyEndpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-contentsafety', parameters('appName'), parameters('environment')))), '2025-06-01').endpoint]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "condition": "[parameters('deployRedisCache')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "redisCache", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "keyVault": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "configureApplicationPermissions": { + "value": "[parameters('configureApplicationPermissions')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "10855483054816904335" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "authenticationType": { + "type": "string" + }, + "configureApplicationPermissions": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.Cache/redis", + "apiVersion": "2024-11-01", + "name": "[toLower(format('{0}-{1}-redis', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "properties": { + "sku": { + "name": "Standard", + "family": "C", + "capacity": 0 + }, + "enableNonSslPort": false, + "minimumTlsVersion": "1.2", + "redisConfiguration": { + "aad-enabled": "true" + } + }, + "tags": "[parameters('tags')]" + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Cache/redis/{0}', toLower(format('{0}-{1}-redis', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-redis', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardLogCategories.value]", + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]", + "[resourceId('Microsoft.Cache/redis', toLower(format('{0}-{1}-redis', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + }, + { + "condition": "[and(equals(parameters('authenticationType'), 'key'), parameters('configureApplicationPermissions'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storeRedisCacheSecret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVault')]" + }, + "secretName": { + "value": "redis-cache-key" + }, + "secretValue": { + "value": "[listKeys(resourceId('Microsoft.Cache/redis', toLower(format('{0}-{1}-redis', parameters('appName'), parameters('environment')))), '2024-11-01').primaryKey]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "18077870757859157550" + } + }, + "parameters": { + "keyVaultName": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretValue": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "SecretUri": { + "type": "string", + "value": "[format('{0}secrets/{1}', reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2025-05-01').vaultUri, parameters('secretName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Cache/redis', toLower(format('{0}-{1}-redis', parameters('appName'), parameters('environment'))))]" + ] + } + ], + "outputs": { + "redisCacheName": { + "type": "string", + "value": "[toLower(format('{0}-{1}-redis', parameters('appName'), parameters('environment')))]" + }, + "redisCacheHostName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Cache/redis', toLower(format('{0}-{1}-redis', parameters('appName'), parameters('environment')))), '2024-11-01').hostName]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "condition": "[parameters('deploySpeechService')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "speechService", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "keyVault": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "configureApplicationPermissions": { + "value": "[parameters('configureApplicationPermissions')]" + }, + "enablePrivateNetworking": { + "value": "[parameters('enablePrivateNetworking')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "12749834719068646180" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "authenticationType": { + "type": "string" + }, + "configureApplicationPermissions": { + "type": "bool" + }, + "enablePrivateNetworking": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2024-10-01", + "name": "[toLower(format('{0}-{1}-speech', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "kind": "SpeechServices", + "sku": { + "name": "S0" + }, + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), 'Disabled', 'Enabled')]", + "customSubDomainName": "[toLower(format('{0}-{1}-speech', parameters('appName'), parameters('environment')))]" + }, + "tags": "[parameters('tags')]" + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', toLower(format('{0}-{1}-speech', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-speech', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardLogCategories.value]", + "metrics": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.standardMetricsCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]", + "[resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-speech', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + }, + { + "condition": "[and(equals(parameters('authenticationType'), 'key'), parameters('configureApplicationPermissions'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storeSpeechServiceSecret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVault')]" + }, + "secretName": { + "value": "speech-service-key" + }, + "secretValue": { + "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-speech', parameters('appName'), parameters('environment')))), '2024-10-01').key1]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "18077870757859157550" + } + }, + "parameters": { + "keyVaultName": { + "type": "string" + }, + "secretName": { + "type": "string" + }, + "secretValue": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "SecretUri": { + "type": "string", + "value": "[format('{0}secrets/{1}', reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2025-05-01').vaultUri, parameters('secretName'))]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-speech', parameters('appName'), parameters('environment'))))]" + ] + } + ], + "outputs": { + "speechServiceName": { + "type": "string", + "value": "[toLower(format('{0}-{1}-speech', parameters('appName'), parameters('environment')))]" + }, + "speechServiceEndpoint": { + "type": "string", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', toLower(format('{0}-{1}-speech', parameters('appName'), parameters('environment')))), '2024-10-01').endpoint]" + }, + "speechServiceAuthenticationType": { + "type": "string", + "value": "[parameters('authenticationType')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]" + ] + }, + { + "condition": "[parameters('deployVideoIndexerService')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "videoIndexerService", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "enableDiagLogging": { + "value": "[parameters('enableDiagLogging')]" + }, + "logAnalyticsId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics'), '2025-04-01').outputs.logAnalyticsId.value]" + }, + "storageAccount": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'storageAccount'), '2025-04-01').outputs.name.value]" + }, + "openAiServiceName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI'), '2025-04-01').outputs.openAIName.value]" + }, + "enablePrivateNetworking": { + "value": "[parameters('enablePrivateNetworking')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "5008986833957108258" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "enableDiagLogging": { + "type": "bool" + }, + "logAnalyticsId": { + "type": "string" + }, + "storageAccount": { + "type": "string" + }, + "openAiServiceName": { + "type": "string" + }, + "enablePrivateNetworking": { + "type": "bool" + } + }, + "resources": [ + { + "type": "Microsoft.VideoIndexer/accounts", + "apiVersion": "2025-04-01", + "name": "[toLower(format('{0}-{1}-video', parameters('appName'), parameters('environment')))]", + "location": "[parameters('location')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), 'Disabled', 'Enabled')]", + "storageServices": { + "resourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccount'))]" + }, + "openAiServices": { + "resourceId": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('openAiServiceName'))]" + } + }, + "tags": "[parameters('tags')]" + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.VideoIndexer/accounts/{0}', toLower(format('{0}-{1}-video', parameters('appName'), parameters('environment'))))]", + "name": "[toLower(format('{0}-diagnostics', toLower(format('{0}-{1}-video', parameters('appName'), parameters('environment')))))]", + "properties": { + "workspaceId": "[parameters('logAnalyticsId')]", + "logs": "[reference(resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs'), '2025-04-01').outputs.limitedLogCategories.value]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'diagnosticConfigs')]", + "[resourceId('Microsoft.VideoIndexer/accounts', toLower(format('{0}-{1}-video', parameters('appName'), parameters('environment'))))]" + ] + }, + { + "condition": "[parameters('enableDiagLogging')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "diagnosticConfigs", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "14992132232820252472" + } + }, + "variables": { + "standardRetentionPolicy": { + "enabled": false, + "days": 0 + }, + "standardLogCategories": [ + { + "categoryGroup": "Audit", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "limitedLogCategories": [ + { + "categoryGroup": "allLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "standardMetricsCategories": [ + { + "category": "AllMetrics", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "transactionMetricsCategories": [ + { + "category": "Transaction", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ], + "webAppLogCategories": [ + { + "category": "AppServiceAntivirusScanAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceHTTPLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceConsoleLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAppLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceFileAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceIPSecAuditLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServicePlatformLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + }, + { + "category": "AppServiceAuthenticationLogs", + "enabled": true, + "retentionPolicy": "[variables('standardRetentionPolicy')]" + } + ] + }, + "resources": [], + "outputs": { + "limitedLogCategories": { + "type": "array", + "value": "[variables('limitedLogCategories')]" + }, + "standardRetentionPolicy": { + "type": "object", + "value": "[variables('standardRetentionPolicy')]" + }, + "standardLogCategories": { + "type": "array", + "value": "[variables('standardLogCategories')]" + }, + "standardMetricsCategories": { + "type": "array", + "value": "[variables('standardMetricsCategories')]" + }, + "transactionMetricsCategories": { + "type": "array", + "value": "[variables('transactionMetricsCategories')]" + }, + "webAppLogCategories": { + "type": "array", + "value": "[variables('webAppLogCategories')]" + } + } + } + } + } + ], + "outputs": { + "videoIndexerServiceName": { + "type": "string", + "value": "[toLower(format('{0}-{1}-video', parameters('appName'), parameters('environment')))]" + }, + "videoIndexerAccountId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.VideoIndexer/accounts', toLower(format('{0}-{1}-video', parameters('appName'), parameters('environment')))), '2025-04-01').accountId]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'logAnalytics')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'storageAccount')]" + ] + }, + { + "condition": "[parameters('configureApplicationPermissions')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "setPermissions", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "webAppName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'appService'), '2025-04-01').outputs.name.value]" + }, + "authenticationType": { + "value": "[parameters('authenticationType')]" + }, + "enterpriseAppServicePrincipalId": { + "value": "[parameters('enterpriseAppServicePrincipalId')]" + }, + "keyVaultName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "cosmosDBName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'cosmosDB'), '2025-04-01').outputs.cosmosDbName.value]" + }, + "acrName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'azureContainerRegistry'), '2025-04-01').outputs.acrName.value]" + }, + "openAIName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI'), '2025-04-01').outputs.openAIName.value]" + }, + "docIntelName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'docIntel'), '2025-04-01').outputs.documentIntelligenceServiceName.value]" + }, + "storageAccountName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'storageAccount'), '2025-04-01').outputs.name.value]" + }, + "searchServiceName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'searchService'), '2025-04-01').outputs.searchServiceName.value]" + }, + "speechServiceName": "[if(parameters('deploySpeechService'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'speechService'), '2025-04-01').outputs.speechServiceName.value), createObject('value', ''))]", + "redisCacheName": "[if(parameters('deployRedisCache'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'redisCache'), '2025-04-01').outputs.redisCacheName.value), createObject('value', ''))]", + "contentSafetyName": "[if(parameters('deployContentSafety'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'contentSafety'), '2025-04-01').outputs.contentSafetyName.value), createObject('value', ''))]", + "videoIndexerName": "[if(parameters('deployVideoIndexerService'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'videoIndexerService'), '2025-04-01').outputs.videoIndexerServiceName.value), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "17165122895470513967" + } + }, + "parameters": { + "webAppName": { + "type": "string" + }, + "authenticationType": { + "type": "string" + }, + "keyVaultName": { + "type": "string" + }, + "enterpriseAppServicePrincipalId": { + "type": "string" + }, + "cosmosDBName": { + "type": "string" + }, + "acrName": { + "type": "string" + }, + "openAIName": { + "type": "string" + }, + "docIntelName": { + "type": "string" + }, + "storageAccountName": { + "type": "string" + }, + "speechServiceName": { + "type": "string" + }, + "searchServiceName": { + "type": "string" + }, + "redisCacheName": { + "type": "string" + }, + "contentSafetyName": { + "type": "string" + }, + "videoIndexerName": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('keyVaultName'))]", + "name": "[guid(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'kv-secrets-user')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[equals(parameters('authenticationType'), 'managed_identity')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('cosmosDBName'))]", + "name": "[guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'cosmos-contributor')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[equals(parameters('authenticationType'), 'managed_identity')]", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", + "apiVersion": "2023-04-15", + "name": "[format('{0}/{1}', parameters('cosmosDBName'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'cosmos-data-contributor'))]", + "properties": { + "roleDefinitionId": "[format('{0}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName')))]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('acrName'))]", + "name": "[guid(resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'acr-pull-role')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[equals(parameters('authenticationType'), 'managed_identity')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('openAIName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAIName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'openai-user')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[equals(parameters('authenticationType'), 'managed_identity')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('openAIName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAIName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'enterpriseApp-CognitiveServicesOpenAIUserRole')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "principalId": "[parameters('enterpriseAppServicePrincipalId')]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[equals(parameters('authenticationType'), 'managed_identity')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('docIntelName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('docIntelName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'doc-intel-user')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[equals(parameters('authenticationType'), 'managed_identity')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'storage-blob-data-contributor')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(equals(parameters('speechServiceName'), '')), equals(parameters('authenticationType'), 'managed_identity'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('speechServiceName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('speechServiceName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'speech-service-user')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[equals(parameters('authenticationType'), 'managed_identity')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('searchServiceName'))]", + "name": "[guid(resourceId('Microsoft.Search/searchServices', parameters('searchServiceName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'search-index-data-contributor')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[equals(parameters('authenticationType'), 'managed_identity')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('searchServiceName'))]", + "name": "[guid(resourceId('Microsoft.Search/searchServices', parameters('searchServiceName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'search-service-contributor')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(equals(parameters('contentSafetyName'), '')), equals(parameters('authenticationType'), 'managed_identity'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('contentSafetyName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('contentSafetyName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'content-safety-user')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[not(equals(parameters('videoIndexerName'), ''))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), resourceId('Microsoft.VideoIndexer/accounts', parameters('videoIndexerName')), 'video-indexer-storage-blob-data-contributor')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalId": "[reference(resourceId('Microsoft.VideoIndexer/accounts', parameters('videoIndexerName')), '2025-04-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[not(equals(parameters('videoIndexerName'), ''))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('openAIName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAIName')), resourceId('Microsoft.VideoIndexer/accounts', parameters('videoIndexerName')), 'video-indexer-cog-services-contributor')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]", + "principalId": "[reference(resourceId('Microsoft.VideoIndexer/accounts', parameters('videoIndexerName')), '2025-04-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[not(equals(parameters('videoIndexerName'), ''))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('openAIName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('openAIName')), resourceId('Microsoft.VideoIndexer/accounts', parameters('videoIndexerName')), 'video-indexer-cog-services-user')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]", + "principalId": "[reference(resourceId('Microsoft.VideoIndexer/accounts', parameters('videoIndexerName')), '2025-04-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[not(equals(parameters('redisCacheName'), ''))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Cache/redis/{0}', parameters('redisCacheName'))]", + "name": "[guid(resourceId('Microsoft.Cache/redis', parameters('redisCacheName')), resourceId('Microsoft.Web/sites', parameters('webAppName')), 'redis-cache-contributor')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e0f68234-74aa-48ed-b826-c38b57376e17')]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('webAppName')), '2022-03-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'azureContainerRegistry')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'appService')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'contentSafety')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'cosmosDB')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'docIntel')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'redisCache')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'searchService')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'speechService')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'storageAccount')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'videoIndexerService')]" + ] + }, + { + "condition": "[parameters('enablePrivateNetworking')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "privateNetworking", + "resourceGroup": "[variables('rgName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualNetworkId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'virtualNetwork'), '2025-04-01').outputs.vNetId.value]" + }, + "privateEndpointSubnetId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'virtualNetwork'), '2025-04-01').outputs.privateNetworkSubnetId.value]" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "tags": { + "value": "[variables('tags')]" + }, + "keyVaultName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "cosmosDBName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'cosmosDB'), '2025-04-01').outputs.cosmosDbName.value]" + }, + "acrName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'azureContainerRegistry'), '2025-04-01').outputs.acrName.value]" + }, + "searchServiceName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'searchService'), '2025-04-01').outputs.searchServiceName.value]" + }, + "docIntelName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'docIntel'), '2025-04-01').outputs.documentIntelligenceServiceName.value]" + }, + "storageAccountName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'storageAccount'), '2025-04-01').outputs.name.value]" + }, + "openAIName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI'), '2025-04-01').outputs.openAIName.value]" + }, + "webAppName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'appService'), '2025-04-01').outputs.name.value]" + }, + "contentSafetyName": "[if(parameters('deployContentSafety'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'contentSafety'), '2025-04-01').outputs.contentSafetyName.value), createObject('value', ''))]", + "speechServiceName": "[if(parameters('deploySpeechService'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'speechService'), '2025-04-01').outputs.speechServiceName.value), createObject('value', ''))]", + "videoIndexerName": "[if(parameters('deployVideoIndexerService'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'videoIndexerService'), '2025-04-01').outputs.videoIndexerServiceName.value), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "3584007603660948740" + } + }, + "parameters": { + "virtualNetworkId": { + "type": "string" + }, + "privateEndpointSubnetId": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "keyVaultName": { + "type": "string" + }, + "cosmosDBName": { + "type": "string" + }, + "acrName": { + "type": "string" + }, + "searchServiceName": { + "type": "string" + }, + "docIntelName": { + "type": "string" + }, + "storageAccountName": { + "type": "string" + }, + "openAIName": { + "type": "string" + }, + "webAppName": { + "type": "string" + }, + "contentSafetyName": { + "type": "string" + }, + "speechServiceName": { + "type": "string" + }, + "videoIndexerName": { + "type": "string" + } + }, + "variables": { + "$fxv#0": { + "azurecloud": { + "aisearch": "privatelink.search.windows.net", + "blobStorage": "privatelink.blob.core.windows.net", + "cognitiveServices": "privatelink.cognitiveservices.azure.com", + "containerRegistry": "privatelink.azurecr.io", + "cosmosDb": "privatelink.documents.azure.com", + "keyVault": "privatelink.vaultcore.azure.net", + "openAi": "privatelink.openai.azure.com", + "webSites": "privatelink.azurewebsites.net" + }, + "azureusgovernment": { + "aisearch": "privatelink.search.azure.us", + "blobStorage": "privatelink.blob.core.usgovcloudapi.net", + "cognitiveServices": "privatelink.cognitiveservices.azure.us", + "containerRegistry": "privatelink.azurecr.us", + "cosmosDb": "privatelink.documents.azure.us", + "keyVault": "privatelink.vaultcore.azure.us", + "openAi": "privatelink.openai.azure.us", + "webSites": "privatelink.azurewebsites.us" + } + }, + "cloudName": "[toLower(environment().name)]", + "privateDnsZoneData": "[variables('$fxv#0')]", + "aiSearchDnsZoneName": "[variables('privateDnsZoneData')[variables('cloudName')].aisearch]", + "blobStorageDnsZoneName": "[variables('privateDnsZoneData')[variables('cloudName')].blobStorage]", + "cognitiveServicesDnsZoneName": "[variables('privateDnsZoneData')[variables('cloudName')].cognitiveServices]", + "containerRegistryDnsZoneName": "[variables('privateDnsZoneData')[variables('cloudName')].containerRegistry]", + "cosmosDbDnsZoneName": "[variables('privateDnsZoneData')[variables('cloudName')].cosmosDb]", + "keyVaultDnsZoneName": "[variables('privateDnsZoneData')[variables('cloudName')].keyVault]", + "openAiDnsZoneName": "[variables('privateDnsZoneData')[variables('cloudName')].openAi]", + "webSitesDnsZoneName": "[variables('privateDnsZoneData')[variables('cloudName')].webSites]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "keyVaultDNSZone", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "zoneName": { + "value": "[variables('keyVaultDnsZoneName')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "name": { + "value": "kv" + }, + "vNetId": { + "value": "[parameters('virtualNetworkId')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "6963784194716310503" + } + }, + "parameters": { + "zoneName": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "vNetId": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('zoneName')]", + "location": "global", + "tags": "[parameters('tags')]" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('zoneName'), toLower(format('{0}-{1}-{2}-pe-dnszonelink', parameters('appName'), parameters('environment'), parameters('name'))))]", + "location": "global", + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('vNetId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + ] + } + ], + "outputs": { + "privateDnsZoneId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + }, + "privateDnsZoneName": { + "type": "string", + "value": "[parameters('zoneName')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "keyVaultPE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "kv" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "vault" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'keyVaultDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'keyVaultDNSZone')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "cosmosDbDNSZone", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "zoneName": { + "value": "[variables('cosmosDbDnsZoneName')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "name": { + "value": "cosmosDb" + }, + "vNetId": { + "value": "[parameters('virtualNetworkId')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "6963784194716310503" + } + }, + "parameters": { + "zoneName": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "vNetId": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('zoneName')]", + "location": "global", + "tags": "[parameters('tags')]" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('zoneName'), toLower(format('{0}-{1}-{2}-pe-dnszonelink', parameters('appName'), parameters('environment'), parameters('name'))))]", + "location": "global", + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('vNetId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + ] + } + ], + "outputs": { + "privateDnsZoneId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + }, + "privateDnsZoneName": { + "type": "string", + "value": "[parameters('zoneName')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "cosmosDbPE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "cosmosDb" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "sql" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'cosmosDbDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'cosmosDbDNSZone')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "acrDNSZone", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "zoneName": { + "value": "[variables('containerRegistryDnsZoneName')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "name": { + "value": "acr" + }, + "vNetId": { + "value": "[parameters('virtualNetworkId')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "6963784194716310503" + } + }, + "parameters": { + "zoneName": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "vNetId": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('zoneName')]", + "location": "global", + "tags": "[parameters('tags')]" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('zoneName'), toLower(format('{0}-{1}-{2}-pe-dnszonelink', parameters('appName'), parameters('environment'), parameters('name'))))]", + "location": "global", + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('vNetId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + ] + } + ], + "outputs": { + "privateDnsZoneId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + }, + "privateDnsZoneName": { + "type": "string", + "value": "[parameters('zoneName')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "acrPE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "acr" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "registry" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'acrDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'acrDNSZone')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "searchServiceDNSZone", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "zoneName": { + "value": "[variables('aiSearchDnsZoneName')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "name": { + "value": "searchService" + }, + "vNetId": { + "value": "[parameters('virtualNetworkId')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "6963784194716310503" + } + }, + "parameters": { + "zoneName": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "vNetId": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('zoneName')]", + "location": "global", + "tags": "[parameters('tags')]" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('zoneName'), toLower(format('{0}-{1}-{2}-pe-dnszonelink', parameters('appName'), parameters('environment'), parameters('name'))))]", + "location": "global", + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('vNetId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + ] + } + ], + "outputs": { + "privateDnsZoneId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + }, + "privateDnsZoneName": { + "type": "string", + "value": "[parameters('zoneName')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "searchServicePE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "searchService" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.Search/searchServices', parameters('searchServiceName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "searchService" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'searchServiceDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'searchServiceDNSZone')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "docIntelDNSZone", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "zoneName": { + "value": "[variables('cognitiveServicesDnsZoneName')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "name": { + "value": "docIntelService" + }, + "vNetId": { + "value": "[parameters('virtualNetworkId')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "6963784194716310503" + } + }, + "parameters": { + "zoneName": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "vNetId": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('zoneName')]", + "location": "global", + "tags": "[parameters('tags')]" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('zoneName'), toLower(format('{0}-{1}-{2}-pe-dnszonelink', parameters('appName'), parameters('environment'), parameters('name'))))]", + "location": "global", + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('vNetId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + ] + } + ], + "outputs": { + "privateDnsZoneId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + }, + "privateDnsZoneName": { + "type": "string", + "value": "[parameters('zoneName')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "docIntelPE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "docIntelService" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('docIntelName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "account" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'docIntelDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'docIntelDNSZone')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storageAccountDNSZone", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "zoneName": { + "value": "[variables('blobStorageDnsZoneName')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "name": { + "value": "storage" + }, + "vNetId": { + "value": "[parameters('virtualNetworkId')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "6963784194716310503" + } + }, + "parameters": { + "zoneName": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "vNetId": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('zoneName')]", + "location": "global", + "tags": "[parameters('tags')]" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('zoneName'), toLower(format('{0}-{1}-{2}-pe-dnszonelink', parameters('appName'), parameters('environment'), parameters('name'))))]", + "location": "global", + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('vNetId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + ] + } + ], + "outputs": { + "privateDnsZoneId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + }, + "privateDnsZoneName": { + "type": "string", + "value": "[parameters('zoneName')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "storageAccountPE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "storageAccount" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "blob" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'storageAccountDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'storageAccountDNSZone')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "openAiDNSZone", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "zoneName": { + "value": "[variables('openAiDnsZoneName')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "name": { + "value": "openAiService" + }, + "vNetId": { + "value": "[parameters('virtualNetworkId')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "6963784194716310503" + } + }, + "parameters": { + "zoneName": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "vNetId": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('zoneName')]", + "location": "global", + "tags": "[parameters('tags')]" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('zoneName'), toLower(format('{0}-{1}-{2}-pe-dnszonelink', parameters('appName'), parameters('environment'), parameters('name'))))]", + "location": "global", + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('vNetId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + ] + } + ], + "outputs": { + "privateDnsZoneId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + }, + "privateDnsZoneName": { + "type": "string", + "value": "[parameters('zoneName')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "openAiPE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "openAiService" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('openAIName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "account" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'openAiDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'openAiDNSZone')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "webAppDNSZone", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "zoneName": { + "value": "[variables('webSitesDnsZoneName')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "name": { + "value": "webApp" + }, + "vNetId": { + "value": "[parameters('virtualNetworkId')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "6963784194716310503" + } + }, + "parameters": { + "zoneName": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "vNetId": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('zoneName')]", + "location": "global", + "tags": "[parameters('tags')]" + }, + { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('zoneName'), toLower(format('{0}-{1}-{2}-pe-dnszonelink', parameters('appName'), parameters('environment'), parameters('name'))))]", + "location": "global", + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('vNetId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + ] + } + ], + "outputs": { + "privateDnsZoneId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('zoneName'))]" + }, + "privateDnsZoneName": { + "type": "string", + "value": "[parameters('zoneName')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "webAppPE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "webApp" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.Web/sites', parameters('webAppName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "sites" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'webAppDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'webAppDNSZone')]" + ] + }, + { + "condition": "[not(equals(parameters('contentSafetyName'), ''))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "contentSafetyPE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "contentSafety" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('contentSafetyName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "account" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'docIntelDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'docIntelDNSZone')]" + ] + }, + { + "condition": "[not(equals(parameters('speechServiceName'), ''))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "speechServicePE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "speechService" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('speechServiceName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "account" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'docIntelDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'docIntelDNSZone')]" + ] + }, + { + "condition": "[not(equals(parameters('videoIndexerName'), ''))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "videoIndexerPE", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "videoIndexerService" + }, + "location": { + "value": "[parameters('location')]" + }, + "appName": { + "value": "[parameters('appName')]" + }, + "environment": { + "value": "[parameters('environment')]" + }, + "serviceResourceID": { + "value": "[resourceId('Microsoft.VideoIndexer/accounts', parameters('videoIndexerName'))]" + }, + "subnetId": { + "value": "[parameters('privateEndpointSubnetId')]" + }, + "groupIDs": { + "value": [ + "account" + ] + }, + "privateDnsZoneIds": { + "value": [ + "[reference(resourceId('Microsoft.Resources/deployments', 'docIntelDNSZone'), '2025-04-01').outputs.privateDnsZoneId.value]" + ] + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.33.27573", + "templateHash": "1838240609393686445" + } + }, + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "appName": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "serviceResourceID": { + "type": "string" + }, + "subnetId": { + "type": "string" + }, + "groupIDs": { + "type": "array" + }, + "privateDnsZoneIds": { + "type": "array", + "defaultValue": [] + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "condition": "[greater(length(parameters('privateDnsZoneIds')), 0)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-05-01", + "name": "[format('{0}/{1}', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))), 'default')]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneIds'))]", + "input": { + "name": "[last(split(parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name'))))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2021-05-01", + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('subnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[toLower(format('{0}-{1}-{2}-pe', parameters('appName'), parameters('environment'), parameters('name')))]", + "properties": { + "privateLinkServiceId": "[parameters('serviceResourceID')]", + "groupIds": "[parameters('groupIDs')]" + } + } + ], + "customNetworkInterfaceName": "[toLower(format('{0}-{1}-{2}-nic', parameters('appName'), parameters('environment'), parameters('name')))]" + }, + "tags": "[parameters('tags')]" + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'docIntelDNSZone')]" + ] + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'azureContainerRegistry')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'appService')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'contentSafety')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'cosmosDB')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'docIntel')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI')]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName'))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'searchService')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'speechService')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'storageAccount')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'videoIndexerService')]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'virtualNetwork')]" + ] + } + ], + "outputs": { + "var_acrName": { + "type": "string", + "value": "[toLower(format('{0}{1}acr', parameters('appName'), parameters('environment')))]" + }, + "var_authenticationType": { + "type": "string", + "value": "[toLower(parameters('authenticationType'))]" + }, + "var_blobStorageEndpoint": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'storageAccount'), '2025-04-01').outputs.endpoint.value]" + }, + "var_configureApplication": { + "type": "bool", + "value": "[parameters('configureApplicationPermissions')]" + }, + "var_contentSafetyEndpoint": { + "type": "string", + "value": "[if(parameters('deployContentSafety'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'contentSafety'), '2025-04-01').outputs.contentSafetyEndpoint.value, '')]" + }, + "var_cosmosDb_accountName": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'cosmosDB'), '2025-04-01').outputs.cosmosDbName.value]" + }, + "var_cosmosDb_uri": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'cosmosDB'), '2025-04-01').outputs.cosmosDbUri.value]" + }, + "var_deploymentLocation": { + "type": "string", + "value": "[reference(subscriptionResourceId('Microsoft.Resources/resourceGroups', variables('rgName')), '2022-09-01', 'full').location]" + }, + "var_documentIntelligenceServiceEndpoint": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'docIntel'), '2025-04-01').outputs.documentIntelligenceServiceEndpoint.value]" + }, + "var_keyVaultName": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultName.value]" + }, + "var_keyVaultUri": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'keyVault'), '2025-04-01').outputs.keyVaultUri.value]" + }, + "var_openAIEndpoint": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI'), '2025-04-01').outputs.openAIEndpoint.value]" + }, + "var_openAIGPTModels": { + "type": "array", + "value": "[parameters('gptModels')]" + }, + "var_openAIResourceGroup": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'openAI'), '2025-04-01').outputs.openAIResourceGroup.value]" + }, + "var_openAIEmbeddingModels": { + "type": "array", + "value": "[parameters('embeddingModels')]" + }, + "var_redisCacheHostName": { + "type": "string", + "value": "[if(parameters('deployRedisCache'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'redisCache'), '2025-04-01').outputs.redisCacheHostName.value, '')]" + }, + "var_rgName": { + "type": "string", + "value": "[variables('rgName')]" + }, + "var_searchServiceEndpoint": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'searchService'), '2025-04-01').outputs.searchServiceEndpoint.value]" + }, + "var_speechServiceEndpoint": { + "type": "string", + "value": "[if(parameters('deploySpeechService'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'speechService'), '2025-04-01').outputs.speechServiceEndpoint.value, '')]" + }, + "var_subscriptionId": { + "type": "string", + "value": "[subscription().subscriptionId]" + }, + "var_videoIndexerAccountId": { + "type": "string", + "value": "[if(parameters('deployVideoIndexerService'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'videoIndexerService'), '2025-04-01').outputs.videoIndexerAccountId.value, '')]" + }, + "var_videoIndexerName": { + "type": "string", + "value": "[if(parameters('deployVideoIndexerService'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'videoIndexerService'), '2025-04-01').outputs.videoIndexerServiceName.value, '')]" + }, + "var_containerRegistry": { + "type": "string", + "value": "[variables('containerRegistry')]" + }, + "var_imageName": { + "type": "string", + "value": "[if(contains(parameters('imageName'), ':'), split(parameters('imageName'), ':')[0], parameters('imageName'))]" + }, + "var_imageTag": { + "type": "string", + "value": "[split(parameters('imageName'), ':')[1]]" + }, + "var_webService": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('rgName')), 'Microsoft.Resources/deployments', 'appService'), '2025-04-01').outputs.name.value]" + }, + "var_enablePrivateNetworking": { + "type": "bool", + "value": "[parameters('enablePrivateNetworking')]" + } + } +} \ No newline at end of file From 095c2b3b66118696e765f73bb05ecbb1cdaca6b1 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Fri, 16 Jan 2026 21:31:43 +0000 Subject: [PATCH 17/19] fix: correct links and typos in README.md for improved clarity and navigation --- deployers/bicep/README.md | 149 ++++++++++++++------------------------ 1 file changed, 54 insertions(+), 95 deletions(-) diff --git a/deployers/bicep/README.md b/deployers/bicep/README.md index c2c51bf0..90a82c88 100644 --- a/deployers/bicep/README.md +++ b/deployers/bicep/README.md @@ -3,21 +3,21 @@ >Strongly encourage administrators to use Visual Studio Code and Dev Containers for this deployment type. ## Table of Contents
-- [Deployment Variables](##Deployment_Variables) -- [Deployment Process](##Deployment_Process) - - [Pre-Configuration](###Pre-Configuration) - - [Create the application registration](####Create_the_application_registration) - - [Deployment Process](###Deployment_Process) - - [Configure AZD Environment](####Configure_AZD_Environment) - - [Deployment Prompts](####Deployment_Prompts) - - [Post Deployment Tasks](###Post_Deployment_Tasks) -- [Cleanup / Deprovision](##Cleanup_/_Deprovisioning) -- [Workarounds](##Workarounds) +- [Deployment Variables](#Deployment-Variables) +- [Deployment Process](#Deployment-Process) + - [Pre-Configuration](#Pre-Configuration) + - [Create the application registration](#Create-the-application-registration) + - [Deployment Process](#Deployment-Process-1) + - [Configure AZD Environment](#Configure-AZD-Environment) + - [Deployment Prompts](#Deployment-Prompts) + - [Post Deployment Tasks](#Post-Deployment-Tasks) +- [Cleanup / Deprovision](#Cleanup-/-Deprovisioning) +- [Helpful Info](#Helpful-Info) --- ## Deployment Variables -The folloiwng variables will be used within this document: +The following variables will be used within this document: - *\* - This will become the beginning of each of the objects created. Minimum of 3 characters, maximum of 12 characters. No Spaces or special characters. - *\* - This will be used as part of the object names as well as with the AZD environments. **Example:** *dev/qa/prod*. @@ -31,7 +31,7 @@ The below steps cover the process to deploy the Simple Chat application to an Az ### Pre-Configuration: -The following procedure must be completed with a user that has permissions to create an application registration in the users Entra tenanat. If this procedure is to be completed by a different user, the following files should be provided: +The following procedure must be completed with a user that has permissions to create an application registration in the users Entra tenant. If this procedure is to be completed by a different user, the following files should be provided: `./deployers/Initialize-EntraApplication.ps1`
`./deployers/azurecli/appRegistrationRoles.json` @@ -95,17 +95,30 @@ Using the bash terminal in Visual Studio Code `azd env new ` - Use the same value for the \ that was used in the application registration. -`azd env select ` - select the new environment +`azd env select ` - select the new environment. + +`azd provision --preview` - identify what will be deployed with the current configuration. `azd up` - This step will begin the deployment process. +#### Service Limitations of USGovCloud + +- Services not presently available in US Gov Region + - Azure Video Indexer + +- Notes: + - Verify the gptModels and embeddingModels skuName and versions to ensure selected region availablity. Deployment may override the default models. + - Limited region availablity for the following services: + - ContentSafety + - SpeechService + - DocumentIntelligence + #### Deployment Prompts > For each of the following parameters ensure the value noted in *\* matches settings as noted above. - - Select an Azure Subscription to use: *\* @@ -127,33 +142,29 @@ On the completion of the deployment, a URL will be presented, the user may use t ### Post Deployment Tasks: -Once logged in to the newly deployed application with admin credentials, the application will need to be set up with several configurations: +Once logged in to the newly deployed application with admin credentials, review the application configuration in the Admin Settings: + +1. Admin Settings > AI Models > GPT Configuration & Embeddings Configuration. Application is pre-configured with the chosen security model (key / managed identity). Select "Test GPT Connection" and "Test Embedding Connection" to verify connection. + +1. Admin Settings > Scale > Redis Cache (if enabled) - Select "Test Redis Connection" -1. AI Models > GPT Configuration & Embeddings Configuration. Application is pre-configured with the chosen security model (key / managed identity). Select "Test GPT Connection" and "Test Embedding Connection" to verify connection. +1. Admin Settings > Workspaces > Multi-Modal Vision Analysis - Select "Test Vision Analysis" - > Known Bug: User will be unable to Fetch GPT or Embedding models.
-Workaround: Set configurations in CosmosDB. For details see [Workarounds](##Workarounds) below. +1. Admin Settings > Search & Extract > Azure AI Search + > Known Bug: Unable to test "Managed Identity" authentication type. Must use "Key" for validation but application will run under Managed Identity" -1. Logging > Application Insights Logging > "Enable Application Insights Global Logging - Set to "ON" -1. Citations > Ehnahced Citations > "Enable Enhanced Citations" - Set to "ON" - - Configure "All Filetypes" - - "Storage Account Authentication Type" = Managed Identity - - "Storage Account Blob Endpoint" = "https://\\sa.blob.core.windows.net" (or appropiate domain if in Azure Gov.) -1. Safety > Conversation Archiving > "Enable Conversation Archiving" - Set to "ON" -1. Search & Extract > Azure AI Search - - "Search Endpoint" = "https://\-\-search.search.windows.net" (or appropiate domain if in Azure Gov.) - > Known Bug: Unable to configure "Managed Identity" authentication type. Must use "Key" - "Authentication Type" - Key - "Search Key" - *Pre-populated from key vault value*. - At the top of the Admin Page you'll see warning boxes indicating Index Schema Mismatch. - Click "Create user Index" - Click "Create group Index" - - Click "Create public Index" -1. Search & Extract > Document Intelligence - - "Document Intelligence Endpoint" = "https://\-\-docintel.cognitiveservices.azure.com/" (or appropiate domain if in Azure Gov.) - - "Authentication Type" - Managed Identity + - Click "Create public Index" + - Select "Test Azure AI Search Connection" -User shoud now be able to fully use Simple Chat application. +1. Search & Extract > Document Intelligence - Select "Test Document Intelligence Connection" + + +User should now be able to fully use Simple Chat application. --- ## Cleanup / Deprovisioning @@ -163,67 +174,15 @@ User shoud now be able to fully use Simple Chat application. `cd ./deployers`
`azd down --purge` - This will delete all deployed resource for this solution and purge key vault, document intelligence, OpenAI services. - --- -## Workarounds - -- Fetching GPT and Embedding Models. - - Grant the current user data access to Cosmos DB from a BASH command shell - - `PRINCIPAL_ID=$(az ad signed-in-user show --query id --output tsv)` - - `az cosmosdb sql role assignment create --account-name --cosmos --resource-group --rg --principal-id $PRINCIPAL_ID --scope "/" --role-definition-id 00000000-0000-0000-0000-000000000002` - - Open CosmosDB in Azure Portal and connect to the `--cosmos` service. - - Data Explorer > SimpleChat > settings > items - - Replace the following values: - ``` - "gpt_model": { - "selected": [], - "all": [] - }, - ``` - - with - - ``` - "gpt_model": { - "selected": [ - { - "deploymentName": "gpt-4o", - "modelName": "gpt-4o" - } - ], - "all": [ - { - "deploymentName": "gpt-4o", - "modelName": "gpt-4o" - } - ] - }, - ``` - - and - - ``` - "embedding_model": { - "selected": [], - "all": [] - }, - ``` - - with - - ``` - "embedding_model": { - "selected": [ - "deploymentName": "text-embedding-3-small", - "modelName": "text-embedding-3-small" - ], - "all": [ - "deploymentName": "text-embedding-3-small", - "modelName": "text-embedding-3-small" - ] - }, - ``` - - - Update settings in the Cosmos UI and click Save. - - Refresh web page and you shound now be able to Test the GPT and Embedding models. +## Helpful Info + +- If Key based authentication is selected, ensure keys are rotated per organizational requirements. + +- If a deployment failure is encountered, often times, rerunning the deployment will clear the temporary error. + +- For CosmosDB, when private networking is selected, by default during deployment, the users local IP address and 0.0.0.0 (representing the internal Azure Services) is added to the firewall. This becomes not-applicable on completion of the deployment when CosmosDB is configured for private networking only. + +- To evaluate any infrastructure changes between versions, with AZD the user can run: +`azd provision --preview` From 456751e9303f52a2077c55c751e6c0f1a69463af Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Tue, 20 Jan 2026 19:54:08 +0000 Subject: [PATCH 18/19] fix incorrect postup step numbering. Fix disabling public network access for webapp if private network is enabled --- deployers/azure.yaml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/deployers/azure.yaml b/deployers/azure.yaml index 2fb2c025..4ab51aa9 100644 --- a/deployers/azure.yaml +++ b/deployers/azure.yaml @@ -212,7 +212,7 @@ hooks: echo "Configuring private networking..." echo "" - echo "[1/3] Disabling public network access for CosmosDB..." + echo "[1/4] Disabling public network access for CosmosDB..." if az cosmosdb update --name ${var_cosmosDb_accountName} \ --resource-group ${var_rgName} \ --public-network-access Disabled > /dev/null; then @@ -223,7 +223,7 @@ hooks: fi echo "" - echo "[2/3] Disabling public network access for Key Vault..." + echo "[2/4] Disabling public network access for Key Vault..." if az keyvault update --name ${var_keyVaultName} \ --resource-group ${var_rgName} \ --public-network-access Disabled > /dev/null; then @@ -234,7 +234,7 @@ hooks: fi echo "" - echo "[3/3] Disabling public network access for Azure Container Registry..." + echo "[3/4] Disabling public network access for Azure Container Registry..." if az acr update --name ${var_acrName} \ --resource-group ${var_rgName} \ --public-network-enabled false > /dev/null; then @@ -244,17 +244,17 @@ hooks: exit 1 fi - # Uncomment when ready to disable web app public access - # echo "" - # echo "[4/4] Disabling public network access for Web Application..." - # if az webapp update --name ${var_webService} \ - # --resource-group ${var_rgName} \ - # --public-network-access Disabled; then - # echo "✓ Web Application public access disabled" - # else - # echo "✗ ERROR: Failed to disable Web Application public access" >&2 - # exit 1 - # fi + echo "" + echo "[4/4] Disabling public network access for Web Application..." + if az resource update --name ${var_webService} \ + --resource-group ${var_rgName} \ + --resource-type "Microsoft.Web/sites" \ + --set properties.publicNetworkAccess=Disabled; then + echo "✓ Web Application public access disabled" + else + echo "✗ ERROR: Failed to disable Web Application public access" >&2 + exit 1 + fi echo "" echo "✓ Private networking configured successfully" From 47a3381b662d8c85b97c944c0199e253c678d773 Mon Sep 17 00:00:00 2001 From: SteveCInVA Date: Tue, 20 Jan 2026 19:55:21 +0000 Subject: [PATCH 19/19] Fix to support prompting of allowedIPaddresses and enable setting allowed IP addresses for ACR. --- deployers/bicep/README.md | 248 +++++++++++++++++- deployers/bicep/main.bicep | 32 ++- .../modules/azureContainerRegistry.bicep | 5 + 3 files changed, 263 insertions(+), 22 deletions(-) diff --git a/deployers/bicep/README.md b/deployers/bicep/README.md index 90a82c88..0cf883af 100644 --- a/deployers/bicep/README.md +++ b/deployers/bicep/README.md @@ -4,6 +4,7 @@ ## Table of Contents
- [Deployment Variables](#Deployment-Variables) +- [Prerequisites](#Prerequisites) - [Deployment Process](#Deployment-Process) - [Pre-Configuration](#Pre-Configuration) - [Create the application registration](#Create-the-application-registration) @@ -13,6 +14,10 @@ - [Post Deployment Tasks](#Post-Deployment-Tasks) - [Cleanup / Deprovision](#Cleanup-/-Deprovisioning) - [Helpful Info](#Helpful-Info) + - [Private Networking](#Private-Networking) +- [Azure Government (USGov) Considerations](#Azure-Government-USGov-Considerations) +- [Frequently Asked Questions](#Frequently-Asked-Questions) +- [Troubleshooting](#Troubleshooting) --- @@ -24,6 +29,31 @@ The following variables will be used within this document: - *\* - Options will be *AzureCloud | AzureUSGovernment* - *\* - Should be presented in the form *imageName:label* **Example:** *simple-chat:latest* +--- + +## Prerequisites + +Before deploying, ensure you have: + +1. **Azure Subscription** with Owner or Contributor permissions +2. **Azure CLI** (version 2.50.0 or later) +3. **Azure Developer CLI (azd)** (version 1.5.0 or later) +4. **Docker** installed and running (for container builds) +5. **PowerShell** (for the Entra app registration script) +6. **Permissions to create an Entra ID Application Registration** (or coordinate with your Entra admin) + +### Required Azure Resource Providers +Ensure the following resource providers are registered in your subscription: +- `Microsoft.Web` +- `Microsoft.DocumentDB` +- `Microsoft.CognitiveServices` +- `Microsoft.Search` +- `Microsoft.Storage` +- `Microsoft.KeyVault` +- `Microsoft.ContainerRegistry` +- `Microsoft.Insights` +- `Microsoft.OperationalInsights` + ## Deployment Process @@ -103,20 +133,53 @@ Using the bash terminal in Visual Studio Code #### Service Limitations of USGovCloud -- Services not presently available in US Gov Region - - Azure Video Indexer - -- Notes: - - Verify the gptModels and embeddingModels skuName and versions to ensure selected region availablity. Deployment may override the default models. - - Limited region availablity for the following services: - - ContentSafety - - SpeechService - - DocumentIntelligence +> ⚠️ **Important:** Review this section carefully before deploying to Azure Government. + +- **Services NOT available in Azure Government:** + - Azure Video Indexer - Set `deployVideoIndexerService` to `false` + +- **SKU Restrictions:** + - **GlobalStandard SKU is NOT available** - Azure OpenAI models must use `Standard` SKU instead + - Default deployment uses `GlobalStandard` - override `gptModels` and `embeddingModels` parameters + +- **Model Availability:** + - Verify the `gptModels` and `embeddingModels` model names and versions are available in your target USGov region + - Model availability may differ from Azure Commercial - check [Azure OpenAI Service models](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models) + +- **Limited Regional Availability:** + - ContentSafety - typically only USGov Virginia, USGov Arizona + - SpeechService - verify feature availability (Neural voices may be limited) + - DocumentIntelligence - prebuilt models may differ + +**Example USGov Model Configuration Override:** +```json +{ + "gptModels": [ + { + "modelName": "gpt-4o", + "modelVersion": "2024-05-13", + "skuName": "Standard", + "skuCapacity": 100 + } + ], + "embeddingModels": [ + { + "modelName": "text-embedding-ada-002", + "modelVersion": "2", + "skuName": "Standard", + "skuCapacity": 100 + } + ] +} +``` #### Deployment Prompts > For each of the following parameters ensure the value noted in *\* matches settings as noted above. +> If you are unsure what a parameter is used for, see specific help for each parameter by entering "?" at that prompt. + - Select an Azure Subscription to use: *\* -Provisioning may take between 10-40 minutes depending on the options selected. +Provisioning may take between 5-40 minutes depending on the options selected. On the completion of the deployment, a URL will be presented, the user may use to access the site. @@ -181,8 +244,171 @@ User should now be able to fully use Simple Chat application. - If a deployment failure is encountered, often times, rerunning the deployment will clear the temporary error. -- For CosmosDB, when private networking is selected, by default during deployment, the users local IP address and 0.0.0.0 (representing the internal Azure Services) is added to the firewall. This becomes not-applicable on completion of the deployment when CosmosDB is configured for private networking only. +- When private networking is selected, 0.0.0.0 (representing the internal Azure Services) is added to the CosmosDB firewall in addition to any IP's added to the 'allowedIpAddresses' parameter. Users are encouraged to include the IP address of the deployment server in the 'allowedIpAddresses. This becomes not-applicable on completion of the deployment when CosmosDB, Key Vault, Azure Container Registry and the Web Application is configured for private networking only. If the 'allowedIpAddresses parameter is not used, the administrator can manually add in the deployment server IP address to the Settings > Networking section of the coresponding service(s) and rerun the deployment. - To evaluate any infrastructure changes between versions, with AZD the user can run: `azd provision --preview` +### Private Networking + +When private networking is configured, access from the developers workstation to push updates and new Azure configurations will be blocked. In addition, testing the web application when not on a VPN attached to the private network subnet is expected to be blocked. + +During initial deployment, if post an error is raised "failed running post hooks: 'postprovision'" the deployment is being blocked from executing scripts against the CosmosDB service. Ensure the deployment workstation IP address is added to the "allowedIPAddresses" parameter. Similar messages may be seen from the Azure Container Registry Service. + +When private networking is enabled, to test the web applicaiton, users may configure a VPN into the deployed vNet (space is provided for this) or the administration may adjust the networking limitations to the deployed website. This may be accomplished with the following script: + +`az webapp update --name --app --resource-group --rg --public-network-access Enabled;` + +To permit redeployment of Azure infrastructure services, the following script may be used to enable access when private networking is enabled. + +``` +az cosmosdb update --name --cosmos --resource-group --rg --public-network-access enabled +az keyvault update --name --kv --resource-group --rg --public-network-access enabled +az acr update --name acr --resource-group --rg --public-network-enabled true +az resource update --name --app --resource-group --rg --resource-type "Microsoft.Web/sites" --set properties.publicNetworkAccess=Enabled +``` + +--- + +## Azure Government (USGov) Considerations + +### Services Deployed + +| Service | Azure Commercial | Azure Government | Notes | +|---------|------------------|------------------|-------| +| App Service | ✅ | ✅ | Premium V3 tier | +| Cosmos DB | ✅ | ✅ | Serverless mode | +| Azure OpenAI | ✅ | ✅ | Standard SKU only in USGov | +| Azure AI Search | ✅ | ✅ | Basic tier | +| Document Intelligence | ✅ | ✅ | Limited regions | +| Storage Account | ✅ | ✅ | Standard LRS | +| Key Vault | ✅ | ✅ | Standard tier | +| Container Registry | ✅ | ✅ | Basic tier | +| Application Insights | ✅ | ✅ | | +| Log Analytics | ✅ | ✅ | | +| Content Safety | ✅ | ⚠️ Limited | Not all regions | +| Speech Service | ✅ | ⚠️ Limited | Feature restrictions | +| Video Indexer | ✅ | ❌ Not Available | | +| Redis Cache | ✅ | ✅ | Standard tier | + +### Endpoint Differences + +The deployment automatically handles the following endpoint differences: +- ACR Domain: `.azurecr.io` → `.azurecr.us` +- Entra Login: `login.microsoftonline.com` → `login.microsoftonline.us` +- OpenID Issuer: `sts.windows.net` → `login.microsoftonline.us` +- Private DNS Zones: Automatically configured for USGov + +--- + +## Frequently Asked Questions + +### General Questions + +**Q: How long does deployment take?** +A: Initial deployment typically takes 15-40 minutes depending on options selected. Subsequent deployments are faster. + +**Q: What Azure permissions do I need?** +A: You need Owner or Contributor role on the target subscription, plus ability to create Entra ID app registrations (or work with your Entra admin). + +**Q: Can I deploy to an existing resource group?** +A: No, the deployment creates a new resource group named `--rg`. + +**Q: What is the default authentication type?** +A: You can choose between `key` (API keys stored in Key Vault) or `managed_identity` (recommended for production). + +### Model Configuration + +**Q: How do I customize which GPT models are deployed?** +A: Override the `gptModels` parameter with your desired configuration: +```json +[ + { + "modelName": "gpt-4o", + "modelVersion": "2024-11-20", + "skuName": "GlobalStandard", + "skuCapacity": 100 + } +] +``` + +**Q: What's the difference between GlobalStandard and Standard SKU?** +A: `GlobalStandard` provides access to Azure's global AI infrastructure with higher availability but is not available in Azure Government. `Standard` is region-specific and is required for USGov deployments. + +### Networking + +**Q: Can I deploy without private networking initially and add it later?** +A: Yes, set `enablePrivateNetworking` to `false` initially. You can enable it later but this requires re-running the deployment. + +**Q: Why do I need to add my IP address to allowedIpAddresses?** +A: During deployment, scripts need to access Cosmos DB and other services. Your IP must be allowed through the firewall temporarily. + +### Costs + +**Q: What's the estimated monthly cost?** +A: Base infrastructure (without optional services) costs approximately: +- App Service Plan (P1v3): ~$150/month +- Cosmos DB (Serverless): Pay-per-request +- Azure OpenAI: Pay-per-token +- Azure AI Search (Basic): ~$70/month +- Other services: Variable based on usage + +### Upgrading + +**Q: How do I upgrade to a new version?** +A: Run `azd up` again from the updated codebase. Use `azd provision --preview` to review changes first. + +--- + +## Troubleshooting + +### Common Deployment Errors + +**Error: "failed running post hooks: 'postprovision'"** +- **Cause:** Deployment scripts cannot access Cosmos DB or other services +- **Solution:** Add your IP address to the `allowedIpAddresses` parameter and redeploy + +**Error: "The subscription is not registered to use namespace 'Microsoft.CognitiveServices'"** +- **Cause:** Required resource provider not registered +- **Solution:** Run `az provider register --namespace Microsoft.CognitiveServices` + +**Error: "Quota exceeded for deployment"** +- **Cause:** Azure OpenAI quota limits reached +- **Solution:** Request quota increase or reduce `skuCapacity` in model configuration + +**Error: "InvalidTemplateDeployment - GlobalStandard SKU not available"** +- **Cause:** Attempting USGov deployment with GlobalStandard SKU +- **Solution:** Use `Standard` SKU for all models in Azure Government + +**Error: "Resource 'Microsoft.VideoIndexer/accounts' not found"** +- **Cause:** Video Indexer not available in region (especially USGov) +- **Solution:** Set `deployVideoIndexerService` to `false` + +### Post-Deployment Issues + +**Issue: Cannot access the web application** +- Verify the Entra app registration is configured correctly +- Check that admin consent was granted for API permissions +- Ensure users are assigned to the enterprise application + +**Issue: "Test Connection" fails in Admin Settings** +- For Managed Identity: Wait 5-10 minutes for role assignments to propagate +- For Key Authentication: Verify secrets exist in Key Vault +- Check Application Insights for detailed error logs + +**Issue: AI Search shows "Index Schema Mismatch"** +- This is expected on first deployment +- Click "Create user Index", "Create group Index", "Create public Index" in Admin Settings + +### Logs and Diagnostics + +Enable diagnostic logging by setting `enableDiagLogging` to `true`. Logs are sent to: +- Log Analytics Workspace: `--logs` +- Application Insights: `--ai` + +View application logs: +```bash +az webapp log tail --name --app --resource-group --rg +``` + + diff --git a/deployers/bicep/main.bicep b/deployers/bicep/main.bicep index b0fa9b80..b336aee9 100644 --- a/deployers/bicep/main.bicep +++ b/deployers/bicep/main.bicep @@ -113,17 +113,18 @@ param embeddingModels array = [ ] //---------------- -// cosmos db firewall -// this is needed to allow access during deployment. Should be the ipAddress of the machine running the deployment -// may be a single ip address or a full range in cidr format. EG. 123.45.67.125/32 or 123.45.67.125 or 10.10.1.0/24 -// strongly encourage makking sure 0.0.0.0 is included to allow Azure services to access cosmos db -param allowedIpAddresses array = [ - { - ipAddressOrRange: '0.0.0.0' //--- required to allow Azure services to access cosmos db - } - //--- Add your IP address here during development -] +// allowed IP addresses for resources +@description('''Comma separated list of IP addresses or ranges to allow access to resources when private networking is enabled. +Leave blank if not using private networking. +- Format for single IP: 'x.x.x.x' +- Format for range: 'x.x.x.x/y' +- Example: 1.2.3.4, 2.3.4.5/32 +''') +param allowedIpAddresses string +var allowedIpAddressesSplit = empty(allowedIpAddresses) ? [] : split(allowedIpAddresses!, ',') +var allowedIpAddressesArray = [for ip in allowedIpAddressesSplit: trim(ip)] //---------------- + // optional services @description('''Enable deployment of Content Safety service and related resources. @@ -153,6 +154,14 @@ var acrName = toLower('${appName}${environment}acr') var containerRegistry = '${acrName}${acrCloudSuffix}' var containerImageName = '${containerRegistry}/${imageName}' var vNetName = '${appName}-${environment}-vnet' +var allowedIpsForCosmos = union(['0.0.0.0'], allowedIpAddressesArray) +var cosmosDbIpRules = [for ip in allowedIpsForCosmos: { + ipAddressOrRange: ip +}] +var acrIpRules = [for ip in allowedIpAddressesArray: { + action: 'Allow' + value: ip +}] //========================================================= // Resource group deployment @@ -267,7 +276,7 @@ module cosmosDB 'modules/cosmosDb.bicep' = { authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions enablePrivateNetworking: enablePrivateNetworking - allowedIpAddresses: allowedIpAddresses + allowedIpAddresses: cosmosDbIpRules } } @@ -288,6 +297,7 @@ module acr 'modules/azureContainerRegistry.bicep' = { authenticationType: authenticationType configureApplicationPermissions: configureApplicationPermissions enablePrivateNetworking: enablePrivateNetworking + allowedIpAddresses: acrIpRules } } diff --git a/deployers/bicep/modules/azureContainerRegistry.bicep b/deployers/bicep/modules/azureContainerRegistry.bicep index 43edc07b..d4ed9b6d 100644 --- a/deployers/bicep/modules/azureContainerRegistry.bicep +++ b/deployers/bicep/modules/azureContainerRegistry.bicep @@ -12,6 +12,7 @@ param authenticationType string param configureApplicationPermissions bool param enablePrivateNetworking bool +param allowedIpAddresses array = [] // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { @@ -29,6 +30,10 @@ resource acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = { properties: { adminUserEnabled: true publicNetworkAccess: 'Enabled' // configuration is set in post provision step in azure.yaml with post deployment script + networkRuleSet: enablePrivateNetworking ? { + defaultAction: 'Deny' + ipRules: allowedIpAddresses + } : null } tags: tags }