From 6e22fd1d5a84b03e2e768886fa3640b5676f07cb Mon Sep 17 00:00:00 2001 From: greenie-msft Date: Thu, 2 Apr 2026 15:20:01 -0700 Subject: [PATCH] Add Durable Task Scheduler backend and disable storage shared key access - Add DTS and task hub Bicep modules with RBAC role assignments - Configure durable task to use Azure Managed backend via DTS - Disable storage account shared key access (allowSharedKeyAccess: false) - Upgrade azure-functions-durable to 1.5.0 with compatible aiohttp 3.13.5 - Add DTS abbreviations for resource naming --- infra/abbreviations.json | 2 + infra/core/durable_task/dts-access.bicep | 18 ++++++++ infra/core/durable_task/dts.bicep | 31 +++++++++++++ infra/core/host/function.bicep | 4 ++ infra/core/storage/storage-account.bicep | 1 + infra/main.bicep | 57 ++++++++++++++++++++++++ src/host.json | 12 +++-- src/requirements.txt | 8 ++-- 8 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 infra/core/durable_task/dts-access.bicep create mode 100644 infra/core/durable_task/dts.bicep diff --git a/infra/abbreviations.json b/infra/abbreviations.json index a4fc9df..d7677a1 100644 --- a/infra/abbreviations.json +++ b/infra/abbreviations.json @@ -37,6 +37,8 @@ "devicesProvisioningServices": "provs-", "devicesProvisioningServicesCertificates": "pcert-", "documentDBDatabaseAccounts": "cosmos-", + "durableTaskSchedulers": "dts-", + "durableTaskHubs": "th-", "eventGridDomains": "evgd-", "eventGridDomainsTopics": "evgt-", "eventGridEventSubscriptions": "evgs-", diff --git a/infra/core/durable_task/dts-access.bicep b/infra/core/durable_task/dts-access.bicep new file mode 100644 index 0000000..1c435b9 --- /dev/null +++ b/infra/core/durable_task/dts-access.bicep @@ -0,0 +1,18 @@ +param principalID string +param roleDefinitionID string +param dtsName string +param principalType string + +resource dts 'Microsoft.DurableTask/schedulers@2025-04-01-preview' existing = { + name: dtsName +} + +resource dtsRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(dts.id, principalID, roleDefinitionID) + scope: dts + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionID) + principalId: principalID + principalType: principalType + } +} diff --git a/infra/core/durable_task/dts.bicep b/infra/core/durable_task/dts.bicep new file mode 100644 index 0000000..d5b93a6 --- /dev/null +++ b/infra/core/durable_task/dts.bicep @@ -0,0 +1,31 @@ +param ipAllowlist array +param location string +param tags object = {} +param name string +param taskhubname string +param skuName string +param skuCapacity int = 0 + +resource dts 'Microsoft.DurableTask/schedulers@2025-04-01-preview' = { + location: location + tags: tags + name: name + properties: { + ipAllowlist: ipAllowlist + sku: skuName == 'Dedicated' ? { + name: skuName + capacity: skuCapacity + } : { + name: skuName + } + } +} + +resource taskhub 'Microsoft.DurableTask/schedulers/taskHubs@2025-04-01-preview' = { + parent: dts + name: taskhubname +} + +output dts_NAME string = dts.name +output dts_URL string = dts.properties.endpoint +output TASKHUB_NAME string = taskhub.name diff --git a/infra/core/host/function.bicep b/infra/core/host/function.bicep index b64e6ef..049e7ac 100644 --- a/infra/core/host/function.bicep +++ b/infra/core/host/function.bicep @@ -14,6 +14,8 @@ param diEndpoint string param openAIEndpoint string param searchServiceName string param appInsightsName string +param dtsURL string = '' +param taskHubName string = '' resource sourceStorageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = { name: sourceStorageAccountName @@ -106,6 +108,8 @@ resource flexFunctionApp 'Microsoft.Web/sites@2023-12-01' = { DI_ENDPOINT: diEndpoint AZURE_OPENAI_ENDPOINT: openAIEndpoint SEARCH_SERVICE_ENDPOINT: searchServiceEndpoint + DURABLE_TASK_SCHEDULER_CONNECTION_STRING: 'Endpoint=${dtsURL};Authentication=ManagedIdentity;ClientID=${identityClientId}' + TASKHUB_NAME: taskHubName } } } diff --git a/infra/core/storage/storage-account.bicep b/infra/core/storage/storage-account.bicep index aef120a..0dc75d5 100644 --- a/infra/core/storage/storage-account.bicep +++ b/infra/core/storage/storage-account.bicep @@ -15,6 +15,7 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { accessTier: 'Hot' allowBlobPublicAccess: false allowCrossTenantReplication: false + allowSharedKeyAccess: false supportsHttpsTrafficOnly: true defaultToOAuthAuthentication: true minimumTlsVersion: 'TLS1_2' diff --git a/infra/main.bicep b/infra/main.bicep index 235592e..5d2e46e 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -3,12 +3,23 @@ targetScope = 'subscription' param environmentName string param location string +param dtsName string = '' +param taskHubName string = '' +param dtsLocation string = location +param dtsSkuName string = 'Consumption' +param dtsCapacity int = 0 + +@description('Id of the user identity to be used for testing and debugging. This is not required in production. Leave empty if not needed.') +param principalId string = '' + var abbrs = loadJsonContent('./abbreviations.json') var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) var functionAppName = '${abbrs.webSitesFunctions}${resourceToken}' var functionContainerName = 'app-package-${functionAppName}' +var dtsResourceName = !empty(dtsName) ? dtsName : '${abbrs.durableTaskSchedulers}${resourceToken}' +var taskHubResourceName = !empty(taskHubName) ? taskHubName : '${abbrs.durableTaskHubs}${resourceToken}' var tags = { 'azd-env-name': environmentName } @@ -112,6 +123,8 @@ module flexFunction 'core/host/function.bicep' = { diEndpoint: documentIntelligence.outputs.endpoint openAIEndpoint: openAI.outputs.endpoint searchServiceName: searchService.outputs.name + dtsURL: dts.outputs.dts_URL + taskHubName: dts.outputs.TASKHUB_NAME } } @@ -128,6 +141,50 @@ module eventgrid 'core/integration/eventgrid.bicep' = { output SOURCE_STORAGE_ACCOUNT_NAME string = storage[0].outputs.storageAccountName +// Durable Task Scheduler +module dts 'core/durable_task/dts.bicep' = { + scope: resourceGroup + name: 'dtsResource' + params: { + name: dtsResourceName + taskhubname: taskHubResourceName + location: dtsLocation + tags: tags + ipAllowlist: [ + '0.0.0.0/0' + ] + skuName: dtsSkuName + skuCapacity: dtsCapacity + } +} + +// Durable Task Data Contributor role ID +var dtsRoleDefinitionId = '0ad04412-c4d5-4796-b79c-f76d14c8d402' + +// Allow access from function app to DTS using user assigned managed identity +module dtsRoleAssignment 'core/durable_task/dts-access.bicep' = { + name: 'dtsRoleAssignment' + scope: resourceGroup + params: { + roleDefinitionID: dtsRoleDefinitionId + principalID: userAssignedIdentity.outputs.identityPrincipalId + principalType: 'ServicePrincipal' + dtsName: dts.outputs.dts_NAME + } +} + +// Allow the deployer identity to access the DTS dashboard +module dtsDashboardRoleAssignment 'core/durable_task/dts-access.bicep' = if (!empty(principalId)) { + name: 'dtsDashboardRoleAssignment' + scope: resourceGroup + params: { + roleDefinitionID: dtsRoleDefinitionId + principalID: principalId + principalType: 'User' + dtsName: dts.outputs.dts_NAME + } +} + output RESOURCE_GROUP_NAME string = resourceGroup.name output SYSTEM_TOPIC_NAME string = eventgrid.outputs.systemTopicName output FUNCTION_APP_NAME string = functionAppName diff --git a/src/host.json b/src/host.json index c01f55b..81245d7 100644 --- a/src/host.json +++ b/src/host.json @@ -1,14 +1,20 @@ { "version": "2.0", + "logging": { + "logLevel": { + "DurableTask.AzureManagedBackend": "Information" + } + }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[4.*, 5.0.0)" }, "extensions": { "durableTask": { - "tracing": { - "DistributedTracingEnabled": true, - "Version": "V2" + "hubName": "%TASKHUB_NAME%", + "storageProvider": { + "type": "azureManaged", + "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING" } } } diff --git a/src/requirements.txt b/src/requirements.txt index b17cd7d..2c62fa1 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,9 +1,9 @@ # DO NOT include azure-functions-worker in this file # The Python Worker is managed by Azure Functions platform # Manually managing azure-functions-worker may cause unexpected issues -aiohappyeyeballs==2.4.4 -aiohttp==3.11.11 -aiosignal==1.3.2 +aiohappyeyeballs==2.6.1 +aiohttp==3.13.5 +aiosignal==1.4.0 annotated-types==0.7.0 anyio==4.8.0 async-timeout==5.0.1 @@ -13,7 +13,7 @@ azure-ai-documentintelligence==1.0.0b4 azure-common==1.1.28 azure-core==1.32.0 azure-functions==1.21.3 -azure-functions-durable==1.2.10 +azure-functions-durable==1.5.0 azure-identity==1.19.0 azure-search-documents==11.6.0b6 azure-storage-blob==12.24.0