private/Resolve-MsecSentinelWorkspace.ps1
|
function Resolve-MsecSentinelWorkspace { <# .SYNOPSIS The Log Analytics workspaces a Sentinel command should act on, with an ARM token and the SecurityInsights base URL already built for each. .DESCRIPTION Shared by the Sentinel commands so they cannot disagree about which workspace a name means, or about what a missing one implies. NAMED AND NOT FOUND IS AN ERROR, NEVER AN EMPTY RESULT. Building a request URL from an empty ResourceId yields '/providers/Microsoft.SecurityInsights/...' with no scope at all, which Azure rejects as an authorization failure - sending the reader to check RBAC for a workspace that was never located. .PARAMETER SubscriptionId Subscription to switch to first. Defaults to the active Az context. .PARAMETER ResourceGroupName Narrows the workspace search. .PARAMETER WorkspaceName A single workspace. Without it every workspace in scope is returned, and the caller decides what to do with several. .OUTPUTS One PSCustomObject with Headers, Subscription and Workspaces; each workspace carries Name, ResourceGroupName, ResourceId and Base (the SecurityInsights URL). #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [string] $SubscriptionId, [string] $ResourceGroupName, [string] $WorkspaceName ) $context = Get-AzContext -ErrorAction SilentlyContinue if (-not $context) { throw 'No Azure context. Run Connect-MsecAzure first - Sentinel is an Azure resource, so this reads as you rather than as the msec app.' } if ($SubscriptionId -and $context.Subscription.Id -ne $SubscriptionId) { Write-Verbose "Switching context to subscription $SubscriptionId" $context = Set-AzContext -SubscriptionId $SubscriptionId -Scope Process -ErrorAction Stop } $subscription = $context.Subscription.Id $arm = (Get-MsecEnvironment).ArmResource $tokenResponse = Get-AzAccessToken -ResourceUrl "$arm/" -ErrorAction Stop $token = if ($tokenResponse.Token -is [System.Security.SecureString]) { [System.Net.NetworkCredential]::new('', $tokenResponse.Token).Password } else { [string] $tokenResponse.Token } $workspaceParams = @{ ErrorAction = 'Stop' } if ($ResourceGroupName) { $workspaceParams['ResourceGroupName'] = $ResourceGroupName } if ($WorkspaceName -and $ResourceGroupName) { $workspaceParams['Name'] = $WorkspaceName } # Get-AzOperationalInsightsWorkspace throws a bare "Operation returned an invalid status # code 'NotFound'" for a name that does not exist, naming neither the name nor the # subscription. Caught so the message below is the one the reader sees. $workspaces = @() try { $workspaces = @(Get-AzOperationalInsightsWorkspace @workspaceParams) } catch { if ("$($_.Exception.Message)" -notmatch 'NotFound|ResourceNotFound') { throw } Write-Verbose "Workspace lookup returned NotFound: $($_.Exception.Message)" } if ($WorkspaceName) { $workspaces = @($workspaces | Where-Object { $_.Name -eq $WorkspaceName }) } if (-not $workspaces.Count) { $where = @( if ($WorkspaceName) { "workspace '$WorkspaceName'" } if ($ResourceGroupName) { "resource group '$ResourceGroupName'" } ) -join ' in ' if ($where) { throw "No Log Analytics $where found in subscription $subscription. Check the name, and that the active context is the right subscription - Get-AzContext shows which." } Write-Warning "No Log Analytics workspaces found in subscription $subscription. This is not the same as a Sentinel with no incidents." } [PSCustomObject]@{ Headers = @{ Authorization = "Bearer $token" } Subscription = $subscription Workspaces = @($workspaces | ForEach-Object { [PSCustomObject]@{ Name = $_.Name ResourceGroupName = $_.ResourceGroupName ResourceId = $_.ResourceId Base = "$arm$($_.ResourceId)/providers/Microsoft.SecurityInsights" } }) } } |