tests/Test-Assessment.25382.ps1
|
<#
.SYNOPSIS Validates that traffic forwarding profiles are scoped to appropriate users and groups for controlled deployment. .DESCRIPTION This test checks if enabled traffic forwarding profiles for Microsoft 365, Private Access, and Internet Access have proper user/group assignments to ensure controlled rollout and prevent security gaps. .NOTES Test ID: 25382 Category: Global Secure Access Required API: networkAccess/forwardingProfiles (beta), servicePrincipals (v1.0) #> function Test-Assessment-25382 { [ZtTest( Category = 'Global Secure Access', ImplementationCost = 'Low', MinimumLicense = ('Entra_Premium_Private_Access', 'Entra_Premium_Internet_Access'), Pillar = 'Network', RiskLevel = 'High', SfiPillar = 'Protect networks', TenantType = ('Workforce'), TestId = 25382, Title = 'Traffic forwarding profiles are scoped to appropriate users and groups for controlled deployment', UserImpact = 'Low' )] [CmdletBinding()] param() #region Data Collection Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose $activity = 'Checking traffic forwarding profiles configuration' Write-ZtProgress -Activity $activity -Status 'Getting traffic forwarding profiles' # Query Q1: Get all traffic forwarding profiles with associations and service principal $forwardingProfiles = Invoke-ZtGraphRequest -RelativeUri 'networkAccess/forwardingProfiles' -Select "id,name,state,trafficForwardingType,associations,servicePrincipal" -ApiVersion beta # Initialize test variables $testResultMarkdown = '' $passed = $false $profileResults = @() $hasDisabledProfiles = $false $hasEnabledProfileWithoutAssignments = $false #endregion Data Collection #region Assessment Logic # Check each profile's assignments if($forwardingProfiles -and $forwardingProfiles.Count -gt 0){ foreach ($forwardingProfile in $forwardingProfiles) { $profileInfo = @{ Name = $forwardingProfile.name Type = $forwardingProfile.trafficForwardingType State = $forwardingProfile.state RemoteNetworkCount = ($forwardingProfile.associations ?? @()).Count ServicePrincipalId = $null AppId = $null AssignmentType = 'Not checked' TotalAssignments = 0 UserCount = 0 GroupCount = 0 } # Handle disabled profiles if ($forwardingProfile.state -ne 'enabled') { $hasDisabledProfiles = $true $profileInfo.AssignmentType = 'N/A' $profileResults += $profileInfo continue } # Handle enabled profiles try { $sp = $forwardingProfile.servicePrincipal if (-not $sp) { $profileInfo.AssignmentType = 'Error' $hasEnabledProfileWithoutAssignments = $true $profileResults += $profileInfo continue } $profileInfo.ServicePrincipalId = $sp.id $profileInfo.AppId = $sp.appId Write-ZtProgress -Activity $activity -Status "Checking assignments for $($forwardingProfile.name)" # Query Q2: Get service principal details with app role assignments $servicePrincipal = Invoke-ZtGraphRequest -RelativeUri "servicePrincipals/$($sp.id)" -Select "appRoleAssignmentRequired" -ApiVersion v1.0 -QueryParameters @{ '$expand' = "appRoleAssignedTo(`$select=principalType)" } if (-not $servicePrincipal.appRoleAssignmentRequired) { # All users assigned - this is valid $profileInfo.AssignmentType = 'All Users' $profileInfo.TotalAssignments = 'All Users' } elseif ($servicePrincipal.appRoleAssignedTo -and $servicePrincipal.appRoleAssignedTo.Count -gt 0) { # Specific assignments exist - count users and groups in single pass $grouped = $servicePrincipal.appRoleAssignedTo | Group-Object -Property principalType -AsHashTable $profileInfo.AssignmentType = 'Specific' $profileInfo.UserCount = ($grouped['User'] ?? @()).Count $profileInfo.GroupCount = ($grouped['Group'] ?? @()).Count $profileInfo.TotalAssignments = $servicePrincipal.appRoleAssignedTo.Count } else { # Assignment required but no assignments found - FAIL $profileInfo.AssignmentType = 'None' $hasEnabledProfileWithoutAssignments = $true } } catch { Write-PSFMessage "Failed to check assignments for profile $($forwardingProfile.name): $_" -Level Warning $profileInfo.AssignmentType = 'Error' $hasEnabledProfileWithoutAssignments = $true } $profileResults += $profileInfo } } else { # No profiles found or empty - FAIL $hasEnabledProfileWithoutAssignments = $true } # Determine pass/fail/investigate if ($hasEnabledProfileWithoutAssignments) { $passed = $false $testResultMarkdown = "Traffic forwarding profile scoping could not be validated. One or more enabled profiles have no user/group assignments.`n`n%TestResult%" } elseif ($hasDisabledProfiles) { $passed = $false $testResultMarkdown = "Some of the Traffic forwarding profiles are disabled, please review your configuration, if this is intentional.`n`n%TestResult%" } else { $passed = $true $testResultMarkdown = "Traffic forwarding profiles are scoped appropriately.`n`n%TestResult%" } #endregion Assessment Logic #region Report Generation $mdInfo = '' if ($profileResults.Count -gt 0) { $portalLink = 'https://entra.microsoft.com/#view/Microsoft_Azure_Network_Access/ForwardingProfile.ReactView' $enabledCount = ($profileResults | Where-Object { $_.State -eq 'enabled' }).Count $totalCount = $profileResults.Count $mdInfo += "`n## Traffic forwarding profiles summary`n`n" $mdInfo += "- **Total Profiles:** $totalCount`n" $mdInfo += "- **Enabled Profiles:** $enabledCount`n" $mdInfo += "- **Disabled Profiles:** $($totalCount - $enabledCount)`n`n" $mdInfo += "## [Traffic forwarding profiles]($portalLink)`n`n" $mdInfo += "| Profile name | Type | State | Remote networks count | Users | Groups | Assignment scope |`n" $mdInfo += "| :----------- | :--- | :---- | :-------------- | :---- | :----- | :--------------- |`n" foreach ($profile in $profileResults) { $isEnabled = $profile.State -eq 'enabled' $stateDisplay = if ($isEnabled) { '✅ Enabled' } else { '❌ Disabled' } $typeLabel = switch ($profile.Type) { 'm365' { 'Microsoft 365' } 'internet' { 'Internet' } 'private' { 'Private Access' } default { $profile.Type } } $usersDisplay = if ($isEnabled -and $profile.AssignmentType -eq 'All Users') { 'All' } elseif ($isEnabled) { $profile.UserCount } else { 'N/A' } $groupsDisplay = if ($isEnabled -and $profile.AssignmentType -eq 'All Users') { 'All' } elseif ($isEnabled) { $profile.GroupCount } else { 'N/A' } $assignmentScopeDisplay = if (-not $isEnabled) { 'N/A' } elseif ($profile.AssignmentType -eq 'All Users') { '✅ All Users' } elseif ($profile.AssignmentType -eq 'Specific') { "Specific ($($profile.TotalAssignments))" } elseif ($profile.AssignmentType -eq 'None') { '❌ None' } else { $profile.AssignmentType } # Create hyperlink for assignment scope if service principal exists if ($profile.ServicePrincipalId -and $profile.AppId -and $isEnabled) { $assignmentLink = 'https://entra.microsoft.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/Users/objectId/{0}/appId/{1}/preferredSingleSignOnMode~/null/servicePrincipalType/Application/fromNav/' -f $profile.ServicePrincipalId, $profile.AppId $assignmentScopeDisplayLink = "[$(Get-SafeMarkdown($assignmentScopeDisplay))]($assignmentLink)" } else { $assignmentScopeDisplayLink = $assignmentScopeDisplay } $mdInfo += "| $($profile.Name) | $typeLabel | $stateDisplay | $($profile.RemoteNetworkCount) | $usersDisplay | $groupsDisplay | $assignmentScopeDisplayLink |`n" } } # Replace the placeholder with detailed information $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo #endregion Report Generation $params = @{ TestId = '25382' Title = 'Traffic forwarding profiles are scoped to appropriate users and groups for controlled deployment' Status = $passed Result = $testResultMarkdown } if ($hasDisabledProfiles -and -not $hasEnabledProfileWithoutAssignments) { $params.CustomStatus = 'Investigate' } Add-ZtTestResultDetail @params } |