如何通过服务主体登录名(Azure SQL Server的AAD管理员)创建服务主体Azure SQL数据库用户

时间:2020-07-14 22:17:28

标签: sql sql-server azure continuous-integration azure-sql-database

我们正在使用ARM模板创建Azure SQL Server和数据库。我们已经在SQL Server上将AAD组设置为AAD管理员。该组包含我们在“应用程序注册”下创建的服务主体。

部署成功,我可以看到为SQL Server正确设置了AAD管理员。但是,当我尝试从该服务主体的部署管道运行脚本时,例如创建用户,则失败。

例如该powershell脚本成功获取了令牌并打开了连接,但失败并显示错误。

$tenant = "xxxx-xxxx-xxxxxx"
$applicationId = $(Get-AzADServicePrincipal -DisplayName "MyServicePrincipal").ApplicationId
$clientSecret = "yyyy-yyyyy-yyyy" 
$subscriptionId = "xyxy-xyxy-xyxy-xyxy"

$secstr = New-Object -TypeName System.Security.SecureString
$clientSecret.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $applicationId, $secstr

Connect-AzAccount -Tenant $tenant -SubscriptionId $subscriptionId -ServicePrincipal -Credential $cred


$resourceAppIdURI = 'https://database.windows.net/'

$authority = ('https://login.windows.net/{0}' -f $tenant)
$ClientCred = [Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential]::new($applicationId, $clientSecret)
$authContext = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext]::new($authority)
$authResult = $authContext.AcquireTokenAsync($resourceAppIdURI, $ClientCred)
$Token = $authResult.Result.AccessToken

$conn = New-Object System.Data.SqlClient.SQLConnection 
$SQLServerName = "sql-myApplicationDbServer-dev.database.windows.net"
$DatabaseName = 'sqldb-myApplicationDb-dev'

$conn.ConnectionString = "Data Source=$SQLServerName;Initial Catalog=$DatabaseName;Connect Timeout=30"
$conn.AccessToken = $($Token)
$conn.Open() 
$query = @"
            IF NOT EXISTS (
                SELECT  [name]
                FROM    sys.database_principals
                WHERE   [name] = 'myAppServiceName'
            )
            BEGIN
                CREATE USER [myAppServiceName] FROM EXTERNAL PROVIDER;
                ALTER ROLE db_datareader ADD MEMBER [myAppServiceName];
                ALTER ROLE db_datawriter ADD MEMBER [myAppServiceName];
                ALTER ROLE db_ddladmin ADD MEMBER [myAppServiceName];
            END
        "@
$command = $conn.CreateCommand()
$command.CommandText = $query
$result = $command.ExecuteNonQuery()
$result
$conn.Close() 

错误

...
PS C:\Users\TJ> $conn.Open()
PS C:\Users\TJ> $command = $conn.CreateCommand()
PS C:\Users\TJ> $command.CommandText = $query
PS C:\Users\TJ> $result = $command.ExecuteNonQuery()
MethodInvocationException: Exception calling "ExecuteNonQuery" with "0" argument(s): "Principal 'myAppServiceName' could not be resolved. Error message: ''
Cannot add the principal 'myAppServiceName', because it does not exist or you do not have permission.
Cannot add the principal 'myAppServiceName', because it does not exist or you do not have permission.
Cannot add the principal 'myAppServiceName', because it does not exist or you do not have permission."

但是,如果我在服务主体所属的SQL Server AAD管理员组中添加自己的AAD用户,则可以成功运行脚本。

我认为在运行任何创建用户脚本之前,服务主体需要关联的用户和适当的权限。如何从服务主体登录名执行此操作(将其管理员记住为SQL Server AAD管理员组的成员)。这将是管道的一部分,因此我不想在手动干预下运行任何脚本。

1 个答案:

答案 0 :(得分:0)

因此,服务主体似乎没有足够的权限来创建用户。 Microsoft声明他们正计划对其进行修复。目前,有一种解决方法。

诀窍是使用语法

