How to set Microsoft Graph API permissions on Azure Managed Service Identity with PowerShell 7

Viewed 534

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.

Update 1

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.

1 Answers

Long day! The problem was between chair and 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 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.

Automated authentication.

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 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.

There is an open issue on Github here. If you'd like Connect-MgGraph to support -Credential please consider giving it the thumbs up.

Secret-Based SPN

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.

Side note:

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.

Related