На сервере Azure SQL может ли администратор AD, который также является субъектом службы, выполнить запрос к базе данных master? - PullRequest
3 голосов
/ 30 мая 2020

Дано:

  1. An Azure SQL Сервер - MyAzureSqlServer
  2. Субъект-служба - MyServicePrincipal
  3. Субъект-служба настроена в качестве администратора AD сервера Azure SQL. (Azure Модуль Portal и Az Powershell не допускает этого, но Azure CLI и REST API позволяют)

У меня есть код Powershell, который запускает SELECT 1 в данной базе данных в вышеупомянутом Azure SQL Сервер:

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"

Где Get-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
}

И Invoke-SqlQueryThruAdoNet это:

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()
    }
}

И это работает отлично для любой базы данных, кроме главной, для которой я получаю:

[MyAzureSqlServer.database. windows .net \ master] Ошибка входа для пользователя '4 ... 1@2...b' . (SqlError 18456, LineNumber = 65536, ClientConnectionId = b8f4f657-2772-4306-b222-4533013227d1)

, где 4...1 - это идентификатор клиента MyServicePrincipal, а 2...b - наш Azure Идентификатор клиента AD

Итак, я знаю, что токен доступа в порядке, потому что я могу выполнять запросы к другим базам данных. Именно master является проблемой c. Есть ли решение для этого? Конечно, он должен работать с субъектом-службой, являющимся администратором AD.

EDIT 1

Как я уже упоминал, существует 2 способа настроить субъект-службу как Администратор AD:

  • Используя Azure CLI. На самом деле это просто:
az sql server ad-admin create --resource-group {YourAzureSqlResourceGroupName} `
        --server-name {YourAzureSqlServerName} `
        --display-name {ADAdminName} `
        --object-id {ServicePrincipalObjectId}

{ADAdminName} может быть любым, но мы передаем отображаемое имя субъекта-службы.

Теперь, пока это работает, мы отказались от Azure CLI в пользу Az Powershell, поскольку последний не сохраняет учетные данные участника-службы на диске в виде открытого текста. Однако функция Az Powershell Set-AzSqlServerActiveDirectoryAdministrator не принимает участника-службы. Тем не менее, 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
}

Она использует уже знакомую (см. Выше) функцию Get-AzureAuthenticationToken. Для наших нужд он устанавливает текущего вошедшего в систему участника-службы как администратора AD.

1 Ответ

1 голос
/ 02 июня 2020

Согласно моему тесту, когда мы напрямую устанавливаем принципала службы Azure как Azure SQL администратора AD, это вызовет некоторые проблемы. Мы не можем войти в базу данных master с помощью правила обслуживания. Потому что для входа в систему администратора Azure AD должен быть пользователь Azure AD или группа Azure AD. Для получения дополнительных сведений см. Документ

Поэтому, если вы хотите установить принципала службы Azure как Azure SQL администратора AD, нам необходимо создать Azure Группа безопасности AD, добавьте принципала службы в качестве члена группы, установите группу Azure AD как Azure SQL администратор AD.

Например

  1. Настроить Azure Администратор AD
Connect-AzAccount

$group=New-AzADGroup -DisplayName SQLADADmin -MailNickname SQLADADmin 

$sp=Get-AzADServicePrincipal -DisplayName "TodoListService-OBO-sample-v2"

Add-AzADGroupMember -MemberObjectId $sp.Id -TargetGroupObjectId $group.id

$sp=Get-AzADServicePrincipal -DisplayName "<your sq name>"

Remove-AzSqlServerActiveDirectoryAdministrator -ResourceGroupName "<>" -ServerName "<>" -force

Set-AzSqlServerActiveDirectoryAdministrator -ResourceGroupName "<>" -ServerName "<>" -DisplayName $group.DisplayName -ObjectId $group.id

enter image description here

запрос

$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()
    }

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...