diff --git a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 index 06dd3d2e7f..274bf4b034 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 +++ b/PowerShell/ScubaGear/Modules/CreateReport/CreateReport.psm1 @@ -270,13 +270,33 @@ function New-Report { # Create a section header for the licensing information $LicensingHTML = "

Tenant Licensing Information

" + $LicenseTable + # Create a section for privileged service principals + $privilegedServicePrincipalsTable = $SettingsExport.privileged_service_principals.psobject.properties | ForEach-Object { + $principal = $_.Value + [pscustomobject]@{ + "Display Name" = $principal.DisplayName + "Service Principal ID" = $principal.ServicePrincipalId + "Roles" = ($principal.roles -join ", ") + "App ID" = $principal.AppId + + } + } | ConvertTo-Html -Fragment + + $privilegedServicePrincipalsTable = $privilegedServicePrincipalsTable -replace '^(.*?)', '
' + + # Create a section header for the service principal information + $privilegedServicePrincipalsTableHTML = "

Privileged Service Principal Table

" + $privilegedServicePrincipalsTable + + $ReportHTML = $ReportHTML.Replace("{AADWARNING}", $AADWarning) $ReportHTML = $ReportHTML.Replace("{LICENSING_INFO}", $LicensingHTML) + $ReportHTML = $ReportHTML.Replace("{SERVICE_PRINCIPAL}", $privilegedServicePrincipalsTableHTML) $CapJson = ConvertTo-Json $SettingsExport.cap_table_data } else { $ReportHTML = $ReportHTML.Replace("{AADWARNING}", $NoWarning) $ReportHTML = $ReportHTML.Replace("{LICENSING_INFO}", "") + $ReportHTML = $ReportHTML.Replace("{SERVICE_PRINCIPAL}", "") $CapJson = "null" } diff --git a/PowerShell/ScubaGear/Modules/CreateReport/IndividualReport/IndividualReport.html b/PowerShell/ScubaGear/Modules/CreateReport/IndividualReport/IndividualReport.html index df97a34cf1..e43d33ecb4 100644 --- a/PowerShell/ScubaGear/Modules/CreateReport/IndividualReport/IndividualReport.html +++ b/PowerShell/ScubaGear/Modules/CreateReport/IndividualReport/IndividualReport.html @@ -26,6 +26,7 @@

{TITLE}

{AADWARNING}

{TABLES} {LICENSING_INFO} + {SERVICE_PRINCIPAL} diff --git a/PowerShell/ScubaGear/Modules/Providers/ExportAADProvider.psm1 b/PowerShell/ScubaGear/Modules/Providers/ExportAADProvider.psm1 index 64403f7b01..1ba2ba533e 100644 --- a/PowerShell/ScubaGear/Modules/Providers/ExportAADProvider.psm1 +++ b/PowerShell/ScubaGear/Modules/Providers/ExportAADProvider.psm1 @@ -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) { + + # 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" + } 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 { +# 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 diff --git a/PowerShell/ScubaGear/Modules/Providers/ProviderHelpers/CommandTracker.psm1 b/PowerShell/ScubaGear/Modules/Providers/ProviderHelpers/CommandTracker.psm1 index a365903beb..d8c91fc325 100644 --- a/PowerShell/ScubaGear/Modules/Providers/ProviderHelpers/CommandTracker.psm1 +++ b/PowerShell/ScubaGear/Modules/Providers/ProviderHelpers/CommandTracker.psm1 @@ -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 class CommandTracker { [string[]]$SuccessfulCommands = @() diff --git a/PowerShell/ScubaGear/RequiredVersions.ps1 b/PowerShell/ScubaGear/RequiredVersions.ps1 index 0d77de27c4..f57ed4c245 100644 --- a/PowerShell/ScubaGear/RequiredVersions.ps1 +++ b/PowerShell/ScubaGear/RequiredVersions.ps1 @@ -64,5 +64,10 @@ $ModuleList = @( ModuleName = 'powershell-yaml' ModuleVersion = [version] '0.4.2' MaximumVersion = [version] '0.99.99999' + }, + @{ + ModuleName = 'Microsoft.Graph.Beta.Applications' + ModuleVersion = [version] '2.0.0' + MaximumVersion = [version] '2.99.99999' } )