Framework/Helpers/OMSHelper.ps1
Set-StrictMode -Version Latest Class OMSHelper{ static [string] $DefaultOMSType = "AzSK_AAD" static [string] $CommandEventType = "AzSK_AAD_CommandEvent" hidden static [int] $isOMSSettingValid = 0 #-1:Fail (OMS Empty, OMS Return Error) | 0:Local hidden static [int] $isAltOMSSettingValid = 0 # Create the function to create and post the request # BUGBUG: Need to rename OMSType here...it is used for 'LogType' in almost all other places. Perhaps OMSInstance (=OMS, AltOMS) or something? static PostOMSData([string] $OMSWorkspaceID, [string] $SharedKey, $Body, $LogType, $OMSType) { try { if(($OMSType | Measure-Object).Count -gt 0 -and [OMSHelper]::$("is"+$OMSType+"SettingValid") -ne -1) { if([string]::IsNullOrWhiteSpace($LogType)) { $LogType = [OMSHelper]::DefaultOMSType } [string] $method = "POST" [string] $contentType = "application/json" [string] $resource = "/api/logs" $rfc1123date = [System.DateTime]::UtcNow.ToString("r") [int] $contentLength = $Body.Length [string] $signature = [OMSHelper]::GetOMSSignature($OMSWorkspaceID , $SharedKey , $rfc1123date ,$contentLength ,$method ,$contentType ,$resource) [string] $uri = "https://" + $OMSWorkspaceID + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01" [DateTime] $TimeStampField = [System.DateTime]::UtcNow $headers = @{ "Authorization" = $signature; "Log-Type" = $LogType; "x-ms-date" = $rfc1123date; "time-generated-field" = $TimeStampField; } $response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $Body -UseBasicParsing } } catch { $warningMsg="" if($OMSType -eq 'OMS' -or $OMSType -eq 'AltOMS') { switch([OMSHelper]::$("is"+$OMSType+"SettingValid")) { 0 { $warningMsg += "The $($OMSType) workspace id or key is invalid in the local settings file. You can use Set-AzSKOMSSettings with correct values to update it.";} 1 { $warningMsg += "The $($OMSType) workspace id or key is invalid in the ContinuousAssurance configuration. You can use Update-AzSKContinuousAssurance with the correct OMS values to correct it."; } } [EventBase]::PublishGenericCustomMessage(" `r`nWARNING: $($warningMsg)", [MessageType]::Warning); #Flag to disable OMS scan [OMSHelper]::$("is"+$OMSType+"SettingValid") = -1 } } } static [string] GetOMSSignature ($OMSWorkspaceID, $SharedKey, $Date, $ContentLength, $Method, $ContentType, $Resource) { [string] $xHeaders = "x-ms-date:" + $Date [string] $stringToHash = $Method + "`n" + $ContentLength + "`n" + $ContentType + "`n" + $xHeaders + "`n" + $Resource [byte[]]$bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash) [byte[]]$keyBytes = [Convert]::FromBase64String($SharedKey) [System.Security.Cryptography.HMACSHA256] $sha256 = New-Object System.Security.Cryptography.HMACSHA256 $sha256.Key = $keyBytes [byte[]]$calculatedHash = $sha256.ComputeHash($bytesToHash) $encodedHash = [Convert]::ToBase64String($calculatedHash) $authorization = 'SharedKey {0}:{1}' -f $OMSWorkspaceID,$encodedHash return $authorization } static [PSObject[]] GetOMSBodyObjects([SVTEventContext] $eventContext,[AzSKContextDetails] $AzSKContext) { [PSObject[]] $output = @(); [array] $eventContext.ControlResults | ForEach-Object{ Set-Variable -Name ControlResult -Value $_ -Scope Local $out = [OMSModel]::new() if($eventContext.IsResource()) { $out.ResourceType=$eventContext.ResourceContext.ResourceType $out.ResourceGroup=$eventContext.ResourceContext.ResourceGroupName $out.ResourceName=$eventContext.ResourceContext.ResourceName $out.ResourceId = $eventContext.ResourceContext.ResourceId $out.ChildResourceName=$ControlResult.ChildResourceName $out.PartialScanIdentifier=$eventContext.PartialScanIdentifier } $out.Reference=$eventContext.Metadata.Reference $out.ControlStatus=$ControlResult.VerificationResult.ToString() $out.ActualVerificationResult=$ControlResult.ActualVerificationResult.ToString() $out.ControlId=$eventContext.ControlItem.ControlID $out.TenantName=$eventContext.TenantContext.TenantName $out.tenantId=$eventContext.TenantContext.tenantId $out.FeatureName=$eventContext.FeatureName $out.Recommendation=$eventContext.ControlItem.Recommendation $out.ControlSeverity=$eventContext.ControlItem.ControlSeverity.ToString() $out.Source=$AzSKContext.Source $out.Tags=$eventContext.ControlItem.Tags $out.RunIdentifier = $AzSKContext.RunIdentifier $out.HasRequiredAccess = $ControlResult.CurrentSessionContext.Permissions.HasRequiredAccess $out.ScannerVersion = $AzSKContext.Version $out.IsBaselineControl = $eventContext.ControlItem.IsBaselineControl $out.HasAttestationWritePermissions = $ControlResult.CurrentSessionContext.Permissions.HasAttestationWritePermissions $out.HasAttestationReadPermissions = $ControlResult.CurrentSessionContext.Permissions.HasAttestationReadPermissions $out.IsLatestPSModule = $ControlResult.CurrentSessionContext.IsLatestPSModule $out.PolicyOrgName = $AzSKContext.PolicyOrgName $out.IsControlInGrace = $ControlResult.IsControlInGrace #mapping the attestation properties if($null -ne $ControlResult -and $null -ne $ControlResult.StateManagement -and $null -ne $ControlResult.StateManagement.AttestedStateData) { $attestedData = $ControlResult.StateManagement.AttestedStateData; $out.AttestationStatus = $ControlResult.AttestationStatus.ToString(); $out.AttestedBy = $attestedData.AttestedBy; $out.Justification = $attestedData.Justification; $out.AttestedDate = $attestedData.AttestedDate $out.ExpiryDate = $attestedData.ExpiryDate } $output += $out } return $output } static [void] PostApplicableControlSet([SVTEventContext[]] $contexts,[AzSKContextDetails] $AzSKContext) { if (($contexts | Measure-Object).Count -lt 1) { return; } $set = [OMSHelper]::ConvertToSimpleSet($contexts,$AzSKContext); [OMSHelper]::WriteControlResult($set,"AzSK_Inventory") #$omsMetadata = [ConfigurationManager]::LoadServerConfigFile("OMSSettings.json") #[OMSHelper]::WriteControlResult($omsMetadata,"AzSK_MetaData") } static [void] WriteControlResult([PSObject[]] $omsDataObject, [string] $OMSEventType) { try { $settings = [ConfigurationManager]::GetAzSKSettings() if([string]::IsNullOrWhiteSpace($OMSEventType)) { $OMSEventType = $settings.OMSType } #TODO: This check may not be needed, given the IsSendingOMSEvents() check in the handler! if((-not [string]::IsNullOrWhiteSpace($settings.OMSWorkspaceId)) -or (-not [string]::IsNullOrWhiteSpace($settings.AltOMSWorkspaceId))) { $omsDataObject | ForEach-Object{ Set-Variable -Name tempBody -Value $_ -Scope Local $body = $tempBody | ConvertTo-Json $omsBodyByteArray = ([System.Text.Encoding]::UTF8.GetBytes($body)) #publish to primary workspace if(-not [string]::IsNullOrWhiteSpace($settings.OMSWorkspaceId) -and [OMSHelper]::isOMSSettingValid -ne -1) { [OMSHelper]::PostOMSData($settings.OMSWorkspaceId, $settings.OMSSharedKey, $omsBodyByteArray, $OMSEventType, 'OMS') } #publish to secondary workspace if(-not [string]::IsNullOrWhiteSpace($settings.AltOMSWorkspaceId) -and [OMSHelper]::isAltOMSSettingValid -ne -1) { [OMSHelper]::PostOMSData($settings.AltOMSWorkspaceId, $settings.AltOMSSharedKey, $omsBodyByteArray, $OMSEventType, 'AltOMS') } } } } catch { throw ([SuppressedException]::new("Error sending events to OMS. The following exception occurred: `r`n$($_.Exception.Message) `r`nFor more on AzSK OMS setup, refer: https://aka.ms/devopskit/ca")); } } static [PSObject[]] ConvertToSimpleSet($contexts,[AzSKContextDetails] $AzSKContext) { $ControlSet = [System.Collections.ArrayList]::new() foreach ($item in $contexts) { $set = [OMSResourceInvModel]::new() $set.RunIdentifier = $AzSKContext.RunIdentifier $set.tenantId = $item.TenantContext.tenantId $set.TenantName = $item.TenantContext.TenantName $set.Source = $AzSKContext.Source $set.ScannerVersion = $AzSKContext.Version $set.FeatureName = $item.FeatureName if([Helpers]::CheckMember($item,"ResourceContext")) { $set.ResourceGroupName = $item.ResourceContext.ResourceGroupName $set.ResourceName = $item.ResourceContext.ResourceName $set.ResourceId = $item.ResourceContext.ResourceId } $set.ControlIntId = $item.ControlItem.Id $set.ControlId = $item.ControlItem.ControlID $set.ControlSeverity = $item.ControlItem.ControlSeverity $set.Tags = $item.ControlItem.Tags $set.IsBaselineControl = $item.ControlItem.IsBaselineControl $ControlSet.Add($set) } return $ControlSet; } static [void] SetOMSDetails($currentInstance) { #Check if Settings already contain details of OMS $settings = [ConfigurationManager]::GetAzSKSettings() if([string]::IsNullOrWhiteSpace($settings.OMSWorkspaceId) -or [string]::IsNullOrWhiteSpace($settings.OMSSharedKey)) { [OMSHelper]::isOMSSettingValid = -1 } if([string]::IsNullOrWhiteSpace($settings.AltOMSWorkspaceId) -or [string]::IsNullOrWhiteSpace($settings.AltOMSSharedKey)) { [OMSHelper]::isAltOMSSettingValid = -1 } #If either of the settings are valid, remember so in OMSOutput object to help with efficiency if ([OMSHelper]::isOMSSettingValid -ne -1 -or [OMSHelper]::isAltOMSSettingValid -ne -1) { $currentInstance.SetSendingOMSEvents() } } static PostResourceInventory([AzSKContextDetails] $AzSKContext) { if($AzSKContext.Source.Equals("CC", [System.StringComparison]::OrdinalIgnoreCase) -or $AzSKContext.Source.Equals("CA", [System.StringComparison]::OrdinalIgnoreCase)){ $resourceSet = [System.Collections.ArrayList]::new() [ResourceInventory]::FetchResources(); foreach($resource in [ResourceInventory]::FilteredResources){ $set = [OMSResourceModel]::new() $set.RunIdentifier = $AzSKContext.RunIdentifier $set.tenantId = $resource.tenantId #$set.TenantName = $item.TenantContext.TenantName $set.Source = $AzSKContext.Source $set.ScannerVersion = $AzSKContext.Version $set.ResourceType = $resource.ResourceType $set.ResourceGroupName = $resource.ResourceGroupName $set.ResourceName = $resource.Name $set.ResourceId = $resource.ResourceId $resourceSet.Add($set) } [OMSHelper]::WriteControlResult($resourceSet,"AzSK_Inventory") $omsMetadata = [ConfigurationManager]::LoadServerConfigFile("OMSSettings.json") [OMSHelper]::WriteControlResult($omsMetadata,"AzSK_MetaData") } } hidden static [PSObject] QueryStatusfromWorkspace([string] $workspaceId,[string] $query) { $result=$null; try { $body = @{query=$query}; $url="https://api.loganalytics.io/beta/workspaces/" +$workspaceId+"/api/query?api-version=2017-01-01-preview" $response=[WebRequestHelper]::InvokePostWebRequest($url , $body); # Formating the response obtained from querying workspace. if(($response | Measure-Object).Count -gt 0) { $data = $response; #Out of four tables obtained, the first table contains result of query if(($data | Measure-Object).Count -gt 0) { $table= $data.Tables[0]; $Columns=$table.Columns; $objectView = @{}; $j = 0; if($null -ne $table) { foreach ($valuetable in $table) { foreach ($row in $table.Rows) { $i = 0; $count=$valuetable.Columns.Count; $properties = @{} foreach($col in $Columns) { if($i -lt $count) { $properties[$col.ColumnName] = $row[$i]; } $i++; } $objectView[$j] = (New-Object PSObject -Property $properties) $j++; } } $result=$objectView; } } } } catch { [EventBase]::PublishGenericCustomMessage($_) } return $result; } } Class OMSModel { [string] $RunIdentifier [string] $ResourceType [string] $ResourceGroup [string] $Reference [string] $ResourceName [string] $ChildResourceName [string] $ResourceId [string] $ControlStatus [string] $ActualVerificationResult [string] $ControlId [string] $TenantName [string] $tenantId [string] $FeatureName [string] $Source [string] $Recommendation [string] $ControlSeverity [string] $TimeTakenInMs [string] $AttestationStatus [string] $AttestedBy [string] $Justification [string] $AttestedDate [bool] $HasRequiredAccess [bool] $HasAttestationWritePermissions [bool] $HasAttestationReadPermissions [bool] $IsLatestPSModule [bool] $IsControlInGrace [string[]] $Tags [string] $ScannerVersion [bool] $IsBaselineControl [string] $ExpiryDate [string] $PartialScanIdentifier [string] $PolicyOrgName } Class OMSResourceInvModel{ [string] $RunIdentifier [string] $tenantId [string] $TenantName [string] $Source [string] $ScannerVersion [string] $FeatureName [string] $ResourceGroupName [string] $ResourceName [string] $ResourceId [string] $ControlId [string] $ControlIntId [string] $ControlSeverity [string[]] $Tags [bool] $IsBaselineControl } Class OMSResourceModel{ [string] $RunIdentifier [string] $tenantId [string] $Source [string] $ScannerVersion [string] $ResourceType [string] $ResourceGroupName [string] $ResourceName [string] $ResourceId } Class AzSKContextDetails { [string] $RunIdentifier [string] $Version [string] $Source [string] $PolicyOrgName } Class CommandModel{ [string] $EventName [string] $RunIdentifier [string] $PartialScanIdentifier [string] $ModuleVersion [string] $MethodName [string] $ModuleName [string] $Parameters [string] $tenantId [string] $TenantName } |