I had to setup RBAC for an application in Exchange Online recently. By application, I mean an Entra ID app registration which needs to have rights to do certain tasks in Exchange Online for a specific scope of users (in this case I wanted it to manage distribution groups for a certain scope).
Seemed straight forward on paper – the official instructions say you have to create the Entra ID app registration (no need to give it any Entra specific permissions), then create a pointer to that in Exchange Online:
1 2 3 4 5 6 |
# Display name of the app registration that was created $displayName = "ABC App Registration $graphObj = Get-MgServicePrincipal -Filter "DisplayName eq '$displayName'" # Create an Exchange pointer to the Entra app registration New-ServicePrincipal -AppId $graphObj.AppId -ObjectId $graphObj.Id -DisplayName $displayName |
And then assign a role to it and scope it as needed.
1 |
New-ManagementRoleAssignment -Name "${displayName}-FullAccess" -Role "Application Exchange Full Access" -App "$displayName" -CustomResourceScope "Management Scope - ${displayName}" |
(Not going into the details of how to create a management scope etc).
In theory this should work, and there are many blog posts detailing the same steps – this being an excellent one. But in my case it wasn’t what I was looking for.
The first red flag was the kind of permissions I can assign. The only roles I can apply are application roles, and that’s just this lot:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
> Get-ManagementRole | ?{ $_.Name -like "Application*" } Name RoleType ---- -------- ApplicationImpersonation ApplicationImpersonation Application Mail.Read ApplicationMailRead Application Mail.ReadBasic ApplicationMailReadBasic Application Mail.ReadWrite ApplicationMailReadWrite Application Mail.Send ApplicationMailSend Application MailboxSettings.Read ApplicationMailboxSettingsRead Application MailboxSettings.ReadWrite ApplicationMailboxSettingsReadWrite Application Calendars.Read ApplicationCalendarsRead Application Calendars.ReadWrite ApplicationCalendarsReadWrite Application Contacts.Read ApplicationContactsRead Application Contacts.ReadWrite ApplicationContactsReadWrite Application Mail Full Access MailFullAccessApp Application Exchange Full Access ExchangeFullAccessApp Application EWS.AccessAsApp ApplicationEWSAccessAsApp |
They all seem to be user related, rather than groups which is what I was looking for. I found a page with the description of these and even “Application Exchange Full Access” seems to be user related:
Worse, when I tried connecting using the App Registration I created, I kept getting an error:
1 2 |
> Connect-ExchangeOnline -AppId "$appId" -CertificateFilePath "\path\to\cert.pfx" -Organization "$tenantName <span style="color: rgb(255, 0, 0);" data-mce-style="color: #ff0000;">OperationStopped: UnAuthorized</span> |
What actually works though, is connection via Graph (i.e. Connect-MgGraph
). But Graph can’t manipulate distribution groups, and there are no permissions for groups either anyways.
Basically this RBAC model replaces Application Access policies. Application Access policies didn’t require you to create an Exchange service principal though, and it required you to add permissions on the Entra side; all you did on the Exchange Online side was scope that App Registration to a user or group of users like so:
1 2 3 4 5 6 7 8 |
$params = @{ AccessRight = "RestrictAccess" AppId = $appId PolicyScopeGroupId = $restrictedEmail Description = "Restrict this app to the $restrictedEmail group mailboxes" } New-ApplicationAccessPolicy @params |
Anyways, here’s what I finally did to achieve my end-goal of having an App Registration that can perform certain tasks on a bunch of groups (tasks such as update group membership, make changes, etc).
First, I created an App Registration as before. I added one permission to it in Entra. This let’s it access Exchange Online.
Then I created a new role, based on the existing “Distribution Groups” role.
1 |
New-ManagementRole -Name "My_Distribution Groups" -Parent "Distribution Groups" -Description "Distribution Groups role customized" |
From this I remove any cmdlets I don’t want the App Registration to have access. (In my case, remove the ability to create new groups, and also update certain attributes of groups).
1 2 |
# Remove the cmdlets that are to do with creating new groups (New-DistributionGroup and New-DynamicDistributionGroup) Get-ManagementRoleEntry "My_Distribution Groups\*" | Where-Object { $_.Name -Like "New*" } | ForEach-Object { Remove-ManagementRoleEntry -Identity "$($_.id)\$($_.name)" } |
1 2 3 |
Set-ManagementRoleEntry "My_Distribution Groups\Set-DistributionGroup" -Parameters "RequireSenderAuthenticationEnabled" -RemoveParameter Set-ManagementRoleEntry "My_Distribution Groups\Set-DynamicDistributionGroup" -Parameters "RequireSenderAuthenticationEnabled" -RemoveParameter |
Create the Exchange Online pointer to the Entra App Registration.
1 2 3 4 5 |
$displayName = "ABC App Registration" $graphObj = Get-MgServicePrincipal -Filter "DisplayName eq '$displayName'" # Create an Exchange pointer to the Entra app registration New-ServicePrincipal -AppId $graphObj.AppId -ObjectId $graphObj.Id -DisplayName $displayName |
I already have a management scope from before. So I now tie these together by creating a role group which has the role above, scoped to the scope I want, and with the App Registration as its member.
1 2 3 4 5 6 7 8 9 |
$newRoleGroupParams = @{ "Name" = "ABC - Distribution Groups Management" "Description" = "My_Distribution Groups role scoped for ABC" "Roles" = "My_Distribution Groups" "Members" = $graphObj.Id "CustomRecipientWriteScope" = "Management Scope - ABC" } New-RoleGroup @newRoleGroupParams |
And that’s it!
Now I can use Connect-ExchangeOnline
with the App Registration and it has the permissions I defined upon the scope I created.
What’s not so obvious with New-RoleGroup
is that the Members
aren’t limited to mailboxes or mail enabled security groups but can also be a service principal!
Ditto for Add-RoleGroupMember
. It too takes service principals.
1 |
Add-RoleGroupMember -Identity "ABC - Distribution Groups Management" -Member $graphObj.Id -BypassSecurityGroupManagerCheck |
Glad I figured that out. 🙂