tests/Test-Assessment.41080.ps1
|
<#
.SYNOPSIS Checks that Conditional Access App Control session policies are enabled for cloud apps. .NOTES Test ID: 41080 Workshop Task: SECOPS-080 Pillar: SecOps Category: Identity threat protection Required permission: Policy.Read.All #> function Test-Assessment-41080 { [ZtTest( Category = 'Identity threat protection', CompatibleLicense = ('ADALLOM_S_STANDALONE&AAD_PREMIUM'), ImplementationCost = 'Medium', Pillar = 'SecOps', RiskLevel = 'High', Service = ('Graph'), SfiPillar = 'Protect identities and secrets', TenantType = ('Workforce'), TestId = 41080, Title = 'Conditional Access App Control session policies are enforced for sensitive cloud apps', UserImpact = 'Medium' )] [CmdletBinding()] param() #region Data Collection Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose $activity = 'Checking Conditional Access App Control session policies' Write-ZtProgress -Activity $activity -Status 'Querying enabled Conditional Access policies' try { $enabledPolicies = Invoke-ZtGraphRequest -RelativeUri 'identity/conditionalAccess/policies' -ApiVersion beta -Filter "state eq 'enabled'" -Select 'id,displayName,state,conditions,sessionControls' -ErrorAction Stop } catch { $httpStatus = Get-ZtHttpStatusCode -ErrorRecord $_ Write-PSFMessage "Failed to retrieve Conditional Access policies (HTTP $httpStatus): $_" -Tag Test -Level Warning $resultMessage = if ($httpStatus -in @(401, 403)) { '⚠️ The Conditional Access policy collection could not be retrieved because the assessment account lacks Policy.Read.All permission. Grant the permission and re-run the assessment.' } else { '⚠️ The Conditional Access policy collection could not be retrieved because Microsoft Graph returned a transient or unexpected error. Verify connectivity and re-run the assessment.' } $params = @{ TestId = '41080' Title = 'Conditional Access App Control session policies are enforced for sensitive cloud apps' Status = $false Result = $resultMessage CustomStatus = 'Investigate' } Add-ZtTestResultDetail @params return } #endregion Data Collection #region Assessment Logic $enabledPolicies = @($enabledPolicies) $policyResults = foreach ($policy in $enabledPolicies) { $targetApps = @($policy.conditions.applications.includeApplications | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) $sessionControl = $policy.sessionControls.cloudAppSecurity $isEnabled = $null -ne $sessionControl -and $sessionControl.isEnabled -eq $true $isMatchingPolicy = $isEnabled -and $targetApps.Count -gt 0 $knownTargets = @($targetApps | Where-Object { $_ -in @('All', 'Office365', 'MicrosoftAdminPortals') } | ForEach-Object { switch ($_) { 'All' { 'All cloud apps' } 'Office365' { 'Office 365' } 'MicrosoftAdminPortals' { 'Microsoft admin portals' } } }) $selectedTargetCount = @($targetApps | Where-Object { $_ -notin @('All', 'Office365', 'MicrosoftAdminPortals') }).Count if ($selectedTargetCount -gt 0) { $selectedTargetLabel = if ($selectedTargetCount -eq 1) { '1 selected app' } else { "$selectedTargetCount selected apps" } $knownTargets += $selectedTargetLabel } $cloudAppSecurityType = if ($sessionControl.cloudAppSecurityType) { $sessionControl.cloudAppSecurityType } else { 'Not configured' } $rowStatus = if (-not $isMatchingPolicy) { 'Fail' } elseif ($cloudAppSecurityType -eq 'unknownFutureValue') { 'Investigate' } else { 'Pass' } [PSCustomObject]@{ PolicyDisplayName = $policy.displayName PolicyId = $policy.id State = $policy.state TargetApps = if ($knownTargets.Count -gt 0) { $knownTargets -join ', ' } else { 'None' } CloudAppSecurityType = $cloudAppSecurityType IsEnabled = $isEnabled Matches = $isMatchingPolicy RowStatus = $rowStatus } } $policyResults = @($policyResults) $matchingPolicies = @($policyResults | Where-Object Matches) $knownMatchingPolicies = @($matchingPolicies | Where-Object CloudAppSecurityType -ne 'unknownFutureValue') $passed = $false $customStatus = $null if ($knownMatchingPolicies.Count -gt 0) { $passed = $true $testResultMarkdown = "✅ At least one enabled Conditional Access policy enforces Microsoft Defender for Cloud Apps session control via Conditional Access App Control.`n`n%TestResult%" } elseif ($matchingPolicies.Count -gt 0) { $customStatus = 'Investigate' $testResultMarkdown = "⚠️ Conditional Access App Control is enabled, but every matching policy returned an unknown Cloud App Security type. Review the policies in Microsoft Entra.`n`n%TestResult%" } else { $testResultMarkdown = "❌ No enabled Conditional Access policy has Defender for Cloud Apps session control enabled for a cloud app.`n`n%TestResult%" } #endregion Assessment Logic #region Report Generation $portalUrl = 'https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade' $policyUrlTemplate = 'https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/{0}' $maxDisplay = 10 $displayPolicies = @($matchingPolicies | Sort-Object -Property PolicyDisplayName | Select-Object -First $maxDisplay) $tableRows = '' foreach ($policy in $displayPolicies) { $policyName = "[$(Get-SafeMarkdown -Text $policy.PolicyDisplayName)]($($policyUrlTemplate -f $policy.PolicyId))" $targetApps = Get-SafeMarkdown -Text $policy.TargetApps $securityType = Get-SafeMarkdown -Text $policy.CloudAppSecurityType $isEnabled = if ($policy.IsEnabled) { 'True' } else { 'False' } $status = switch ($policy.RowStatus) { 'Pass' { '✅ Pass' } 'Fail' { '❌ Fail' } 'Investigate' { '⚠️ Investigate' } } $tableRows += "| $policyName | $($policy.State) | $targetApps | $securityType | $isEnabled | $status |`n" } if ($displayPolicies.Count -eq 0) { $tableRows = "| No matching policies found | — | — | — | — | ❌ Fail |`n" } elseif ($matchingPolicies.Count -gt $maxDisplay) { $remaining = $matchingPolicies.Count - $maxDisplay $tableRows += "`n... and $remaining more. [Microsoft Entra > Conditional Access > Policies]($portalUrl)`n" } $formatTemplate = @' ## [Microsoft Entra > Conditional Access > Policies]({0}) | Policy display name | State | Target apps | Cloud App Security type | Is enabled | Status | | :------------------ | :---- | :---------- | :---------------------- | :--------- | :----- | {1} '@ $mdInfo = $formatTemplate -f $portalUrl, $tableRows $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo #endregion Report Generation $params = @{ TestId = '41080' Title = 'Conditional Access App Control session policies are enforced for sensitive cloud apps' Status = $passed Result = $testResultMarkdown } if ($customStatus) { $params.CustomStatus = $customStatus } Add-ZtTestResultDetail @params } |