diff --git a/src/powershell/tests/Test-Assessment.25411.md b/src/powershell/tests/Test-Assessment.25411.md new file mode 100644 index 000000000..60efcfd51 --- /dev/null +++ b/src/powershell/tests/Test-Assessment.25411.md @@ -0,0 +1,9 @@ +TLS Inspection empowers Microsoft Entra Global Secure Access to securely interpret encrypted traffic, enabling advanced protection. While basic Web Content Filtering works without it, TLS Inspection is required for many Internet Access features like URL Filtering. By decrypting and inspecting TLS sessions, organizations gain deeper visibility and control, ensuring policies are applied effectively to safeguard users and data. + +**Remediation action** + +Please check the article below for guidance on configuring the TLS Inspection Policy. +- [Configure Transport Layer Security Inspection Policies - Global Secure Access | Microsoft Learn](https://learn.microsoft.com/en-us/entra/global-secure-access/how-to-transport-layer-security) + + +%TestResult% diff --git a/src/powershell/tests/Test-Assessment.25411.ps1 b/src/powershell/tests/Test-Assessment.25411.ps1 new file mode 100644 index 000000000..3250d4323 --- /dev/null +++ b/src/powershell/tests/Test-Assessment.25411.ps1 @@ -0,0 +1,250 @@ +<# +.SYNOPSIS + TLS inspection is enabled and correctly configured for outbound traffic in Global Secure Access. +.DESCRIPTION + Verifies that a TLS Inspection policy is properly configured. It will fail if no TLS Inspection policy exists, if the policy is not linked to a Security Profile, or if no Conditional Access policy assigning that Security Profile can be identified. +#> + +function Test-Assessment-25411 { + [ZtTest( + Category = 'Global Secure Access', + ImplementationCost = 'High', + MinimumLicense = ('Entra_Premium_Internet_Access'), + Pillar = 'Network', + RiskLevel = 'High', + SfiPillar = 'Protect networks', + TenantType = ('Workforce'), + TestId = 25411, + Title = 'TLS inspection is enabled and correctly configured for outbound traffic in Global Secure Access', + UserImpact = 'Medium' + )] + [CmdletBinding()] + param() + + # Define constants + [int]$BASELINE_PROFILE_PRIORITY = 65000 + + #region Data Collection + Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose + + $activity = 'TLS inspection is enabled and correctly configured for outbound traffic in Global Secure Access.' + Write-ZtProgress -Activity $activity -Status 'Querying TLS inspection policies' + + # Step 1: Get TLS Inspection policies + $tlsInspectionPolicies = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/tlsInspectionPolicies' -ApiVersion beta + + # Step 2: List all policies in the Baseline Profile and in each Security Profile + Write-ZtProgress -Activity $activity -Status 'Querying filtering profiles and policies' + $filteringProfiles = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/filteringProfiles' -QueryParameters @{ + '$select' = 'id,name,description,state,version,priority' + '$expand' = 'policies($select=id,state;$expand=policy($select=id,name,version)),conditionalAccessPolicies($select=id,displayName)' + } -ApiVersion beta + + # Query all Conditional Access policies with details + Write-ZtProgress -Activity $activity -Status 'Querying Conditional Access policies' + $allCAPolicies = Get-ZtConditionalAccessPolicy + + # Build CA profile lookup for O(1) access instead of O(N) search per profile + Write-ZtProgress -Activity $activity -Status 'Building Conditional Access policy lookup' + $caProfileLookup = @{} + foreach ($cap in $allCAPolicies) { + $session = $cap.sessionControls + if ($null -ne $session -and $null -ne $session.globalSecureAccessFilteringProfile) { + $sessionProfileId = $session.globalSecureAccessFilteringProfile.profileId + $sessionEnabled = $session.globalSecureAccessFilteringProfile.isEnabled + + if ($sessionEnabled -eq $true -and $cap.state -eq 'enabled') { + if (-not $caProfileLookup.ContainsKey($sessionProfileId)) { + $caProfileLookup[$sessionProfileId] = @() + } + $caProfileLookup[$sessionProfileId] += [PSCustomObject]@{ + Id = $cap.id + DisplayName = $cap.displayName + State = $cap.state + } + } + } + } + + #endregion Data Collection + + #region Data Processing + # Graph responses are automatically unwrapped by Invoke-ZtGraphRequest + $enabledSecurityProfiles = @() + $enabledBaseLineProfiles = @() + + # Iterate each TLS inspection policy and find linked profiles + foreach ($tlsPolicy in $tlsInspectionPolicies) { + $tlsId = $tlsPolicy.id + $baseLineProfileFound = $false + foreach ($profileItem in $filteringProfiles) { + $profilePolicies = @() + if ($null -ne $profileItem.policies) { + $profilePolicies = $profileItem.policies + } + + foreach ($plink in $profilePolicies) { + $plinkType = $plink.'@odata.type' + $linkedPolicyId = $null + # Only process tlsInspectionPolicyLink entries + if ($plinkType -eq '#microsoft.graph.networkaccess.tlsInspectionPolicyLink' -and $null -ne $plink.policy) { + $linkedPolicyId = $plink.policy.id + } + + if ($null -ne $linkedPolicyId -and $linkedPolicyId -eq $tlsId) { + $linkState = if ($null -ne $plink.state) { + $plink.state + } + else { + 'unknown' + } + $profileState = if ($null -ne $profileItem.state) { + $profileItem.state + } + else { + 'unknown' + } + $priority = if ($null -ne $profileItem.priority) { + [int]$profileItem.priority + } + else { + $null + } + + if ($priority -eq $BASELINE_PROFILE_PRIORITY) { + # Baseline Profile: apply without CA + + if ($linkState -eq 'enabled' -and $profileState -eq 'enabled') { + $baseLineProfileFound = $true + $enabledBaseLineProfiles += [PSCustomObject]@{ + ProfileId = $profileItem.id + ProfileName = $profileItem.name + ProfileState = $profileState + ProfilePriority = $priority + TLSPolicyId = $tlsId + TLSPolicyName = $plink.policy.name + TLSPolicyLinkState = $linkState + } + break + } + } elseif ($null -ne $priority -and $priority -lt $BASELINE_PROFILE_PRIORITY) { + # Security Profile: must be applied via Conditional Access + # Validate CA policies reference this profile via sessionControls + $matchedCAPolicies = @() + if ($caProfileLookup.ContainsKey($profileItem.id)) { + $matchedCAPolicies = $caProfileLookup[$profileItem.id] + } + + if ($matchedCAPolicies.Count -gt 0 -and $profileState -eq 'enabled' -and $linkState -eq 'enabled') { + $enabledSecurityProfiles += [PSCustomObject]@{ + ProfileId = $profileItem.id + ProfileName = $profileItem.name + ProfileState = $profileState + ProfilePriority = $priority + TLSPolicyId = $tlsId + TLSPolicyName = $plink.policy.name + TLSPolicyLinkState = $linkState + MatchedCAPolicies = $matchedCAPolicies + CAPolicyCount = $matchedCAPolicies.Count + DefaultAction = if ($null -ne $tlsPolicy.settings) { + $tlsPolicy.settings.defaultAction + } + else { + 'unknown' + } + } + } + } + } + } + if ($baseLineProfileFound) { + break + } + } + } + + #endregion Data Processing + #region Assessment logic + + $testResultMarkdown = '' + $passed = $false + $mdInfo = '' + + if ($null -eq $tlsInspectionPolicies -or $tlsInspectionPolicies.Count -eq 0) { + $testResultMarkdown = "❌ TLS Inspection Policy has not been properly configured. `n`n%TestResult%" + $passed = $false + } + elseif ($enabledBaseLineProfiles.Count -gt 0) { + $testResultMarkdown = "✅ TLS Inspection Policy is enabled and properly configured to inspect encrypted outbound traffic.`n`n%TestResult%" + $passed = $true + } + elseif ($enabledSecurityProfiles.Count -gt 0) { + $testResultMarkdown = "✅ TLS Inspection Policy is enabled and properly configured to inspect encrypted outbound traffic.`n`n%TestResult%" + $passed = $true + } + else { + $testResultMarkdown = "❌ TLS Inspection Policy has not been properly configured.`n`n%TestResult%" + $passed = $false + } + + #endregion Assessment logic + + #region Report Generation + + if ($enabledBaseLineProfiles.Count -gt 0) { + + $mdInfo += "`n## TLS Inspection Policies Linked to Baseline Profiles`n`n" + $mdInfo += "| Linked Profile Name | Linked Profile Priority | Linked Policy Name | Policy Link State | Profile State |`n" + $mdInfo += "| :--- | :--- | :--- | :--- | :--- |`n" + foreach ($policy in $enabledBaseLineProfiles) { + $baseLineProfilePortalLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/EditProfileMenuBlade.MenuView/~/basics/profileId/$(($policy.ProfileId))" + $tlsPolicyPortalLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/EditTlsInspectionPolicyMenuBlade.MenuView/~/basics/policyId/$(($policy.TLSPolicyId))" + $profileName = Get-SafeMarkdown -Text $policy.ProfileName + $profilePriority = $policy.ProfilePriority + $tlsPolicyName = Get-SafeMarkdown -Text $policy.TLSPolicyName + $tlsPolicyLinkState = $policy.TLSPolicyLinkState + $profileState = $policy.ProfileState + $mdInfo += "| [$profileName]($baseLineProfilePortalLink) | $profilePriority | [$tlsPolicyName]($tlsPolicyPortalLink) | $tlsPolicyLinkState | $profileState |`n" + } + } + + if ($enabledSecurityProfiles.Count -gt 0) { + $mdInfo += "`n## Security Profiles Linked to Conditional Access Policies`n`n" + $mdInfo += "| Linked Profile Name | Linked Profile Priority | CA Policy Names | CA Policy State | Profile State | TLS Inspection Policy Name | Default Action |`n" + $mdInfo += "| :--- | :--- | :--- | :--- | :--- | :--- | :--- |`n" + foreach ($enabledProfile in $enabledSecurityProfiles) { + $securityProfilePortalLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/EditProfileMenuBlade.MenuView/~/basics/profileId/$(($enabledProfile.ProfileId))" + $tlsPolicyPortalLink = "https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/EditTlsInspectionPolicyMenuBlade.MenuView/~/basics/policyId/$(($enabledProfile.TLSPolicyId))" + $profileName = Get-SafeMarkdown -Text $enabledProfile.ProfileName + $profilePriority = $enabledProfile.ProfilePriority + # Build CA policy links + $caPolicyLinksMarkdown = @() + $caPolicyStatesList = @() + foreach ($caPolicy in $enabledProfile.MatchedCAPolicies) { + $caPolicyPortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/$($caPolicy.Id)" + $safeName = Get-SafeMarkdown -Text $caPolicy.DisplayName + $caPolicyLinksMarkdown += "[$safeName]($caPolicyPortalLink)" + $caPolicyStatesList += $caPolicy.State + } + $caPolicyNamesLinked = $caPolicyLinksMarkdown -join ', ' + $caPolicyStates = $caPolicyStatesList -join ', ' + $profileState = $enabledProfile.ProfileState + $tlsPolicyName = Get-SafeMarkdown -Text $enabledProfile.TLSPolicyName + $defaultAction = $enabledProfile.DefaultAction + $mdInfo += "| [$profileName]($securityProfilePortalLink) | $profilePriority | $caPolicyNamesLinked | $caPolicyStates | $profileState | [$tlsPolicyName]($tlsPolicyPortalLink) | $defaultAction |`n" + } + } + + $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo + #endregion Report Generation + + + $params = @{ + TestId = '25411' + Status = $passed + Result = $testResultMarkdown + } + + # Add test result details + Add-ZtTestResultDetail @params +}