In this tutorial, I will show you how to use a combination of Microsoft Graph PowerShell, Managed Identities and Azure Automation to create a secure way of reporting on application admin grant consents to Graph API permissions weekly via email. This will help any information security teams identify if an Administrator granted more than the necessary permissions to an application in Microsoft Entra.
What is admin permission consent?
Admin permission consent is a ‘tenant-wide’ consent that is given to an application in Microsoft Entra for specific permissions. This means that the publisher of that application will be able to access the data it has been granted access to, as well as anyone who can authenticate to that application. Due to this nature, admin permissions grants are highly privileged and the operations should be monitored.
For example, I am an Azure Administrator, managing a custom application hosted in Azure and some related resources in the Azure subscription. I have no access to manage directory objects in Microsoft Entra; however, the custom application I manage does need to access and modify some Microsoft Entra directory objects. To meet this requirement I ask the Microsoft Entra administrator to register an application (or service principal) in Microsoft Entra and consent the user.readwrite.all permission to it, thus allowing my application to modify user objects in Microsoft Entra. Inadvertently, an attack path has now been created where an unauthorised user (me) could potentially change resources that I am not authorised to do so as I have complete control over the custom application.
Luckily for us admins, each time an admin consent for permission is granted to an application, a log is stored in the Enterprise Applications Audit Log with all the information we need to investigate the action and make an informed decision on its validity.
Who can grant admin permissions in Microsoft Entra?
There are two roles in Microsoft Entrawhich provide the ability to grant admin permission consent for applications. These are:
- Global Administrator
- Privileged role administrator
In a practical sense, neither role is less privileged than the other. While at face value the Global Administrator role holds more rights, the Privileged role administrator can simply elevate privileges as required while not holding as many explicit permissions itself.
Script permissions
The script I have written to monitor these admin permission consent logs uses 3 permissions which need to be assigned to the Managed identity used to authenticate the Azure Automation running the script, these are:
- auditlog.read.all
- application.read.all
- mail.send
To create a Managed Identity and assign the above permissions, check out my tutorial for using Managed Identities with Microsoft Graph PowerShell:
How to use Managed Identities with Microsoft Graph PowerShell
Admin permissions consent report script
Below you can find the script which will filter the Enterprise Applications log for permissions consent, collate them into a report and email them to the destination. Be sure to modify the following fields:
- Managed_Identity_ID
- RECIPIENT ADDRESS
- SENDER_ADDRESS
<#
AUTHOR: Daniel Bradley
LINKEDIN: https://www.linkedin.com/in/danielbradley2/
TWITTER: https://twitter.com/DanielatOCN
WEBSITE: https://ourcloudnetwork.com/
Info: This script was written by Daniel Bradley for the ourcloudnetwork.com blog
#>
# Declare variables
$ManagedIdentityId = ""
$RecipientAddress = ""
$SenderAddress = ""
#Connect to Microsoft Graph
Connect-MgGraph -scope auditlog.read.all, application.read.all, mail.send
# Get date minus 7 days
$a = $(get-date).AddDays(-7).ToString("yyyy-MM-dd")
# Get application consent logs
$uri = "https://graph.microsoft.com/beta/auditLogs/directoryAudits?`$filter=ActivityDateTime ge $a and ActivityDisplayName eq 'Consent to application'"
$logs = Invoke-MgGraphRequest -uri $uri -Method Get -OutputType PSObject | select -expand value
# Create Report object
$Report = [System.Collections.Generic.List[Object]]::new()
# Loop through each log
ForEach ($log in $logs) {
# null temporary variable
$id = $null
$app = $null
# Get current app id
$id = ($log.additionalDetails) | Where-Object {$_.Key -eq 'AppID'}
# Get Application Name
if ($log.targetResources.type -eq "ServicePrincipal"){
$App = Get-MgBetaServicePrincipal -Filter "AppId eq '$($id.value)'"
} else {
$app = Get-MgBetaApplication -Filter "AppId eq '$($id.value)'"
}
# Extract consented permissions
$perms = (($log.targetResources.modifiedproperties) | Where-Object {$_.DisplayName -eq 'ConsentAction.Permissions'}).newvalue
$perms -match "(?<=Scope:)(.*?)(?=, CreatedDateTime:)" | out-null
$permissions = $matches[1]
# build report object
$obj = [PSCustomObject][ordered]@{
"date" = $log.activityDateTime
"App Name" = $app.DisplayName
"Admin Consent" = (($log.targetResources.modifiedProperties) | Where-Object {$_.DisplayName -eq 'ConsentContext.IsAdminConsent'}).NewValue
"Permissions" = $permissions
"Consented by" = $log.initiatedBy.user.userPrincipalName
}
$report.Add($obj)
}
# Convert report to HTML
$report = $report | ConvertTo-Html
$Report = $report -replace '<table>', '<table border="1">'
# Build message params
$params = @{
message = @{
subject = "Automated - Application Permissions Consents"
body = @{
contentType = "html"
content = "$Report"
}
toRecipients = @(
@{
emailAddress = @{
address = "$RecipientAddress"
}
}
)
}
saveToSentItems = "false"
}
# Send message
Send-MgBetaUserMail -UserId $SenderAddress -BodyParameter $params
Automating the report with Azure Automation
Azure Automation allows you to schedule the report script to run on a regular basis. The idea is to run the report each week to evaluate which applications have been consented with what permissions. To do this, you will need an Azure subscription as well as a global admin or the privileged role administrator account to handle the initial permission consents for the Managed Identity required to enable this script to run.
As I have gone into more detail in another tutorial for this step, for this you should see my following post:
How to Run Microsoft Graph PowerShell Scripts With Azure Automation