在Azure SQL Server中,作为服务主体的AD管理员能否在主数据库上运行查询?
鉴于:在Azure SQL Server中,作为服务主体的AD管理员能否在主数据库上运行查询?,azure,azure-sql-database,azure-sql-server,Azure,Azure Sql Database,Azure Sql Server,鉴于: Azure SQL Server-MyAzureSQL Server 服务主体-MyServicePrincipal 服务主体被配置为Azure SQL Server的AD管理员。(Azure Portal和Az Powershell模块不允许,但Azure CLI和REST API允许) 我的Powershell代码在上述Azure SQL Server中的给定数据库上运行SELECT 1: param($db) $AzContext = Get-AzContext
MyAzureSQL Server
MyServicePrincipal
SELECT 1
:
param($db)
$AzContext = Get-AzContext # Assume this returns the Az Context for MyServicePrincipal
$TenantId = $AzContext.Tenant.Id
$ClientId = $AzContext.Account.Id
$SubscriptionId = $AzContext.Subscription.Id
$ClientSecret = $AzContext.Account.ExtendedProperties.ServicePrincipalSecret
$token = Get-AzureAuthenticationToken -TenantID $TenantId -ClientID $ClientId -ClientSecret $ClientSecret -ResourceAppIDUri "https://database.windows.net/"
Invoke-SqlQueryThruAdoNet -ConnectionString "Server=MyAzureSqlServer.database.windows.net;database=$db" -AccessToken $token -Query "SELECT 1"
其中获取AzureAuthenticationToken
是:
function Get-AzureAuthenticationToken(
[Parameter(Mandatory)][String]$TenantID,
[Parameter(Mandatory)][String]$ClientID,
[Parameter(Mandatory)][String]$ClientSecret,
[Parameter(Mandatory)][String]$ResourceAppIDUri)
{
$tokenResponse = Invoke-RestMethod -Method Post -UseBasicParsing `
-Uri "https://login.windows.net/$TenantID/oauth2/token" `
-Body @{
resource = $ResourceAppIDUri
client_id = $ClientID
grant_type = 'client_credentials'
client_secret = $ClientSecret
} -ContentType 'application/x-www-form-urlencoded'
Write-Verbose "Access token type is $($tokenResponse.token_type), expires $($tokenResponse.expires_on)"
$tokenResponse.access_token
}
function Invoke-SqlQueryThruAdoNet(
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$ConnectionString,
[parameter(Mandatory=$true)]
[string]$Query,
$QueryTimeout = 30,
[string]$AccessToken
)
{
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
try
{
$SqlConnection.ConnectionString = $ConnectionString
if ($AccessToken)
{
$SqlConnection.AccessToken = $AccessToken
}
$SqlConnection.Open()
$SqlCmd.CommandTimeout = $QueryTimeout
$SqlCmd.CommandText = $Query
$SqlCmd.Connection = $SqlConnection
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.SelectCommand = $SqlCmd
[void]$SqlAdapter.Fill($DataSet)
$res = $null
if ($DataSet.Tables.Count)
{
$res = $DataSet.Tables[$DataSet.Tables.Count - 1]
}
$res
}
finally
{
$SqlAdapter.Dispose()
$SqlCmd.Dispose()
$SqlConnection.Dispose()
}
}
调用SqlQueryThruAdoNet是:
function Get-AzureAuthenticationToken(
[Parameter(Mandatory)][String]$TenantID,
[Parameter(Mandatory)][String]$ClientID,
[Parameter(Mandatory)][String]$ClientSecret,
[Parameter(Mandatory)][String]$ResourceAppIDUri)
{
$tokenResponse = Invoke-RestMethod -Method Post -UseBasicParsing `
-Uri "https://login.windows.net/$TenantID/oauth2/token" `
-Body @{
resource = $ResourceAppIDUri
client_id = $ClientID
grant_type = 'client_credentials'
client_secret = $ClientSecret
} -ContentType 'application/x-www-form-urlencoded'
Write-Verbose "Access token type is $($tokenResponse.token_type), expires $($tokenResponse.expires_on)"
$tokenResponse.access_token
}
function Invoke-SqlQueryThruAdoNet(
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$ConnectionString,
[parameter(Mandatory=$true)]
[string]$Query,
$QueryTimeout = 30,
[string]$AccessToken
)
{
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
try
{
$SqlConnection.ConnectionString = $ConnectionString
if ($AccessToken)
{
$SqlConnection.AccessToken = $AccessToken
}
$SqlConnection.Open()
$SqlCmd.CommandTimeout = $QueryTimeout
$SqlCmd.CommandText = $Query
$SqlCmd.Connection = $SqlConnection
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.SelectCommand = $SqlCmd
[void]$SqlAdapter.Fill($DataSet)
$res = $null
if ($DataSet.Tables.Count)
{
$res = $DataSet.Tables[$DataSet.Tables.Count - 1]
}
$res
}
finally
{
$SqlAdapter.Dispose()
$SqlCmd.Dispose()
$SqlConnection.Dispose()
}
}
它在任何数据库上都能正常工作,除了主数据库,我得到:
用户“4”的[MyAzureSqlServer.database.windows.net\master]登录失败。。。1@2...b'. (SqlError 18456,行号=65536,客户端连接ID=b8f4f657-2772-4306-b222-453301327D1)
其中4…1
是MyServicePrincipal
的客户端Id,2…b
是我们的Azure AD租户Id
所以我知道访问令牌是可以的,因为我可以在其他数据库上运行查询。尤其是主机
有问题。有解决办法吗?当然,它必须与作为广告管理员的服务主体一起工作
编辑1
正如我所提到的,有两种方法可以将服务主体配置为AD管理员:
- 使用Azure CLI。其实很简单:
{ADAdminName}
可以是任何内容,但我们传递服务主体的显示名称
现在,虽然这样做有效,但我们还是放弃了Azure CLI,转而使用Az Powershell,因为后者不会以明文形式在磁盘上持久保存服务主体凭据。但是,Az Powershell的函数不接受服务主体。但是Azure REST API确实允许这样做,因此我们有以下定制PS函数来完成这项工作:
function Set-MyAzSqlServerActiveDirectoryAdministrator
{
[CmdLetBinding(DefaultParameterSetName = 'NoObjectId')]
param(
[Parameter(Mandatory, Position = 0)][string]$ResourceGroupName,
[Parameter(Mandatory, Position = 1)][string]$ServerName,
[Parameter(ParameterSetName = 'ObjectId', Mandatory)][ValidateNotNullOrEmpty()]$ObjectId,
[Parameter(ParameterSetName = 'ObjectId', Mandatory)][ValidateNotNullOrEmpty()]$DisplayName
)
$AzContext = Get-AzContext
if (!$AzContext)
{
throw "No Az context is found."
}
$TenantId = $AzContext.Tenant.Id
$ClientId = $AzContext.Account.Id
$SubscriptionId = $AzContext.Subscription.Id
$ClientSecret = $AzContext.Account.ExtendedProperties.ServicePrincipalSecret
if ($PsCmdlet.ParameterSetName -eq 'NoObjectId')
{
$sp = Get-AzADServicePrincipal -ApplicationId $ClientId
$DisplayName = $sp.DisplayName
$ObjectId = $sp.Id
}
$path = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.Sql/servers/$ServerName/administrators/activeDirectory"
$apiUrl = "https://management.azure.com${path}?api-version=2014-04-01"
$jsonBody = @{
id = $path
name = 'activeDirectory'
properties = @{
administratorType = 'ActiveDirectory'
login = $DisplayName
sid = $ObjectId
tenantId = $TenantId
}
} | ConvertTo-Json -Depth 99
$token = Get-AzureAuthenticationToken -TenantID $TenantId -ClientID $ClientId -ClientSecret $ClientSecret -ResourceAppIDUri "https://management.core.windows.net/"
$headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-RestMethod $apiUrl -Method Put -Headers $headers -Body $jsonBody
}
它使用已经熟悉的(见上文)函数
获取AzureAuthenticationToken
。根据我们的需要,它将当前登录的服务主体设置为AD admin。根据我的测试,当我们直接将Azure服务主体设置为Azure SQL AD admin时,它将导致一些问题。我们无法使用服务原则登录master
数据库。因为Azure广告管理员登录名应该是Azure广告用户或Azure广告组。有关更多详细信息,请参阅
因此,如果您想将Azure服务主体设置为Azure SQL AD admin,我们需要创建一个Azure AD安全组,将服务主体添加为组的成员,将Azure AD组设置为Azure SQL AD admin
比如说
连接AzaAccount
$group=New AzADGroup-DisplayName SQLADADmin-Mail昵称SQLADADmin
$sp=获取AzADServicePrincipal-显示名称“TodoListService-OBO-sample-v2”
添加AzADGroupMember-MemberObjectId$sp.Id-TargetGroupObjectId$group.Id
$sp=获取AzADServicePrincipal-DisplayName“”
删除AZSSQLServerActiveDirectoryAdministrator-ResourceGroupName”“-ServerName”“-force
设置AzSqlServerActiveDirectoryAdministrator-ResourceGroupName”“-ServerName”“-DisplayName$group.DisplayName-ObjectId$group.id
质疑
$appId=“”
$password=“”
$secpasswd=converttosecurestring$password-AsPlainText-Force
$mycreds=New Object System.Management.Automation.PSCredential($appId,$secpasswd)
连接AzaAccount-ServicePrincipal-凭据$mycreds-租户“”
#领取代币
$context=获取上下文
$dexResourceUrl=https://database.windows.net/'
$token=[Microsoft.Azure.Commands.Common.Authentication.AzureSession]::实例.AuthenticationFactory.Authentication($context.Account,
$context.Environment,
$context.Tenant.Id.ToString(),
$null,
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::从不,
$null,$dexResourceUrl).AccessToken
$SqlConnection=新对象System.Data.SqlClient.SqlConnection
$SqlCmd=New Object System.Data.SqlClient.SqlCommand
$ConnectionString=“数据源=testsql08.database.windows.net;初始目录=master;”
#查询当前数据库名称
$Query=“选择数据库名称()
尝试
{
$SqlConnection.ConnectionString=$ConnectionString
如果($token)
{
$SqlConnection.AccessToken=$token
}
$SqlConnection.Open()
$SqlCmd.CommandText=$Query
$SqlCmd.Connection=$SqlConnection
$DataSet=新对象System.Data.DataSet
$SqlAdapter.SelectCommand=$SqlCmd
[void]$SqlAdapter.Fill($DataSet)
$res=$null
if($DataSet.Tables.Count)
{
$res=$DataSet.Tables[$DataSet.Tables.Count-1]
}
$res
}
最后
{
$SqlAdapter.Dispose()
$SqlCmd.Dispose()
$SqlConnection.Dispose()
}
您能告诉我如何将服务主体配置为Azure AD AD admin吗?请参阅编辑1。它对您有用吗?让我对答案进行评论。+1,但我将等待其他答案,因为这里的AD admin是一个组,而不是服务主体。我们不能使用这种方法,因为每个pod都部署了一个专用的服务主体。创建SP的代码没有修改广告组的权限。@mark您能告诉我为什么需要修改广告组吗?因为当我们引导一个新pod时,会创建一个专用的服务主体。引导代码具有创建新服务主体的权限,但不具有修改任何现有广告组或创建新广告组的权限。
$appId = "<your sp app id>"
$password = "<your sp password>"
$secpasswd = ConvertTo-SecureString $password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($appId, $secpasswd)
Connect-AzAccount -ServicePrincipal -Credential $mycreds -Tenant "<your AD tenant id>"
#get token
$context =Get-AzContext
$dexResourceUrl='https://database.windows.net/'
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account,
$context.Environment,
$context.Tenant.Id.ToString(),
$null,
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never,
$null, $dexResourceUrl).AccessToken
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$ConnectionString="Data Source=testsql08.database.windows.net; Initial Catalog=master;"
# query the current database name
$Query="SELECT DB_NAME()"
try
{
$SqlConnection.ConnectionString = $ConnectionString
if ($token)
{
$SqlConnection.AccessToken = $token
}
$SqlConnection.Open()
$SqlCmd.CommandText = $Query
$SqlCmd.Connection = $SqlConnection
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.SelectCommand = $SqlCmd
[void]$SqlAdapter.Fill($DataSet)
$res = $null
if ($DataSet.Tables.Count)
{
$res = $DataSet.Tables[$DataSet.Tables.Count - 1]
}
$res
}
finally
{
$SqlAdapter.Dispose()
$SqlCmd.Dispose()
$SqlConnection.Dispose()
}