Reports/New-CISSarifReport.ps1
|
function New-CISSarifReport { <# .SYNOPSIS Generates a SARIF v2.1.0 report from CIS benchmark results. .DESCRIPTION Creates a Static Analysis Results Interchange Format (SARIF) report compatible with GitHub Code Scanning, Azure DevOps, and other security tools. #> [CmdletBinding()] param( [Parameter(Mandatory)] [PSCustomObject[]]$Results, [Parameter(Mandatory)] [string]$OutputPath, [Parameter()] [hashtable]$Metadata = @{} ) # Map CIS status to SARIF level $levelMap = @{ 'FAIL' = 'error' 'WARNING' = 'warning' 'ERROR' = 'error' 'INFO' = 'note' 'PASS' = 'none' } # Map CIS severity to SARIF security-severity $severityScore = @{ 'Critical' = '9.5' 'High' = '7.5' 'Medium' = '5.5' 'Low' = '3.5' 'Informational' = '1.0' } # Build rules array $rules = [System.Collections.Generic.List[hashtable]]::new() $ruleIndex = @{} $idx = 0 foreach ($r in $Results) { if (-not $ruleIndex.ContainsKey($r.ControlId)) { $ruleIndex[$r.ControlId] = $idx $idx++ $rule = [ordered]@{ id = "CIS-$($r.ControlId)" name = ($r.Title -replace '[^\w\s-]', '').Trim() shortDescription = [ordered]@{ text = $r.Title } fullDescription = [ordered]@{ text = if ($r.Description) { $r.Description } else { $r.Title } } helpUri = if ($r.References -and $r.References.Count -gt 0) { $r.References[0] } else { 'https://www.cisecurity.org/benchmark/azure' } properties = [ordered]@{ tags = @('security', 'compliance', 'CIS', "CIS-$($r.Section)") 'security-severity' = if ($severityScore[$r.Severity]) { $severityScore[$r.Severity] } else { '5.5' } precision = if ($r.AssessmentStatus -eq 'Automated') { 'high' } else { 'low' } } } if ($r.Remediation) { $rule.help = [ordered]@{ text = $r.Remediation markdown = "**Remediation:** $($r.Remediation)" } } $rules.Add($rule) } } # Build results array (only non-PASS results) $sarifResults = [System.Collections.Generic.List[hashtable]]::new() foreach ($r in $Results) { if ($r.Status -eq 'PASS') { continue } $sarifResult = [ordered]@{ ruleId = "CIS-$($r.ControlId)" ruleIndex = $ruleIndex[$r.ControlId] level = if ($levelMap[$r.Status]) { $levelMap[$r.Status] } else { 'warning' } message = [ordered]@{ text = if ($r.Details) { $r.Details } else { "$($r.Title): $($r.Status)" } } properties = [ordered]@{ controlId = $r.ControlId section = $r.Section subsection = $r.Subsection assessmentStatus = $r.AssessmentStatus profileLevel = $r.ProfileLevel severity = $r.Severity totalResources = $r.TotalResources passedResources = $r.PassedResources failedResources = $r.FailedResources } } if ($r.AffectedResources -and $r.AffectedResources.Count -gt 0) { $locations = [System.Collections.Generic.List[hashtable]]::new() foreach ($res in $r.AffectedResources) { $locations.Add([ordered]@{ physicalLocation = [ordered]@{ artifactLocation = [ordered]@{ uri = "azure://subscription/$(if ($Metadata.SubscriptionId) { $Metadata.SubscriptionId } else { 'unknown' })" } } message = [ordered]@{ text = $res } }) } $sarifResult.locations = $locations.ToArray() } $sarifResults.Add($sarifResult) } # Use centralized version from module scope $toolVersion = if ($script:CISModuleVersion) { $script:CISModuleVersion } else { '5.1.0' } $bmkVersion = if ($script:CISBenchmarkVersion) { $script:CISBenchmarkVersion } else { 'v5.0.0' } $benchmarkLabel = "CIS Microsoft Azure Foundations Benchmark $bmkVersion" # Build SARIF document $sarif = [ordered]@{ '$schema' = 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json' version = '2.1.0' runs = @( [ordered]@{ tool = [ordered]@{ driver = [ordered]@{ name = 'CIS Azure Benchmark' version = $toolVersion semanticVersion = $toolVersion informationUri = 'https://www.cisecurity.org/benchmark/azure' rules = $rules.ToArray() } } results = $sarifResults.ToArray() invocations = @( [ordered]@{ executionSuccessful = $true startTimeUtc = if ($Metadata.ScanTimestamp) { $Metadata.ScanTimestamp } else { [DateTime]::UtcNow.ToString('o') } } ) properties = [ordered]@{ subscriptionName = if ($Metadata.SubscriptionName) { $Metadata.SubscriptionName } else { 'N/A' } subscriptionId = if ($Metadata.SubscriptionId) { $Metadata.SubscriptionId } else { 'N/A' } tenantId = if ($Metadata.TenantId) { $Metadata.TenantId } else { 'N/A' } benchmarkVersion = $benchmarkLabel } } ) } $sarifJson = $sarif | ConvertTo-Json -Depth 20 -Compress:$false $sarifJson | Out-File -FilePath $OutputPath -Encoding UTF8 -Force Write-Verbose "SARIF report written to: $OutputPath" return $OutputPath } |