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
39 changes: 35 additions & 4 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,19 @@ module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-id
}
}

// ========== SQL Operations User Assigned Identity ========== //
// Dedicated identity for backend SQL operations with limited permissions (db_datareader, db_datawriter)
var sqlUserAssignedIdentityResourceName = 'id-sql-${solutionSuffix}'
module sqlUserAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = {
name: take('avm.res.managed-identity.user-assigned-identity.${sqlUserAssignedIdentityResourceName}', 64)
params: {
name: sqlUserAssignedIdentityResourceName
location: solutionLocation
tags: tags
enableTelemetry: enableTelemetry
}
}

// ========== Network Module ========== //
module network 'modules/network.bicep' = if (enablePrivateNetworking) {
name: take('network-${solutionSuffix}-deployment', 64)
Expand Down Expand Up @@ -450,7 +463,7 @@ var aiRelatedDnsZoneIndices = [
@batchSize(5)
module avmPrivateDnsZones 'br/public:avm/res/network/private-dns-zone:0.7.1' = [
for (zone, i) in privateDnsZones: if (enablePrivateNetworking && (empty(existingFoundryProjectResourceId) || !contains(aiRelatedDnsZoneIndices, i))) {
name: 'dns-zone-${i}'
name: 'avm.res.network.private-dns-zone.${split(zone, '.')[1]}'
params: {
name: zone
tags: tags
Expand Down Expand Up @@ -509,6 +522,11 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = {
principalType: 'ServicePrincipal'
roleDefinitionIdOrName: 'Key Vault Administrator'
}
{
principalId: sqlUserAssignedIdentity.outputs.principalId
principalType: 'ServicePrincipal'
roleDefinitionIdOrName: 'Key Vault Secrets User'
}
]
secrets: [
{
Expand Down Expand Up @@ -896,7 +914,11 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = {
connectionPolicy: 'Redirect'
databases: [
{
availabilityZone: enableRedundancy ? 1 : -1
zoneRedundant: enableRedundancy ? true : false
// When enableRedundancy is true (zoneRedundant=true), set availabilityZone to -1
// to let Azure automatically manage zone placement across multiple zones.
// When enableRedundancy is false, also use -1 (no specific zone assignment).
availabilityZone: -1
collation: 'SQL_Latin1_General_CP1_CI_AS'
diagnosticSettings: enableMonitoring
? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }]
Expand Down Expand Up @@ -988,7 +1010,7 @@ module webSite 'modules/web-sites.bicep' = {
name: webSiteResourceName
tags: tags
location: solutionLocation
managedIdentities: { userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId] }
managedIdentities: { userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId, sqlUserAssignedIdentity!.outputs.resourceId] }
kind: 'app,linux,container'
serverFarmResourceId: webServerFarm.?outputs.resourceId
siteConfig: {
Expand Down Expand Up @@ -1035,7 +1057,7 @@ module webSite 'modules/web-sites.bicep' = {
AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: collectionName
AZURE_COSMOSDB_DATABASE: cosmosDbDatabaseName
AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback
SQLDB_USER_MID: userAssignedIdentity.outputs.clientId
SQLDB_USER_MID: sqlUserAssignedIdentity.outputs.clientId
AZURE_AI_SEARCH_ENDPOINT: 'https://${aiSearchName}.search.windows.net'
AZURE_SQL_SYSTEM_PROMPT: functionAppSqlPrompt
AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT: functionAppCallTranscriptSystemPrompt
Expand Down Expand Up @@ -1226,6 +1248,12 @@ output MANAGEDIDENTITY_WEBAPP_NAME string = userAssignedIdentity.outputs.name

@description('Client ID of the managed identity used by the web app.')
output MANAGEDIDENTITY_WEBAPP_CLIENTID string = userAssignedIdentity.outputs.clientId

@description('Name of the managed identity used for SQL database operations.')
output MANAGEDIDENTITY_SQL_NAME string = sqlUserAssignedIdentity.outputs.name

@description('Client ID of the managed identity used for SQL database operations.')
output MANAGEDIDENTITY_SQL_CLIENTID string = sqlUserAssignedIdentity.outputs.clientId
@description('Name of the AI Search service.')
output AI_SEARCH_SERVICE_NAME string = aiSearchName

Expand Down Expand Up @@ -1367,3 +1395,6 @@ output USE_AI_PROJECT_CLIENT string = useAIProjectClientFlag

@description('Indicates whether the internal stream should be used.')
output USE_INTERNAL_STREAM string = useInternalStream

@description('The client ID of the managed identity.')
output AZURE_CLIENT_ID string = userAssignedIdentity.outputs.clientId
22 changes: 12 additions & 10 deletions infra/scripts/process_sample_data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
keyvaultName="$5"
sqlServerName="$6"
SqlDatabaseName="$7"
webAppManagedIdentityClientId="$8"
webAppManagedIdentityDisplayName="$9"
sqlManagedIdentityClientId="$8"
sqlManagedIdentityDisplayName="$9"
aiSearchName="${10}"
aif_resource_id="${11}"

Expand Down Expand Up @@ -316,12 +316,14 @@
SqlDatabaseName=$(azd env get-value SQLDB_DATABASE)
fi

if [ -z "$webAppManagedIdentityClientId" ]; then
webAppManagedIdentityClientId=$(azd env get-value MANAGEDIDENTITY_WEBAPP_CLIENTID)
if [ -z "$sqlManagedIdentityClientId" ]; then
# Use the SQL-specific managed identity for database operations with limited permissions
sqlManagedIdentityClientId=$(azd env get-value MANAGEDIDENTITY_SQL_CLIENTID)
fi

if [ -z "$webAppManagedIdentityDisplayName" ]; then
webAppManagedIdentityDisplayName=$(azd env get-value MANAGEDIDENTITY_WEBAPP_NAME)
if [ -z "$sqlManagedIdentityDisplayName" ]; then
# Use the SQL-specific managed identity for database operations with limited permissions
sqlManagedIdentityDisplayName=$(azd env get-value MANAGEDIDENTITY_SQL_NAME)
fi

if [ -z "$aiSearchName" ]; then
Expand All @@ -335,8 +337,8 @@
azSubscriptionId=$(azd env get-value AZURE_SUBSCRIPTION_ID)

# Check if all required arguments are provided
if [ -z "$resourceGroupName" ] || [ -z "$cosmosDbAccountName" ] || [ -z "$storageAccount" ] || [ -z "$fileSystem" ] || [ -z "$keyvaultName" ] || [ -z "$sqlServerName" ] || [ -z "$SqlDatabaseName" ] || [ -z "$webAppManagedIdentityClientId" ] || [ -z "$webAppManagedIdentityDisplayName" ] || [ -z "$aiSearchName" ] || [ -z "$aif_resource_id" ]; then
echo "Usage: $0 <resourceGroupName> <cosmosDbAccountName> <storageAccount> <storageContainerName> <keyvaultName> <sqlServerName> <sqlDatabaseName> <webAppUserManagedIdentityClientId> <webAppUserManagedIdentityDisplayName> <aiSearchName> <aiFoundryResourceGroup> <aif_resource_id>"
if [ -z "$resourceGroupName" ] || [ -z "$cosmosDbAccountName" ] || [ -z "$storageAccount" ] || [ -z "$fileSystem" ] || [ -z "$keyvaultName" ] || [ -z "$sqlServerName" ] || [ -z "$SqlDatabaseName" ] || [ -z "$sqlManagedIdentityClientId" ] || [ -z "$sqlManagedIdentityDisplayName" ] || [ -z "$aiSearchName" ] || [ -z "$aif_resource_id" ]; then
echo "Usage: $0 <resourceGroupName> <cosmosDbAccountName> <storageAccount> <storageContainerName> <keyvaultName> <sqlServerName> <sqlDatabaseName> <sqlManagedIdentityClientId> <sqlManagedIdentityDisplayName> <aiSearchName> <aiFoundryResourceGroup> <aif_resource_id>"
exit 1
fi

Expand Down Expand Up @@ -437,8 +439,8 @@
# Call create_sql_user_and_role.sh
echo "Running create_sql_user_and_role.sh"
bash infra/scripts/add_user_scripts/create_sql_user_and_role.sh "$sqlServerName.database.windows.net" "$SqlDatabaseName" '[
{"clientId":"'"$webAppManagedIdentityClientId"'", "displayName":"'"$webAppManagedIdentityDisplayName"'", "role":"db_datareader"},
{"clientId":"'"$webAppManagedIdentityClientId"'", "displayName":"'"$webAppManagedIdentityDisplayName"'", "role":"db_datawriter"}
{"clientId":"'"$sqlManagedIdentityClientId"'", "displayName":"'"$sqlManagedIdentityDisplayName"'", "role":"db_datareader"},
{"clientId":"'"$sqlManagedIdentityClientId"'", "displayName":"'"$sqlManagedIdentityDisplayName"'", "role":"db_datawriter"}
]'
if [ $? -ne 0 ]; then
echo "Error: create_sql_user_and_role.sh failed."
Expand Down
3 changes: 2 additions & 1 deletion src/App/backend/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ def __init__(self):
self.SQL_USERNAME = os.getenv("SQLDB_USERNAME")
self.SQL_PASSWORD = os.getenv("SQLDB_PASSWORD")
self.ODBC_DRIVER = "{ODBC Driver 18 for SQL Server}"
self.MID_ID = os.getenv("SQLDB_USER_MID")
self.MID_ID = os.getenv("AZURE_CLIENT_ID")
self.SQL_MID_ID = os.getenv("SQLDB_USER_MID")

# System Prompts
self.SQL_SYSTEM_PROMPT = os.environ.get("AZURE_SQL_SYSTEM_PROMPT")
Expand Down
2 changes: 1 addition & 1 deletion src/App/backend/services/sqldb_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
database = config.SQL_DATABASE
username = config.SQL_USERNAME
password = config.SQL_PASSWORD
mid_id = config.MID_ID
mid_id = config.SQL_MID_ID


def dict_cursor(cursor):
Expand Down