-
Notifications
You must be signed in to change notification settings - Fork 248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add privileged service principals table to AAD report #1467
base: main
Are you sure you want to change the base?
Changes from all commits
7daf9ab
ff45ea4
d6fe4ae
b7c2a5b
c94fb2f
3e05974
67f3a45
97a307e
7200154
e715335
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ <h1>{TITLE}</h1> | |
<h4>{AADWARNING}</h4> | ||
{TABLES} | ||
{LICENSING_INFO} | ||
{SERVICE_PRINCIPAL} | ||
</main> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -111,21 +111,37 @@ function Export-AADProvider { | |
# The RequiredServicePlan variable is used so that PIM Cmdlets are only executed if the tenant has the premium license | ||
$RequiredServicePlan = $ServicePlans | Where-Object -Property ServicePlanName -eq -Value "AAD_PREMIUM_P2" | ||
|
||
# Get-PrivilegedUser provides a list of privileged users and their role assignments. | ||
if ($RequiredServicePlan) { | ||
# If the tenant has the premium license then we also include calls to PIM APIs | ||
$PrivilegedUsers = $Tracker.TryCommand("Get-PrivilegedUser", @{"TenantHasPremiumLicense"=$true; "M365Environment"=$M365Environment}) | ||
$PrivilegedObjects = $Tracker.TryCommand("Get-PrivilegedUser", @{"TenantHasPremiumLicense"=$true; "M365Environment"=$M365Environment}) | ||
} | ||
else{ | ||
$PrivilegedUsers = $Tracker.TryCommand("Get-PrivilegedUser", @{"TenantHasPremiumLicense"=$false; "M365Environment"=$M365Environment}) | ||
else { | ||
$PrivilegedObjects = $Tracker.TryCommand("Get-PrivilegedUser", @{"TenantHasPremiumLicense"=$false; "M365Environment"=$M365Environment}) | ||
} | ||
# The Converto-Json call below doesn't need to have the input wrapped in an | ||
# array (e.g, "ConvertTo-Json (@PrivilegedUsers)") because $PrivilegedUsers is | ||
# a dictionary, not an array, and ConvertTo-Json doesn't mess up dictionaries like it does arrays | ||
$PrivilegedUsers = $PrivilegedUsers | ConvertTo-Json | ||
|
||
# # Split the objects into users and service principals | ||
$PrivilegedUsers = @{} | ||
$PrivilegedServicePrincipals = @{} | ||
|
||
#PrivilegedObjects is an array because of the tracker.trycommand, and so the first index is the hashtable | ||
foreach ($key in $PrivilegedObjects[0].Keys) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we certain that $PrivilegedObjects will always contain at least one entry? Otherwise accessing the 0th index may throw an error if an entry does not exist. Either way, I also think it would help to see some error handling to check if $PrivilegedObjects[0].Keys is not null prior to entering the loop. |
||
|
||
# Check if it has ServicePrincipalId property instead of AppId | ||
if ($null -ne $PrivilegedObjects[0][$key].ServicePrincipalId) { | ||
$PrivilegedServicePrincipals[$key] = $PrivilegedObjects[0][$key] | ||
} | ||
else { | ||
$PrivilegedUsers[$key] = $PrivilegedObjects[0][$key] | ||
} | ||
} | ||
|
||
$PrivilegedUsers = ConvertTo-Json $PrivilegedUsers | ||
$PrivilegedServicePrincipals = ConvertTo-Json $PrivilegedServicePrincipals | ||
|
||
# While ConvertTo-Json won't mess up a dict as described in the above comment, | ||
# on error, $TryCommand returns an empty list, not a dictionary. | ||
$PrivilegedUsers = if ($null -eq $PrivilegedUsers) {"{}"} else {$PrivilegedUsers} | ||
$PrivilegedServicePrincipals = if ($null -eq $PrivilegedServicePrincipals) {"{}"} else {$PrivilegedServicePrincipals} | ||
|
||
# Get-PrivilegedRole provides a list of security configurations for each privileged role and information about Active user assignments | ||
if ($RequiredServicePlan){ | ||
|
@@ -188,6 +204,7 @@ function Export-AADProvider { | |
"cap_table_data": $CapTableData, | ||
"authorization_policies": $AuthZPolicies, | ||
"privileged_users": $PrivilegedUsers, | ||
"privileged_service_principals": $PrivilegedServicePrincipals, | ||
"privileged_roles": $PrivilegedRoles, | ||
"service_plans": $ServicePlans, | ||
"directory_settings": $DirectorySettings, | ||
|
@@ -274,6 +291,9 @@ function Get-PrivilegedUser { | |
if ($Objecttype -eq "user") { | ||
LoadObjectDataIntoPrivilegedUserHashtable -RoleName $Role.DisplayName -PrivilegedUsers $PrivilegedUsers -ObjectId $User.Id -TenantHasPremiumLicense $TenantHasPremiumLicense -M365Environment $M365Environment -Objecttype "user" | ||
} | ||
elseif ($Objecttype -eq "servicePrincipal") { | ||
LoadObjectDataIntoPrivilegedUserHashtable -RoleName $Role.DisplayName -PrivilegedUsers $PrivilegedUsers -ObjectId $User.Id -TenantHasPremiumLicense $TenantHasPremiumLicense -M365Environment $M365Environment -Objecttype "serviceprincipal" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a new unit test to handle $Objecttype="serviceprincipal" in LoadObjectDataIntoPrivilegedUserHashTable.Tests.ps1? |
||
} | ||
elseif ($Objecttype -eq "group") { | ||
# In this context $User.Id is a group identifier | ||
$GroupId = $User.Id | ||
|
@@ -391,6 +411,23 @@ function LoadObjectDataIntoPrivilegedUserHashtable { | |
} | ||
} | ||
|
||
elseif ($Objecttype -eq "serviceprincipal") { | ||
|
||
# In this section we need to add the service principal information to the "service principal" hashtable | ||
if (-Not $PrivilegedUsers.ContainsKey($ObjectId)) { | ||
$AADServicePrincipal = Get-MgBetaServicePrincipal -ServicePrincipalId $ObjectId -ErrorAction Stop | ||
$PrivilegedUsers[$ObjectId] = @{ | ||
"DisplayName" = $AADServicePrincipal.DisplayName | ||
"ServicePrincipalId" = $AADServicePrincipal.Id | ||
"AppId" = $AADServicePrincipal.AppId | ||
"roles" = @() | ||
} | ||
} | ||
if ($PrivilegedUsers[$ObjectId].roles -notcontains $RoleName) { | ||
$PrivilegedUsers[$ObjectId].roles += $RoleName | ||
} | ||
} | ||
|
||
elseif ($Objecttype -eq "group") { | ||
# In this context $ObjectId is a group identifier so we need to iterate the group members | ||
$GroupId = $ObjectId | ||
|
@@ -411,6 +448,22 @@ function LoadObjectDataIntoPrivilegedUserHashtable { | |
$PrivilegedUsers[$GroupMember.Id].roles += $RoleName | ||
} | ||
} | ||
elseif ($Membertype -eq "serviceprincipal") { | ||
|
||
# In this section we need to add the service principal information to the "service principal" hashtable | ||
if (-Not $PrivilegedUsers.ContainsKey($GroupMember.Id)) { | ||
$AADServicePrincipal = Get-MgBetaServicePrincipal -ServicePrincipalId $GroupMember.Id -ErrorAction Stop | ||
$PrivilegedUsers[$GroupMember.Id] = @{ | ||
"DisplayName" = $AADServicePrincipal.DisplayName | ||
"ServicePrincipalId" = $AADServicePrincipal.Id | ||
"AppId" = $AADServicePrincipal.AppId | ||
"roles" = @() | ||
} | ||
} | ||
if ($PrivilegedUsers[$GroupMember.Id].roles -notcontains $RoleName) { | ||
$PrivilegedUsers[$GroupMember.Id].roles += $RoleName | ||
} | ||
} | ||
} | ||
|
||
# Since this is a group, we need to also process assignments in PIM in case it is in PIM for Groups | ||
|
@@ -437,7 +490,107 @@ function LoadObjectDataIntoPrivilegedUserHashtable { | |
} | ||
|
||
} | ||
|
||
# function LoadObjectDataIntoPrivilegedUserHashtable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove comment blocks |
||
# param ( | ||
# [Parameter(Mandatory=$true)] | ||
# [ValidateNotNullOrEmpty()] | ||
# [string]$RoleName, | ||
|
||
# [Parameter(Mandatory=$true)] | ||
# [hashtable]$PrivilegedUsers, | ||
|
||
# [Parameter(Mandatory=$true)] | ||
# [ValidateNotNullOrEmpty()] | ||
# [string]$ObjectId, | ||
|
||
# [Parameter(Mandatory=$true)] | ||
# [ValidateNotNullOrEmpty()] | ||
# [bool]$TenantHasPremiumLicense, | ||
|
||
# [Parameter(Mandatory=$true)] | ||
# [ValidateNotNullOrEmpty()] | ||
# [string]$M365Environment, | ||
|
||
# [Parameter()] | ||
# [string]$Objecttype = "", | ||
|
||
# [Parameter()] | ||
# [int]$Recursioncount = 0 | ||
# ) | ||
|
||
# if ($recursioncount -ge 2) { | ||
# return | ||
# } | ||
|
||
# if ($Objecttype -eq "") { | ||
# try { | ||
# $DirectoryObject = Get-MgBetaDirectoryObject -ErrorAction Stop -DirectoryObjectId $ObjectId | ||
# } catch { | ||
# if ($_.Exception.Message -match "Request_ResourceNotFound") { | ||
# Write-Warning "Processing privileged users. Resource $ObjectId may have been recently deleted from the directory because it was not found." | ||
# return | ||
# } | ||
# else { | ||
# throw $_ | ||
# } | ||
# } | ||
# $Objecttype = $DirectoryObject.AdditionalProperties."@odata.type" -replace "#microsoft.graph." | ||
# } | ||
|
||
# if ($Objecttype -eq "user") { | ||
# if (-Not $PrivilegedUsers.ContainsKey($ObjectId)) { | ||
# $AADUser = Get-MgBetaUser -ErrorAction Stop -UserId $ObjectId | ||
# $PrivilegedUsers[$ObjectId] = @{"DisplayName"=$AADUser.DisplayName; "OnPremisesImmutableId"=$AADUser.OnPremisesImmutableId; "roles"=@()} | ||
# } | ||
# if ($PrivilegedUsers[$ObjectId].roles -notcontains $RoleName) { | ||
# $PrivilegedUsers[$ObjectId].roles += $RoleName | ||
# } | ||
# } | ||
# elseif ($Objecttype -eq "servicePrincipal") { | ||
# if (-Not $PrivilegedUsers.ContainsKey($ObjectId)) { | ||
# $AADServicePrincipal = Get-MgBetaServicePrincipal -ServicePrincipalId $ObjectId -ErrorAction Stop | ||
# $PrivilegedUsers[$ObjectId] = @{ | ||
# "DisplayName" = $AADServicePrincipal.DisplayName | ||
# "ServicePrincipalId" = $AADServicePrincipal.Id | ||
# "AppId" = $AADServicePrincipal.AppId | ||
# "roles" = @() | ||
# } | ||
# } | ||
# if ($PrivilegedUsers[$ObjectId].roles -notcontains $RoleName) { | ||
# $PrivilegedUsers[$ObjectId].roles += $RoleName | ||
# } | ||
# } | ||
# elseif ($Objecttype -eq "group") { | ||
# $GroupId = $ObjectId | ||
# $GroupMembers = Get-MgBetaGroupMember -All -ErrorAction Stop -GroupId $GroupId | ||
|
||
# foreach ($GroupMember in $GroupMembers) { | ||
# $Membertype = $GroupMember.AdditionalProperties."@odata.type" -replace "#microsoft.graph." | ||
# if ($Membertype -eq "user" -or $Membertype -eq "servicePrincipal") { | ||
# if (-Not $PrivilegedUsers.ContainsKey($GroupMember.Id)) { | ||
# LoadObjectDataIntoPrivilegedUserHashtable -RoleName $RoleName -PrivilegedUsers $PrivilegedUsers -ObjectId $GroupMember.Id -TenantHasPremiumLicense $TenantHasPremiumLicense -M365Environment $M365Environment -Objecttype $Membertype | ||
# } | ||
# if ($PrivilegedUsers[$GroupMember.Id].roles -notcontains $RoleName) { | ||
# $PrivilegedUsers[$GroupMember.Id].roles += $RoleName | ||
# } | ||
# } | ||
# } | ||
|
||
# if ($TenantHasPremiumLicense) { | ||
# $graphArgs = @{ | ||
# "commandlet" = "Get-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleInstance" | ||
# "queryParams" = @{'$filter' = "groupId eq '$GroupId'"} | ||
# "M365Environment" = $M365Environment } | ||
# $PIMGroupMembers = Invoke-GraphDirectly @graphArgs | ||
# foreach ($GroupMember in $PIMGroupMembers) { | ||
# if ($GroupMember.AccessId -ne "member") { continue } | ||
# $PIMEligibleUserId = $GroupMember.PrincipalId | ||
# $LoopIterationRecursioncount = $Recursioncount + 1 | ||
# LoadObjectDataIntoPrivilegedUserHashtable -RoleName $RoleName -PrivilegedUsers $PrivilegedUsers -ObjectId $PIMEligibleUserId -TenantHasPremiumLicense $TenantHasPremiumLicense -M365Environment $M365Environment -Recursioncount $LoopIterationRecursioncount | ||
# } | ||
# } | ||
# } | ||
# } | ||
function AddRuleSource{ | ||
<# | ||
.NOTES | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
Import-Module -Name $PSScriptRoot/../ExportEXOProvider.psm1 -Function Get-ScubaSpfRecord, Get-ScubaDkimRecord, Get-ScubaDmarcRecord | ||
Import-Module -Name $PSScriptRoot/../ExportAADProvider.psm1 -Function Get-PrivilegedRole, Get-PrivilegedUser | ||
Import-Module -Name $PSScriptRoot/../ExportAADProvider.psm1 -Function Get-PrivilegedRole, Get-PrivilegedUser, Get-PrivilegedServicePrincipal | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see |
||
|
||
class CommandTracker { | ||
[string[]]$SuccessfulCommands = @() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logic in AADProvider:
If $PrivilegedServicePrincipals is $null, then it equals {}. Otherwise, set equal to $PrivilegedServicePrincipals
Then in the AADProvider json set "privileged_service_principals" equal to $PrivilegedServicePrincipals
If "privileged_service_principals" = {}, then $principal.DisplayName, $principal.ServicePrincipalId, etc. are nonexistent properties. Can we add some error handling here?
We could check
$SettingsExport.privileged_service_principals.PSObject.Count > 0
or some other condition.