Reputation: 3100
To grant Microsoft Graph API permissions to a User-Assigned Managed Service Identity or System-Assigned Managed Service Identity, one has to use PowerShell. All the articles I can find (e.g. this) point to using the New-AzureAdServiceAppRoleAssignment
cmdlet from the AzureAD
PowerShell module, however, this module doesn't support anything newer than Windows PowerShell version 5.1 and as I need to complete the task on a Linux-based build agent, I need to find a way supported by PowerShell (core).
Looking at the last update date of AzureAD
, I suspect MS aren't planning on updating it further and besides a lot of the functionality has already moved to the Az
PowerShell module, however, the critical cmdlet doesn't appear to have made it across. (AzureADPreview
has the same issues). I am not sure which direction Microsoft are heading, but to confuse matters, there is the Microsoft.Graph
module; more specifically Microsoft.Graph.Applications
. Reviewing the list of cmdlets in the module, the most likely candidate to achieve the same task is New-MgServicePrincipalAppRoleAssignment
, however, I cannot get it to work.
The following is the AzureAD
Windows PowerShell (5.1) way (that works with the new Az
cmdlets):
$DestinationTenantId = "a3186524-d3d5-4820-8cb5-9ad21badb14a"
$MsiName = "myUserMSI" # Name of system-assigned or user-assigned managed service identity. (System-assigned use same name as resource)
# Graph API permissions to set
$oPermissions = @(
"Directory.ReadWrite.All"
"Group.ReadWrite.All"
)
$GraphAppId = "00000003-0000-0000-c000-000000000000" # Don't change this.
$oMsi = Get-AzADServicePrincipal -Filter "displayName eq '$MsiName'"
$oGraphSpn = Get-AzADServicePrincipal -Filter "appId eq '$GraphAppId'"
$oAppRole = $oGraphSpn.AppRole | Where-Object {($_.Value -in $oPermissions) -and ($_.AllowedMemberType -contains "Application")}
Connect-AzureAD -TenantId $DestinationTenantId
foreach($AppRole in $oAppRole)
{
New-AzureAdServiceAppRoleAssignment `
-ObjectId $oMsi.Id `
-PrincipalId $oMsi.Id `
-ResourceId $oGraphSpn.Id `
-Id $AppRole.Id `
-Verbose
}
Here is my attempt at using the Microsoft.Graph.Applications
module with PowerShell (v.7.2.5).
$DestinationTenantId = "a3186524-d3d5-4820-8cb5-9ad21badb14a"
$MsiName = "myUserMSI" # Name of system-assigned or user-assigned managed service identity. (System-assigned use same name as resource)
$oPermissions = @(
"Directory.ReadWrite.All"
"Group.ReadWrite.All"
"GroupMember.ReadWrite.All"
"User.ReadWrite.All"
"RoleManagement.ReadWrite.Directory"
)
$GraphAppId = "00000003-0000-0000-c000-000000000000" # Don't change this.
$oMsi = Get-AzADServicePrincipal -Filter "displayName eq '$MsiName'"
$oGraphSpn = Get-AzADServicePrincipal -Filter "appId eq '$GraphAppId'"
$oAppRole = $oGraphSpn.AppRole | Where-Object {($_.Value -in $oPermissions) -and ($_.AllowedMemberType -contains "Application")}
Connect-MgGraph -TenantId $DestinationTenantId
foreach($AppRole in $oAppRole)
{
$oAppRoleAssignment = @{
"PrincipalId" = $oMSI.Id
"ResourceId" = $GraphAppId
"AppRoleId" = $AppRole.Id
}
New-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId $oAppRoleAssignment.PrincipalId `
-BodyParameter $oAppRoleAssignment `
-Verbose
}
This results in the following error:
PS C:\> . "Set-ApiPermissionsForMI.ps1"
VERBOSE: Performing the operation "New-MgServicePrincipalAppRoleAssignment_Create1" on target "Call remote 'ServicePrincipalsCreateAppRoleAssignments1' operation".
New-MgServicePrincipalAppRoleAssignment_Create1: C:\Set-ApiPermissionsForMI.ps1:36:3
Line |
36 | New-MgServicePrincipalAppRoleAssignment `
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Resource '00000003-0000-0000-c000-000000000000' does not exist or one of its queried reference-property objects are not present.
I see it's complaining that the "special" service principal for MS Graph is missing, but as the AzureAD
cmdlet worked, I know it is correct, but I don't know if this cmdlet is even the correct replacement - if there even is a replacement that works with PowerShell core.
If anyone has a solution for my problem, I'd be very grateful if you could share, please.
T.I.A.
I've found this document describing the mapping of AzureAD
to Microsoft.Graph
cmdlets. I have the correct cmdlet:
AzureAD | Microsoft.Graph.Applications |
---|---|
New-AzureADServiceAppRoleAssignment | New-MgServicePrincipalAppRoleAssignment |
The document also confirms that Microsoft are replacing the AzureAD
module with the Microsoft.Graph
module.
Upvotes: 5
Views: 10727
Reputation: 3100
Long day! The problem was between the chair and the keyboard! Here is the working solution:
$DestinationTenantId = "a3186524-d3d5-4820-8cb5-9ad21badb14a"
$MsiName = "myUserMSI" # Name of system-assigned or user-assigned managed service identity. (System-assigned use the same name as resource).
$oPermissions = @(
"Directory.ReadWrite.All"
"Group.ReadWrite.All"
"GroupMember.ReadWrite.All"
"User.ReadWrite.All"
"RoleManagement.ReadWrite.Directory"
)
$GraphAppId = "00000003-0000-0000-c000-000000000000" # Don't change this.
$oMsi = Get-AzADServicePrincipal -Filter "displayName eq '$MsiName'"
$oGraphSpn = Get-AzADServicePrincipal -Filter "appId eq '$GraphAppId'"
$oAppRole = $oGraphSpn.AppRole | Where-Object {($_.Value -in $oPermissions) -and ($_.AllowedMemberType -contains "Application")}
Connect-MgGraph -TenantId $DestinationTenantId
foreach($AppRole in $oAppRole)
{
$oAppRoleAssignment = @{
"PrincipalId" = $oMSI.Id
#"ResourceId" = $GraphAppId
"ResourceId" = $oGraphSpn.Id
"AppRoleId" = $AppRole.Id
}
New-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId $oAppRoleAssignment.PrincipalId `
-BodyParameter $oAppRoleAssignment `
-Verbose
}
The problem was in the hashtable $oAppRoleAssignment.ResourceId
.
It should be $oGraphSpn.Id
and not $GraphAppId
. I was passing the App ID, not the SPN ID of the App.
You may have noticed the Connect-MgGraph
cmdlet. As I mentioned in the question, this will need to work on a build agent and therefore I need automated credentials. Microsoft have decided to make us jump through a bunch of hoops. We can't pass it as a PSCredential object. We have to create another certificate-based SPN and set up the public and private keys. I haven't done this bit yet, but there is a guide here. See Update 2.
There is an open issue on Github here. If you'd like Connect-MgGraph
to support -Credential
please consider giving it the thumbs up.
This isn't required anymore. See update 2.
Here's how to authenticate with a secret-based SPN:
# Replace below with your details (these are not the actual GUIDs).
$appid = '1a0f530d-e288-4f71-9870-f72e0079e6c3'
$tenantid = '9734136b-c9d8-43f7-9c99-29737c23e5c9'
$secret = '<YOUR-CLIENT-SECRET>'
$body = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $appid
Client_Secret = $secret
}
$connection = Invoke-RestMethod `
-Uri https://login.microsoftonline.com/$tenantid/oauth2/v2.0/token `
-Method POST `
-Body $body
$token = $connection.access_token
Connect-MgGraph -AccessToken $token
Source: https://helloitsliam.com/2022/04/20/connect-to-microsoft-graph-powershell-using-an-app-registration/
Remember to assign the Service Principal (SPN) the required Graph API Application permissions in Azure AD.
There is the Add-AzAdAppPermission cmdlet, but, unfortunately, it appears this only works for AD applications as opposed to MSIs. I tried running it, but it errored, unable to find my ObjectID
.
I've been asked to update this, but it was a long time ago and I am not 100% sure what I was trying to do, so please read the comments. Microsoft has done a lot of work to Microsoft.Graph
PS module and I note one can now log in with a secret-based SPN.
I know MS documentation always shows -scopes
, but to be honest, I don't get why I need to add "scopes". Surely it should be able to work out what permissions my user/SPN has. More often than not, I don't need to add any scopes. Maybe that's because I was a Global Admin on the tenant I was working on at the time.
Upvotes: 6
Reputation: 29
To assign multiple Graph APi permissions to multiple (user-defined) Managed Identities I used the following script. It does not depend on any Az-* Powershell Module, but solely uses the "Microsoft.Graph.Applications" PS Module which should be supported for the next few years.
$tenantID = "12345678-1234-1234-1234-123456789abc"
$graphAppId = "00000003-0000-0000-c000-000000000000"
$permissions = @("User.Read.All", "Application.ReadWrite.All")
$managedIdentities = @("mi-1", "mi-2", "mi-3") # Names of system-assigned or user-assigned managed service identity. (System-assigned use same name as resource).
Connect-MgGraph -TenantId $tenantID -NoWelcome -Scopes "AppRoleAssignment.ReadWrite.All", "Directory.Read.All"
$sp = Get-MgServicePrincipal -Filter "appId eq '$graphAppId'"
$managedIdentities | ForEach-Object {
$msi = Get-MgServicePrincipal -Filter "displayName eq '$_'"
$appRoles = $sp.AppRoles | Where-Object {($_.Value -in $permissions) -and ($_.AllowedMemberTypes -contains "Application")}
$appRoles | ForEach-Object {
$appRoleAssignment = @{
"PrincipalId" = $msi.Id
"ResourceId" = $sp.Id
"AppRoleId" = $_.Id
}
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $appRoleAssignment.PrincipalId -BodyParameter $appRoleAssignment -Verbose
}
}
Disconnect-MgGraph
Furthermore the "Get-AzADServicePrincipal" does not find the managed identity (at least based on its displayName).
Upvotes: 1