|
| 1 | +<# |
| 2 | +.SYNOPSIS |
| 3 | + A web content filtering policy governs Copilot Studio agent traffic through the baseline profile |
| 4 | +
|
| 5 | +.DESCRIPTION |
| 6 | + Evaluates whether the Global Secure Access baseline profile is enabled and linked to an enabled, |
| 7 | + administrator-configured web content filtering policy. The baseline profile is the only supported |
| 8 | + enforcement path for Copilot Studio agent traffic, so without such a policy an agent's HTTP node |
| 9 | + action or connector can reach web destinations the organization intended to block. |
| 10 | +
|
| 11 | +.NOTES |
| 12 | + Test ID: 27028 |
| 13 | + Pillar: Network |
| 14 | + Risk Level: High |
| 15 | + SFI Pillar: Protect networks |
| 16 | + Required API: networkAccess/filteringProfiles (beta) |
| 17 | +#> |
| 18 | + |
| 19 | +function Test-Assessment-27028 { |
| 20 | + [ZtTest( |
| 21 | + Category = 'AI Gateway', |
| 22 | + ImplementationCost = 'Medium', |
| 23 | + Service = ('Graph'), |
| 24 | + CompatibleLicense = ('Entra_Premium_Internet_Access'), |
| 25 | + Pillar = 'Network', |
| 26 | + RiskLevel = 'High', |
| 27 | + SfiPillar = 'Protect networks', |
| 28 | + TenantType = ('Workforce'), |
| 29 | + TestId = 27028, |
| 30 | + Title = 'A web content filtering policy governs Copilot Studio agent traffic through the baseline profile', |
| 31 | + UserImpact = 'Medium' |
| 32 | + )] |
| 33 | + [CmdletBinding()] |
| 34 | + param() |
| 35 | + |
| 36 | + # The baseline profile is identified by its fixed priority and is the only profile supported for agent traffic. |
| 37 | + [int]$baselineProfilePriority = 65000 |
| 38 | + # Allow-all placeholder policy that ships with every tenant; it isn't an administrator-configured restriction. |
| 39 | + [string]$defaultPolicyName = 'All websites' |
| 40 | + |
| 41 | + #region Data Collection |
| 42 | + Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose |
| 43 | + |
| 44 | + $activity = 'Evaluating web content filtering on the Global Secure Access baseline profile' |
| 45 | + Write-ZtProgress -Activity $activity -Status 'Querying filtering profiles' |
| 46 | + |
| 47 | + # Q1: Get the baseline profile and its linked policies |
| 48 | + $filteringProfiles = @() |
| 49 | + $errorMsg = $null |
| 50 | + $httpStatusCode = $null |
| 51 | + |
| 52 | + try { |
| 53 | + $filteringProfiles = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/filteringProfiles' -QueryParameters @{ |
| 54 | + '$filter' = "priority eq $baselineProfilePriority" |
| 55 | + '$select' = 'id,name,state,priority' |
| 56 | + '$expand' = 'policies($select=id,state;$expand=policy($select=id,name,version))' |
| 57 | + } -ApiVersion beta -ErrorAction Stop |
| 58 | + } |
| 59 | + catch { |
| 60 | + $errorMsg = $_ |
| 61 | + $httpStatusCode = Get-ZtHttpStatusCode -ErrorRecord $_ |
| 62 | + Write-PSFMessage "Failed to retrieve filtering profiles (HTTP $httpStatusCode): $errorMsg" -Tag Test -Level Warning |
| 63 | + } |
| 64 | + #endregion Data Collection |
| 65 | + |
| 66 | + #region Assessment Logic |
| 67 | + $passed = $false |
| 68 | + $customStatus = $null |
| 69 | + $testResultMarkdown = '' |
| 70 | + $baselineState = 'Not found' |
| 71 | + $enabledPolicyNames = @() |
| 72 | + |
| 73 | + if ($httpStatusCode -eq 404) { |
| 74 | + # The filtering profile resource is unavailable, so the required enforcement path is absent: same outcome as an empty result. |
| 75 | + Write-PSFMessage 'Global Secure Access filtering profiles are not available in this tenant.' -Tag Test -Level Verbose |
| 76 | + } |
| 77 | + |
| 78 | + if ($errorMsg -and $httpStatusCode -ne 404) { |
| 79 | + $customStatus = 'Investigate' |
| 80 | + $testResultMarkdown = if ($httpStatusCode -in 401, 403) { |
| 81 | + '⚠️ Unable to read the Global Secure Access baseline profile due to insufficient permissions. Grant the **NetworkAccess.Read.All** Microsoft Graph permission and assign the **Global Secure Access Administrator** or **Security Reader** role, then rerun the assessment.' |
| 82 | + } |
| 83 | + else { |
| 84 | + '⚠️ Unable to retrieve the Global Secure Access filtering profiles due to an API error. Please rerun the assessment.' |
| 85 | + } |
| 86 | + } |
| 87 | + else { |
| 88 | + $baselineProfile = $filteringProfiles | Where-Object { $_.priority -eq $baselineProfilePriority } | Select-Object -First 1 |
| 89 | + |
| 90 | + if ($baselineProfile) { |
| 91 | + $baselineState = $baselineProfile.state |
| 92 | + $enabledPolicyNames = @($baselineProfile.policies | Where-Object { |
| 93 | + $_.'@odata.type' -eq '#microsoft.graph.networkaccess.filteringPolicyLink' -and |
| 94 | + $_.state -eq 'enabled' -and |
| 95 | + $_.policy.name -and |
| 96 | + $_.policy.name -ne $defaultPolicyName |
| 97 | + } | ForEach-Object { $_.policy.name }) |
| 98 | + } |
| 99 | + |
| 100 | + $passed = $baselineState -eq 'enabled' -and $enabledPolicyNames.Count -gt 0 |
| 101 | + |
| 102 | + if ($passed) { |
| 103 | + $testResultMarkdown = "✅ The Global Secure Access baseline profile is enabled and linked to an administrator-configured web content filtering policy that governs Copilot Studio agent traffic.`n`n%TestResult%" |
| 104 | + } |
| 105 | + else { |
| 106 | + $testResultMarkdown = "❌ The Global Secure Access baseline profile isn't enabled or lacks an enabled administrator-configured web content filtering policy, leaving Copilot Studio agent web traffic unrestricted by that policy.`n`n%TestResult%" |
| 107 | + } |
| 108 | + } |
| 109 | + #endregion Assessment Logic |
| 110 | + |
| 111 | + #region Report Generation |
| 112 | + $mdInfo = '' |
| 113 | + |
| 114 | + if (-not $customStatus) { |
| 115 | + $baselineStateDisplay = if ($baselineState -eq 'enabled') { '✅ Enabled' } else { "❌ $baselineState" } |
| 116 | + $policyNamesDisplay = if ($enabledPolicyNames.Count -gt 0) { |
| 117 | + ($enabledPolicyNames | Sort-Object -Unique | ForEach-Object { Get-SafeMarkdown $_ }) -join ', ' |
| 118 | + } |
| 119 | + else { |
| 120 | + 'None' |
| 121 | + } |
| 122 | + $statusDisplay = if ($passed) { '✅ Pass' } else { '❌ Fail' } |
| 123 | + |
| 124 | + $formatTemplate = @' |
| 125 | +## [Global Secure Access Security Profiles]({0}) |
| 126 | +
|
| 127 | +| Baseline profile state | Enabled web content filtering policy name(s) | Status | |
| 128 | +| :--------------------- | :------------------------------------------- | :----- | |
| 129 | +{1} |
| 130 | +'@ |
| 131 | + |
| 132 | + $portalLink = 'https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/FilteringPolicyProfiles.ReactView' |
| 133 | + $tableRows = "| $baselineStateDisplay | $policyNamesDisplay | $statusDisplay |`n" |
| 134 | + $mdInfo = $formatTemplate -f $portalLink, $tableRows |
| 135 | + } |
| 136 | + |
| 137 | + $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo |
| 138 | + #endregion Report Generation |
| 139 | + |
| 140 | + $params = @{ |
| 141 | + TestId = '27028' |
| 142 | + Title = 'A web content filtering policy governs Copilot Studio agent traffic through the baseline profile' |
| 143 | + Status = $passed |
| 144 | + Result = $testResultMarkdown |
| 145 | + } |
| 146 | + if ($customStatus) { |
| 147 | + $params.CustomStatus = $customStatus |
| 148 | + } |
| 149 | + Add-ZtTestResultDetail @params |
| 150 | +} |
0 commit comments