-
Notifications
You must be signed in to change notification settings - Fork 156
AI - 61002 - Microsoft Sentinel is onboarded on at least one Log Analytics workspace #1241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # Enumerates all Log Analytics workspaces across accessible subscriptions using a single | ||
| # Azure Resource Graph query (spec Q1+Q2), then checks the Sentinel onboarding state for | ||
| # each workspace (spec Q3) via the Microsoft.SecurityInsights/onboardingStates/default endpoint. | ||
| # | ||
| # Returns an array of [PSCustomObject] (SubscriptionName, WorkspaceName, ResourceGroup, | ||
| # WorkspaceId, SentinelOnboarded). | ||
| # Returns $null when the ARG query fails so the caller can issue a skip. | ||
| function Get-SentinelWorkspaceData { | ||
| [CmdletBinding()] | ||
| param( | ||
| [string]$Activity = 'Fetching Sentinel workspace data' | ||
| ) | ||
|
|
||
| # Q1 + Q2: Single ARG query joins subscription names onto workspace records so one | ||
| # round-trip covers both steps. ARG respects caller RBAC automatically. | ||
| $argQuery = @" | ||
| resources | ||
| | where type =~ 'microsoft.operationalinsights/workspaces' | ||
| | join kind=leftouter ( | ||
| resourcecontainers | ||
| | where type =~ 'microsoft.resources/subscriptions' | ||
| | project subscriptionName=name, subscriptionId | ||
| ) on subscriptionId | ||
| | project | ||
| workspaceName=name, | ||
| workspaceId=id, | ||
| resourceGroup, | ||
| subscriptionId, | ||
| subscriptionName | ||
| | order by subscriptionName asc, workspaceName asc | ||
| "@ | ||
|
|
||
| Write-ZtProgress -Activity $Activity -Status 'Enumerating Log Analytics workspaces via Resource Graph' | ||
| $allWorkspaces = @() | ||
| try { | ||
| $allWorkspaces = @(Invoke-ZtAzureResourceGraphRequest -Query $argQuery) | ||
| Write-PSFMessage "ARG query returned $($allWorkspaces.Count) Log Analytics workspace(s)." -Tag Test -Level VeryVerbose | ||
| } | ||
| catch { | ||
| Write-PSFMessage "Azure Resource Graph query failed: $($_.Exception.Message)" -Tag Test -Level Warning | ||
| return $null | ||
| } | ||
|
|
||
| # Q3: For each workspace query the Sentinel onboarding state. | ||
| # HTTP 200 = onboarded; HTTP 404 = not onboarded. | ||
| # -FullResponse prevents a 404 from throwing so the status code can be inspected. | ||
| $results = @() | ||
| foreach ($workspace in $allWorkspaces) { | ||
| Write-ZtProgress -Activity $Activity -Status "Checking Sentinel onboarding on workspace '$($workspace.workspaceName)'" | ||
|
|
||
| $sentinelOnboarded = $false | ||
| try { | ||
| $sentinelPath = "$($workspace.workspaceId)/providers/Microsoft.SecurityInsights/onboardingStates/default?api-version=2024-03-01" | ||
| $response = Invoke-ZtAzureRequest -Path $sentinelPath -FullResponse -ErrorAction Stop | ||
| $sentinelOnboarded = ($response.StatusCode -eq 200) | ||
| } | ||
| catch { | ||
| Write-PSFMessage "Error checking Sentinel onboarding for workspace '$($workspace.workspaceName)': $_" -Tag Test -Level Warning | ||
| } | ||
|
|
||
| $results += [PSCustomObject]@{ | ||
| SubscriptionName = $workspace.subscriptionName | ||
| WorkspaceName = $workspace.workspaceName | ||
| ResourceGroup = $workspace.resourceGroup | ||
| WorkspaceId = $workspace.workspaceId | ||
| SentinelOnboarded = $sentinelOnboarded | ||
| } | ||
| } | ||
|
|
||
| # Use the unary comma operator so an empty array is preserved as an array | ||
| # (not collapsed to $null by the pipeline) and the caller can distinguish | ||
| # "no workspaces found" from an ARG failure ($null return above). | ||
| return , $results | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| 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. | ||
|
|
||
| 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. | ||
|
|
||
| **Remediation action** | ||
|
|
||
| - [What is Microsoft Sentinel?](https://learn.microsoft.com/azure/sentinel/overview) | ||
| - [Quickstart: Onboard Microsoft Sentinel](https://learn.microsoft.com/azure/sentinel/quickstart-onboard) | ||
| - [Design your Microsoft Sentinel workspace architecture](https://learn.microsoft.com/azure/sentinel/design-your-workspace-architecture) | ||
| - [Sentinel onboarding states — Create (REST API)](https://learn.microsoft.com/rest/api/securityinsights/sentinel-onboarding-states/create) | ||
|
|
||
| <!--- Results ---> | ||
| %TestResult% |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| <# | ||
| .SYNOPSIS | ||
| Checks whether Microsoft Sentinel is onboarded on at least one Log Analytics workspace. | ||
|
|
||
| .DESCRIPTION | ||
| This test enumerates all Log Analytics workspaces across in-scope Azure subscriptions and | ||
| verifies that at least one has Microsoft Sentinel onboarded. Sentinel is required as a central | ||
| SIEM before any other AI threat detection control in this pillar can correlate signals across | ||
| the environment. | ||
|
|
||
| Evaluation steps: | ||
| 1. Use Azure Resource Graph to enumerate all Log Analytics workspaces (combining subscription | ||
| listing and workspace listing in one query). | ||
| 2. For each workspace, query the Sentinel onboarding state resource via the | ||
| Microsoft.SecurityInsights/onboardingStates/default ARM endpoint. | ||
| 3. Pass if at least one workspace returns HTTP 200 (Sentinel is onboarded). | ||
| 4. Fail if workspaces exist but every one returns HTTP 404 (Sentinel not onboarded). | ||
| 5. Skip if no Log Analytics workspaces are found across accessible subscriptions. | ||
|
|
||
| .NOTES | ||
| Test ID: 61002 | ||
| Workshop Task: AI_089 | ||
| Pillar: AI | ||
| Category: AI Threat Detection | ||
| Required permissions: | ||
| - Reader on each subscription (for Log Analytics workspace enumeration) | ||
| - Microsoft Sentinel Reader on each workspace (for onboarding state query) | ||
| #> | ||
|
|
||
| function Test-Assessment-61002 { | ||
|
|
||
| [ZtTest( | ||
| Category = 'AI Threat Detection', | ||
| ImplementationCost = 'Medium', | ||
| Service = ('Azure'), | ||
| MinimumLicense = ('Microsoft_Sentinel'), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we agreed to use |
||
| Pillar = 'AI', | ||
| RiskLevel = 'High', | ||
| SfiPillar = 'Monitor and detect cyberthreats', | ||
| TenantType = ('Workforce'), | ||
| TestId = 61002, | ||
| Title = 'Microsoft Sentinel is onboarded on at least one Log Analytics workspace', | ||
| UserImpact = 'Low' | ||
| )] | ||
| [CmdletBinding()] | ||
| param() | ||
|
|
||
| #region Data Collection | ||
| Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose | ||
| $activity = 'Evaluating Microsoft Sentinel onboarding state across Log Analytics workspaces' | ||
|
|
||
| # Verify Azure connection | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This check is redundant when the test is run through |
||
| Write-ZtProgress -Activity $activity -Status 'Checking Azure connection' | ||
| $azContext = Get-AzContext -ErrorAction SilentlyContinue | ||
| if (-not $azContext) { | ||
| Write-PSFMessage 'Not connected to Azure.' -Level Warning | ||
| Add-ZtTestResultDetail -SkippedBecause NotConnectedAzure | ||
| return | ||
| } | ||
|
|
||
|
Manoj-Kesana marked this conversation as resolved.
|
||
| # Delegate all data fetching (Q1+Q2+Q3) to the private helper. | ||
| # $null return signals an ARG failure; empty array signals no workspaces found. | ||
| $workspaceResults = Get-SentinelWorkspaceData -Activity $activity | ||
| # $null signals an ARG query failure (e.g. no access to Resource Graph). | ||
| # An empty array (Count -eq 0) means no workspaces exist – spec says Skip, not Fail. | ||
| if ($null -eq $workspaceResults) { | ||
| Add-ZtTestResultDetail -SkippedBecause NoAzureAccess | ||
| return | ||
| } | ||
|
|
||
| # Per spec: zero workspaces → Skipped, not Failed. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The spec says: |
||
| if ($workspaceResults.Count -eq 0) { | ||
| Write-PSFMessage 'No Log Analytics workspaces found across accessible subscriptions.' -Tag Test -Level VeryVerbose | ||
| Add-ZtTestResultDetail -SkippedBecause NotApplicable | ||
| return | ||
| } | ||
| #endregion Data Collection | ||
|
|
||
| #region Assessment Logic | ||
| $onboardedWorkspaces = @($workspaceResults | Where-Object { $_.SentinelOnboarded }) | ||
| $passed = $onboardedWorkspaces.Count -ge 1 | ||
|
|
||
| if ($passed) { | ||
| $testResultMarkdown = "✅ Microsoft Sentinel is onboarded on at least one Log Analytics workspace.`n`n%TestResult%" | ||
| } | ||
| else { | ||
| $testResultMarkdown = "❌ No Log Analytics workspace in scope has Microsoft Sentinel onboarded.`n`n%TestResult%" | ||
| } | ||
| #endregion Assessment Logic | ||
|
|
||
| #region Report Generation | ||
| $workspacesPortalUrl = 'https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.OperationalInsights%2Fworkspaces' | ||
| $workspacePortalTemplate = 'https://portal.azure.com/#resource{0}/overview' | ||
|
|
||
| $formatTemplate = @' | ||
|
|
||
|
|
||
| ### [{0}]({1}) | ||
|
|
||
| | Subscription | Workspace | Resource group | Sentinel onboarded | | ||
| | :----------- | :-------- | :------------- | :----------------- | | ||
| {2} | ||
|
|
||
| **Summary:** | ||
|
|
||
| - Total workspaces: {3} | ||
| - Workspaces with Sentinel onboarded: {4} | ||
| '@ | ||
|
|
||
| $onboardedCount = $onboardedWorkspaces.Count | ||
|
|
||
| $tableRows = '' | ||
| foreach ($result in $workspaceResults) { | ||
| $subscriptionName = Get-SafeMarkdown -Text $result.SubscriptionName | ||
| $workspaceName = Get-SafeMarkdown -Text $result.WorkspaceName | ||
| $resourceGroup = Get-SafeMarkdown -Text $result.ResourceGroup | ||
| $workspaceLink = "[$workspaceName]($($workspacePortalTemplate -f $result.WorkspaceId))" | ||
| $onboardedLabel = if ($result.SentinelOnboarded) { '✅ Yes' } else { '❌ No' } | ||
| $tableRows += "| $subscriptionName | $workspaceLink | $resourceGroup | $onboardedLabel |`n" | ||
| } | ||
|
|
||
| $mdInfo = $formatTemplate -f 'Workspaces and their Sentinel onboarding state', $workspacesPortalUrl, $tableRows, $workspaceResults.Count, $onboardedCount | ||
|
|
||
| $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo | ||
| #endregion Report Generation | ||
|
|
||
| $params = @{ | ||
| TestId = '61002' | ||
| Title = 'Microsoft Sentinel is onboarded on at least one Log Analytics workspace' | ||
| Status = $passed | ||
| Result = $testResultMarkdown | ||
| } | ||
|
|
||
| Add-ZtTestResultDetail @params | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code treats every non-200 Q3 response as “Sentinel not onboarded,” including
403 Forbidden. BecauseInvoke-ZtAzureRequest -FullResponsereturns the response instead of throwing on non-2xx, thecatchwill not handle a 403. A user lacking Microsoft Sentinel Reader on a workspace can get a false fail row ofNoinstead of the ARM-requiredNoAzureAccessskip. The helper should explicitly branch on200,404, and403/Forbidden, and surface access failure to the caller.