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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.263"
VERSION = "0.261.264"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
2 changes: 2 additions & 0 deletions deployers/azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ hooks:
export var_redisCachePort=${var_redisCachePort}
export var_videoIndexerArmApiVersion=${var_videoIndexerArmApiVersion}
export var_videoIndexerEndpoint=${var_videoIndexerEndpoint}
export var_videoIndexerLocation=${var_videoIndexerLocation}
export var_videoIndexerName=${var_videoIndexerName}
export var_deploymentLocation=${var_deploymentLocation}
export var_videoIndexerAccountId=${var_videoIndexerAccountId}
Expand Down Expand Up @@ -232,6 +233,7 @@ hooks:
'var_videoIndexerAccountId',
'var_videoIndexerArmApiVersion',
'var_videoIndexerEndpoint',
'var_videoIndexerLocation',
'var_videoIndexerName',
'var_webService'
)
Expand Down
58 changes: 54 additions & 4 deletions deployers/azurecli/deploy-simplechat.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
$param_AzureOpenAiEmbeddingModelVersion = "" # Leave blank to use the default for the selected cloud and region.
$param_AzureOpenAiEmbeddingDeploymentCapacity = 80
$param_DeployVideoIndexerService = $false
$param_VideoIndexerLocation = "" # Optional Video Indexer region. Leave blank to use $paramLocation. Set it when $paramLocation does not offer Video Indexer (for example, northcentralus); a different region gets a dedicated Video Indexer storage account there.

Check warning on line 206 in deployers/azurecli/deploy-simplechat.ps1

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
$param_CustomGraphUrl = ""
$param_CustomIdentityUrl = ""
$param_CustomResourceManagerUrl = ""
Expand Down Expand Up @@ -1388,6 +1389,15 @@
$storageAccountName = Get-GloballyUniqueResourceName -ResourceTypeSuffix $paramStorageAccountSuffix # Storage names are strict (lowercase, no hyphens, 3-24 chars)
if ($storageAccountName.Length -gt 24) { $storageAccountName = $storageAccountName.Substring(0, 24) }
if ($storageAccountName.Length -lt 3) { Write-Error "Generated storage account name '$storageAccountName' is too short. Adjust base name or suffix." ; exit 1 }
$videoIndexerLocation = if ([string]::IsNullOrWhiteSpace($param_VideoIndexerLocation)) { $paramLocation } else { $param_VideoIndexerLocation }
$videoIndexerLocation = ($videoIndexerLocation -replace '\s', '').ToLower()
# Video Indexer keeps its media in a storage account in its own region.

Check warning on line 1394 in deployers/azurecli/deploy-simplechat.ps1

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
$videoIndexerUsesDedicatedStorage = $videoIndexerLocation -ne (($paramLocation -replace '\s', '').ToLower())

Check warning on line 1395 in deployers/azurecli/deploy-simplechat.ps1

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
$videoIndexerStorageAccountName = Get-GloballyUniqueResourceName -ResourceTypeSuffix "vi"

Check warning on line 1396 in deployers/azurecli/deploy-simplechat.ps1

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains dynamic execution, persistence, or system access marker. Recommendation%3A Do not execute changed lifecycle scripts or installers while this finding is unresolved.

Check warning on line 1396 in deployers/azurecli/deploy-simplechat.ps1

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
if ($videoIndexerStorageAccountName.Length -gt 24) { $videoIndexerStorageAccountName = $videoIndexerStorageAccountName.Substring(0, 24) }

Check warning on line 1397 in deployers/azurecli/deploy-simplechat.ps1

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
# The app identity needs generateAccessToken. Video Indexer Account Contributor is the least-privilege

Check warning on line 1398 in deployers/azurecli/deploy-simplechat.ps1

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
# built-in role; Contributor is used where that newer role has not been confirmed.

Check warning on line 1399 in deployers/azurecli/deploy-simplechat.ps1

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
$videoIndexerAppRoleName = if ($globalWhichAzurePlatform -eq "AzureCloud") { "Video Indexer Account Contributor" } else { "Contributor" }