CREATE USER [myAppServiceName] WITH SID=<0xappServiceServicePrincipalSid>, TYPE=E

,结果还表明,数据库可以理解的SID与我们在天蓝色中获得的objectId不同(例如在MSI情况下)。在我的案例中,这是为appService创建的应用程序服务主体的一种转换。

所以我需要先运行此程序以获得应用程序服务服务主体

$appServiceServicePrincipal = (Get-AzADServicePrincipal -SearchString myAppServiceName).ApplicationId

现在我们必须将其转换为数据库可以理解的SID,因此请在数据库中运行以下操作

SELECT CONVERT(VARCHAR(1000),CAST(CAST('$ appServiceServicePrincipal'AS UNIQUEIDENTIFIER)AS VARBINARY(16)),1);

并在我之前提到的SID=<0xappServiceServicePrincipalSid>中使用该值

然后它起作用。这是完整的示例。

$tenant = "xxxx-xxxx-xxxxxx"
$applicationId = $(Get-AzADServicePrincipal -DisplayName "AspBuildApps").ApplicationId
$clientSecret = "yyyy-yyyyy-yyyy" 
$subscriptionId = "xyxy-xyxy-xyxy-xyxy"
$appServiceName = 'myAppServiceName' #get it from ARM output

$secstr = New-Object -TypeName System.Security.SecureString
$clientSecret.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $applicationId, $secstr

Connect-AzAccount -Tenant $tenant -SubscriptionId $subscriptionId -ServicePrincipal -Credential $cred

$resourceAppIdURI = 'https://database.windows.net/'

$authority = ('https://login.windows.net/{0}' -f $tenant)
$ClientCred = [Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential]::new($applicationId, $clientSecret)
$authContext = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext]::new($authority)
$authResult = $authContext.AcquireTokenAsync($resourceAppIdURI, $ClientCred)
$Token = $authResult.Result.AccessToken

$conn = New-Object System.Data.SqlClient.SQLConnection 
$SQLServerName = "sql-myApplicationDbServer-dev.database.windows.net"
$DatabaseName = 'sqldb-myApplicationDb-dev'
$conn.ConnectionString = "Data Source=$SQLServerName;Initial Catalog=$DatabaseName;Connect Timeout=30"
$conn.AccessToken = $($Token)

$appServiceServicePrincipal = (Get-AzADServicePrincipal -SearchString $appServiceName).ApplicationId

$conn.Open() 

$query = @"
SELECT CONVERT(VARCHAR(1000), CAST(CAST('$($appServiceServicePrincipal)' AS UNIQUEIDENTIFIER) AS VARBINARY(16)),1);
"@

$command = $conn.CreateCommand()
$command.CommandText = $query
$appServiceServicePrincipalSid  = $command.ExecuteScalar()
Write-Host "appServiceServicePrincipalSid="$appServiceServicePrincipalSid

$query = @"
            IF NOT EXISTS (
                SELECT  [name]
                FROM    sys.database_principals
                WHERE   [name] = '$($appServiceName)'
            )
            BEGIN
                CREATE USER [$($appServiceName)] WITH SID=$($appServiceServicePrincipalSid), TYPE=E
                ALTER ROLE db_datareader ADD MEMBER [$($appServiceName)];
                ALTER ROLE db_datawriter ADD MEMBER [$($appServiceName)];
                ALTER ROLE db_ddladmin ADD MEMBER [$($appServiceName)];
            END
        "@

$command = $conn.CreateCommand()
$command.CommandText = $query
$result = $command.ExecuteNonQuery()
Write-Host $result
$conn.Close() 

感谢@LeonYue向我指出正确的方向。我想回答一下。

对我有帮助的链接:

https://blog.bredvid.no/handling-azure-managed-identity-access-to-azure-sql-in-an-azure-devops-pipeline-1e74e1beb10b

https://roadtoalm.com/2020/01/31/automatically-provision-a-azure-sql-db-with-a-managed-service-identity-msi/