Skip to content

Commit bef80da

Browse files
authored
Merge pull request #1263 from microsoft/AI-61002
AI - 61002 - Microsoft Sentinel is onboarded on at least one Log Analytics workspace
2 parents ab0d562 + fe92869 commit bef80da

3 files changed

Lines changed: 253 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Enumerates all Log Analytics workspaces across accessible subscriptions using a single
2+
# Azure Resource Graph query (spec Q1+Q2), then checks the Sentinel onboarding state for
3+
# each workspace (spec Q3) via the Microsoft.SecurityInsights/onboardingStates/default endpoint.
4+
#
5+
# Returns an array of [PSCustomObject] (SubscriptionName, WorkspaceName, ResourceGroup,
6+
# WorkspaceId, SentinelOnboarded).
7+
# Returns $null when the ARG query fails so the caller can issue a skip.
8+
function Get-SentinelWorkspaceData {
9+
[CmdletBinding()]
10+
param(
11+
[string]$Activity = 'Fetching Sentinel workspace data'
12+
)
13+
14+
# Q1 + Q2: Single ARG query joins subscription names onto workspace records so one
15+
# round-trip covers both steps. ARG respects caller RBAC automatically.
16+
$argQuery = @"
17+
resources
18+
| where type =~ 'microsoft.operationalinsights/workspaces'
19+
| join kind=leftouter (
20+
resourcecontainers
21+
| where type =~ 'microsoft.resources/subscriptions'
22+
| where tostring(properties.state) =~ 'Enabled'
23+
| project subscriptionName=name, subscriptionId
24+
) on subscriptionId
25+
| project
26+
workspaceName=name,
27+
workspaceId=id,
28+
resourceGroup,
29+
subscriptionId,
30+
subscriptionName
31+
| order by subscriptionName asc, workspaceName asc
32+
"@
33+
34+
Write-ZtProgress -Activity $Activity -Status 'Enumerating Log Analytics workspaces via Resource Graph'
35+
$allWorkspaces = @()
36+
try {
37+
$allWorkspaces = @(Invoke-ZtAzureResourceGraphRequest -Query $argQuery)
38+
Write-PSFMessage "ARG query returned $($allWorkspaces.Count) Log Analytics workspace(s)." -Tag Test -Level VeryVerbose
39+
}
40+
catch {
41+
$httpStatusCode = $null
42+
if ($_.Exception.Message -match 'with status (\d+):') {
43+
$httpStatusCode = [int]$Matches[1]
44+
}
45+
elseif ($_.Exception.Response) {
46+
$httpStatusCode = [int]$_.Exception.Response.StatusCode
47+
}
48+
49+
if ($httpStatusCode -in @(401, 403)) {
50+
Write-PSFMessage "Azure Resource Graph query returned $httpStatusCode — insufficient permissions to enumerate Log Analytics workspaces." -Tag Test -Level Warning
51+
return 'Forbidden'
52+
}
53+
Write-PSFMessage "Azure Resource Graph query failed: $($_.Exception.Message)" -Tag Test -Level Warning
54+
return $null
55+
}
56+
57+
# Q3: For each workspace query the Sentinel onboarding state.
58+
# HTTP 200 = onboarded; HTTP 404 = not onboarded; HTTP 401/403 = permission error.
59+
# -FullResponse prevents non-2xx responses from throwing so the status code can be
60+
# inspected explicitly. Anything other than 200/404 is treated as an error.
61+
$results = @()
62+
foreach ($workspace in $allWorkspaces) {
63+
Write-ZtProgress -Activity $Activity -Status "Checking Sentinel onboarding on workspace '$($workspace.workspaceName)'"
64+
65+
$sentinelOnboarded = $false
66+
try {
67+
$sentinelPath = "$($workspace.workspaceId)/providers/Microsoft.SecurityInsights/onboardingStates/default?api-version=2024-03-01"
68+
$response = Invoke-ZtAzureRequest -Path $sentinelPath -FullResponse -ErrorAction Stop
69+
70+
switch ([int]$response.StatusCode) {
71+
200 { $sentinelOnboarded = $true }
72+
404 { $sentinelOnboarded = $false }
73+
{ $_ -in @(401, 403) } {
74+
Write-PSFMessage "Sentinel onboarding check for workspace '$($workspace.workspaceName)' returned $($response.StatusCode) — insufficient permissions." -Tag Test -Level Warning
75+
return 'Forbidden'
76+
}
77+
default {
78+
Write-PSFMessage "Sentinel onboarding check for workspace '$($workspace.workspaceName)' returned unexpected status $($response.StatusCode)." -Tag Test -Level Warning
79+
}
80+
}
81+
}
82+
catch {
83+
Write-PSFMessage "Error checking Sentinel onboarding for workspace '$($workspace.workspaceName)': $_" -Tag Test -Level Warning
84+
}
85+
86+
$results += [PSCustomObject]@{
87+
SubscriptionName = $workspace.subscriptionName
88+
WorkspaceName = $workspace.workspaceName
89+
ResourceGroup = $workspace.resourceGroup
90+
WorkspaceId = $workspace.workspaceId
91+
SentinelOnboarded = $sentinelOnboarded
92+
}
93+
}
94+
95+
# Use the unary comma operator so an empty array is preserved as an array
96+
# (not collapsed to $null by the pipeline) and the caller can distinguish
97+
# "no workspaces found" from an ARG failure ($null return above).
98+
return , $results
99+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Microsoft Sentinel is a cloud-native SIEM that correlates security signals from across your environment into incidents your SOC can act on. AI workloads generate events across identity, cloud posture, and threat protection simultaneously — a central workspace is the only place those signals can be assembled into a coherent incident. This check verifies Sentinel is onboarded to at least one Log Analytics workspace, which every other AI threat detection control in this pillar depends on.
2+
3+
When Microsoft Sentinel is not onboarded to a Log Analytics workspace, security signals from AI workloads land in isolated product portals with no central point of correlation. Threat actors who compromise an agent identity can exploit this fragmentation because each product sees only its own slice of the attack — an anomalous Entra sign-in, a bulk Graph API call, and a Defender for AI Services alert each get triaged in isolation with no shared context. Without Sentinel, the organization cannot assemble the cross-product pattern that would reveal the full attack chain and trigger an automated response.
4+
5+
**Remediation action**
6+
7+
- [What is Microsoft Sentinel?](https://learn.microsoft.com/azure/sentinel/overview)
8+
- [Quickstart: Onboard Microsoft Sentinel](https://learn.microsoft.com/azure/sentinel/quickstart-onboard)
9+
- [Design your Microsoft Sentinel workspace architecture](https://learn.microsoft.com/azure/sentinel/design-your-workspace-architecture)
10+
- [Sentinel onboarding states — Create (REST API)](https://learn.microsoft.com/rest/api/securityinsights/sentinel-onboarding-states/create)
11+
12+
<!--- Results --->
13+
%TestResult%
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<#
2+
.SYNOPSIS
3+
Checks whether Microsoft Sentinel is onboarded on at least one Log Analytics workspace.
4+
5+
.DESCRIPTION
6+
This test enumerates all Log Analytics workspaces across in-scope Azure subscriptions and
7+
verifies that at least one has Microsoft Sentinel onboarded. Sentinel is required as a central
8+
SIEM before any other AI threat detection control in this pillar can correlate signals across
9+
the environment.
10+
11+
Evaluation steps:
12+
1. Use Azure Resource Graph to enumerate all Log Analytics workspaces (combining subscription
13+
listing and workspace listing in one query).
14+
2. For each workspace, query the Sentinel onboarding state resource via the
15+
Microsoft.SecurityInsights/onboardingStates/default ARM endpoint.
16+
3. Pass if at least one workspace returns HTTP 200 (Sentinel is onboarded).
17+
4. Fail if workspaces exist but every one returns HTTP 404 (Sentinel not onboarded).
18+
5. Skip if no Log Analytics workspaces are found across accessible subscriptions.
19+
20+
.NOTES
21+
Test ID: 61002
22+
Workshop Task: AI_089
23+
Pillar: AI
24+
Category: AI Threat Detection
25+
Required permissions:
26+
- Reader on each subscription (for Log Analytics workspace enumeration)
27+
- Microsoft Sentinel Reader on each workspace (for onboarding state query)
28+
#>
29+
30+
function Test-Assessment-61002 {
31+
32+
[ZtTest(
33+
Category = 'AI Threat Detection',
34+
ImplementationCost = 'Medium',
35+
Service = ('Azure'),
36+
MinimumLicense = ('Microsoft_Sentinel'),
37+
Pillar = 'AI',
38+
RiskLevel = 'High',
39+
SfiPillar = 'Monitor and detect cyberthreats',
40+
TenantType = ('Workforce'),
41+
TestId = 61002,
42+
Title = 'Microsoft Sentinel is onboarded on at least one Log Analytics workspace',
43+
UserImpact = 'Low'
44+
)]
45+
[CmdletBinding()]
46+
param()
47+
48+
#region Data Collection
49+
Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose
50+
$activity = 'Evaluating Microsoft Sentinel onboarding state across Log Analytics workspaces'
51+
52+
# Delegate all data fetching (Q1+Q2+Q3) to the private helper.
53+
# 'Forbidden' signals a 403 on the ARG workspace query (spec: Investigate).
54+
# $null signals any other ARG failure.
55+
# An empty array signals no workspaces found (spec: Skip).
56+
$workspaceResults = Get-SentinelWorkspaceData -Activity $activity
57+
58+
# Per spec: Q2 returns 403 → Investigate.
59+
if ($workspaceResults -eq 'Forbidden') {
60+
$params = @{
61+
TestId = '61002'
62+
Title = 'Microsoft Sentinel is onboarded on at least one Log Analytics workspace'
63+
Status = $false
64+
Result = '⚠️ Some of the queried resources returned status indicating insufficient permissions. Please make sure you have at least reader access to the Azure subscriptions being tested.'
65+
CustomStatus = 'Investigate'
66+
}
67+
Add-ZtTestResultDetail @params
68+
return
69+
}
70+
71+
# $null signals a non-403 ARG failure.
72+
if ($null -eq $workspaceResults) {
73+
Add-ZtTestResultDetail -SkippedBecause NotSupported
74+
return
75+
}
76+
77+
# Per spec: zero workspaces → Skipped, not Failed.
78+
if ($workspaceResults.Count -eq 0) {
79+
Write-PSFMessage 'No Log Analytics workspaces found across accessible subscriptions.' -Tag Test -Level VeryVerbose
80+
Add-ZtTestResultDetail -SkippedBecause NotApplicable
81+
return
82+
}
83+
#endregion Data Collection
84+
85+
#region Assessment Logic
86+
$onboardedWorkspaces = @($workspaceResults | Where-Object { $_.SentinelOnboarded })
87+
$passed = $onboardedWorkspaces.Count -ge 1
88+
89+
if ($passed) {
90+
$testResultMarkdown = "✅ Microsoft Sentinel is onboarded on at least one Log Analytics workspace.`n`n%TestResult%"
91+
}
92+
else {
93+
$testResultMarkdown = "❌ No Log Analytics workspace in scope has Microsoft Sentinel onboarded.`n`n%TestResult%"
94+
}
95+
#endregion Assessment Logic
96+
97+
#region Report Generation
98+
$workspacesPortalUrl = 'https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.OperationalInsights%2Fworkspaces'
99+
$workspacePortalTemplate = 'https://portal.azure.com/#resource{0}/overview'
100+
101+
$formatTemplate = @'
102+
103+
104+
### [{0}]({1})
105+
106+
| Subscription | Workspace | Resource group | Sentinel onboarded |
107+
| :----------- | :-------- | :------------- | :----------------- |
108+
{2}
109+
110+
**Summary:**
111+
112+
- Total workspaces: {3}
113+
- Workspaces with Sentinel onboarded: {4}
114+
'@
115+
116+
$onboardedCount = $onboardedWorkspaces.Count
117+
118+
$tableRows = ''
119+
foreach ($result in $workspaceResults) {
120+
$subscriptionName = Get-SafeMarkdown -Text $result.SubscriptionName
121+
$workspaceName = Get-SafeMarkdown -Text $result.WorkspaceName
122+
$resourceGroup = Get-SafeMarkdown -Text $result.ResourceGroup
123+
$workspaceLink = "[$workspaceName]($($workspacePortalTemplate -f $result.WorkspaceId))"
124+
$onboardedLabel = if ($result.SentinelOnboarded) { '✅ Yes' } else { '❌ No' }
125+
$tableRows += "| $subscriptionName | $workspaceLink | $resourceGroup | $onboardedLabel |`n"
126+
}
127+
128+
$mdInfo = $formatTemplate -f 'Workspaces and their Sentinel onboarding state', $workspacesPortalUrl, $tableRows, $workspaceResults.Count, $onboardedCount
129+
130+
$testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo
131+
#endregion Report Generation
132+
133+
$params = @{
134+
TestId = '61002'
135+
Title = 'Microsoft Sentinel is onboarded on at least one Log Analytics workspace'
136+
Status = $passed
137+
Result = $testResultMarkdown
138+
}
139+
140+
Add-ZtTestResultDetail @params
141+
}

0 commit comments

Comments
 (0)