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"
Related
On a App Registration I want to remove the App Roles by first disable the active ones and afterwards removing them.
I checked the commands by the developer tools what is executed and I come to the following request:
$graphApiUrl = 'https://graph.microsoft.com/v1.0/applications'+'/'+$apiAppReg.appId
$body = '{"appRoles":[{"description":"Applications can read","displayName":"Reader","id":"xxxxxxx-xxxx-xxxx-xxxx-xxxxxx","isEnabled":false,"value":"Read","allowedMemberTypes":["Application"]}]}
'
az rest --method PATCH --url $graphApiUrl --headers 'Content-Type=application/json' --body '$body'
I tried this in all orders, but it seems that this is resulting in an error.
btw. the boby is copy from the request from the azure website
After reproducing from my end, I could able to achieve your requirement using PowerShell. To Disable the App Role I have set IsEnabled=$false for each app role in my Application.
Below is the script that worked for me to disable the app role.
$App = Get-AzureADApplication -Filter "appId eq '$appId'"
$AppRoles = $app.AppRoles
for($I=0;$I -lt $AppRoles.Count;$I++)
{
$app.AppRoles[$I].IsEnabled=$false
Set-AzureADApplication -ObjectId $app.ObjectId -AppRoles $AppRoles
}
To Remove the app roles from the application I have used $AppRoles.Remove($AppRoles[$I]). Below is the complete code that worked to delete the app roles from my Application.
for($I=0;$I -lt $AppRoles.Count;$I++)
{
$AppRoles.Remove($AppRoles[$I])
Set-AzureADApplication -ObjectId $app.ObjectId -AppRoles $AppRoles
}
Below is the complete working code
Connect-AzureAD
$AppId = "<APPLICATION_ID>"
$ObjectId = "<OBJECT_ID>"
$App = Get-AzureADApplication -Filter "appId eq '$appId'"
$AppRoles = $app.AppRoles
for($I=0;$I -lt $AppRoles.Count;$I++)
{
$app.AppRoles[$I].IsEnabled=$false
Set-AzureADApplication -ObjectId $app.ObjectId -AppRoles $AppRoles
}
for($I=0;$I -lt $AppRoles.Count;$I++)
{
$AppRoles.Remove($AppRoles[$I])
Set-AzureADApplication -ObjectId $app.ObjectId -AppRoles $AppRoles
}
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
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
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()
}
Here is the code that I have written and modified several times, but still cannot get to work. Any help would be greatly appreciated. I keep receiving the following errors:
Get-AzureADUser : Cannot bind argument to parameter 'ObjectId' because it is null** and **New-AzureADUserAppRoleAssignment : Cannot bind argument to parameter 'ObjectId' because it is null.
# Assign the global values to the variables for the script.
$app_name = "App Name"
$app_role_name = "User"
$users = Get-Content 'Path\Users.txt'
$Credential=Get-StoredCredential -UserName #####
# Connect to Azure AD using Azure AD Powershell
Connect-AzureAD -Credential $Credential
# Get the user to assign, and the service principal for the app to assign to
foreach ($user in $users) {
$AADuser = Get-AzureADUser -ObjectId $user
$sp = Get-AzureADServicePrincipal -Filter "displayName eq '$app_name'"
$appRole = $sp.AppRoles | Where-Object { $_.DisplayName -eq $app_role_name }
# Assign the user to the app role
New-AzureADUserAppRoleAssignment -ObjectId $user.ObjectId -PrincipalId $user.ObjectId -ResourceId $sp.ObjectId -Id $appRole.Id
}'''