Framework/Listeners/OMS/OMSOutput.ps1
Set-StrictMode -Version Latest class OMSOutput: ListenerBase { hidden static [OMSOutput] $Instance = $null; #Default source is kept as SDL / PowerShell. #This value must be set in respective environment i.e. CICD,CC [string] $OMSSource; hidden static [bool] $IsIssueLogged = $false OMSOutput() { } static [OMSOutput] GetInstance() { if($null -eq [OMSOutput]::Instance) { [OMSOutput]::Instance = [OMSOutput]::new(); } return [OMSOutput]::Instance; } [void] RegisterEvents() { $this.UnregisterEvents(); # Mandatory: Generate Run Identifier Event $this.RegisterEvent([AzSdkRootEvent]::GenerateRunIdentifier, { $currentInstance = [OMSOutput]::GetInstance(); try { $currentInstance.SetRunIdentifier([AzSdkRootEventArgument] ($Event.SourceArgs | Select-Object -First 1)); [OMSOutput]::IsIssueLogged = $false } catch { $currentInstance.PublishException($_); } }); $this.RegisterEvent([SVTEvent]::EvaluationCompleted, { $currentInstance = [OMSOutput]::GetInstance(); try { $invocationContext = [System.Management.Automation.InvocationInfo] $currentInstance.InvocationContext $SVTEventContexts = [SVTEventContext[]] $Event.SourceArgs foreach($svtEventContext in $SVTEventContexts) { $currentInstance.WriteControlResult($svtEventContext); } } catch { $currentInstance.PublishException($_); } }); } hidden [void] WriteControlResult([SVTEventContext] $eventContext) { try { $settings = [ConfigurationManager]::GetAzSdkSettings() if(-not [string]::IsNullOrWhiteSpace($settings.OMSSource)) { $this.OMSSource = $settings.OMSSource } if(-not [string]::IsNullOrWhiteSpace($settings.OMSWorkspaceId)) { $tempBodyObjects = $this.GetOMSBodyObjects($this.OMSSource,$eventContext) #need to prioritize this $tempBodyObjects | ForEach-Object{ Set-Variable -Name tempBody -Value $_ -Scope Local $body = $tempBody | ConvertTo-Json $this.PostOMSData($settings.OMSWorkspaceId, $settings.OMSSharedKey, ([System.Text.Encoding]::UTF8.GetBytes($body)),$settings.OMSType) } } } catch { [Exception] $ex = [Exception]::new(("Invalid OMS Settings: " + $_.Exception.ToString()), $_.Exception) throw [SuppressedException] $ex } } hidden [PSObject[]] GetOMSBodyObjects([string] $Source,[SVTEventContext] $eventContext) { [PSObject[]] $output = @(); [array] $eventContext.ControlResults | ForEach-Object{ Set-Variable -Name ControlResult -Value $_ -Scope Local $out = "" | Select-Object ResourceType, ResourceGroup, Reference, ResourceName, ChildResourceName, ControlStatus, ActualVerificationResult, ControlId, SubscriptionName, SubscriptionId, FeatureName, Source, Recommendation, ControlSeverity, TimeTakenInMs, AttestationStatus, AttestedBy, Justification if($eventContext.IsResource()) { $out.ResourceType=$eventContext.ResourceContext.ResourceType $out.ResourceGroup=$eventContext.ResourceContext.ResourceGroupName $out.ResourceName=$eventContext.ResourceContext.ResourceName $out.ChildResourceName=$ControlResult.ChildResourceName } $out.Reference=$eventContext.Metadata.Reference $out.ControlStatus=$ControlResult.VerificationResult.ToString() $out.ActualVerificationResult=$ControlResult.ActualVerificationResult.ToString() $out.ControlId=$eventContext.ControlItem.ControlID $out.SubscriptionName=$eventContext.SubscriptionContext.SubscriptionName $out.SubscriptionId=$eventContext.SubscriptionContext.SubscriptionId $out.FeatureName=$eventContext.FeatureName $out.Recommendation=$eventContext.ControlItem.Recommendation $out.ControlSeverity=$eventContext.ControlItem.ControlSeverity.ToString() $out.Source=$Source #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.TimeTakenInMs=[int] $Metrics["TimeTakenInMs"] $output += $out } return $output } hidden [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 } # Create the function to create and post the request hidden PostOMSData([string] $OMSWorkspaceID, [string] $SharedKey, $Body, $LogType) { try{ [string] $method = "POST" [string] $contentType = "application/json" [string] $resource = "/api/logs" $rfc1123date = [System.DateTime]::UtcNow.ToString("r") [int] $contentLength = $Body.Length [string] $signature = $this.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 { if(-not [OMSOutput]::IsIssueLogged) { $this.PublishCustomMessage("An error occurred while pushing data to OMS. Please check logs for more details. AzSDK control evaluation results will not be sent to the configured OMS workspace from this environment until the error is resolved.", [MessageType]::Error); $this.PublishException($_); [OMSOutput]::IsIssueLogged = $true } } } } |