diff --git a/application/single_app/config.py b/application/single_app/config.py index 08c0adf1..38226ffc 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -200,8 +200,11 @@ def get_allowed_extensions(enable_video=False, enable_audio=False): WORD_CHUNK_SIZE = 400 if AZURE_ENVIRONMENT == "custom" or CUSTOM_IDENTITY_URL_VALUE or CUSTOM_GRAPH_AUTHORITY_URL_VALUE: - AUTHORITY = f"{CUSTOM_IDENTITY_URL_VALUE}/{TENANT_ID}" - authority = CUSTOM_GRAPH_AUTHORITY_URL_VALUE or CUSTOM_IDENTITY_URL_VALUE or AUTHORITY.rstrip(f'/{TENANT_ID}') + AUTHORITY = f"{CUSTOM_IDENTITY_URL_VALUE.rstrip('/')}/{TENANT_ID}" + base_authority = CUSTOM_GRAPH_AUTHORITY_URL_VALUE or CUSTOM_IDENTITY_URL_VALUE + if not base_authority: + base_authority = AUTHORITY.rstrip('/').removesuffix(f"/{TENANT_ID}") + authority = base_authority elif AZURE_ENVIRONMENT == "usgovernment": AUTHORITY = f"https://login.microsoftonline.us/{TENANT_ID}" authority = AzureAuthorityHosts.AZURE_GOVERNMENT diff --git a/deployers/bicep/main.bicep b/deployers/bicep/main.bicep index bcf19d31..6adc0d6a 100644 --- a/deployers/bicep/main.bicep +++ b/deployers/bicep/main.bicep @@ -6,13 +6,18 @@ targetScope = 'subscription' param location string @description('''The target Azure Cloud environment. -- Accepted values are: AzureCloud, AzureUSGovernment -- Default is AzureCloud''') +- Accepted values are: AzureCloud, AzureUSGovernment, public, usgovernment, custom +- Default is based on the ARM cloud name''') @allowed([ - 'AzureCloud' - 'AzureUSGovernment' + 'AzureCloud' // public, keep allowed values for backwards compatibility + 'AzureUSGovernment' // usgovernment + 'public' + 'usgovernment' + 'custom' ]) -param cloudEnvironment string +param cloudEnvironment string = az.environment().name == 'AzureCloud' ? 'public' : (az.environment().name == 'AzureUSGovernment' ? 'usgovernment' : 'custom') +// SimpleChat expects public, usgovernment or custom +var scCloudEnvironment = cloudEnvironment == 'AzureCloud' ? 'public' : (cloudEnvironment == 'AzureUSGovernment' ? 'usgovernment' : cloudEnvironment) @description('''The name of the application to be deployed. - Name may only contain letters and numbers @@ -80,6 +85,20 @@ param enableDiagLogging bool - Default is false''') param enablePrivateNetworking bool +// --- Custom Azure Environment Parameters (for 'custom' azureEnvironment) --- +@description('Custom blob storage URL suffix, e.g. blob.core.usgovcloudapi.net') +param customBlobStorageSuffix string = 'blob.${az.environment().suffixes.storage}' +@description('Custom Graph API URL, e.g. https://graph.microsoft.us') +param customGraphUrl string? // az.environment().graph is legacy AD, do not use +@description('Custom Identity URL, e.g. https://login.microsoftonline.us/') +param customIdentityUrl string = az.environment().authentication.loginEndpoint +@description('Custom Resource Manager URL, e.g. https://management.usgovcloudapi.net') +param customResourceManagerUrl string = az.environment().resourceManager +@description('Custom Cognitive Services scope ex: https://cognitiveservices.azure.com/.default') +param customCognitiveServicesScope string = 'https://cognitiveservices.azure.com/.default' +@description('Custom search resource URL for token audience, e.g. https://search.azure.us') +param customSearchResourceUrl string = 'https://search.azure.com' + @description('''Array of GPT model names to deploy to the OpenAI resource.''') param gptModels array = [ { @@ -424,7 +443,7 @@ module appService 'modules/appService.bicep' = { logAnalyticsId: logAnalytics.outputs.logAnalyticsId appServicePlanId: appServicePlan.outputs.appServicePlanId containerImageName: containerImageName - azurePlatform: cloudEnvironment + azurePlatform: scCloudEnvironment cosmosDbName: cosmosDB.outputs.cosmosDbName searchServiceName: searchService.outputs.searchServiceName openAiServiceName: openAI.outputs.openAIName @@ -439,6 +458,14 @@ module appService 'modules/appService.bicep' = { enablePrivateNetworking: enablePrivateNetworking #disable-next-line BCP318 // expect one value to be null if private networking is disabled appServiceSubnetId: enablePrivateNetworking? virtualNetwork.outputs.appServiceSubnetId : '' + + // --- Custom Azure Environment Parameters (for 'custom' azureEnvironment) --- + customBlobStorageSuffix: customBlobStorageSuffix + customGraphUrl: customGraphUrl + customIdentityUrl: customIdentityUrl + customResourceManagerUrl: customResourceManagerUrl + customCognitiveServicesScope: customCognitiveServicesScope + customSearchResourceUrl: customSearchResourceUrl } } diff --git a/deployers/bicep/modules/appService.bicep b/deployers/bicep/modules/appService.bicep index 5d9aa471..8ef72b09 100644 --- a/deployers/bicep/modules/appService.bicep +++ b/deployers/bicep/modules/appService.bicep @@ -27,6 +27,23 @@ param keyVaultUri string param enablePrivateNetworking bool param appServiceSubnetId string = '' +// --- Custom Azure Environment Parameters (for 'custom' azureEnvironment) --- +@description('Custom blob storage URL suffix, e.g. blob.core.usgovcloudapi.net') +param customBlobStorageSuffix string? +@description('Custom Graph API URL, e.g. https://graph.microsoft.us') +param customGraphUrl string? +@description('Custom Identity URL, e.g. https://login.microsoftonline.us') +param customIdentityUrl string? +@description('Custom Resource Manager URL, e.g. https://management.usgovcloudapi.net') +param customResourceManagerUrl string? +@description('Custom Cognitive Services scope ex: https://cognitiveservices.azure.com/.default') +param customCognitiveServicesScope string? +@description('Custom search resource URL for token audience, e.g. https://search.azure.us') +param customSearchResourceUrl string? + +var tenantId = tenant().tenantId +var openIdMetadataUrl = '${az.environment().authentication.loginEndpoint}${tenantId}/v2.0/.well-known/openid-configuration' + // Import diagnostic settings configurations module diagnosticConfigs 'diagnosticSettings.bicep' = if (enableDiagLogging) { name: 'diagnosticConfigs' @@ -55,7 +72,7 @@ resource appInsights 'Microsoft.Insights/components@2020-02-02' existing = { name: appInsightsName } -var acrDomain = azurePlatform == 'AzureUSGovernment' ? '.azurecr.us' : '.azurecr.io' +var acrDomain = az.environment().suffixes.acrLoginServer // add web app resource webApp 'Microsoft.Web/sites@2022-03-01' = { @@ -77,7 +94,7 @@ resource webApp 'Microsoft.Web/sites@2022-03-01' = { ftpsState: 'Disabled' healthCheckPath: '/external/healthcheck' appSettings: [ - { name: 'AZURE_ENDPOINT', value: azurePlatform == 'AzureUSGovernment' ? 'usgovernment' : 'public' } + { name: 'AZURE_ENVIRONMENT', value: azurePlatform } { name: 'SCM_DO_BUILD_DURING_DEPLOYMENT', value: 'false' } { name: 'AZURE_COSMOS_ENDPOINT', value: cosmosDb.properties.documentEndpoint } { name: 'AZURE_COSMOS_AUTHENTICATION_TYPE', value: toLower(authenticationType) } @@ -150,8 +167,18 @@ resource webApp 'Microsoft.Web/sites@2022-03-01' = { { name: 'InstrumentationEngine_EXTENSION_VERSION', value: 'disabled' } { name: 'SnapshotDebugger_EXTENSION_VERSION', value: 'disabled' } { name: 'XDT_MicrosoftApplicationInsights_BaseExtensions', value: 'disabled' } - { name: 'XDT_MicrosoftApplicationInsights_Mode', value: 'recommended' } - { name: 'XDT_MicrosoftApplicationInsights_PreemptSdk', value: 'disabled' } + {name: 'XDT_MicrosoftApplicationInsights_Mode', value: 'recommended' } + {name: 'XDT_MicrosoftApplicationInsights_PreemptSdk', value: 'disabled' } + ...(azurePlatform == 'custom' ? [ + {name: 'CUSTOM_GRAPH_URL_VALUE', value: customGraphUrl ?? ''} + {name: 'CUSTOM_IDENTITY_URL_VALUE', value: customIdentityUrl ?? ''} + {name: 'CUSTOM_RESOURCE_MANAGER_URL_VALUE', value: customResourceManagerUrl ?? ''} + {name: 'CUSTOM_BLOB_STORAGE_URL_VALUE', value: customBlobStorageSuffix ?? ''} + {name: 'CUSTOM_COGNITIVE_SERVICES_URL_VALUE', value: customCognitiveServicesScope ?? ''} + {name: 'CUSTOM_SEARCH_RESOURCE_MANAGER_URL_VALUE', value: customSearchResourceUrl ?? ''} + {name: 'KEY_VAULT_DOMAIN', value: az.environment().suffixes.keyvaultDns} + {name: 'CUSTOM_OIDC_METADATA_URL_VALUE', value: openIdMetadataUrl ?? ''}] + : []) ] } clientAffinityEnabled: false @@ -205,7 +232,7 @@ resource authSettings 'Microsoft.Web/sites/config@2022-03-01' = { azureActiveDirectory: { enabled: true registration: { - openIdIssuer: azurePlatform == 'AzureUSGovernment' ? 'https://login.microsoftonline.us/${tenant().tenantId}/' : 'https://sts.windows.net/${tenant().tenantId}/' + openIdIssuer: '${az.environment().authentication.loginEndpoint}${tenant().tenantId}/' clientId: enterpriseAppClientId clientSecretSettingName: 'MICROSOFT_PROVIDER_AUTHENTICATION_SECRET' } diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index 89e53a81..2417ce7c 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -6,6 +6,15 @@ #### New Features +* **Custom Azure Environment Support in Bicep Deployment** + * Added `custom` as a supported `cloudEnvironment` value alongside `public` and `usgovernment`, enabling deployment to sovereign or custom Azure environments via Bicep. + * New Bicep parameters for custom environments: `customBlobStorageSuffix`, `customGraphUrl`, `customIdentityUrl`, `customResourceManagerUrl`, `customCognitiveServicesScope`, and `customSearchResourceUrl`. All of these are automatically populated from `az.environment()` defaults except `customGraphUrl`, which must be explicitly provided for custom cloud environments and can be overridden as needed. + * The `cloudEnvironment` parameter now defaults intelligently based on `az.environment().name`, and legacy values (`AzureCloud`, `AzureUSGovernment`) are mapped to SimpleChat's expected values (`public`, `usgovernment`). + * Custom environment app settings (`CUSTOM_GRAPH_URL_VALUE`, `CUSTOM_IDENTITY_URL_VALUE`, `CUSTOM_RESOURCE_MANAGER_URL_VALUE`, etc.) are conditionally injected only when `azurePlatform == 'custom'`. + * Replaced hardcoded ACR domain logic and auth issuer URLs with dynamic `az.environment()` lookups for better cross-cloud compatibility. + * Fixed trailing slash handling in `AUTHORITY` URL construction in `config.py` using `rstrip('/')`. + * (Ref: `deployers/bicep/main.bicep`, `deployers/bicep/modules/appService.bicep`, `config.py`, sovereign cloud support) + * **Redis Key Vault Authentication** * Added a new `key_vault` authentication type for Redis, allowing the Redis access key to be retrieved securely from Azure Key Vault at runtime rather than stored directly in settings. * Applies across all Redis usage paths: app settings cache (`app_settings_cache.py`), session management (`app.py`), and the Redis test connection flow (`route_backend_settings.py`).