Skip to content

Commit 405de67

Browse files
Changed roleassignment logic
1 parent 2288363 commit 405de67

File tree

3 files changed

+46
-43
lines changed

3 files changed

+46
-43
lines changed

infra/create-sql-user-and-role.bicep

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,34 +31,32 @@ resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-
3131
name: managedIdentityName
3232
}
3333

34-
resource createSqlUserAndRole 'Microsoft.Resources/deploymentScripts@2023-08-01' = [
35-
for databaseRole in databaseRoles: {
36-
name: 'sqlUserRole-${guid(principalId, databaseRole, sqlServerName, sqlDatabaseName)}'
37-
location: location
38-
tags: tags
39-
kind: 'AzurePowerShell'
40-
identity: {
41-
type: 'UserAssigned'
42-
userAssignedIdentities: {
43-
'${managedIdentity.id}': {}
44-
}
45-
}
46-
properties: {
47-
forceUpdateTag: uniqueScriptId
48-
azPowerShellVersion: '7.2'
49-
retentionInterval: 'PT1H'
50-
cleanupPreference: 'OnSuccess'
51-
arguments: join(
52-
[
53-
'-SqlServerName \'${sqlServerName}\''
54-
'-SqlDatabaseName \'${sqlDatabaseName}\''
55-
'-ClientId \'${principalId}\''
56-
'-DisplayName \'${principalName}\''
57-
'-DatabaseRole \'${databaseRole}\''
58-
],
59-
' '
60-
)
61-
scriptContent: loadTextContent('./scripts/add_user_scripts/create-sql-user-and-role.ps1')
34+
resource createSqlUserAndRole 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
35+
name: 'sqlUserRole-${guid(principalId, sqlServerName, sqlDatabaseName)}'
36+
location: location
37+
tags: tags
38+
kind: 'AzurePowerShell'
39+
identity: {
40+
type: 'UserAssigned'
41+
userAssignedIdentities: {
42+
'${managedIdentity.id}': {}
6243
}
6344
}
64-
]
45+
properties: {
46+
forceUpdateTag: uniqueScriptId
47+
azPowerShellVersion: '7.2'
48+
retentionInterval: 'PT1H'
49+
cleanupPreference: 'OnSuccess'
50+
arguments: join(
51+
[
52+
'-SqlServerName \'${sqlServerName}\''
53+
'-SqlDatabaseName \'${sqlDatabaseName}\''
54+
'-ClientId \'${principalId}\''
55+
'-DisplayName \'${principalName}\''
56+
'-DatabaseRoles \'${join(databaseRoles, ',')}\''
57+
],
58+
' '
59+
)
60+
scriptContent: loadTextContent('./scripts/add_user_scripts/create-sql-user-and-role.ps1')
61+
}
62+
}

infra/main.json

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"_generator": {
66
"name": "bicep",
77
"version": "0.36.177.2456",
8-
"templateHash": "2040642101386319700"
8+
"templateHash": "15970509425891056575"
99
}
1010
},
1111
"parameters": {
@@ -2454,7 +2454,7 @@
24542454
"_generator": {
24552455
"name": "bicep",
24562456
"version": "0.36.177.2456",
2457-
"templateHash": "14904292388458610103"
2457+
"templateHash": "11761603685152215086"
24582458
}
24592459
},
24602460
"parameters": {
@@ -2609,7 +2609,7 @@
26092609
"_generator": {
26102610
"name": "bicep",
26112611
"version": "0.36.177.2456",
2612-
"templateHash": "12065007697995808067"
2612+
"templateHash": "15963212505186290885"
26132613
}
26142614
},
26152615
"parameters": {
@@ -2677,7 +2677,7 @@
26772677
}
26782678
},
26792679
"variables": {
2680-
"$fxv#0": "#Requires -Version 7.2\r\n\r\n<#\r\n.SYNOPSIS\r\n Creates a SQL user and assigns the user account to one or more roles.\r\n\r\n.DESCRIPTION\r\n During an application deployment, the managed identity (and potentially the developer identity)\r\n must be added to the SQL database as a user and assigned to one or more roles. This script\r\n accomplishes this task using the owner-managed identity for authentication.\r\n\r\n.PARAMETER SqlServerName\r\n The name of the Azure SQL Server resource.\r\n\r\n.PARAMETER SqlDatabaseName\r\n The name of the Azure SQL Database where the user will be created.\r\n\r\n.PARAMETER ClientId\r\n The Client (Principal) ID (GUID) of the identity to be added.\r\n\r\n.PARAMETER DisplayName\r\n The Object (Principal) display name of the identity to be added.\r\n\r\n.PARAMETER DatabaseRole\r\n The database role that should be assigned to the user (e.g., db_datareader, db_datawriter, db_owner).\r\n#>\r\n\r\nParam(\r\n [string] $SqlServerName,\r\n [string] $SqlDatabaseName,\r\n [string] $ClientId,\r\n [string] $DisplayName,\r\n [string] $DatabaseRole\r\n)\r\n\r\n# Using specific version of SqlServer module to avoid issues with newer versions\r\n$SqlServerModuleVersion = \"22.3.0\"\r\n\r\nfunction Resolve-Module($moduleName) {\r\n # If module is imported; say that and do nothing\r\n if (Get-Module | Where-Object { $_.Name -eq $moduleName }) {\r\n Write-Debug \"Module $moduleName is already imported\"\r\n } elseif (Get-Module -ListAvailable | Where-Object { $_.Name -eq $moduleName }) {\r\n Import-Module $moduleName\r\n } elseif (Find-Module -Name $moduleName | Where-Object { $_.Name -eq $moduleName }) {\r\n # Use specific version for SqlServer\r\n if ($ModuleName -eq \"SqlServer\") {\r\n Install-Module -Name $ModuleName -RequiredVersion $SqlServerModuleVersion -Force -Scope CurrentUser\r\n } else {\r\n Install-Module -Name $ModuleName -Force\r\n }\r\n Import-Module $moduleName\r\n } else {\r\n Write-Error \"Module $moduleName not found\"\r\n [Environment]::exit(1)\r\n }\r\n}\r\n\r\n###\r\n### MAIN SCRIPT\r\n###\r\nResolve-Module -moduleName Az.Resources\r\nResolve-Module -moduleName SqlServer\r\n\r\n$sql = @\"\r\nDECLARE @username nvarchar(max) = N'$($DisplayName)';\r\nDECLARE @clientId uniqueidentifier = '$($ClientId)';\r\nDECLARE @sid NVARCHAR(max) = CONVERT(VARCHAR(max), CONVERT(VARBINARY(16), @clientId), 1);\r\nDECLARE @cmd NVARCHAR(max) = N'CREATE USER [' + @username + '] WITH SID = ' + @sid + ', TYPE = E;';\r\nIF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = @username)\r\nBEGIN\r\n EXEC(@cmd)\r\nEND\r\nEXEC sp_addrolemember '$($DatabaseRole)', @username;\r\n\"@\r\n\r\nWrite-Output \"`nSQL:`n$($sql)`n`n\"\r\n\r\n$token = (Get-AzAccessToken -AsSecureString -ResourceUrl https://database.windows.net/).Token\r\n$ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token)\r\ntry {\r\n $serverInstance = if ($SqlServerName -like \"*.database.windows.net\") { \r\n $SqlServerName \r\n } else { \r\n \"$SqlServerName.database.windows.net\" \r\n }\r\n $plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)\r\n Invoke-Sqlcmd -ServerInstance $serverInstance -Database $SqlDatabaseName -AccessToken $plaintext -Query $sql -ErrorAction 'Stop'\r\n} finally {\r\n # The following line ensures that sensitive data is not left in memory.\r\n $plainText = [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr)\r\n}"
2680+
"$fxv#0": "#Requires -Version 7.2\r\n\r\n<#\r\n.SYNOPSIS\r\n Creates a SQL user and assigns the user account to one or more roles.\r\n\r\n.DESCRIPTION\r\n During an application deployment, the managed identity (and potentially the developer identity)\r\n must be added to the SQL database as a user and assigned to one or more roles. This script\r\n accomplishes this task using the owner-managed identity for authentication.\r\n\r\n.PARAMETER SqlServerName\r\n The name of the Azure SQL Server resource.\r\n\r\n.PARAMETER SqlDatabaseName\r\n The name of the Azure SQL Database where the user will be created.\r\n\r\n.PARAMETER ClientId\r\n The Client (Principal) ID (GUID) of the identity to be added.\r\n\r\n.PARAMETER DisplayName\r\n The Object (Principal) display name of the identity to be added.\r\n\r\n.PARAMETER DatabaseRoles\r\n A comma-separated string of database roles to assign (e.g., 'db_datareader,db_datawriter')\r\n#>\r\n\r\nParam(\r\n [string] $SqlServerName,\r\n [string] $SqlDatabaseName,\r\n [string] $ClientId,\r\n [string] $DisplayName,\r\n [string] $DatabaseRoles\r\n)\r\n\r\n# Using specific version of SqlServer module to avoid issues with newer versions\r\n$SqlServerModuleVersion = \"22.3.0\"\r\n\r\nfunction Resolve-Module($moduleName) {\r\n # If module is imported; say that and do nothing\r\n if (Get-Module | Where-Object { $_.Name -eq $moduleName }) {\r\n Write-Debug \"Module $moduleName is already imported\"\r\n } elseif (Get-Module -ListAvailable | Where-Object { $_.Name -eq $moduleName }) {\r\n Import-Module $moduleName\r\n } elseif (Find-Module -Name $moduleName | Where-Object { $_.Name -eq $moduleName }) {\r\n # Use specific version for SqlServer\r\n if ($ModuleName -eq \"SqlServer\") {\r\n Install-Module -Name $ModuleName -RequiredVersion $SqlServerModuleVersion -Force -Scope CurrentUser\r\n } else {\r\n Install-Module -Name $ModuleName -Force\r\n }\r\n Import-Module $moduleName\r\n } else {\r\n Write-Error \"Module $moduleName not found\"\r\n [Environment]::exit(1)\r\n }\r\n}\r\n\r\n###\r\n### MAIN SCRIPT\r\n###\r\nResolve-Module -moduleName Az.Resources\r\nResolve-Module -moduleName SqlServer\r\n\r\n# Split comma-separated roles into an array\r\n$roleArray = $DatabaseRoles -split ','\r\n\r\n$roleSql = \"\"\r\nforeach ($role in $roleArray) {\r\n $trimmedRole = $role.Trim()\r\n $roleSql += \"EXEC sp_addrolemember N'$trimmedRole', N'$DisplayName';`n\"\r\n}\r\n\r\n$sql = @\"\r\nDECLARE @username nvarchar(max) = N'$($DisplayName)';\r\nDECLARE @clientId uniqueidentifier = '$($ClientId)';\r\nDECLARE @sid NVARCHAR(max) = CONVERT(VARCHAR(max), CONVERT(VARBINARY(16), @clientId), 1);\r\nDECLARE @cmd NVARCHAR(max) = N'CREATE USER [' + @username + '] WITH SID = ' + @sid + ', TYPE = E;';\r\nIF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = @username)\r\nBEGIN\r\n EXEC(@cmd)\r\nEND\r\n$($roleSql)\r\n\"@\r\n\r\nWrite-Output \"`nSQL:`n$($sql)`n`n\"\r\n\r\n$token = (Get-AzAccessToken -AsSecureString -ResourceUrl https://database.windows.net/).Token\r\n$ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token)\r\ntry {\r\n $serverInstance = if ($SqlServerName -like \"*.database.windows.net\") { \r\n $SqlServerName \r\n } else { \r\n \"$SqlServerName.database.windows.net\" \r\n }\r\n $plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr)\r\n Invoke-Sqlcmd -ServerInstance $serverInstance -Database $SqlDatabaseName -AccessToken $plaintext -Query $sql -ErrorAction 'Stop'\r\n} finally {\r\n # The following line ensures that sensitive data is not left in memory.\r\n $plainText = [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr)\r\n}"
26812681
},
26822682
"resources": {
26832683
"managedIdentity": {
@@ -2687,13 +2687,9 @@
26872687
"name": "[parameters('managedIdentityName')]"
26882688
},
26892689
"createSqlUserAndRole": {
2690-
"copy": {
2691-
"name": "createSqlUserAndRole",
2692-
"count": "[length(parameters('databaseRoles'))]"
2693-
},
26942690
"type": "Microsoft.Resources/deploymentScripts",
26952691
"apiVersion": "2023-08-01",
2696-
"name": "[format('sqlUserRole-{0}', guid(parameters('principalId'), parameters('databaseRoles')[copyIndex()], parameters('sqlServerName'), parameters('sqlDatabaseName')))]",
2692+
"name": "[format('sqlUserRole-{0}', guid(parameters('principalId'), parameters('sqlServerName'), parameters('sqlDatabaseName')))]",
26972693
"location": "[parameters('location')]",
26982694
"tags": "[parameters('tags')]",
26992695
"kind": "AzurePowerShell",
@@ -2708,7 +2704,7 @@
27082704
"azPowerShellVersion": "7.2",
27092705
"retentionInterval": "PT1H",
27102706
"cleanupPreference": "OnSuccess",
2711-
"arguments": "[join(createArray(format('-SqlServerName ''{0}''', parameters('sqlServerName')), format('-SqlDatabaseName ''{0}''', parameters('sqlDatabaseName')), format('-ClientId ''{0}''', parameters('principalId')), format('-DisplayName ''{0}''', parameters('principalName')), format('-DatabaseRole ''{0}''', parameters('databaseRoles')[copyIndex()])), ' ')]",
2707+
"arguments": "[join(createArray(format('-SqlServerName ''{0}''', parameters('sqlServerName')), format('-SqlDatabaseName ''{0}''', parameters('sqlDatabaseName')), format('-ClientId ''{0}''', parameters('principalId')), format('-DisplayName ''{0}''', parameters('principalName')), format('-DatabaseRoles ''{0}''', join(parameters('databaseRoles'), ','))), ' ')]",
27122708
"scriptContent": "[variables('$fxv#0')]"
27132709
}
27142710
}

