/
WorkloadIdentityInfo.ps1
215 lines (189 loc) · 9.93 KB
/
WorkloadIdentityInfo.ps1
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<# Pre-Requisites
Environment:
- PowerShell Core
- Installed PowerShell Module "SentinelEnrichment"
- Enabled System-Assigned Managed Identity
Required permissions:
- Azure RBAC:
- Microsoft Sentinel Contributor on Resource Group-Level
- Microsoft Graph API (Application):
- Application.Read.All
- Group.Read.All
- RoleManagement.Read.Directory
#>
# Global Variables
$WatchListName = "WorkloadIdentityInfo"
$SentinelSubscriptionId = Get-AutomationVariable -Name 'SentinelSubscriptionId'
$SentinelResourceGroupName = Get-AutomationVariable -Name 'SentinelResourceGroupName'
$SentinelWorkspaceName = Get-AutomationVariable -Name 'SentinelWorkspaceName'
$ErrorActionPreference = "Stop"
$WatchListParameters = @{
SearchKey = $null
DisplayName = $null
NewWatchlistItems = ""
SubscriptionId = $SentinelSubscriptionId
ResourceGroupName = $SentinelResourceGroupName
WorkspaceName = $SentinelWorkspaceName
Identifiers = @("Entra ID", "Automated Enrichment")
}
try {
Import-Module "SentinelEnrichment" -ErrorAction Stop
} catch {
throw "Cannot load module SentinelEnrichment. Please install the module from the PowerShell gallery"
}
#region Connect to Azure and Microsoft Graph
try {
Write-Output "Sign-in with Managed Identity to Azure..."
Connect-AzAccount -Identity -Subscription $SentinelSubscriptionId
Write-Output "Succesfully logged into $SentinelSubscriptionId"
Write-Output "Sign-in to Microsoft Graph with Managed Identity..."
Connect-MgGraph -Identity -NoWelcome
$MgContext = Get-MgContext
Write-Output "Succesfully logged to Tenant $($MgContext.TenantId)"
} catch {
Write-Error -Message $_.Exception
throw $_.Exception
}
#endregion
#region Get list of all tenant service principals, applications, 1st party apps and directory roles and app roles.
Write-Verbose "Query tenant service principals - https://graph.microsoft.com/v1.0/serviceprincipals"
$ServicePrincipals = Invoke-GkSeMgGraphRequest -Uri "https://graph.microsoft.com/v1.0/serviceprincipals"
Write-Verbose "Query tenant applications - https://graph.microsoft.com/v1.0/applications"
$Applications = Invoke-GkSeMgGraphRequest -Uri "https://graph.microsoft.com/v1.0/applications"
Write-Verbose "Query directory role templates for mapping ID to name and further details"
$DirectoryRoleDefinitions = Invoke-GkSeMgGraphRequest -Uri "https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions" | select-object displayName, templateId, isPrivileged, isBuiltin
Write-Verbose "Query app roles for mapping ID to name"
$SPObjectWithAppRoles = $ServicePrincipals | where-object {$null -ne $_.AppRoles}
$AppRoles = foreach ($SPObjectWithAppRole in $SPObjectWithAppRoles) {
$SPObjectWithAppRole.AppRoles | foreach-object {
[PSCustomObject]@{
"AppId" = $SPObjectWithAppRole.appId
"ServicePrincipalObjectId" = $SPObjectWithAppRole.id
"AppRoleId" = $_.id
"AppRoleDisplayName" = $_.value
}
}
}
Write-Verbose "Query list of first party apps"
try {
$ProgressPreference = 'SilentlyContinue'
$FirstPartyApps = Invoke-WebRequest -UseBasicParsing -Method GET -Uri "https://raw.githubusercontent.com/merill/microsoft-info/main/_info/MicrosoftApps.json" | ConvertFrom-Json
} catch {
Write-Warning "Issue to query list of first party apps from GitHub - $($_.Exception)"
}
#endregion
#region Gathering details for each Workload Identity and add to Array List
Write-Verbose "Get details for enrichment of service principals"
$NewWatchlistItems = [System.Collections.Concurrent.ConcurrentBag[psobject]]::new()
$ServicePrincipals | ForEach-Object -Parallel {
$ServicePrincipal = $_
try {
Write-Verbose "Collecting data for $($ServicePrincipal.displayName)"
Write-Verbose "Query Application of ServicePrincipal `"$($ServicePrincipal.displayName)`""
try {
$Application = $using:Applications | Where-Object appId -eq $ServicePrincipal.AppId
} catch {
Write-Verbose "Can not find app registration for $($ServicePrincipal.DisplayName)"
}
Write-Verbose "Query Application Permissions of ServicePrincipal `"$($ServicePrincipal.displayName)`""
try {
$AssignedAppRoles = New-Object System.Collections.ArrayList
$SPRoleAssignments = (Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$($ServicePrincipal.id)/appRoleAssignments" -Verbose:$False)['value']
$SPRoleAssignments = foreach ($SPRoleAssignment in $SPRoleAssignments) {
$AppRole = $using:AppRoles | where-object { $_.appRoleId -eq $SPRoleAssignment.appRoleId -and $_.ServicePrincipalObjectId -eq $SPRoleAssignment.resourceId }
[PSCustomObject]@{
"ResourceAppId" = $AppRole.AppId
"ResourceDisplayName" = $SPRoleAssignment.resourceDisplayName
"AppRoleId" = $SPRoleAssignment.appRoleId
"AppRoleDisplayName" = $AppRole.AppRoleDisplayName
}
}
$AssignedAppRoles = $SPRoleAssignments | ConvertTo-Json -Compress -AsArray
} catch {
Write-Error -Message $_.Exception
throw $_.Exception
}
Write-Verbose "Query Group Memberships of ServicePrincipal `"$($ServicePrincipal.displayName)`""
try {
$GroupMemberships = New-Object System.Collections.ArrayList
$TransitiveMemberOf = Invoke-GkSeMgGraphRequest -Uri "https://graph.microsoft.com/v1.0/serviceprincipals/$($ServicePrincipal.id)/transitiveMemberOf" | Select-Object id, displayName, isAssignableToRole
foreach ($GroupMembership in $TransitiveMemberOf) {
$GroupMemberships.Add($GroupMembership) | Out-Null
}
$GroupMemberships = $GroupMemberships | ConvertTo-Json -Compress -AsArray
} catch {
Write-Error -Message $_.Exception
throw $_.Exception
}
Write-Verbose "Query Directory Roles of ServicePrincipal `"$($ServicePrincipal.displayName)`""
try {
$TransitiveRoleAssignments = New-Object System.Collections.ArrayList
$HeaderParams = @{
'ConsistencyLevel' = "eventual"
}
$TransitiveRoleAssignments = (Invoke-MgGraphRequest -Method Get -Headers $HeaderParams -Uri "https://graph.microsoft.com/beta/roleManagement/directory/transitiveRoleAssignments?`$count=true&`$filter=principalId eq '$($ServicePrincipal.Id)'")['value']
$TransitiveRoleAssignments = foreach ($TransitiveRoleAssignment in $TransitiveRoleAssignments) {
$RoleDefinition = $using:DirectoryRoleDefinitions | where-object { $_.templateid -eq $TransitiveRoleAssignment.roleDefinitionId }
[PSCustomObject]@{
"RoleDefinitionName" = $RoleDefinition.displayName
"RoleDefinitionId" = $TransitiveRoleAssignment.roleDefinitionId
"ResourceScope" = $TransitiveRoleAssignment.resourceScope
"RoleAssignmentId" = $TransitiveRoleAssignment.id
"IsPrivileged" = $RoleDefinition.isPrivileged
}
}
$AssignedRoles = $TransitiveRoleAssignments | ConvertTo-Json -Compress -AsArray
} catch {
Write-Error -Message $_.Exception
throw $_.Exception
}
if ( $ServicePrincipal.AppId -in $using:FirstPartyApps.AppId ) {
$IsFirstPartyApp = $true
} else {
$IsFirstPartyApp = $false
}
if ( $null -ne $ServicePrincipal.appId) {
$CurrentItem = [PSCustomObject]@{
"ServicePrincipalObjectId" = $ServicePrincipal.Id
"AppObjectId" = $Application.Id
"AppId" = $ServicePrincipal.AppId
"AppDisplayName" = $ServicePrincipal.DisplayName
"CreatedDateTime" = $ServicePrincipal.createdDateTime
"IsAccountEnabled" = $ServicePrincipal.accountEnabled
"DisabledByMicrosoft" = $ServicePrincipal.DisabledByMicrosoftStatus
"AppOwnerTenantId" = $ServicePrincipal.AppOwnerOrganizationId
"IsFirstPartyApp" = $IsFirstPartyApp
"ServicePrincipalType" = $ServicePrincipal.servicePrincipalType
"SignInAudience" = $ServicePrincipal.SignInAudience
"UserAssignmentRequired" = $ServicePrincipal.appRoleAssignmentRequired
"ServiceManagementReference" = $ServicePrincipal.serviceManagementReference
"AssignedAppRoles" = $AssignedAppRoles
"GroupMembership" = $GroupMemberships
"AssignedRoles" = $AssignedRoles
"Tags" = @("Entra ID", "Automated Enrichment")
}
($using:NewWatchlistItems).Add( $CurrentItem ) | Out-Null
}
} catch {
Write-Warning "Could not add $($ServicePrincipal.displayName) - Error $($_.Exception)"
Continue
}
}
#endregion
#region Update all watchlist items
Write-Output "Write information to watchlist: $WatchListName"
if ( $null -ne $NewWatchlistItems ) {
$WatchListPath = Join-Path $PWD "$($WatchListName).csv"
$NewWatchlistItems | Export-Csv -Path $WatchListPath -NoTypeInformation -Encoding utf8 -Delimiter ","
$Parameters = @{
WatchListFilePath = $WatchListPath
DisplayName = $WatchListName
itemsSearchKey = "ServicePrincipalObjectId"
SubscriptionId = $SentinelSubscriptionId
ResourceGroupName = $SentinelResourceGroupName
WorkspaceName = $SentinelWorkspaceName
DefaultDuration = "P14D"
}
New-GkSeAzSentinelWatchlist @Parameters
}
#endregion