Check warning on line 1400 in deployers/azurecli/deploy-simplechat.ps1

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
#$containerRegistryName = Get-GloballyUniqueResourceName -ResourceTypeSuffix $paramContainerRegistrySuffix # ACR names are strict
$entraGroupName_Admins = "$($paramBaseName)-$($paramEnvironment)-$($paramEntraGroupNameSuffix)-Admins"
$entraGroupName_Users = "$($paramBaseName)-$($paramEnvironment)-$($paramEntraGroupNameSuffix)-Users"
Expand Down Expand Up @@ -2006,16 +2016,37 @@

# --- Create Azure Video Indexer Service ---
if ($param_DeployVideoIndexerService) {
Write-Host "`n=====> Creating Azure Video Indexer Service: $($videoIndexerName)..."
Write-Host "`n=====> Creating Azure Video Indexer Service: $($videoIndexerName) in $($videoIndexerLocation)..."
$videoIndexerSubscriptionId = az account show --query id --output tsv
$videoIndexerArmBaseUrl = $paramArmUrl.TrimEnd('/')
$videoIndexerUri = "$videoIndexerArmBaseUrl/subscriptions/$videoIndexerSubscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.VideoIndexer/accounts/$videoIndexerName?api-version=$param_VideoIndexerArmApiVersion"
$existingVideoIndexer = az rest --method get --uri $videoIndexerUri 2>$null | ConvertFrom-Json

if (-not $existingVideoIndexer) {
if ($videoIndexerUsesDedicatedStorage) {

Check warning on line 2026 in deployers/azurecli/deploy-simplechat.ps1

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
Write-Host "Video Indexer region '$videoIndexerLocation' differs from '$paramLocation'. Using storage account '$videoIndexerStorageAccountName' in the Video Indexer region."
$videoIndexerStorageResourceId = az storage account show --name $videoIndexerStorageAccountName --resource-group $resourceGroupName --query id --output tsv 2>$null
if (-not $videoIndexerStorageResourceId) {
# Only Video Indexer uses this account, and it connects as a trusted Azure service. A disabled
# public endpoint would also block that path, so private networking denies all other traffic instead.
$videoIndexerStorageDefaultAction = if ($param_EnablePrivateNetworking) { 'Deny' } else { 'Allow' }
$videoIndexerStorageTags = @($tags.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" })
$videoIndexerStorageResourceId = az storage account create --name $videoIndexerStorageAccountName --resource-group $resourceGroupName `
--location $videoIndexerLocation --sku $paramStorageSku --kind StorageV2 --access-tier Hot `
--allow-blob-public-access false --min-tls-version TLS1_2 --public-network-access Enabled `
--default-action $videoIndexerStorageDefaultAction --bypass AzureServices `
--tags $videoIndexerStorageTags --query id --output tsv
if ($LASTEXITCODE -ne 0 -or -not $videoIndexerStorageResourceId) {
Write-Warning "Failed to create Video Indexer storage account '$($videoIndexerStorageAccountName)'."
}
}
} else {
$videoIndexerStorageResourceId = az storage account show --name $storageAccountName --resource-group $resourceGroupName --query id --output tsv
}

$videoIndexerProperties = @{
storageServices = @{
resourceId = $(az storage account show --name $storageAccountName --resource-group $resourceGroupName --query id --output tsv)
resourceId = $videoIndexerStorageResourceId
}
}

Expand All @@ -2035,7 +2066,7 @@
}

$videoIndexerBody = @{
location = $paramLocation
location = $videoIndexerLocation
identity = @{
type = 'SystemAssigned'
}
Expand All @@ -2053,6 +2084,10 @@
} else {
Write-Host "Azure Video Indexer Service '$videoIndexerName' already exists."
}

if ($existingVideoIndexer) {
Write-Host "Configure Admin Settings > AI Video Intelligence with Resource Group '$resourceGroupName', Account Name '$videoIndexerName', Account ID '$($existingVideoIndexer.properties.accountId)', and Location '$($existingVideoIndexer.location)'."
}
}

# --- Create App Service Settings ---
Expand Down Expand Up @@ -2311,7 +2346,8 @@
$videoIndexerPrincipalId = az resource show --ids $videoIndexerResourceId --api-version $param_VideoIndexerArmApiVersion --query "identity.principalId" --output tsv 2>$null

if (-not [string]::IsNullOrWhiteSpace($videoIndexerPrincipalId)) {
$storageResourceId = az storage account show --name $storageAccountName --resource-group $resourceGroupName --query "id" --output tsv
# Use the storage account the Video Indexer account is actually connected to.
$storageResourceId = az resource show --ids $videoIndexerResourceId --api-version $param_VideoIndexerArmApiVersion --query "properties.storageServices.resourceId" --output tsv
$assignment = az role assignment list --assignee $videoIndexerPrincipalId --scope "$storageResourceId" --query "[?roleDefinitionName=='Storage Blob Data Contributor']" --output json | ConvertFrom-Json
if (-not $assignment) {
az role assignment create --assignee $videoIndexerPrincipalId --role "Storage Blob Data Contributor" --scope "$storageResourceId" | Out-Null
Expand All @@ -2331,6 +2367,20 @@
} else {
Write-Warning "Azure Video Indexer principal ID could not be resolved. Skipping Video Indexer RBAC assignments."
}

# SimpleChat calls Video Indexer generateAccessToken with the App Service system-assigned identity.
if (-not [string]::IsNullOrWhiteSpace($videoIndexerResourceId) -and -not [string]::IsNullOrWhiteSpace($appService_SystemManagedIdentity_ObjectId)) {
$assignment = az role assignment list --assignee $appService_SystemManagedIdentity_ObjectId --scope "$videoIndexerResourceId" --query "[?roleDefinitionName=='$videoIndexerAppRoleName']" --output json | ConvertFrom-Json
if (-not $assignment) {
az role assignment create --assignee-object-id $appService_SystemManagedIdentity_ObjectId --assignee-principal-type ServicePrincipal `
--role "$videoIndexerAppRoleName" --scope "$videoIndexerResourceId" | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Warning "Failed to grant '$videoIndexerAppRoleName' on Video Indexer to the App Service managed identity."
}
}
} else {
Write-Warning "Azure Video Indexer or App Service identity could not be resolved. Grant the App Service managed identity '$videoIndexerAppRoleName' on the Video Indexer account manually."
}
}


Expand Down
46 changes: 46 additions & 0 deletions deployers/bicep/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Deployment Prompts](#Deployment-Prompts)
- [Post Deployment Tasks](#Post-Deployment-Tasks)
- [Key Vault secret permissions](#key-vault-secret-permissions)
- [Video Indexer region and permissions](#video-indexer-region-and-permissions)
- [Cleanup / Deprovision](#Cleanup-/-Deprovisioning)
- [Helpful Info](#Helpful-Info)
- [Private Networking](#Private-Networking)
Expand Down Expand Up @@ -69,6 +70,7 @@ Ensure the following resource providers are registered in your subscription:
- `Microsoft.ContainerRegistry`
- `Microsoft.Insights`
- `Microsoft.OperationalInsights`
- `Microsoft.VideoIndexer` (only when `deployVideoIndexerService` is `true`)


## Deployment Process
Expand Down Expand Up @@ -364,6 +366,46 @@ If your platform team owns IAM instead, `configureApplicationPermissions=false`

Offline contract coverage is in [test_deployer_key_vault_secret_permissions.py](../../functional_tests/test_deployer_key_vault_secret_permissions.py); it checks both Bicep modules, generated ARM, permission-disable conditions, and runtime principals without deploying resources.

## Video Indexer region and permissions

Implemented in application version **0.261.264** (`application/single_app/config.py`) and deployer version **1.0.33** (`deployers/version.txt`).

### Choosing a Video Indexer region

Azure Video Indexer is not offered in every region. North Central US, for example, has no Video Indexer, so `deployVideoIndexerService=true` in that region used to fail Azure Resource Manager validation before anything was deployed. Set `VIDEO_INDEXER_LOCATION` to a region that offers Video Indexer and keep the rest of SimpleChat in `AZURE_LOCATION`:

```powershell
azd env set DEPLOY_VIDEO_INDEXER_SERVICE true
azd env set VIDEO_INDEXER_LOCATION centralus
azd provision --preview
```

- Leave `VIDEO_INDEXER_LOCATION` unset to deploy Video Indexer in `AZURE_LOCATION`, as earlier versions did.
- When the two regions differ, the deployment also creates `<appName><environment>vi`, a Standard general-purpose v2 storage account in the Video Indexer region, because Video Indexer keeps its media in a storage account in its own region. Only Video Indexer uses this account; SimpleChat uploads video to the Video Indexer API and never reads it. With `enablePrivateNetworking=true`, its firewall denies public traffic and admits trusted Azure services, which is how Video Indexer reaches firewalled storage. It needs no private endpoint.
- Choose the region before the first deployment that enables Video Indexer. An existing account cannot move regions; Azure rejects a later deployment that asks for the same account name in another region.
- The preprovision hook compares the selected region with the regions the `Microsoft.VideoIndexer` provider reports. If Video Indexer is unavailable there, the hook lists the supported regions and stops before provisioning. If the list cannot be read, it warns and continues, because Azure Resource Manager still validates the region.

### Application access

SimpleChat calls the Video Indexer `generateAccessToken` Azure Resource Manager API with the App Service **system-assigned** managed identity, whatever `authenticationType` is. Whenever `configureApplicationPermissions=true`, the container app and the optional native Python app each receive a role on the Video Indexer account:

| Cloud | Role | Role definition ID |
|---|---|---|
| Azure Commercial | Video Indexer Account Contributor | `3f99eaab-6f59-4877-adf5-1cacd22e20b0` |
| Azure Government and custom clouds | Contributor | `b24988ac-6180-42a0-ab88-20f7382dd24c` |

Video Indexer Account Contributor is limited to Video Indexer accounts and their access tokens. Contributor is used where that newer built-in role has not been confirmed. The assignment name is `guid(videoIndexerService.id, webApp.id, 'video-indexer-app-access', <role definition ID>)`, and these grants live in `modules/setVideoIndexerPermissions.bicep`.

Earlier deployer versions created the account without this grant, so video processing failed authorization until an administrator added **Contributor** by hand. In Azure Commercial, that manual grant does not conflict with the new assignment; it can stay, or you can remove it afterward to keep only the narrower role. In Azure Government and custom clouds, an identical manual Contributor assignment on the account makes the next deployment return `RoleAssignmentExists`. Keep the manual grant and adopt its GUID in an environment-specific copy of `modules/setVideoIndexerPermissions.bicep`, as described for Key Vault in [Upgrading and adopting existing ARM assignments](#upgrading-and-adopting-existing-arm-assignments).

Video Indexer's own identity keeps **Storage Blob Data Contributor** on the storage account that holds its media. That assignment moved into the same module with an unchanged name, so upgrades do not recreate it.

### Application settings

Post-provision configuration writes the Video Indexer settings only when the deployment created the account, and records the account's own region rather than the deployment region. A provisioning run with `deployVideoIndexerService=false` leaves an existing Video Indexer configuration in **Admin Settings > AI Video Intelligence** untouched; earlier versions blanked the account name and ID and reset the location on every such run.

Offline coverage is in [test_video_indexer_deployment_region_permissions.py](../../functional_tests/test_video_indexer_deployment_region_permissions.py) and [test_deployment_configuration.py](../../functional_tests/test_deployment_configuration.py).

---

### Post Deployment Tasks:
Expand Down Expand Up @@ -616,6 +658,10 @@ If you do not want this deployment to create new Azure OpenAI model deployments,
- **Cause:** Video Indexer API capabilities differ by cloud and region
- **Solution:** Use the cloud-appropriate API profile. Azure Government deployments use the `2024-01-01` profile; commercial deployments use `2025-04-01`; custom clouds can override the API version and endpoint.

**Error: "The provided location '\<region\>' is not available for resource type 'Microsoft.VideoIndexer/accounts'"**
- **Cause:** Azure Video Indexer is not offered in the region Video Indexer is being deployed to.
- **Solution:** Run `azd env set VIDEO_INDEXER_LOCATION <supported-region>` and rerun `azd up`. The rest of SimpleChat stays in `AZURE_LOCATION`. See [Video Indexer region and permissions](#video-indexer-region-and-permissions).

### Post-Deployment Issues

**Issue: Cannot access the web application**
Expand Down
Loading
Loading