-
Notifications
You must be signed in to change notification settings - Fork 37
Expand file tree
/
Copy pathExport-EntraOpsClassificationAppRoles.ps1
More file actions
184 lines (162 loc) · 10.6 KB
/
Copy pathExport-EntraOpsClassificationAppRoles.ps1
File metadata and controls
184 lines (162 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
function Export-EntraOpsClassificationAppRoles {
<#
.SYNOPSIS
Get a JSON file with all classified API Permissions in Entra ID.
.DESCRIPTION
Read JSON classification file and match API permissions in Entra ID tenant to export it as JSON.
.PARAMETER IncludeAuthorizedApiCalls
Include authorized Graph API calls for each API permission.
.PARAMETER AppRoleProvider
Filter the service principals to query for API permissions. Use "MicrosoftGraph" to only query Microsoft Graph or "All" to query all configured providers.
.EXAMPLE
Export all classified API permissions with list of authorized Graph API calls to the path "Classification\Classification_ApiPermissions.json".
Export-EntraOpsClassificationAppRoles -IncludeAuthorizedApiCalls $true
#>
[cmdletbinding()]
param
(
[Parameter(Mandatory = $false)]
$IncludeAuthorizedApiCalls = $False,
[Parameter(Mandatory = $false)]
[ValidateSet("All", "MicrosoftGraph")]
[string] $AppRoleProvider = "All"
)
# Get EntraOps Classifications
$ClassificationAppRoles = Get-Content -Path ./EntraOps_Classification/Classification_ApiPermissions.json | ConvertFrom-Json -Depth 10
# Get Graph API actions
if ($IncludeAuthorizedApiCalls -eq $true) {
$AllAuthorizedApiCalls = Invoke-WebRequest -Method GET -Uri "https://raw.githubusercontent.com/merill/graphpermissions.github.io/main/permissions.csv" | ConvertFrom-Csv
}
# Get information about App Role Provider
$AppRoleProviderIds = switch ($AppRoleProvider) {
"MicrosoftGraph" {
@("00000003-0000-0000-c000-000000000000") # Microsoft Graph
}
default {
@(
"00000003-0000-0000-c000-000000000000", # Microsoft Graph
"00000002-0000-0000-c000-000000000000", # Windows Azure Active Directory
"fc780465-2017-40d4-a0c5-307022471b92", # Microsoft Threat Protection (Defender ATP)
"c161e42e-d4df-4a3d-9b42-e7a3c31f59d4", # Microsoft Intune
"797f4846-ba00-4fd7-ba43-dac1f8f63013", # Azure Service Management
"00000012-0000-0000-c000-000000000000", # Azure Rights Management Services
"73c2949e-da2d-457a-9607-fcc665198967", # Microsoft Purview
"c5393580-f805-4401-95e8-94b7a6ef2fc2", # Office 365 Management APIs
"499b84ac-1321-427f-aa17-267ca6975798", # Azure DevOps
"688413c8-5319-43e1-9a0e-42f49da53686", # Verified ID STS Controller
"3db474b9-6a0c-4840-96ac-1fceb342124f", # Microsoft Entra Verified ID
"58c746b0-a0b0-4647-a8f6-12dde5981638", # Azure AD Identity Governance Insights
"7b7531ad-5926-4f2d-8a1d-38495ad33e17", # Azure Advanced Threat Protection
"93625bc8-bfe2-437a-97e0-3d0060024faa", # Microsoft password reset service
"6bf85cfa-ac8a-4be5-b5de-425a0d0dc016", # Microsoft Entra AD Synchronization Service
"00000002-0000-0ff1-ce00-000000000000", # Exchange Online
"00000003-0000-0ff1-ce00-000000000000", # SharePoint Online
"cc15fd57-2c6c-4117-a88c-83b1d56b4bbe", # Microsoft Teams
"00000009-0000-0000-c000-000000000000", # Power BI Service
"00000007-0000-0000-c000-000000000000", # Dynamics CRM
"ca7f3f0b-7d91-482c-8e09-c5d840d0eac5", # Azure Log Analytics
"475226c6-020e-4fb2-8a90-7a972cbfc1d4", # Power Platform / Power Apps Admin
"0af06dc6-e4b5-4f28-818e-e78e62d137a5", # Windows 365
"b46c3ac5-9da6-418f-a849-0a07a10b3c6c", # Microsoft Entra Permissions Management
"00000005-0000-0ff1-ce00-000000000000" # Yammer / Viva Engage
)
}
}
$AppRoleProviders = foreach ($AppRoleProviderId in $AppRoleProviderIds) {
(Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/beta/servicePrincipals?`$filter=appId eq '$AppRoleProviderId'" -OutputType PSObject).value | select-object appId, appRoles, publishedPermissionScopes
}
# Collect warnings during processing
$UnclassifiedAppRoles = [System.Collections.Generic.List[PSCustomObject]]::new()
# Process application permissions (appRoles) → Classification_AppRoles ('Application' or 'All')
$AppRolesOutput = $AppRoleProviders | foreach-object {
$CurrentAppId = $_.appId
foreach ($AppRole in $_.AppRoles) {
# Apply Classification (Application permissions match ResourceScope 'Application' or 'All')
$AppRoleTierLevelClassification = $ClassificationAppRoles | where-object { ($_.TierLevelDefinition | where-object { $_.ResourceScope -in @("Application", "All") -and $_.ResourceAppId -eq $CurrentAppId }).RoleDefinitionActions -contains $($AppRole.value) } | select-object EAMTierLevelName, EAMTierLevelTagValue
$AppRoleServiceClassification = $ClassificationAppRoles | select-object -ExpandProperty TierLevelDefinition | where-object { $_.ResourceScope -in @("Application", "All") -and $_.ResourceAppId -eq $CurrentAppId -and $_.RoleDefinitionActions -contains $($AppRole.value) } | select-object Service
if ($IncludeAuthorizedApiCalls -eq $True -and $_.appId -eq "00000003-0000-0000-c000-000000000000") {
# Apply Autorized Graph Calls if AppRoleProvider is Microsoft Graph
$AppRoleAuthorizedApiCalls = $AllAuthorizedApiCalls | where-object { $_.PermissionName -contains $($AppRole.value) } | select-object -ExpandProperty API
}
if ($AppRoleTierLevelClassification.Count -gt 1 -and $AppRoleServiceClassification.Count -gt 1) {
Write-Warning "Multiple Tier Level Classification found for $($AppRole.value)"
}
if ($null -eq $AppRoleTierLevelClassification) {
$UnclassifiedAppRoles.Add([PSCustomObject]@{
AppId = $_.appId
AppRoleDisplayName = $AppRole.value
PermissionType = "Application"
})
$AppRoleTierLevelClassification = [PSCustomObject]@{
"EAMTierLevelName" = "Unclassified"
"EAMTierLevelTagValue" = "Unclassified"
}
}
if ($null -eq $AppRoleServiceClassification) {
$AppRoleServiceClassification = [PSCustomObject]@{
"Service" = "Unclassified"
}
}
if ($IncludeAuthorizedApiCalls -eq $True) {
[PSCustomObject]@{
"AppId" = $_.appId
"AppRoleId" = $AppRole.id
"AppRoleDisplayName" = $AppRole.value
"AuthorizedApiCalls" = $AppRoleAuthorizedApiCalls
"Category" = $AppRoleServiceClassification.Service
"EAMTierLevelName" = $AppRoleTierLevelClassification.EAMTierLevelName
"EAMTierLevelTagValue" = $AppRoleTierLevelClassification.EAMTierLevelTagValue
}
} else {
[PSCustomObject]@{
"AppId" = $_.appId
"AppRoleId" = $AppRole.id
"AppRoleDisplayName" = $AppRole.value
"Category" = $AppRoleServiceClassification.Service
"EAMTierLevelName" = $AppRoleTierLevelClassification.EAMTierLevelName
"EAMTierLevelTagValue" = $AppRoleTierLevelClassification.EAMTierLevelTagValue
}
}
}
}
# Identify appRole permissions in Classification_AppRoles (Application/'All') not returned by API
$ExistingAppRoleNames = $AppRolesOutput | Select-Object -ExpandProperty AppRoleDisplayName
$MissingAppRolesInApi = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($TierLevel in $ClassificationAppRoles) {
foreach ($TierDef in $TierLevel.TierLevelDefinition) {
if ($TierDef.ResourceScope -in @("Application", "All") -and $TierDef.ResourceAppId -in $AppRoleProviderIds) {
foreach ($Action in $TierDef.RoleDefinitionActions) {
if ($Action -notin $ExistingAppRoleNames) {
$MissingAppRolesInApi.Add([PSCustomObject]@{
AppId = $TierDef.ResourceAppId
AppRoleDisplayName = $Action
ResourceScope = $TierDef.ResourceScope
EAMTierLevelName = $TierLevel.EAMTierLevelName
Category = $TierDef.Service
})
}
}
}
}
}
$AppRolesOutput = $AppRolesOutput | Sort-Object AppRoleDisplayName
$AppRolesOutput | ConvertTo-Json -Depth 10 | Out-File .\Classification\Classification_AppRoles.json -Force
$AppRolesOutput | Where-Object { $_.AppId -eq "00000003-0000-0000-c000-000000000000" } | ConvertTo-Json -Depth 10 | Out-File .\Classification\Classification_MsGraphAppRoles.json -Force
# ── Warning Summary ───────────────────────────────────────────────────────────
if ($MissingAppRolesInApi.Count -gt 0) {
$missingTable = $MissingAppRolesInApi | Sort-Object ResourceScope, EAMTierLevelName, AppRoleDisplayName |
Format-Table AppRoleDisplayName, ResourceScope, EAMTierLevelName, Category, AppId -AutoSize |
Out-String -Width 220
Write-Warning "[$($MissingAppRolesInApi.Count) appRoles] CLASSIFIED IN Classification_ApiPermissions BUT NOT FOUND IN API"
Write-Warning "Entries defined in EntraOps_Classification/Classification_ApiPermissions.json (ResourceScope: Application or All) with no matching appRole on the queried service principals."
Write-Warning $missingTable
}
if ($UnclassifiedAppRoles.Count -gt 0) {
$unclassifiedTable = $UnclassifiedAppRoles | Sort-Object AppRoleDisplayName |
Format-Table AppRoleDisplayName, PermissionType, AppId -AutoSize |
Out-String -Width 220
Write-Warning "[$($UnclassifiedAppRoles.Count) appRoles] IN API BUT NOT COVERED IN Classification_ApiPermissions"
Write-Warning "appRoles returned by the API with no matching entry in EntraOps_Classification/Classification_ApiPermissions.json (or only defined under a different ResourceScope)."
Write-Warning $unclassifiedTable
}
}