private/tenantinfo/Add-ZtOverviewM365ProtectionCircuit.ps1
|
<#
.SYNOPSIS Builds the Microsoft 365 protection circuit flow (spec 27022). .DESCRIPTION Aggregates the Global Secure Access acquisition check (25376) and the compliant network enforcement check (25379) into a Sankey flow. Each stage keeps its child roll-up verdict; the flow widths only quantify the gap and never replace that verdict. A child that was skipped, errored, timed out or was excluded from the run has no verdict, so its band is reported as unavailable instead of being folded into a confirmed failure. The acquisition axis is proportional and sized from 25376's device counts. Enforcement has no per-item population to subdivide, so it acts as a binary gate over the acquired band. #> function Add-ZtOverviewM365ProtectionCircuit { [CmdletBinding()] param() $tenantInfoName = 'OverviewM365ProtectionCircuit' $activity = 'Building Microsoft 365 protection circuit' Write-ZtProgress -Activity $activity -Status 'Processing' # Skipped, errored and never-run children have no verdict to roll up, so their stage is unavailable $stageStatuses = @{} foreach ($testId in '25376', '25379') { $stageStatuses[$testId] = switch (Get-ZtTestResultStatus -TestId $testId) { 'Passed' { 'Passed' } 'Failed' { 'Failed' } 'Investigate' { 'Investigate' } default { 'Unavailable' } } } $acquisitionStatus = $stageStatuses['25376'] $enforcementStatus = $stageStatuses['25379'] if ($acquisitionStatus -eq 'Unavailable' -and $enforcementStatus -eq 'Unavailable') { Write-PSFMessage '🟦 Skipping: No Microsoft 365 protection circuit results available' -Tag Test -Level VeryVerbose Add-ZtTenantInfo -Name $tenantInfoName -Value $null return } $allStageStatuses = @($acquisitionStatus, $enforcementStatus) $degraded = $allStageStatuses -contains 'Unavailable' $overallStatus = if ($allStageStatuses -contains 'Failed') { 'Failed' } # An unavailable stage cannot be asserted to pass, so the circuit stays short of Passed elseif ($allStageStatuses -contains 'Investigate' -or $degraded) { 'Investigate' } else { 'Passed' } $openStages = @() if ($acquisitionStatus -eq 'Failed') { $openStages += 'Acquisition (25376)' } if ($enforcementStatus -eq 'Failed') { $openStages += 'Enforcement (25379)' } $acquisition = Get-ZtTestData -Name 'M365TrafficAcquisition' $totalDeviceCount = [Math]::Max(0, ($acquisition.TotalDeviceCount -as [int]) ?? 0) $activeDeviceCount = [Math]::Max(0, ($acquisition.ActiveDeviceCount -as [int]) ?? 0) $profileEnabled = [bool]$acquisition.ProfileEnabled $countsAvailable = $totalDeviceCount -gt 0 -and $activeDeviceCount -le $totalDeviceCount # Without usable counts the flow falls back to a normalized all-or-nothing width $total = if ($countsAvailable) { $totalDeviceCount } else { 100 } $acquired = if ($acquisitionStatus -ne 'Passed') { 0 } elseif (-not $countsAvailable) { $total } elseif ($profileEnabled) { $activeDeviceCount } else { 0 } $nodes = @() if ($acquisitionStatus -eq 'Passed') { $nodes += @{ source = 'Total M365 traffic'; target = 'Unprotected - not acquired'; value = $total - $acquired } $nodes += @{ source = 'Total M365 traffic'; target = 'Acquired via Global Secure Access'; value = $acquired } switch ($enforcementStatus) { 'Passed' { $nodes += @{ source = 'Acquired via Global Secure Access'; target = 'Enforced - compliant network'; value = $acquired } } 'Failed' { $nodes += @{ source = 'Acquired via Global Secure Access'; target = 'Acquired but not enforced'; value = $acquired } } 'Investigate' { $nodes += @{ source = 'Acquired via Global Secure Access'; target = 'Acquired, enforcement needs review'; value = $acquired } } 'Unavailable' { $nodes += @{ source = 'Acquired via Global Secure Access'; target = 'Enforcement unavailable'; value = $acquired } } } } else { $acquisitionTarget = switch ($acquisitionStatus) { 'Failed' { 'Unprotected - not acquired' } 'Investigate' { 'Acquisition needs review' } default { 'Acquisition unavailable' } } $nodes += @{ source = 'Total M365 traffic'; target = $acquisitionTarget; value = $total } } $nodes = @($nodes | Where-Object value -gt 0) $description = if ($acquisitionStatus -eq 'Failed' -and $enforcementStatus -eq 'Failed') { 'Both acquisition and enforcement stages are open: Microsoft 365 traffic is neither acquired nor gated by compliant network enforcement.' } elseif ($acquisitionStatus -eq 'Failed') { 'The acquisition stage is open. Compliant network enforcement cannot function because Global Secure Access does not acquire the traffic or generate the required signal.' } elseif ($enforcementStatus -eq 'Failed' -and $acquisitionStatus -eq 'Passed') { 'The enforcement stage is open. Traffic is acquired, but sessions can originate from uncontrolled networks because access is not gated on the compliant network.' } elseif ($enforcementStatus -eq 'Failed') { 'The enforcement stage is open, and acquisition is unavailable. End-to-end protection cannot be confirmed.' } elseif ($overallStatus -eq 'Investigate') { $reviewStages = @() if ($acquisitionStatus -ne 'Passed') { $reviewStages += "Acquisition ($acquisitionStatus)" } if ($enforcementStatus -ne 'Passed') { $reviewStages += "Enforcement ($enforcementStatus)" } "The protection circuit needs review: $($reviewStages -join ', ')." } else { 'The protection circuit is closed: Microsoft 365 traffic is acquired and gated by compliant network enforcement.' } $description += if ($countsAvailable) { " Acquisition is sized from $total observed devices." } else { ' Device counts are unavailable, so the flow uses a normalized all-or-nothing width of 100.' } Add-ZtTenantInfo -Name $tenantInfoName -Value @{ description = $description nodes = $nodes gates = @( @{ testId = '25376'; name = 'Acquisition'; status = $acquisitionStatus } @{ testId = '25379'; name = 'Enforcement'; status = $enforcementStatus } ) overallStatus = $overallStatus openStages = $openStages degraded = $degraded totalDevices = $total countsAvailable = $countsAvailable acquisitionStatus = $acquisitionStatus enforcementStatus = $enforcementStatus } } |