tests/Test-Assessment.61024.ps1
|
<#
.SYNOPSIS Checks that the Microsoft Defender XDR Content Hub solution is installed on the Microsoft Sentinel workspace. .DESCRIPTION Verifies that the Microsoft Defender XDR solution (or its legacy name 'Microsoft 365 Defender') is installed from the Content Hub on at least one Microsoft Sentinel-onboarded Log Analytics workspace. .NOTES Test ID: 61024 Category: AI Threat Detection Pillar: AI Required API: Azure Resource Manager (management.azure.com) #> function Test-Assessment-61024 { [ZtTest( Category = 'AI Threat Detection', MinimumLicense = ('WIN_DEF_ATP', 'MDATP_XPLAT', 'ATA', 'ADALLOM_STANDALONE', 'THREAT_INTELLIGENCE', 'IDENTITY_THREAT_PROTECTION', 'IDENTITY_THREAT_PROTECTION_FOR_EMS_E5', 'SPE_E5', 'Consumption-based: Microsoft Sentinel'), CompatibleLicense = ('WIN_DEF_ATP', 'MDATP_XPLAT', 'ATA', 'ADALLOM_STANDALONE', 'THREAT_INTELLIGENCE', 'IDENTITY_THREAT_PROTECTION', 'IDENTITY_THREAT_PROTECTION_FOR_EMS_E5', 'SPE_E5'), ImplementationCost = 'Low', Pillar = 'AI', RiskLevel = 'High', Service = ('Azure'), SfiPillar = 'Monitor and detect cyberthreats', TenantType = ('Workforce'), TestId = 61024, Title = 'Microsoft Defender XDR (unified) data connector is enabled on the Microsoft Sentinel workspace', UserImpact = 'Low' )] [CmdletBinding()] param() #region Data Collection Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose $activity = 'Checking Microsoft Defender XDR Content Hub solution on Sentinel workspaces' # Q1 + Q2 + onboarding check. # Returns 'Forbidden' on ARG 401/403, $null on unexpected failure, # 'NoSubscriptions' / 'NoWorkspaces' when nothing is in scope. $allWorkspaces = Get-SentinelWorkspaceData -Activity $activity if ($null -eq $allWorkspaces) { $params = @{ TestId = '61024' Title = 'Microsoft Defender XDR (unified) data connector is enabled on the Microsoft Sentinel workspace' Status = $false Result = '⚠️ Azure Resource Graph returned an unexpected error while querying subscriptions or Log Analytics workspaces. This is likely a transient issue, please re-run the assessment.' CustomStatus = 'Investigate' } Add-ZtTestResultDetail @params return } if ($allWorkspaces -eq 'Forbidden') { $params = @{ TestId = '61024' Title = 'Microsoft Defender XDR (unified) data connector is enabled on the Microsoft Sentinel workspace' Status = $false Result = '⚠️ Azure Resource Graph returned insufficient permissions when enumerating subscriptions or workspaces. Ensure you have at least Reader access to the Azure subscriptions being tested.' CustomStatus = 'Investigate' } Add-ZtTestResultDetail @params return } # No enabled subscriptions accessible to the caller. if ($allWorkspaces -eq 'NoSubscriptions') { Write-PSFMessage 'No enabled subscriptions found — skipping Sentinel onboarding check.' -Tag Test -Level VeryVerbose Add-ZtTestResultDetail -SkippedBecause NotApplicable return } # No Log Analytics workspaces across accessible subscriptions. if ($allWorkspaces -eq 'NoWorkspaces') { Write-PSFMessage 'No Log Analytics workspaces found across accessible subscriptions — skipping Sentinel onboarding check.' -Tag Test -Level VeryVerbose Add-ZtTestResultDetail -SkippedBecause NotApplicable return } $checkableWorkspaces = @($allWorkspaces | Where-Object { -not $_.PermissionError }) $forbiddenWorkspaces = @($allWorkspaces | Where-Object { $_.PermissionError }) $onboardedWorkspaces = @($checkableWorkspaces | Where-Object { $_.SentinelOnboarded }) if ($onboardedWorkspaces.Count -eq 0) { if ($forbiddenWorkspaces.Count -gt 0) { # Auth errors on the Sentinel onboarding check mean we cannot confirm whether # those workspaces are onboarded — we cannot rule out a passing workspace exists. $params = @{ TestId = '61024' Title = 'Microsoft Defender XDR (unified) data connector is enabled on the Microsoft Sentinel workspace' Status = $false Result = '⚠️ One or more Log Analytics workspaces returned insufficient permissions when checking Sentinel onboarding state. No Sentinel-onboarded workspace was confirmed among accessible workspaces — the overall state cannot be determined. Ensure Microsoft Sentinel Reader is granted on all workspaces and re-run the assessment.' CustomStatus = 'Investigate' } } else { # Full visibility, no workspace has Sentinel onboarded. $params = @{ TestId = '61024' Title = 'Microsoft Defender XDR (unified) data connector is enabled on the Microsoft Sentinel workspace' Status = $false Result = '❌ No Sentinel-onboarded workspace in tenant.' } } Add-ZtTestResultDetail @params return } $workspaceResults = @() foreach ($workspace in $onboardedWorkspaces) { $packageName = 'Not found' $rowStatus = 'Fail' # Q3: List installed Content Hub solutions and look for Microsoft Defender XDR (or legacy Microsoft 365 Defender) Write-ZtProgress -Activity $activity -Status "Checking content packages on $($workspace.WorkspaceName) in subscription $($workspace.SubscriptionName)" $packagesPath = "$($workspace.WorkspaceId)/providers/Microsoft.SecurityInsights/contentPackages?api-version=2024-09-01" try { $packages = @(Invoke-ZtAzureRequest -Path $packagesPath -ErrorAction Stop) $xdrPackage = $packages | Where-Object { $_.properties.displayName -ieq 'Microsoft Defender XDR' -or $_.properties.displayName -ieq 'Microsoft 365 Defender' } | Select-Object -First 1 if ($xdrPackage) { $packageName = $xdrPackage.properties.displayName $rowStatus = 'Pass' } } catch { $packageName = 'Error' $rowStatus = 'Investigate' Write-PSFMessage "Error querying content packages for workspace '$($workspace.WorkspaceName)' in subscription '$($workspace.SubscriptionName)': $_" -Tag Test -Level Warning } $workspaceResults += [PSCustomObject]@{ SubscriptionName = $workspace.SubscriptionName SubscriptionId = $workspace.SubscriptionId WorkspaceName = $workspace.WorkspaceName ResourceGroup = $workspace.ResourceGroup WorkspaceId = $workspace.WorkspaceId PackageName = $packageName RowStatus = $rowStatus } } #endregion Data Collection #region Assessment Logic $passedItems = @($workspaceResults | Where-Object { $_.RowStatus -eq 'Pass' }) $investigateItems = @($workspaceResults | Where-Object { $_.RowStatus -eq 'Investigate' }) $passed = $passedItems.Count -gt 0 $customStatus = $null if (-not $passed -and $investigateItems.Count -gt 0) { $customStatus = 'Investigate' $testResultMarkdown = "⚠️ The Content Hub API returned an authorization (401/403) or transient (5xx) error on at least one workspace, so the Microsoft Defender XDR connector state could not be determined for those workspaces. Re-run after verifying Microsoft Sentinel Reader on each affected workspace's resource group.`n`n%TestResult%" } elseif ($passed) { $testResultMarkdown = "✅ The Microsoft Defender XDR data connector is enabled on at least one Sentinel-onboarded workspace.`n`n%TestResult%" } else { $testResultMarkdown = "❌ The Microsoft Defender XDR data connector is not enabled on any Sentinel-onboarded workspace.`n`n%TestResult%" } #endregion Assessment Logic #region Report Generation $portalSentinelLink = 'https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/microsoft.securityinsightsarg%2Fsentinel' $tableTitle = 'Sentinel data connectors per workspace' $formatTemplate = @' ## [{0}]({1}) | Subscription | Workspace | Content package | Status | | :----------- | :-------- | :-------------- | :----- | {2} '@ $tableRows = '' $maxDisplay = 10 $statusPriority = @{ Fail = 0; Investigate = 1; Pass = 2 } $displayResults = @($workspaceResults | Sort-Object { $statusPriority[$_.RowStatus] }, SubscriptionName, WorkspaceName) $hasMoreItems = $false if ($workspaceResults.Count -gt $maxDisplay) { $displayResults = @($displayResults | Select-Object -First $maxDisplay) $hasMoreItems = $true } foreach ($result in $displayResults) { $subLink = "https://portal.azure.com/#resource/subscriptions/$($result.SubscriptionId)" $sentinelId = "/subscriptions/$($result.SubscriptionId)/resourcegroups/$($result.ResourceGroup)/providers/microsoft.securityinsightsarg/sentinel/$($result.WorkspaceName)" $workspaceLink = "https://portal.azure.com/#view/Microsoft_Azure_Security_Insights/MainMenuBlade/~/DataConnectors/id/$($sentinelId -replace '/', '%2F')" $subMd = "[$(Get-SafeMarkdown $result.SubscriptionName)]($subLink)" $workspaceMd = "[$(Get-SafeMarkdown $result.WorkspaceName)]($workspaceLink)" $packageMd = $result.PackageName $statusDisplay = switch ($result.RowStatus) { 'Pass' { '✅ Pass' } 'Fail' { '❌ Fail' } 'Investigate' { '⚠️ Investigate' } } $tableRows += "| $subMd | $workspaceMd | $packageMd | $statusDisplay |`n" } if ($hasMoreItems) { $remainingCount = $workspaceResults.Count - $maxDisplay $tableRows += "`n... and $remainingCount more. [View all in Microsoft Sentinel]($portalSentinelLink)`n" } $mdInfo = $formatTemplate -f $tableTitle, $portalSentinelLink, $tableRows $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo #endregion Report Generation $params = @{ TestId = '61024' Title = 'Microsoft Defender XDR (unified) data connector is enabled on the Microsoft Sentinel workspace' Status = $passed Result = $testResultMarkdown } if ($customStatus) { $params.CustomStatus = $customStatus } Add-ZtTestResultDetail @params } |