Private/28-GetAtlasGovernanceScope.ps1
|
function Get-AtlasAdministrativeUnit { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $TenantId, [Parameter(Mandatory)] [AllowEmptyCollection()] [AtlasNode[]] $KnownNode, [Parameter(Mandatory)] [AllowEmptyCollection()] [AtlasEdge[]] $KnownEdge ) $result = [AtlasCollectionResult]::new() $keyById = @{} $nodeByKey = @{} foreach ($node in $KnownNode) { $keyById[$node.Id] = $node.Key $nodeByKey[$node.Key] = $node } $endpoint = '/v1.0/directory/administrativeUnits?$select=id,displayName,description,membershipType,membershipRule,membershipRuleProcessingState,isMemberManagementRestricted,visibility' $response = Invoke-AtlasGraphRequest -Uri $endpoint $memberRequestCount = 0 $memberRetryCount = 0 $memberCount = 0 $scopedAssignmentCount = 0 foreach ($unit in $response.Items) { $unitNode = New-AtlasNode -TenantId $TenantId -Id $unit.id -Kind 'administrativeUnit' -DisplayName $unit.displayName -Properties @{ description = Get-AtlasResponseProperty -InputObject $unit -Name 'description' membershipType = Get-AtlasResponseProperty -InputObject $unit -Name 'membershipType' membershipRule = Get-AtlasResponseProperty -InputObject $unit -Name 'membershipRule' membershipRuleProcessingState = Get-AtlasResponseProperty -InputObject $unit -Name 'membershipRuleProcessingState' isMemberManagementRestricted = Get-AtlasResponseProperty -InputObject $unit -Name 'isMemberManagementRestricted' visibility = Get-AtlasResponseProperty -InputObject $unit -Name 'visibility' } -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' odataType = '#microsoft.graph.administrativeUnit' resourcePath = "/directory/administrativeUnits/$($unit.id)" collector = 'administrativeUnits' } -Tags @('governance', 'delegatedAdministration') $result.Nodes.Add($unitNode) $membersEndpoint = "/v1.0/directory/administrativeUnits/$($unit.id)/members?`$select=id,displayName,userPrincipalName,deviceId" try { $membersResponse = Invoke-AtlasGraphRequest -Uri $membersEndpoint $memberRequestCount += $membersResponse.Metrics.requestCount $memberRetryCount += $membersResponse.Metrics.retryCount } catch { $result.Status = 'partial' $result.Warnings.Add("Members could not be collected for Administrative Unit '$($unit.displayName)': $(Get-AtlasSafeErrorDetail -ErrorRecord $_)") continue } foreach ($member in $membersResponse.Items) { $memberId = Get-AtlasResponseProperty -InputObject $member -Name 'id' if (-not $memberId) { continue } if ($keyById.ContainsKey($memberId)) { $memberKey = $keyById[$memberId] } else { $odataType = Get-AtlasResponseProperty -InputObject $member -Name '@odata.type' $memberKind = switch ($odataType) { '#microsoft.graph.user' { 'user' } '#microsoft.graph.group' { 'group' } '#microsoft.graph.device' { 'device' } default { 'directoryObject' } } $memberName = Get-AtlasResponseProperty -InputObject $member -Name 'displayName' if (-not $memberName) { $memberName = $memberId } $memberNode = New-AtlasNode -TenantId $TenantId -Id $memberId -Kind $memberKind -DisplayName $memberName -Status 'partial' -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' odataType = $odataType resourcePath = "/directoryObjects/$memberId" collector = 'administrativeUnits' } $result.Nodes.Add($memberNode) $keyById[$memberId] = $memberNode.Key $nodeByKey[$memberNode.Key] = $memberNode $memberKey = $memberNode.Key } $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'administrativeUnits' -Endpoint $membersEndpoint -SourceObjectId "$memberId|$($unit.id)" -Fields @{ memberId = $memberId administrativeUnitId = $unit.id } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $memberKey -To $unitNode.Key -Relationship 'memberOfAdministrativeUnit' -State @{ membershipType = Get-AtlasResponseProperty -InputObject $unit -Name 'membershipType' } -EvidenceIds @($evidence.Key) -Source @{ collector = 'administrativeUnits' }) ) $memberCount++ } $expectedScope = "/administrativeUnits/$($unit.id)" foreach ($assignmentEdge in $KnownEdge) { $directoryScopeId = Get-AtlasResponseProperty -InputObject $assignmentEdge.State -Name 'directoryScopeId' if ($directoryScopeId -ne $expectedScope -or $assignmentEdge.Relationship -notin @('assignedRole', 'eligibleRole')) { continue } $roleNode = $nodeByKey[$assignmentEdge.To] $principalNode = $nodeByKey[$assignmentEdge.From] $roleName = if ($roleNode) { $roleNode.DisplayName } else { $assignmentEdge.To } $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $assignmentEdge.To -To $unitNode.Key -Relationship 'scopedToAdministrativeUnit' -State @{ assignmentId = Get-AtlasResponseProperty -InputObject $assignmentEdge.State -Name 'assignmentId' activation = Get-AtlasResponseProperty -InputObject $assignmentEdge.State -Name 'activation' principalId = if ($principalNode) { $principalNode.Id } else { $assignmentEdge.From } } -EvidenceIds @($assignmentEdge.EvidenceIds) -Source @{ collector = 'administrativeUnits'; derivedFrom = $assignmentEdge.Key }) ) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $assignmentEdge.From -To $unitNode.Key -Relationship 'administersAdministrativeUnit' -State @{ roleDefinitionId = if ($roleNode) { $roleNode.Id } else { $null } roleDisplayName = $roleName activation = Get-AtlasResponseProperty -InputObject $assignmentEdge.State -Name 'activation' } -EvidenceIds @($assignmentEdge.EvidenceIds) -Source @{ collector = 'administrativeUnits'; derivedFrom = $assignmentEdge.Key }) ) $scopedAssignmentCount++ } } $result.Metrics = @{ administrativeUnitCount = $response.Items.Count memberCount = $memberCount scopedRoleAssignmentCount = $scopedAssignmentCount requestCount = $response.Metrics.requestCount + $memberRequestCount retryCount = $response.Metrics.retryCount + $memberRetryCount } return $result } function Get-AtlasPrivilegedGroupAssignment { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $TenantId, [Parameter(Mandatory)] [AllowEmptyCollection()] [AtlasNode[]] $KnownNode ) $result = [AtlasCollectionResult]::new() $keyById = @{} $addedNode = @{} $groupNode = @($KnownNode | Where-Object Kind -eq 'group') foreach ($node in $KnownNode) { $keyById[$node.Id] = $node.Key } $requestCount = 0 $retryCount = 0 $failedGroup = @{} $failedRequestCount = 0 $activeCount = 0 $eligibleCount = 0 foreach ($group in $groupNode) { $filter = [uri]::EscapeDataString("groupId eq '$($group.Id)'") $assignmentEndpoint = "/v1.0/identityGovernance/privilegedAccess/group/assignmentScheduleInstances?`$filter=$filter&`$select=id,groupId,principalId,accessId,memberType,assignmentType,startDateTime,endDateTime" $eligibilityEndpoint = "/v1.0/identityGovernance/privilegedAccess/group/eligibilityScheduleInstances?`$filter=$filter&`$select=id,groupId,principalId,accessId,memberType,startDateTime,endDateTime" $scheduleSets = @() foreach ($request in @( @{ Activation = 'active'; Endpoint = $assignmentEndpoint } @{ Activation = 'eligible'; Endpoint = $eligibilityEndpoint } )) { try { $scheduleResponse = Invoke-AtlasGraphRequest -Uri $request.Endpoint $requestCount += $scheduleResponse.Metrics.requestCount $retryCount += $scheduleResponse.Metrics.retryCount $scheduleSets += @{ Items = @($scheduleResponse.Items) Activation = $request.Activation Endpoint = $request.Endpoint } } catch { $failedGroup[$group.Id] = $group.DisplayName $failedRequestCount++ } } foreach ($scheduleSet in $scheduleSets) { foreach ($schedule in $scheduleSet.Items) { $principalId = Get-AtlasResponseProperty -InputObject $schedule -Name 'principalId' if (-not $principalId) { continue } if ($keyById.ContainsKey($principalId)) { $principalKey = $keyById[$principalId] } else { $principalKey = "tenant:$TenantId`:graph:$principalId" if (-not $addedNode.ContainsKey($principalKey)) { $principalNode = New-AtlasNode -TenantId $TenantId -Id $principalId -Kind 'directoryObject' -DisplayName $principalId -Status 'unresolved' -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' resourcePath = "/directoryObjects/$principalId" collector = 'pimGroups' } $result.Nodes.Add($principalNode) $addedNode[$principalKey] = $true } } $accessId = [string] (Get-AtlasResponseProperty -InputObject $schedule -Name 'accessId') $relationship = if ($scheduleSet.Activation -eq 'eligible') { if ($accessId -eq 'owner') { 'pimEligibleOwner' } else { 'pimEligibleMember' } } else { if ($accessId -eq 'owner') { 'pimActiveOwner' } else { 'pimActiveMember' } } $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'pimGroups' -Endpoint $scheduleSet.Endpoint -SourceObjectId $schedule.id -Fields @{ groupId = $group.Id principalId = $principalId accessId = $accessId activation = $scheduleSet.Activation memberType = Get-AtlasResponseProperty -InputObject $schedule -Name 'memberType' assignmentType = Get-AtlasResponseProperty -InputObject $schedule -Name 'assignmentType' startDateTime = Get-AtlasResponseProperty -InputObject $schedule -Name 'startDateTime' endDateTime = Get-AtlasResponseProperty -InputObject $schedule -Name 'endDateTime' } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $principalKey -To $group.Key -Relationship $relationship -State @{ activation = $scheduleSet.Activation accessId = $accessId memberType = Get-AtlasResponseProperty -InputObject $schedule -Name 'memberType' assignmentType = Get-AtlasResponseProperty -InputObject $schedule -Name 'assignmentType' startDateTime = Get-AtlasResponseProperty -InputObject $schedule -Name 'startDateTime' endDateTime = Get-AtlasResponseProperty -InputObject $schedule -Name 'endDateTime' } -EvidenceIds @($evidence.Key) -Source @{ collector = 'pimGroups' }) ) if ($scheduleSet.Activation -eq 'eligible') { $eligibleCount++ } else { $activeCount++ } } } } if ($failedGroup.Count -gt 0) { $result.Status = 'partial' $sample = @($failedGroup.Values | Select-Object -First 5) -join ', ' $result.Warnings.Add("$failedRequestCount PIM for Groups request(s) failed across $($failedGroup.Count) group(s). First affected group(s): $sample. Successful active or eligible results were retained. The signed-in account may need a supported Microsoft Entra role in addition to the delegated Graph permissions.") } $result.Metrics = @{ evaluatedGroupCount = $groupNode.Count failedGroupCount = $failedGroup.Count failedRequestCount = $failedRequestCount activeAssignmentCount = $activeCount eligibleAssignmentCount = $eligibleCount requestCount = $requestCount retryCount = $retryCount } return $result } |