modules/Devolutions.CIEM.Graph/Public/Get-CIEMPAMProgressSummary.ps1
|
function ConvertCIEMPAMCandidate { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Item ) $ErrorActionPreference = 'Stop' switch ([string]$Item.SourceType) { 'Identity' { $capability = 'JIT elevation and approval workflow' $nextStep = 'Review standing privileged access and decide whether it should move to PAM-backed JIT or approval.' } 'AttackPath' { $capability = 'Access brokering and session governance' $nextStep = 'Review the exposed path and decide whether privileged sessions should be brokered and governed through PAM.' } default { throw "Unsupported PAM progress source type '$($Item.SourceType)'." } } [PSCustomObject]@{ Id = [string]$Item.Id SourceType = [string]$Item.SourceType Severity = [string]$Item.Severity Title = [string]$Item.Title Identity = [string]$Item.Identity Target = [string]$Item.Target Reason = [string]$Item.Reason Evidence = [string]$Item.Evidence PAMCapability = $capability RecommendedNextStep = $nextStep Status = 'Candidate' } } function NewCIEMPAMProgressStage { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Name, [Parameter(Mandatory)] [ValidateSet('Complete', 'Pending', 'NotScoped')] [string]$Status, [Parameter(Mandatory)] [string]$Evidence ) $ErrorActionPreference = 'Stop' [PSCustomObject]@{ Name = $Name Status = $Status Evidence = $Evidence } } function Get-CIEMPAMProgressSummary { <# .SYNOPSIS Returns read-only CIEM-to-PAM implementation progress signals. .DESCRIPTION Computes progress and readiness from discovered CIEM data, exposure snapshots, exposure changes, and current risk candidates. This command does not create PAM records, open approvals, update cloud access, or send connector payloads. .PARAMETER Limit Maximum number of PAM candidate items to include. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [ValidateRange(1, 100)] [int]$Limit = 10 ) $ErrorActionPreference = 'Stop' $graphRows = @(Invoke-CIEMQuery -Query 'SELECT COUNT(*) AS c FROM graph_nodes') if ($graphRows.Count -ne 1) { throw "Expected one graph node count row, got $($graphRows.Count)." } $graphNodeCount = [int]$graphRows[0].c $snapshotRows = @(Invoke-CIEMQuery -Query @" SELECT discovery_run_id, COUNT(*) AS exposure_count, SUM(CASE WHEN severity_rank <= 2 THEN 1 ELSE 0 END) AS critical_high_count FROM ciem_exposure_snapshot_items GROUP BY discovery_run_id ORDER BY discovery_run_id ASC "@) $baselineRunId = $null $currentRunId = $null $baselineExposureCount = 0 $currentExposureCount = 0 $currentCriticalHighCount = 0 if ($snapshotRows.Count -gt 0) { $baseline = $snapshotRows[0] $current = $snapshotRows[$snapshotRows.Count - 1] $baselineRunId = [int]$baseline.discovery_run_id $currentRunId = [int]$current.discovery_run_id $baselineExposureCount = [int]$baseline.exposure_count $currentExposureCount = [int]$current.exposure_count $currentCriticalHighCount = [int]$current.critical_high_count } $changeRows = @(Invoke-CIEMQuery -Query @" SELECT COUNT(*) AS total_count, SUM(CASE WHEN change_type = 'NewRisk' THEN 1 ELSE 0 END) AS new_risk_count, SUM(CASE WHEN change_type = 'RiskIncrease' THEN 1 ELSE 0 END) AS risk_increase_count, SUM(CASE WHEN change_type = 'RemovedRisk' THEN 1 ELSE 0 END) AS removed_risk_count FROM ciem_exposure_changes "@) if ($changeRows.Count -ne 1) { throw "Expected one exposure change count row, got $($changeRows.Count)." } $candidateItems = @( foreach ($item in @(Get-CIEMDashboardNeedsAttention -Limit $Limit)) { ConvertCIEMPAMCandidate -Item $item } ) $jitCandidateCount = @($candidateItems | Where-Object { $_.PAMCapability -eq 'JIT elevation and approval workflow' }).Count $brokeredAccessCandidateCount = @($candidateItems | Where-Object { $_.PAMCapability -eq 'Access brokering and session governance' }).Count $changeCount = [int]$changeRows[0].total_count $snapshotCount = $snapshotRows.Count $exposureDelta = $currentExposureCount - $baselineExposureCount $riskBurndownPercent = if ($baselineExposureCount -gt 0) { [math]::Round((($baselineExposureCount - $currentExposureCount) / $baselineExposureCount) * 100, 1) } else { $null } $stages = @( NewCIEMPAMProgressStage ` -Name 'Discovery graph' ` -Status $(if ($graphNodeCount -gt 0) { 'Complete' } else { 'Pending' }) ` -Evidence "$graphNodeCount graph node(s)" NewCIEMPAMProgressStage ` -Name 'Exposure baseline' ` -Status $(if ($snapshotCount -gt 0) { 'Complete' } else { 'Pending' }) ` -Evidence "$snapshotCount exposure snapshot run(s)" NewCIEMPAMProgressStage ` -Name 'Exposure change tracking' ` -Status $(if ($snapshotCount -ge 2 -or $changeCount -gt 0) { 'Complete' } else { 'Pending' }) ` -Evidence "$changeCount exposure change(s)" NewCIEMPAMProgressStage ` -Name 'PAM candidate mapping' ` -Status $(if ($candidateItems.Count -gt 0) { 'Complete' } else { 'Pending' }) ` -Evidence "$($candidateItems.Count) PAM candidate(s)" NewCIEMPAMProgressStage ` -Name 'Outbound PAM actions' ` -Status 'NotScoped' ` -Evidence 'Read-only CIEM scope; no PAM records, approvals, or access changes are created.' ) $completeTrackableStages = @($stages | Where-Object { $_.Status -eq 'Complete' }).Count $readinessPercent = [math]::Round(($completeTrackableStages / 4) * 100, 0) $status = if ($graphNodeCount -eq 0) { 'DiscoveryRequired' } elseif ($candidateItems.Count -eq 0) { 'NoPAMCandidates' } elseif ($snapshotCount -ge 2 -or $changeCount -gt 0) { 'ProgressTracked' } else { 'BaselineReady' } [PSCustomObject]@{ Status = $status ReadinessPercent = [int]$readinessPercent BaselineDiscoveryRunId = $baselineRunId CurrentDiscoveryRunId = $currentRunId BaselineExposureCount = $baselineExposureCount CurrentExposureCount = $currentExposureCount CurrentCriticalHighCount = $currentCriticalHighCount ExposureDelta = $exposureDelta RiskBurndownPercent = $riskBurndownPercent ExposureChangeCount = $changeCount NewRiskCount = [int]$changeRows[0].new_risk_count RiskIncreaseCount = [int]$changeRows[0].risk_increase_count RemovedRiskCount = [int]$changeRows[0].removed_risk_count PAMCandidateCount = $candidateItems.Count JITCandidateCount = $jitCandidateCount BrokeredAccessCandidateCount = $brokeredAccessCandidateCount PAMActionStatus = 'NotScopedReadOnly' Stages = $stages Candidates = $candidateItems } } |