woter324
woter324

Reputation: 3100

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

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.

Upvotes: 5

Views: 10727

Answers (2)

woter324
woter324

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.

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

Secret-Based SPN

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.

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.

Update 2 (2 years later)

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

Oliver Hauck
Oliver Hauck

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

Related Questions