Uncategorized

Cheat Sheet – Azure AD – how application and delegated permissions are exposed in MS Graph and Logs?

While there is some great research and documentation available on this subject already (especially for illicit consent grants 1,2) I’ve found myself missing an single article for explaining how permissions related data is stored and logged in Azure AD; specifically in MS Graph, and Azure AD Audit Logs.

Background.

Since this post focuses on how these permissions are exposed and stored in Azure AD I recommend checking these excellent articles as starting point, if the reader should want some additional context for this posting.

https://learn.microsoft.com/en-us/azure/active-directory/develop/permissions-consent-overview

Applications and ServicePrincipals? What is the distinction?

https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-how-applications-are-added

Grants, permissions and consents in Azure AD and MS Graph

Click image for full the version
Click image for the full version
  • RequiredResourceAccess on Application Registration does not necessarily correlate with granted permission, as it is only used to define access that should be requested on consent. (for multi-tenant app, this reference information is not stored in the same tenant)
    • Users can request dynamic scopes (permissions) that differ from the ones that are configured on applications requiredResource value of the app registration (ref)
Click image for the full version
  • Admins can grant permissions outside the consent experience which are not shown on the requiredResourceAccess of the original application object
    • For applications on the same tenant (single-tenant apps) the application registration view will highlight this difference in case of admin consented permissions. For multi-tenant apps this deviation cannot be inspected in similar manner, as the application object is only hosted on the tenant defined in appOwnerOrganizationId
  • Consent is not the only way to grant permissions. An admin can grant permissions directly by POST request to Graph referencing any servicePrincipal and the permissions in body payload. For example Azure AD permissions granted for managed identities are granted outside the consent experience

//Example admin grants App Permissions directly to MS Graph without consent dialog
az rest --method post --url "https://graph.microsoft.com/v1.0/servicePrincipals/$id/appRoleAssignments" --resource "https://graph.microsoft.com" \
--body "{\"principalId\": \"$id\",\"resourceId\": \"$graphspn\",\"appRoleId\": \"7ab1d382-f21e-4acd-a863-ba3e13f7da61\"}" 

Logs

In Azure AD Audit logs two events are typically created when application is consented with for new permissions. These events contain the following pattern for matching the operation name 'Add delegated permission grant' or 'Add app role assignment to service principal' where values are extracted from TargetResources.modifiedProperties after expanding the targetResources array

  • If the grant was preceded by consent operation 'Consent to application' then permission grant and consent operation will share the same correlationId
  • As mentioned in the previous section addition of new permissions does not necessarily mandate consent operations. In these cases you can change the join type of the query below to rightOuter, and then investigate the correlationId in seperate query for the same time range (You could also easily join these events to the query, but I did not want to bloat the query further)

// Please note, when there is no data the query will fail, this is expected as the query looks to form new columns based on bag_unpack() when there is data, and references those values in latter parts
//
let clientAudits =AuditLogs
| where OperationName has "consent"
| extend user = tostring(InitiatedBy.user.userPrincipalName)
| project ['user'], TargetResources, CorrelationId
| mv-expand  TargetResources
| where isnotempty( TargetResources.displayName)
| extend clientDisplayName = TargetResources.displayName
| extend SPNID = TargetResources.id
//base
| mv-expand propertiesToRetain = TargetResources.modifiedProperties
| where propertiesToRetain.displayName == "DelegatedPermissionGrant.ConsentType" or propertiesToRetain.displayName == "ConsentContext.IsAdminConsent"
| evaluate bag_unpack(propertiesToRetain)
| extend  consentDetails = newValue
| project-away newValue, TargetResources
| extend parse_json(strcat('{"',split(displayName,'.')[1],'":',consentDetails,'}'))
| project-away consentDetails, displayName
| evaluate bag_unpack(Column1);
let ApiAudits =AuditLogs 
| where OperationName has "Add delegated" or OperationName has "Add app role assignment to service principal"
| project TargetResources, CorrelationId
| mv-expand  TargetResources
| where isnotempty( TargetResources.displayName)
| extend API = TargetResources.displayName
//base
| mv-expand propertiesToRetain = TargetResources.modifiedProperties
| where propertiesToRetain.displayName == "DelegatedPermissionGrant.Scope" or propertiesToRetain.displayName == "AppRole.Value" or propertiesToRetain.displayName == "AppRole.displayName"
| evaluate bag_unpack(propertiesToRetain)
| extend  consentDetails = newValue
| project-away newValue, TargetResources
| extend type = iff(displayName == "AppRole.Value",'AppRole.Scope',displayName)
| extend scope = parse_json(strcat('{"',split(type,'.')[1],'":',consentDetails,'}'))
| project-away consentDetails, displayName
| evaluate bag_unpack(scope);
clientAudits
| join kind=leftouter   ApiAudits on CorrelationId
| extend unifiedCorrelationId = iff(isempty(CorrelationId), CorrelationId1, CorrelationId)
| project-away CorrelationId1, CorrelationId
// | distinct SPNID

Key takeaways

  1. API permissions are granted to principals via AppRoles and Oauth2PermissionGrants depending on the principal type
  2. Not all permission grants need prior consent.
  3. RequiredResourceAccess on application object may not always reflect the permissions granted for users in apps residing in the same tenant as servicePrincipal

Happy hunting!

0 comments on “Cheat Sheet – Azure AD – how application and delegated permissions are exposed in MS Graph and Logs?

Jätä kommentti