Skip to content

Commit 46671f7

Browse files
Create new Managed Identity for SQL operations with least privileges
1 parent b17a9c4 commit 46671f7

File tree

4 files changed

+50
-15
lines changed

4 files changed

+50
-15
lines changed

infra/main.bicep

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,19 @@ module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-id
388388
}
389389
}
390390

391+
// ========== SQL Operations User Assigned Identity ========== //
392+
// Dedicated identity for backend SQL operations with limited permissions (db_datareader, db_datawriter)
393+
var sqlUserAssignedIdentityResourceName = 'id-sql-${solutionSuffix}'
394+
module sqlUserAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = {
395+
name: take('avm.res.managed-identity.user-assigned-identity.${sqlUserAssignedIdentityResourceName}', 64)
396+
params: {
397+
name: sqlUserAssignedIdentityResourceName
398+
location: solutionLocation
399+
tags: tags
400+
enableTelemetry: enableTelemetry
401+
}
402+
}
403+
391404
// ========== Network Module ========== //
392405
module network 'modules/network.bicep' = if (enablePrivateNetworking) {
393406
name: take('network-${solutionSuffix}-deployment', 64)
@@ -509,6 +522,11 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = {
509522
principalType: 'ServicePrincipal'
510523
roleDefinitionIdOrName: 'Key Vault Administrator'
511524
}
525+
{
526+
principalId: sqlUserAssignedIdentity.outputs.principalId
527+
principalType: 'ServicePrincipal'
528+
roleDefinitionIdOrName: 'Key Vault Secrets User'
529+
}
512530
]
513531
secrets: [
514532
{
@@ -539,6 +557,10 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = {
539557
name: 'AZURE-SEARCH-ENDPOINT'
540558
value: 'https://${aiSearchName}.search.windows.net'
541559
}
560+
{
561+
name: 'SQLDB-USER-MID'
562+
value: sqlUserAssignedIdentity.outputs.clientId
563+
}
542564
]
543565
enableTelemetry: enableTelemetry
544566
}
@@ -918,6 +940,7 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = {
918940
systemAssigned: true
919941
userAssignedResourceIds: [
920942
userAssignedIdentity.outputs.resourceId
943+
sqlUserAssignedIdentity.outputs.resourceId
921944
]
922945
}
923946
primaryUserAssignedIdentityResourceId: userAssignedIdentity.outputs.resourceId
@@ -988,7 +1011,7 @@ module webSite 'modules/web-sites.bicep' = {
9881011
name: webSiteResourceName
9891012
tags: tags
9901013
location: solutionLocation
991-
managedIdentities: { userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId] }
1014+
managedIdentities: { userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId, sqlUserAssignedIdentity!.outputs.resourceId] }
9921015
kind: 'app,linux,container'
9931016
serverFarmResourceId: webServerFarm.?outputs.resourceId
9941017
siteConfig: {
@@ -1035,7 +1058,7 @@ module webSite 'modules/web-sites.bicep' = {
10351058
AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: collectionName
10361059
AZURE_COSMOSDB_DATABASE: cosmosDbDatabaseName
10371060
AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback
1038-
SQLDB_USER_MID: userAssignedIdentity.outputs.clientId
1061+
SQLDB_USER_MID: sqlUserAssignedIdentity.outputs.clientId
10391062
AZURE_AI_SEARCH_ENDPOINT: 'https://${aiSearchName}.search.windows.net'
10401063
AZURE_SQL_SYSTEM_PROMPT: functionAppSqlPrompt
10411064
AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT: functionAppCallTranscriptSystemPrompt
@@ -1226,6 +1249,12 @@ output MANAGEDIDENTITY_WEBAPP_NAME string = userAssignedIdentity.outputs.name
12261249

12271250
@description('Client ID of the managed identity used by the web app.')
12281251
output MANAGEDIDENTITY_WEBAPP_CLIENTID string = userAssignedIdentity.outputs.clientId
1252+
1253+
@description('Name of the managed identity used for SQL database operations.')
1254+
output MANAGEDIDENTITY_SQL_NAME string = sqlUserAssignedIdentity.outputs.name
1255+
1256+
@description('Client ID of the managed identity used for SQL database operations.')
1257+
output MANAGEDIDENTITY_SQL_CLIENTID string = sqlUserAssignedIdentity.outputs.clientId
12291258
@description('Name of the AI Search service.')
12301259
output AI_SEARCH_SERVICE_NAME string = aiSearchName
12311260

@@ -1367,3 +1396,6 @@ output USE_AI_PROJECT_CLIENT string = useAIProjectClientFlag
13671396

13681397
@description('Indicates whether the internal stream should be used.')
13691398
output USE_INTERNAL_STREAM string = useInternalStream
1399+
1400+
@description('The client ID of the managed identity.')
1401+
output AZURE_CLIENT_ID string = userAssignedIdentity.outputs.clientId

infra/scripts/process_sample_data.sh

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
keyvaultName="$5"
99
sqlServerName="$6"
1010
SqlDatabaseName="$7"
11-
webAppManagedIdentityClientId="$8"
12-
webAppManagedIdentityDisplayName="$9"
11+
sqlManagedIdentityClientId="$8"
12+
sqlManagedIdentityDisplayName="$9"
1313
aiSearchName="${10}"
1414
aif_resource_id="${11}"
1515

@@ -316,12 +316,14 @@
316316
SqlDatabaseName=$(azd env get-value SQLDB_DATABASE)
317317
fi
318318

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

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

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

337339
# Check if all required arguments are provided
338-
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
339-
echo "Usage: $0 <resourceGroupName> <cosmosDbAccountName> <storageAccount> <storageContainerName> <keyvaultName> <sqlServerName> <sqlDatabaseName> <webAppUserManagedIdentityClientId> <webAppUserManagedIdentityDisplayName> <aiSearchName> <aiFoundryResourceGroup> <aif_resource_id>"
340+
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
341+
echo "Usage: $0 <resourceGroupName> <cosmosDbAccountName> <storageAccount> <storageContainerName> <keyvaultName> <sqlServerName> <sqlDatabaseName> <sqlManagedIdentityClientId> <sqlManagedIdentityDisplayName> <aiSearchName> <aiFoundryResourceGroup> <aif_resource_id>"
340342
exit 1
341343
fi
342344

@@ -437,8 +439,8 @@
437439
# Call create_sql_user_and_role.sh
438440
echo "Running create_sql_user_and_role.sh"
439441
bash infra/scripts/add_user_scripts/create_sql_user_and_role.sh "$sqlServerName.database.windows.net" "$SqlDatabaseName" '[
440-
{"clientId":"'"$webAppManagedIdentityClientId"'", "displayName":"'"$webAppManagedIdentityDisplayName"'", "role":"db_datareader"},
441-
{"clientId":"'"$webAppManagedIdentityClientId"'", "displayName":"'"$webAppManagedIdentityDisplayName"'", "role":"db_datawriter"}
442+
{"clientId":"'"$sqlManagedIdentityClientId"'", "displayName":"'"$sqlManagedIdentityDisplayName"'", "role":"db_datareader"},
443+
{"clientId":"'"$sqlManagedIdentityClientId"'", "displayName":"'"$sqlManagedIdentityDisplayName"'", "role":"db_datawriter"}
442444
]'
443445
if [ $? -ne 0 ]; then
444446
echo "Error: create_sql_user_and_role.sh failed."

src/App/backend/common/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,9 @@ def __init__(self):
142142
self.SQL_USERNAME = os.getenv("SQLDB_USERNAME")
143143
self.SQL_PASSWORD = os.getenv("SQLDB_PASSWORD")
144144
self.ODBC_DRIVER = "{ODBC Driver 18 for SQL Server}"
145-
self.MID_ID = os.getenv("SQLDB_USER_MID")
145+
self.MID_ID = os.getenv("AZURE_CLIENT_ID")
146+
self.SQL_MID_ID = os.getenv("SQLDB_USER_MID")
147+
146148

147149
# System Prompts
148150
self.SQL_SYSTEM_PROMPT = os.environ.get("AZURE_SQL_SYSTEM_PROMPT")

src/App/backend/services/sqldb_service.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
database = config.SQL_DATABASE
1818
username = config.SQL_USERNAME
1919
password = config.SQL_PASSWORD
20-
mid_id = config.MID_ID
21-
20+
mid_id = config.SQL_MID_ID
2221

2322
def dict_cursor(cursor):
2423
"""

0 commit comments

Comments
 (0)