In Azure SQL Server can the AD Admin which is also a Service Principal run a query on the master database? - azure

Given:
An Azure SQL Server - MyAzureSqlServer
A Service Principal - MyServicePrincipal
The Service Principal is configured as the AD Admin of the Azure SQL Server. (Azure Portal and Az Powershell module do not allow it, but Azure CLI and the REST API do)
I have Powershell code that runs SELECT 1 on the given database in the aforementioned Azure SQL Server:
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"
Where Get-AzureAuthenticationToken is:
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
}
And Invoke-SqlQueryThruAdoNet is:
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()
}
}
And it works fine on any database, except on the master, for which I get:
[MyAzureSqlServer.database.windows.net\master] Login failed for user '4...1#2...b'. (SqlError 18456, LineNumber = 65536, ClientConnectionId = b8f4f657-2772-4306-b222-4533013227d1)
where 4...1 is the client Id of MyServicePrincipal and 2...b is our Azure AD Tenant Id.
So I know the access token is OK, because I can run queries on other databases. It is specifically the master that is problematic. Is there a solution for that? Of course, it must work with the Service Principal being the AD Admin.
EDIT 1
As I have mentioned there are 2 ways to configure a Service Principal to be the AD Admin:
Using Azure CLI. It is actually straightforward:
az sql server ad-admin create --resource-group {YourAzureSqlResourceGroupName} `
--server-name {YourAzureSqlServerName} `
--display-name {ADAdminName} `
--object-id {ServicePrincipalObjectId}
The {ADAdminName} can be whatever, but we pass the display name of the Service Principal.
Now while this works, we abandoned Azure CLI in favour of Az Powershell, because the latter does not persist Service Principal credentials on disk in clear text. However, Az Powershell's function Set-AzSqlServerActiveDirectoryAdministrator does not accept a Service Principal. Yet the Azure REST API does allow it, hence we have the following custom PS function doing the job:
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
}
It uses the already familiar (see above) function Get-AzureAuthenticationToken. For our needs it sets the currently logged in Service Principal as the AD admin.

According to my test, when we directly set Azure service principal as Azure SQL AD admin, it will cause some problems. We cannot log in master database with the service pricipal. Because Azure AD administrator login should be an Azure AD user or an Azure AD group. For more details, please refer to the document
So if you want to set Azure service principal as Azure SQL AD admin, we need to create an Azure AD security group, add service principal as the group's member the set the Azure AD group as Azure SQL AD admin.
For example
Configure Azure AD admin
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
query
$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()
}

Related

How to add a scope and ApplicationURI to Azure AD Application Registration?

I have a powershell script that creates an azure ad app reg. I've been able to create it, add ms graph permissions and also create a secret. This is what I have so far:
$newapp = New-AzADApplication -DisplayName "mynewApp" -AvailableToOtherTenants $false
Write-Output $newapp
# add a certificate / client secret
$appCredentials = New-AzADAppCredential -ApplicationId $newapp.AppId -StartDate $startDate -EndDate $endDate
$identifierUris = #()
$identifierUris += "api://$newapp.AppId"
$webAppUrl = "https://$functionAppName.azurewebsites.net"
# when you add a redirect URI Azure creates a "web" policy.
$redirectUris = #()
$redirectUris += "$webAppUrl"
Update-AzADApplication -ApplicationId $newapp.AppId -ReplyUrl $redirectUris | Out-Null
#Adds MS Graph User.Read permission
Add-AzADAppPermission -ApplicationId $newapp.AppId -ApiId "00000003-0000-0000-c000-000000000000" -PermissionId "e1fe6dd8-ba31-4d61-89e7-88639da4683d"
But now I need to know how to create the application Uri as depicted below, and also how to create the scope.
You can use New-AzureADApplication command with following parameters
To add Identifier URI - Use -IdentifierUris parameter.
To add Scope - Use -Oauth2Permissions parameter.
New-AzureADApplication
[-AvailableToOtherTenants <Boolean>]
-DisplayName <String>
[-IdentifierUris <System.Collections.Generic.List`1[System.String]>]
[-Oauth2Permissions <System.Collections.Generic.List`1[Microsoft.Open.AzureAD.Model.OAuth2Permission]>]
[-ReplyUrls <System.Collections.Generic.List`1[System.String]>]
[-RequiredResourceAccess <System.Collections.Generic.List`1[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]>]
For example you can create OAuth2Permission object like -
$scope = New-Object Microsoft.Open.AzureAD.Model.OAuth2Permission
$scope.Id = New-Guid
$scope.Value = "user_impersonation"
$scope.UserConsentDisplayName = "<value>"
$scope.UserConsentDescription = "<value>"
$scope.AdminConsentDisplayName = "<value>"
$scope.AdminConsentDescription = "<value>"
$scope.IsEnabled = $true
$scope.Type = "User"
Please refer this documentation for details.
Please check below command to create ApplicationId Uri ,
$newapp = New-AzADApplication -DisplayName "mynewApp" -AvailableToOtherTenants $false
$myappId=$newApp.AppId
Set-AzADApplication -ApplicationId $newApp.AppId -IdentifierUris "api://$myappId"
It may take little time to reflect in portal.
but to create scope , we may need to use azure ad module, and create a new id for user impersonation by enabling Oauth2permissions
please check if references can help
SetupApplications ps1
azure-ad-app-registration-create-scopes
Had similar problem with Azure Application Registration scopes. Here is a simplest solution I have with Az Powershell:
$app = Get-AzAdApplication -ApplicationId "app_guid_here"
$permissionScope = New-Object Microsoft.Azure.Powershell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPermissionScope
$permissionScope.Id = New-Guid
$permissionScope.AdminConsentDescription = "text_here"
$permissionScope.AdminConsentDisplayName = "text_here"
$permissionScope.IsEnabled = $true
$permissionScope.Type = "Admin" #Or "User"
$permissionScope.UserConsentDescription = "text_here"
$permissionScope.UserConsentDisplayName = "text_here"
$permissionScope.Value = "user_impersonation"
$api = $app.Api
$api.Oauth2PermissionScope = $permissionScope
Update-AzADApplication -ApplicationId $appId -Api $api

Consume Azure Authenticated .net core API with PowerShell

I'm trying to invoke an azure ad authenticated .net core api with powershell script. Can someone help me with the powershell script? I have already created an app registration for the api in azure active directory.
I exposed the api protected by Azure, created an application registration for the api, and then used the client credential flow to obtain the token:
script:
$clientID = 'a13b414b-93b3-4aae-bb68-6a40ffxxxxxx'
$secretKey = '4.Yi4qwn_rqe7BK3F6Nfz.c0A7pTxxxxxx'
$tenantID = 'e4c9ab4e-bd27-40d5-8459-230ba2xxxxxx'
$password = ConvertTo-SecureString -String $secretKey -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($ClientID,$password)
Connect-AzureRmAccount -ServicePrincipal -Credential $credential -Tenant $tenantID
$authUrl = "https://login.microsoftonline.com/" + $tenantID + "/oauth2/v2.0/token/"
$body = #{
"scope" = "api://a13b414b-93b3-4aae-bb68-6a40ffxxxxxx/.default";
"grant_type" = "client_credentials";
"client_id" = $ClientID
"client_secret" = $secretKey
}
Write-Output "Getting Authentication-Token"
$adlsToken = Invoke-RestMethod -Uri $authUrl –Method POST -Body $body
Write-Output $adlsToken

Create azure application through Az module and assign API permissions using powershell

I have written a script which creates azure application using Az module, creates secret key, assigns owner. But assigning API permission gives insufficient permission error. The user is an admin user. Still unable to assign API permission. What wrong am I doing?
$ErrorActionPreference = 'Stop'
Connect-AzAccount
Import-Module Az.Resources
$tenant = Get-AzTenant
Set-AzContext -TenantId $tenant.Id
$AppName = Read-Host -Prompt 'Enter Application name '
$myApp = New-AzADApplication -DisplayName $AppName -IdentifierUris "http://$AppName.com"
Write-Host "App registered.."
$sp = New-AzADServicePrincipal -ApplicationId $myApp.ApplicationId -Role Owner
Write-Host "Service principal registered.."
$startDate = Get-Date
$endDate = $startDate.AddYears(100)
$secret = Read-Host -Prompt 'Enter App Secret Key ' -AsSecureString
$secPassword = ConvertTo-SecureString -AsPlainText -Force -String $secret
New-AzADAppCredential -ObjectId $myApp.ObjectId -StartDate $startDate -EndDate $endDate -Password $secPassword
$ResourceAppIdURI = "https://graph.windows.net/"
# $authority = "https://login.microsoftonline.com/$tenant/oauth2/v2.0/token"
$authority = "https://login.windows.net/$tenant/oauth2/token"
$ClientCred = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential" -ArgumentList $myApp.ApplicationId, $secret
$AuthContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority,$false
$AuthContext.TokenCache.Clear()
Start-Sleep -Seconds 10
$Token = $Authcontext.AcquireTokenAsync($ResourceAppIdURI, $ClientCred)
$AuthHeader = #{"Authorization" = $Token.Result.CreateAuthorizationHeader();"Content-Type"="application/json"}
$url = "https://graph.windows.net/$tenant/applications/$($myApp.ObjectID)?api-version=1.6"
Write-Host "URL: " $url
$postData = "{`"requiredResourceAccess`":[{`"resourceAppId`":`"00000003-0000-0000-c000-000000000000`",
`"resourceAccess`":[{`"id`":`"e1fe6dd8-ba31-4d61-89e7-88639da4683d`",`"type`":`"Scope`"}]}]}";
$result = Invoke-RestMethod -Uri $url -Method "PATCH" -Headers $AuthHeader -Body $postData
Write-Host "Result of App API permission: " $result
In my case, the easiest way to do this without messing around with http requests, was to combine the Azure-powershell module and the Az cli module
So, once I have created my new app:
$myApp = New-AzADApplication -DisplayName $AppName -IdentifierUris "http://$AppName.com"
Then I would login into azure using the Az Cli, and, for instance:
Add some api permissions
Grant these permissions directory admin consent ( if needed )
. { $azcliLogin = az login }
. { az account set --subscription $config.subscriptionId }
. { az ad app permission add --id $myApp.appid --api 00000002-0000-0000-c000-000000000000 --api-permissions 78c8a3c8-a07e-4b9e-af1b-b5ccab50a175=Role }
. { $appApiGrant = az ad app permission grant --id $config.azureAccess.appid --api 00000002-0000-0000-c000-000000000000 }
. { az ad app permission admin-consent --id $myApp.appid }
Where:
--api 00000002-0000-0000-c000-000000000000 Refers to Microsoft Graph API
--api-permissions 78c8a3c8-a07e-4b9e-af1b-b5ccab50a175=Role Refers to some role on this api, as Directory.ReadWrite.All
You can get the required API and API-PERMISSIONS guids from the App manifiest in Azure
This way you create the app with the required granted api permissions, in a single powershell script.
If you want to call Azure AAD graph API to assign permissions with OAuth 2.0 client credentials flow, we need to provide enough permissions(Azure AD Graph -> Aapplication permissions -> Application.ReadWrite.All)
Besides, regarding how to assign permissions to AD application with PowerShell, we also can use PowerShell module AzureAD.
For example
Connect-AzureAD
$AppAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]#{
ResourceAppId = "00000003-0000-0000-c000-000000000000";
ResourceAccess =
[Microsoft.Open.AzureAD.Model.ResourceAccess]#{
Id = "";
Type = ""},
[Microsoft.Open.AzureAD.Model.ResourceAccess]#{
Id = "";
Type = ""}
}
Set-AzureADApplication -ObjectId <the app object id> -RequiredResourceAccess $AppAccess
Update
According to my test, when we use Az module, we can use the following method to get access token and call AAD graph rest API. But please note that when you use the method, the account you use to run Connect-AzAccount should be Azure AD Global Admin
Connect-AzAccount
$context =Get-AzContext
$dexResourceUrl='https://graph.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
# assign permissions
$headers =#{}
$headers.Add("Content-Type", "application/json")
$headers.Add("Accept", "application/json")
$headers.Add("Authorization", "Bearer $($token)")
$body = "{
`n `"requiredResourceAccess`": [{
`n `"resourceAppId`": `"00000003-0000-0000-c000-000000000000`",
`n `"resourceAccess`": [
`n {
`n `"id`": `"405a51b5-8d8d-430b-9842-8be4b0e9f324`",
`n `"type`": `"Role`"
`n },
`n {
`n `"id`": `"09850681-111b-4a89-9bed-3f2cae46d706`",
`n `"type`": `"Role`"
`n }
`n ]
`n }
`n ]
`n}
`n"
$url ='https://graph.windows.net/hanxia.onmicrosoft.com/applications/d4975420-841f-47d5-a3d2-0870901f13cd?api-version=1.6'
Invoke-RestMethod $url -Method 'PATCH' -Headers $headers -Body $body
#check if adding the permissions you need
$headers =#{}
$headers.Add("Accept", "application/json")
$headers.Add("Authorization", "Bearer $($token)")
$url ='https://graph.windows.net/hanxia.onmicrosoft.com/applications/<aad application object id>?api-version=1.6'
$response=Invoke-RestMethod $url -Method 'GET' -Headers $headers
$response.requiredResourceAccess | ConvertTo-Json

How to Add Api Permissions to an Azure App Registration using PowerShell

I am figure out the commands in Azure PowerShell to add an the User.Read Ape Permission to my App Registration in Azure.
I can find some examples using *Azure, but would prefer one that uses the *Az commands, e.g. https://learn.microsoft.com/en-us/powershell/azure/?view=azps-2.8.0.
Wonder if anybody knows how to do this? Thanks!
This can currently only be achieved using the Azure AD PowerShell. Please note that there is a difference between Azure AD PowerShell and Azure PowerShell. The Azure AD PowerShell is not simply the old Azure PowerShell module.
Azure AD PowerShell is a separate module. There is no "AZ*" for Azure AD yet. Only couple of most commonly used commands, that have Azure Resource Provider implementation.
Azure PowerShell has a limited set of features for working with Azure AD. If you need more features, like the one you mention, you must use Azure AD PowerShell. Azure AD PowerShell is not depricated and is the officially supported PowerShell module for working with Azure AD.
You can manage these required permissions by the Set-AzureAdApplication cmdlet and passing proper -RequiredResourceAccess object.
In order to construct this object, you must first get a reference to "exposed" permissions. Because permissions are exposed by other service principals.
as I cannot upload whole file, here is a PowerShell script that creates a sample application with required permission to some MS Graph and some Power BI permissions.
Function GetToken
{
param(
[String] $authority = "https://login.microsoftonline.com/dayzure.com/oauth2/token",
[String] $clientId,
[String] $clientSecret,
[String] $resourceId = "https://graph.windows.net"
)
$scope = [System.Web.HttpUtility]::UrlEncode($resourceId)
$encSecret = [System.Web.HttpUtility]::UrlEncode($clientSecret)
$body = "grant_type=client_credentials&resource=$($scope)&client_id=$($clientId)&client_secret=$($encSecret)"
$res = Invoke-WebRequest -Uri $authority -Body $body -Method Post
$authResult = $res.Content | ConvertFrom-Json
return $authResult.access_token
}
#`
# -RequiredResourceAccess #($requiredResourceAccess)
#
Function CreateChildApp
{
param (
[string] $displayName,
[string] $tenantName
)
# create your new application
Write-Output -InputObject ('Creating App Registration {0}' -f $displayName)
if (!(Get-AzureADApplication -SearchString $displayName)) {
$app = New-AzureADApplication -DisplayName $displayName `
-Homepage "https://localhost" `
-ReplyUrls "https://localhost" `
-IdentifierUris ('https://{0}/{1}' -f $tenantName, $displayName)
# create SPN for App Registration
Write-Output -InputObject ('Creating SPN for App Registration {0}' -f $displayName)
# create a password (spn key)
$appPwd = New-AzureADApplicationPasswordCredential -ObjectId $app.ObjectId
$appPwd
# create a service principal for your application
# you need this to be able to grant your application the required permission
$spForApp = New-AzureADServicePrincipal -AppId $app.AppId -PasswordCredentials #($appPwd)
}
else {
Write-Output -InputObject ('App Registration {0} already exists' -f $displayName)
$app = Get-AzureADApplication -SearchString $displayName
}
#endregion
return $app
}
Function GrantAllThePermissionsWeWant
{
param
(
[string] $targetServicePrincipalName,
$appPermissionsRequired,
$childApp,
$spForApp
)
$targetSp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$($targetServicePrincipalName)'"
# Iterate Permissions array
Write-Output -InputObject ('Retrieve Role Assignments objects')
$RoleAssignments = #()
Foreach ($AppPermission in $appPermissionsRequired) {
$RoleAssignment = $targetSp.AppRoles | Where-Object { $_.Value -eq $AppPermission}
$RoleAssignments += $RoleAssignment
}
$ResourceAccessObjects = New-Object 'System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]'
foreach ($RoleAssignment in $RoleAssignments) {
$resourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess"
$resourceAccess.Id = $RoleAssignment.Id
$resourceAccess.Type = 'Role'
$ResourceAccessObjects.Add($resourceAccess)
}
$requiredResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
$requiredResourceAccess.ResourceAppId = $targetSp.AppId
$requiredResourceAccess.ResourceAccess = $ResourceAccessObjects
# set the required resource access
Set-AzureADApplication -ObjectId $childApp.ObjectId -RequiredResourceAccess $requiredResourceAccess
Start-Sleep -s 1
# grant the required resource access
foreach ($RoleAssignment in $RoleAssignments) {
Write-Output -InputObject ('Granting admin consent for App Role: {0}' -f $($RoleAssignment.Value))
New-AzureADServiceAppRoleAssignment -ObjectId $spForApp.ObjectId -Id $RoleAssignment.Id -PrincipalId $spForApp.ObjectId -ResourceId $targetSp.ObjectId
Start-Sleep -s 1
}
}
cls
#globaladminapp
$clientID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
$key = "****"
$tenantId = "aaaaaaaa-bbbb-xxxx-yyyy-aaaaaaaaaaaa";
$TenantName = "customdomain.com";
$AppRegName = "globaladminChild-0003";
$token = GetToken -clientId $clientID -clientSecret $key
Disconnect-AzureAD
Connect-AzureAD -AadAccessToken $token -AccountId $clientID -TenantId $tenantId
$appPermissionsRequired = #('Application.ReadWrite.OwnedBy', 'Device.ReadWrite.All', 'Domain.ReadWrite.All')
$targetServicePrincipalName = 'Windows Azure Active Directory'
#$appPermissionsRequired = #('Files.ReadWrite.All','Sites.FullControl.All','Notes.ReadWrite.All')
#$targetServicePrincipalName = 'Microsoft Graph'
$app = CreateChildApp -displayName $AppRegName -tenantName $TenantName
$spForApp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$($AppRegName)'"
$appPermissionsRequired = #('Tenant.ReadWrite.All')
$targetServicePrincipalName = 'Power BI Service'
GrantAllThePermissionsWeWant -targetServicePrincipalName $targetServicePrincipalName -appPermissionsRequired $appPermissionsRequired -childApp $app -spForApp $spForApp
$appPermissionsRequired = #('Files.ReadWrite.All','Sites.FullControl.All','Notes.ReadWrite.All')
$targetServicePrincipalName = 'Microsoft Graph'
GrantAllThePermissionsWeWant -targetServicePrincipalName $targetServicePrincipalName -appPermissionsRequired $appPermissionsRequired -childApp $app -spForApp $spForApp
The interesting parts are around "apppermissionrequired" and "targetserviceprincipalname" variables.
I can't reply to Rolfo's comment directly as I don't have enough clout yet. While it's true it's not dead simple, it's possible to use both in the same session as of July 2021. Not sure this was always the case, or something was updated to allow it.
#Import modules if needed
$mList = #("AzureAD","Az.Resources","Az.Accounts")
foreach($m in $mList){if ((gmo -l $m).Count -eq 0){Install-Module -Name $m -AllowClobber -Scope CurrentUser -Force}}
#Authentication Popup
Connect-AzAccount
#Use authentication context cached from above to authenticate to AAD graph
$IDObject = Get-AzAccessToken -Resource "https://graph.windows.net"
Connect-AzureAD -AadAccessToken $IDObject.token -AccountId $IDObject.UserId
UPDATE
With the new Graph API we can use the following command to add API permissions to an App Registration/Service Principal using PowerShell. It's much simpler than the old process.
Add-AzADAppPermission -ApplicationId "$spId" -ApiId "00000009-0000-0000-c000-000000000000" -PermissionId "7504609f-c495-4c64-8542-686125a5a36f"
(This is the case for the PowerBI API)
If deploying via an Azure Devops Pipeline I often recommend using the following script to authenticate into AAD:
echo "Install Azure AD module..."
Install-Module -Name "AzureAD" -Force
Import-Module AzureAD -Force
echo "Connect Azure AD..."
$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
echo $context
$graphToken = [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, "https://graph.microsoft.com").AccessToken
echo $graphToken
$aadToken = [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, "https://graph.windows.net").AccessToken
Write-Output "Hi I'm $($context.Account.Id)"
Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id -MsAccessToken $graphToken
echo "Connection ends"

Use Azure Powershell Runbook to add AzureAD user as db_owner to an Azure SQL database

I have a Powershell Runbook where I am trying to add AzureAD user as Database owner on an Azure SQL database.
## Connect
$servicePrincipalConnection = Get-AutomationConnection -Name "AzureRunAsConnection"
Connect-AzureAD `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
## Generate new access token
$cert = Get-AutomationCertificate -Name 'AzureRunAsCertificate'
# Set Resource URI to Azure Database
$resourceAppIdURI = 'https://database.windows.net/'
# Set Authority to Azure AD Tenant
$authority = 'https://login.windows.net/' + $servicePrincipalConnection.TenantId
$ClientCred = [Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate]::new($servicePrincipalConnection.ApplicationId, $cert)
$authContext = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext]::new($authority)
$authResult = $authContext.AcquireTokenAsync($resourceAppIdURI, $ClientCred)
$AccessToken = $authResult.Result.AccessToken
## Execute sql
$AccessToken
$connectionString = "Data Source=MYCOOLSQLSERVER.database.windows.net;Initial Catalog=MYCOOLDATABASE;Connect Timeout=30"
$connection = New-Object -TypeName System.Data.SqlClient.SqlConnection($connectionString)
$query = "Create User [abc#xyz.com] From EXTERNAL PROVIDER;"
$command = New-Object -TypeName System.Data.SqlClient.SqlCommand($query, $connection)
$connection.AccessToken = $AccessToken
$connection.Open()
$command.ExecuteNonQuery()
$connection.Close()
I end up getting the error below where abc#xyz.com is an AzureAD user.
Principal 'abc#xyz.com' could not be resolved.
Is there something that I have missed out?
Once the user gets created, I intend to use Alter role to make him the db_owner.
References:
https://blogs.technet.microsoft.com/stefan_stranger/2018/06/06/connect-to-azure-sql-database-by-obtaining-a-token-from-azure-active-directory-aad/
https://techcommunity.microsoft.com/t5/Azure-Database-Support-Blog/Azure-SQL-Database-Token-based-authentication-with-PowerShell/ba-p/369078
Turns out that there is an undocumented way to do this. Discovered it with the help of Azure support team.
The SQL query to be used is actually:
CREATE USER [abc#xyz.com] WITH SID = $hexSid, Type E;
Here $hexSid can be obtained by the following SQL query
Get the ObjectId of the AzureAD User
$objectId = (Get-AzureADUser -ObjectId "abc#xyz.com").ObjectId
Use the SQL below to get the sid
DECLARE #sid = uniqueidentifier = cast('$objectId' as
uniqueidentifier) select cast(#sid as varbinary(max))
Now ideally, the effective query would have directly used the #sid but that is not possible since the With SID needs a hard coded value. So I had to process the output of the sid query separately like below:
$result = $command.ExecuteScalar() # result is byte array
$hexSid = ($result | ForEach-Object ToString X2) -Join '' # convert to hex format
$hexSid = "0x$hexSid"
With those changes the code in the question just works fine!
Effectively, we are only passing the AzureAD User's objectId in the Create User query.
Complete code:
$targetUser = "abc#xyz.com"
$sqlServerName = "someserver"
$dbName = "somedb"
$accessToken = "" # obtained from SPN connection
# Get SQL SID for the targeted user
Write-Output "Getting AzureADUser $targetUser "
$objectId = (Get-AzureADUser -ObjectId $targetUser).ObjectId
$sidQuery = "DECLARE #sid uniqueidentifier = cast('$objectId' as uniqueidentifier) select cast(#sid as varbinary(max))"
$sidOutput = New-Object -TypeName HashTable
Write-Output "Invoking SQL Query execution to get SID for objectId: $objectId ($targetUser)"
Invoke-OrExecuteSqlQuery -ServerName $sqlServerName -DbName $dbName -Query $sidQuery `
-AccessToken $accessToken -Output $sidOutput
$sidBytes = $sidOutput.Result
Write-Output "SID bytes: $sidBytes"
$hexSid = ($sidBytes | ForEach-Object ToString X2) -Join '' # convert to hex format
$hexSid = "0x$hexSid"
Write-Output "SQL SID is : $hexSid"
# Add AzureAD user to the database
Write-Output "Adding AzureAD user : $targetUser to the db/dw: $dbName"
$createDBUserQuery = "CREATE USER [$targetUser] WITH SID = $hexSid, Type = E;" # create db user
$createUserOutput = New-Object -TypeName HashTable
Invoke-OrExecuteSqlQuery -ServerName $sqlServerName -DbName $dbName -Query $createDBUserQuery `
-AccessToken $accessToken -Output $createUserOutput
Write-Output "Created DB User : $targetUser on the DB : $dbName"
# Set targeted user as the database owner
Write-Output "Setting database admin for db/dw: $dbName"
$makeDBOwnerQuery = "ALTER ROLE db_owner ADD MEMBER [$targetUser];"
$alterSQLDBRoleOutput = New-Object -TypeName HashTable
Invoke-OrExecuteSqlQuery -ServerName $sqlServerName -DbName $dbName -Query $makeDBOwnerQuery `
-AccessToken $accessToken -Output $alterSQLDBRoleOutput
Write-Output "Made $targetUser as the DB Owner"
function Invoke-OrExecuteSqlQuery {
param (
[string] $ServerName,
[string] $DbName,
[string] $Query,
[string] $AccessToken,
[hashtable] $Output
)
$server = "$ServerName.database.windows.net"
$database = $DbName
$query = $Query
Write-Output "Connecting to the SQL DB: $database on the SQL Server: $server"
$connectionString = "Data Source=$server;Initial Catalog=$database;Connect Timeout=30;"
$connection = New-Object -TypeName System.Data.SqlClient.SqlConnection($connectionString)
$connection.AccessToken = $AccessToken
Write-Output "Executing SQL query.."
$command = New-Object -TypeName System.Data.SqlClient.SqlCommand($query, $connection)
$connection.Open()
$result = $command.ExecuteScalar()
$connection.Close()
Write-Output "Execution of SQL query complete"
if ($result) {
$Output.Result = $result
}
}
for this scenario, we should use user password flow to make this command work. Pls try the PS below :
$tenant='<your tenant name/id>'
$username='<SQL admin account>'
$password='<SQL admin password>'
$appid='<app id>'
$appsec='<app secret>'
$SQLServerName = '<azure sql servername>'
$DatabaseName='<db name>'
$body=#{
    "grant_type"="password";
    "resource"="https://database.windows.net/";
    "client_id"=$appid;
"client_secret"=$appsec;
    "username"=$username;
    "password" = $password;
}
 
$result=Invoke-RestMethod -Uri "https://login.windows.net/$tenant/oauth2/token" -Method POST -Body $body 
$conn = New-Object System.Data.SqlClient.SQLConnection
$conn.ConnectionString = "Data Source=$SQLServerName.database.windows.net;Initial Catalog=$DatabaseName;Connect Timeout=30"
$conn.AccessToken = $result.access_token
Write-Verbose "Connect to database and execute SQL script"
$conn.Open()
$query = 'CREATE USER [Account] FROM EXTERNAL PROVIDER;'
$command = New-Object -TypeName System.Data.SqlClient.SqlCommand($query, $conn)
$Result = $command.ExecuteScalar()
$Result
$conn.Close()
Before you run is script, you should grant user_impersonation Azure SQL API access permission for your SPN in Azure AD here :
Grant permission after added :
Btw, pls makre sure that the SQL admin account and SPN are all members of Active Directory admin group :
It works on my side and a record has been added successfully :
If there is anything unclear , pls feel free to let me know : )

Resources