Framework/Core/SVT/Services/LogAnalytics.ps1
Set-StrictMode -Version Latest class LogAnalytics: AzSVTBase { hidden [PSObject] $ResourceObject; LogAnalytics([string] $subscriptionId, [SVTResource] $svtResource): Base($subscriptionId, $svtResource) { $this.GetResourceObject(); } hidden [PSObject] GetResourceObject() { if (-not $this.ResourceObject) { $this.ResourceObject = Get-AzResource -ResourceId $this.ResourceId if (-not $this.ResourceObject) { throw ([SuppressedException]::new(("Resource '{0}' not found under Resource Group '{1}'" -f ($this.ResourceContext.ResourceName), ($this.ResourceContext.ResourceGroupName)), [SuppressedExceptionType]::InvalidOperation)) } } return $this.ResourceObject; } # This functions checks for both control level and resource level RBAC hidden [ControlResult] CheckResourceRBACAccess([ControlResult] $controlResult) { $controlResult = $this.CheckRBACAccess($controlResult) if ( -not ([Helpers]::CheckMember($this.ResourceObject, "Properties.features.enableLogAccessUsingOnlyResourcePermissions") -and $this.ResourceObject.Properties.features.enableLogAccessUsingOnlyResourcePermissions -eq $true)) { $controlResult.VerificationResult = [VerificationResult]::Failed; $controlResult.AddMessage("The currently configured access control mode is 'Require workspace permissions'. Switch to Resource-specific mode for granular RBAC management."); } return $controlResult; } # This function lists the automation account connected to your workspace hidden [ControlResult] CheckAccountsLinkedToWorkspace([ControlResult] $controlResult) { try { $AzureManagementUri = [WebRequestHelper]::GetResourceManagerUrl() # Rest API to fetch linked automation account $uri = [system.string]::Format($AzureManagementUri + "subscriptions/{0}/resourcegroups/{1}/providers/microsoft.operationalinsights/workspaces/{2}/LinkedServices/Automation?api-version=2015-11-01-preview", $this.SubscriptionContext.SubscriptionId, $this.ResourceContext.ResourceGroupName, $this.ResourceContext.ResourceName) # InvokeGetWebRequest enters infinite loop when response status code is 200, but the response content is null. # Due to this, temporarily, we are fetching raw content. $accessToken = [ContextHelper]::GetAccessToken($AzureManagementUri) $authorisationToken = "Bearer " + $accessToken $headers = @{ "Authorization" = $authorisationToken } $contentType = "application/json" $linkedServiceDetail = $null $requestResult = [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Get, $uri, $headers, $null, $contentType, $false, $true) if ($null -ne $requestResult -and $requestResult.StatusCode -eq 200) { $linkedServiceDetail = ConvertFrom-Json $requestResult.Content if (($linkedServiceDetail | Measure-Object).Count -gt 0 -and [Helpers]::CheckMember($linkedServiceDetail, "properties.resourceId")) { $AutomationAccountName = $linkedServiceDetail.properties.resourceId.Split(" / ")[-1] $controlResult.AddMessage([VerificationResult]::Verify, "Log analytics workspaces in linked to [$($AutomationAccountName)] automation account. Verify the RBAC access granted to its Run as Account."); } else { $controlResult.AddMessage([VerificationResult]::Passed, "No automation account is linked to this workspace."); } } } catch { # If exception occur, control should go into Manual state $controlResult.AddMessage([VerificationResult]::Manual, "Unable to get linked automation account for - [$($this.ResourceContext.ResourceName)]."); } return $controlResult; } # This function lists the data retention period for your logs hidden [ControlResult] CheckDataRetentionPeriod([ControlResult] $controlResult) { $controlResult.VerificationResult = [VerificationResult]::Verify; # workspace retention period $controlResult.AddMessage("The currently configured log retention period at workspace level is: $($this.ResourceObject.Properties.retentionInDays)"); # Retention by data type $AzureManagementUri = [WebRequestHelper]::GetResourceManagerUrl() # Rest API to fetch retention period of all data type $uri = [system.string]::Format($AzureManagementUri + "subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.OperationalInsights/workspaces/{2}/Tables?api-version=2017-04-26-preview", $this.SubscriptionContext.SubscriptionId, $this.ResourceContext.ResourceGroupName, $this.ResourceContext.ResourceName) try { $retentionByDataType = [WebRequestHelper]::InvokeGetWebRequest($uri); if (($retentionByDataType | Measure-Object).Count -gt 0 -and [Helpers]::CheckMember($retentionByDataType, "id")) { $ListOfDataTypeWithRetention = @() $ListOfDataTypeWithoutRetention = @() $retentionByDataType | ForEach-Object { $Obj = "" | Select-Object DataTypeName, RetentionInDays if ([Helpers]::CheckMember($_, "properties.RetentionInDays")) { $Obj.DataTypeName = $_.name $Obj.RetentionInDays = $_.properties.RetentionInDays $ListOfDataTypeWithRetention += $Obj } else { $Obj.DataTypeName = $_.name $ListOfDataTypeWithoutRetention += $Obj } } if (($ListOfDataTypeWithRetention | Measure-Object).Count -gt 0) { $controlResult.AddMessage("Below is the list of data type (tables) where retention period is configured:", $ListOfDataTypeWithRetention); } if (($ListOfDataTypeWithoutRetention | Measure-Object).Count -gt 0) { $controlResult.AddMessage("Below is the list of data type (tables) where retention period is not configured (workspace level retention period is applicable for these tables):", $ListOfDataTypeWithoutRetention.DataTypeName); } } } catch { # This is an empty block to avoid breaking the execution flow } return $controlResult; } # This function lists the solutions connected to your workspace along with publisher, containedResources and referencedResources details hidden [ControlResult] CheckSolutionsLinkedToWorkspace([ControlResult] $controlResult) { try { $AzureManagementUri = [WebRequestHelper]::GetResourceManagerUrl() $accessToken = [ContextHelper]::GetAccessToken($AzureManagementUri) if ($null -ne $accessToken) { $authorisationToken = "Bearer " + $accessToken $pathQuery = [system.string]::Format("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.OperationsManagement/solutions?`$filter=properties/workspaceResourceId%2520eq%2520%2527{2}%2527&api-version=2015-11-01-preview", $this.SubscriptionContext.SubscriptionId, $this.ResourceContext.ResourceGroupName, $this.ResourceContext.ResourceName) $headers = @{ "Authorization" = $authorisationToken; "Content-Type" = "application/json"; "x-ms-path-query" = $pathQuery } $uri = "https://management.azure.com/api/invoke" # Rest API to fetch linked solutions' detail $linkedSolutionDetail = [WebRequestHelper]::InvokeGetWebRequest($uri, $headers); if (($linkedSolutionDetail | Measure-Object).Count -gt 0 -and [Helpers]::CheckMember($linkedSolutionDetail, "id")) { $linkedSolutionDetailCustomObj = @() $linkedSolutionDetail | ForEach-Object { $Obj = "" | Select-Object Name, Publisher, containedResources, referencedResources $Obj.Name = $_.plan.name $Obj.Publisher = $_.plan.publisher if ([Helpers]::CheckMember($_, "properties.containedResources")) { $Obj.containedResources = $_.properties.containedResources } if ([Helpers]::CheckMember($_, "properties.referencedResources")) { $Obj.referencedResources = $_.properties.referencedResources } $linkedSolutionDetailCustomObj += $Obj } $controlResult.AddMessage([VerificationResult]::Verify, "Verify the endpoints and azure resources accessed by the solution connected to your workspace.", $linkedSolutionDetailCustomObj); $controlResult.SetStateData("List of linked solutions and resources connected to it:", $linkedSolutionDetailCustomObj); } else { $controlResult.AddMessage([VerificationResult]::Passed, "No solution is linked to this workspace."); } } } catch { # If exception occur, control should go into Manual state $controlResult.AddMessage([VerificationResult]::Manual, "Unable to get linked solutions for - [$($this.ResourceContext.ResourceName)]. Please verify the solution connected to your workspace from portal."); } return $controlResult; } } |