infra/scripts/add_user_scripts/create-sql-user-and-role.ps1

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@
2121
.PARAMETER DisplayName
2222
The Object (Principal) display name of the identity to be added.
2323
24-
.PARAMETER DatabaseRole
25-
The database role that should be assigned to the user (e.g., db_datareader, db_datawriter, db_owner).
24+
.PARAMETER DatabaseRoles
25+
A comma-separated string of database roles to assign (e.g., 'db_datareader,db_datawriter')
2626
#>
2727

2828
Param(
2929
[string] $SqlServerName,
3030
[string] $SqlDatabaseName,
3131
[string] $ClientId,
3232
[string] $DisplayName,
33-
[string] $DatabaseRole
33+
[string] $DatabaseRoles
3434
)
3535

3636
# Using specific version of SqlServer module to avoid issues with newer versions
@@ -62,6 +62,15 @@ function Resolve-Module($moduleName) {
6262
Resolve-Module -moduleName Az.Resources
6363
Resolve-Module -moduleName SqlServer
6464

65+
# Split comma-separated roles into an array
66+
$roleArray = $DatabaseRoles -split ','
67+
68+
$roleSql = ""
69+
foreach ($role in $roleArray) {
70+
$trimmedRole = $role.Trim()
71+
$roleSql += "EXEC sp_addrolemember N'$trimmedRole', N'$DisplayName';`n"
72+
}
73+
6574
$sql = @"
6675
DECLARE @username nvarchar(max) = N'$($DisplayName)';
6776
DECLARE @clientId uniqueidentifier = '$($ClientId)';
@@ -71,7 +80,7 @@ IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = @username)
7180
BEGIN
7281
EXEC(@cmd)
7382
END
74-
EXEC sp_addrolemember '$($DatabaseRole)', @username;
83+
$($roleSql)
7584
"@
7685

7786
Write-Output "`nSQL:`n$($sql)`n`n"

0 commit comments

Comments
 (0)