mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-03-03 15:02:35 +02:00
feat(checkmarxExecuteScan): convert Checkmarx xml report to SARIF (#3696)
* feat(checkmarxExecuteScan): sarif conversion for Checkmarx XML reports * feat(checkmarxExecuteScan): added taxonomies and similarityID * fix(checkmarxExecuteScan): proper handling of ruleId and ruleIndex * fix(sarif): mistype in checkmarx properties * fix(checkmarxExecuteScan): fixed occasional panics when handling audit comment * chore(sarif): proper variable naming * chore(code): fix missing and unrecognized comments * trigger PR * fix(format): extra space Co-authored-by: Sven Merk <33895725+nevskrem@users.noreply.github.com>
This commit is contained in:
parent
6b6208a35c
commit
3c55d3c99c
@ -322,6 +322,20 @@ func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx
|
||||
}
|
||||
reports = append(reports, piperutils.Path{Target: xmlReportName})
|
||||
|
||||
// generate sarif report
|
||||
if config.ConvertToSarif {
|
||||
log.Entry().Info("Calling conversion to SARIF function.")
|
||||
sarif, err := checkmarx.ConvertCxxmlToSarif(xmlReportName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate SARIF")
|
||||
}
|
||||
paths, err := checkmarx.WriteSarif(sarif)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write sarif")
|
||||
}
|
||||
reports = append(reports, paths...)
|
||||
}
|
||||
|
||||
// create toolrecord
|
||||
toolRecordFileName, err := createToolRecordCx(utils.GetWorkspace(), config, results)
|
||||
if err != nil {
|
||||
|
@ -52,6 +52,7 @@ type checkmarxExecuteScanOptions struct {
|
||||
VulnerabilityThresholdUnit string `json:"vulnerabilityThresholdUnit,omitempty"`
|
||||
IsOptimizedAndScheduled bool `json:"isOptimizedAndScheduled,omitempty"`
|
||||
CreateResultIssue bool `json:"createResultIssue,omitempty"`
|
||||
ConvertToSarif bool `json:"convertToSarif,omitempty"`
|
||||
}
|
||||
|
||||
type checkmarxExecuteScanInflux struct {
|
||||
@ -356,6 +357,7 @@ func addCheckmarxExecuteScanFlags(cmd *cobra.Command, stepConfig *checkmarxExecu
|
||||
cmd.Flags().StringVar(&stepConfig.VulnerabilityThresholdUnit, "vulnerabilityThresholdUnit", `percentage`, "The unit for the threshold to apply.")
|
||||
cmd.Flags().BoolVar(&stepConfig.IsOptimizedAndScheduled, "isOptimizedAndScheduled", false, "Whether the pipeline runs in optimized mode and the current execution is a scheduled one")
|
||||
cmd.Flags().BoolVar(&stepConfig.CreateResultIssue, "createResultIssue", false, "Activate creation of a result issue in GitHub.")
|
||||
cmd.Flags().BoolVar(&stepConfig.ConvertToSarif, "convertToSarif", false, "[BETA] Convert the Checkmarx XML (Cxxml) scan results to the open SARIF standard. Uploaded through Cumulus later on.")
|
||||
|
||||
cmd.MarkFlagRequired("password")
|
||||
cmd.MarkFlagRequired("projectName")
|
||||
@ -705,6 +707,15 @@ func checkmarxExecuteScanMetadata() config.StepData {
|
||||
Aliases: []config.Alias{},
|
||||
Default: false,
|
||||
},
|
||||
{
|
||||
Name: "convertToSarif",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "bool",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Outputs: config.StepOutputs{
|
||||
|
290
pkg/checkmarx/cxxml_to_sarif.go
Normal file
290
pkg/checkmarx/cxxml_to_sarif.go
Normal file
@ -0,0 +1,290 @@
|
||||
package checkmarx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/format"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// CxXMLResults : This struct encapsulates everyting in the Cx XML document
|
||||
type CxXMLResults struct {
|
||||
XMLName xml.Name `xml:"CxXMLResults"`
|
||||
InitiatorName string `xml:"InitiatorName,attr"`
|
||||
Owner string `xml:"Owner,attr"`
|
||||
ScanID string `xml:"ScanId,attr"`
|
||||
ProjectID string `xml:"ProjectId,attr"`
|
||||
ProjectName string `xml:"ProjectName,attr"`
|
||||
TeamFullPathOnReportDate string `xml:"TeamFullPathOnReportDate,attr"`
|
||||
DeepLink string `xml:"DeepLink,attr"`
|
||||
ScanStart string `xml:"ScanStart,attr"`
|
||||
Preset string `xml:"Preset,attr"`
|
||||
ScanTime string `xml:"ScanTime,attr"`
|
||||
LinesOfCodeScanned string `xml:"LinesOfCodeScanned,attr"`
|
||||
FilesScanned string `xml:"FilesScanned,attr"`
|
||||
ReportCreationTime string `xml:"ReportCreationTime,attr"`
|
||||
Team string `xml:"Team,attr"`
|
||||
CheckmarxVersion string `xml:"CheckmarxVersion,attr"`
|
||||
ScanComments string `xml:"ScanComments,attr"`
|
||||
ScanType string `xml:"ScanType,attr"`
|
||||
SourceOrigin string `xml:"SourceOrigin,attr"`
|
||||
Visibility string `xml:"Visibility,attr"`
|
||||
Query []CxxmlQuery `xml:"Query"`
|
||||
}
|
||||
|
||||
// CxxmlQuery CxxmlQuery
|
||||
type CxxmlQuery struct {
|
||||
XMLName xml.Name `xml:"Query"`
|
||||
ID string `xml:"id,attr"`
|
||||
Categories string `xml:"categories,attr"`
|
||||
CweID string `xml:"cweId,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Group string `xml:"group,attr"`
|
||||
Severity string `xml:"Severity,attr"`
|
||||
Language string `xml:"Language,attr"`
|
||||
LanguageHash string `xml:"LanguageHash,attr"`
|
||||
LanguageChangeDate string `xml:"LanguageChangeDate,attr"`
|
||||
SeverityIndex int `xml:"SeverityIndex,attr"`
|
||||
QueryPath string `xml:"QueryPath,attr"`
|
||||
QueryVersionCode string `xml:"QueryVersionCode,attr"`
|
||||
Result []CxxmlResult `xml:"Result"`
|
||||
}
|
||||
|
||||
// CxxmlResult CxxmlResult
|
||||
type CxxmlResult struct {
|
||||
XMLName xml.Name `xml:"Result"`
|
||||
NodeID string `xml:"NodeId,attr"`
|
||||
FileName string `xml:"FileName,attr"`
|
||||
Status string `xml:"Status,attr"`
|
||||
Line int `xml:"Line,attr"`
|
||||
Column int `xml:"Column,attr"`
|
||||
FalsePositive bool `xml:"FalsePositive,attr"`
|
||||
Severity string `xml:"Severity,attr"`
|
||||
AssignToUser string `xml:"AssignToUser,attr"`
|
||||
State int `xml:"state,attr"`
|
||||
Remark string `xml:"Remark,attr"`
|
||||
DeepLink string `xml:"DeepLink,attr"`
|
||||
SeverityIndex int `xml:"SeverityIndex,attr"`
|
||||
StatusIndex int `xml:"StatusIndex,attr"`
|
||||
DetectionDate string `xml:"DetectionDate,attr"`
|
||||
Path Path `xml:"Path"`
|
||||
}
|
||||
|
||||
// Path Path
|
||||
type Path struct {
|
||||
XMLName xml.Name `xml:"Path"`
|
||||
ResultID string `xml:"ResultId,attr"`
|
||||
PathID int `xml:"PathId,attr"`
|
||||
SimilarityID string `xml:"SimilarityId,attr"`
|
||||
SourceMethod string `xml:"SourceMethod,attr"`
|
||||
DestinationMethod string `xml:"DestinationMethod,attr"`
|
||||
PathNode []PathNode `xml:"PathNode"`
|
||||
}
|
||||
|
||||
// PathNode PathNode
|
||||
type PathNode struct {
|
||||
XMLName xml.Name `xml:"PathNode"`
|
||||
FileName string `xml:"FileName"`
|
||||
Line int `xml:"Line"`
|
||||
Column int `xml:"Column"`
|
||||
NodeID int `xml:"NodeId"`
|
||||
Name string `xml:"Name"`
|
||||
Type string `xml:"Type"`
|
||||
Length int `xml:"Length"`
|
||||
Snippet Snippet `xml:"Snippet"`
|
||||
}
|
||||
|
||||
// Snippet Snippet
|
||||
type Snippet struct {
|
||||
XMLName xml.Name `xml:"Snippet"`
|
||||
Line Line `xml:"Line"`
|
||||
}
|
||||
|
||||
// Line Line
|
||||
type Line struct {
|
||||
XMLName xml.Name `xml:"Line"`
|
||||
Number int `xml:"Number"`
|
||||
Code string `xml:"Code"`
|
||||
}
|
||||
|
||||
// ConvertCxxmlToSarif is the entrypoint for the Parse function
|
||||
func ConvertCxxmlToSarif(xmlReportName string) (format.SARIF, error) {
|
||||
var sarif format.SARIF
|
||||
data, err := ioutil.ReadFile(xmlReportName)
|
||||
if err != nil {
|
||||
return sarif, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
log.Entry().Error("Error reading audit file at " + xmlReportName + ". This might be that the file is missing, corrupted, or too large. Aborting procedure.")
|
||||
err := errors.New("cannot read audit file")
|
||||
return sarif, err
|
||||
}
|
||||
|
||||
log.Entry().Debug("Calling Parse.")
|
||||
return Parse(data)
|
||||
}
|
||||
|
||||
// Parse function
|
||||
func Parse(data []byte) (format.SARIF, error) {
|
||||
reader := bytes.NewReader(data)
|
||||
decoder := xml.NewDecoder(reader)
|
||||
|
||||
var cxxml CxXMLResults
|
||||
err := decoder.Decode(&cxxml)
|
||||
if err != nil {
|
||||
return format.SARIF{}, err
|
||||
}
|
||||
|
||||
// Process sarif
|
||||
var sarif format.SARIF
|
||||
sarif.Schema = "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json"
|
||||
sarif.Version = "2.1.0"
|
||||
var checkmarxRun format.Runs
|
||||
checkmarxRun.ColumnKind = "utf16CodeUnits"
|
||||
sarif.Runs = append(sarif.Runs, checkmarxRun)
|
||||
rulesArray := []format.SarifRule{}
|
||||
baseURL := "https://" + strings.Split(cxxml.DeepLink, "/")[2] + "CxWebClient/ScanQueryDescription.aspx?"
|
||||
cweIdsForTaxonomies := make(map[string]int) //use a map to avoid duplicates
|
||||
cweCounter := 0
|
||||
|
||||
//CxXML files contain a CxXMLResults > Query object, which represents a broken rule or type of vuln
|
||||
//This Query object contains a list of Result objects, each representing an occurence
|
||||
//Each Result object contains a ResultPath, which represents the exact location of the occurence (the "Snippet")
|
||||
for i := 0; i < len(cxxml.Query); i++ {
|
||||
//add cweid to array
|
||||
cweIdsForTaxonomies[cxxml.Query[i].CweID] = cweCounter
|
||||
cweCounter = cweCounter + 1
|
||||
for j := 0; j < len(cxxml.Query[i].Result); j++ {
|
||||
result := *new(format.Results)
|
||||
|
||||
//General
|
||||
result.RuleID = cxxml.Query[i].ID
|
||||
result.RuleIndex = cweIdsForTaxonomies[cxxml.Query[i].CweID]
|
||||
result.Level = "none"
|
||||
msg := new(format.Message)
|
||||
msg.Text = cxxml.Query[i].Categories
|
||||
result.Message = msg
|
||||
analysisTarget := new(format.ArtifactLocation)
|
||||
analysisTarget.URI = cxxml.Query[i].Result[j].FileName
|
||||
result.AnalysisTarget = analysisTarget
|
||||
if cxxml.Query[i].Name != "" {
|
||||
msg := new(format.Message)
|
||||
msg.Text = cxxml.Query[i].Name
|
||||
}
|
||||
//Locations
|
||||
for k := 0; k < len(cxxml.Query[i].Result[j].Path.PathNode); k++ {
|
||||
loc := *new(format.Location)
|
||||
loc.PhysicalLocation.ArtifactLocation.URI = cxxml.Query[i].Result[j].FileName
|
||||
loc.PhysicalLocation.Region.StartLine = cxxml.Query[i].Result[j].Path.PathNode[k].Line
|
||||
snip := new(format.SnippetSarif)
|
||||
snip.Text = cxxml.Query[i].Result[j].Path.PathNode[k].Snippet.Line.Code
|
||||
loc.PhysicalLocation.Region.Snippet = snip
|
||||
loc.PhysicalLocation.ContextRegion.StartLine = cxxml.Query[i].Result[j].Path.PathNode[k].Line
|
||||
loc.PhysicalLocation.ContextRegion.EndLine = cxxml.Query[i].Result[j].Path.PathNode[k].Line
|
||||
loc.PhysicalLocation.ContextRegion.Snippet = snip
|
||||
result.Locations = append(result.Locations, loc)
|
||||
|
||||
//Related Locations
|
||||
relatedLocation := *new(format.RelatedLocation)
|
||||
relatedLocation.ID = k + 1
|
||||
relatedLocation.PhysicalLocation = *new(format.RelatedPhysicalLocation)
|
||||
relatedLocation.PhysicalLocation.ArtifactLocation = loc.PhysicalLocation.ArtifactLocation
|
||||
relatedLocation.PhysicalLocation.Region = *new(format.RelatedRegion)
|
||||
relatedLocation.PhysicalLocation.Region.StartLine = loc.PhysicalLocation.Region.StartLine
|
||||
relatedLocation.PhysicalLocation.Region.StartColumn = cxxml.Query[i].Result[j].Path.PathNode[k].Column
|
||||
result.RelatedLocations = append(result.RelatedLocations, relatedLocation)
|
||||
|
||||
}
|
||||
|
||||
//Properties
|
||||
props := new(format.SarifProperties)
|
||||
props.Audited = false
|
||||
if cxxml.Query[i].Result[j].Remark != "" {
|
||||
props.Audited = true
|
||||
}
|
||||
props.CheckmarxSimilarityID = cxxml.Query[i].Result[j].Path.SimilarityID
|
||||
props.InstanceID = cxxml.Query[i].Result[j].Path.ResultID + "-" + strconv.Itoa(cxxml.Query[i].Result[j].Path.PathID)
|
||||
props.ToolSeverity = cxxml.Query[i].Result[j].Severity
|
||||
props.ToolSeverityIndex = cxxml.Query[i].Result[j].SeverityIndex
|
||||
props.ToolStateIndex = cxxml.Query[i].Result[j].State
|
||||
switch cxxml.Query[i].Result[j].State {
|
||||
case 1:
|
||||
props.ToolState = "NotExploitable"
|
||||
break
|
||||
case 2:
|
||||
props.ToolState = "Confirmed"
|
||||
break
|
||||
case 3:
|
||||
props.ToolState = "Urgent"
|
||||
break
|
||||
case 4:
|
||||
props.ToolState = "ProposedNotExploitable"
|
||||
break
|
||||
default:
|
||||
props.ToolState = "ToVerify" // Includes case 0
|
||||
break
|
||||
}
|
||||
props.ToolAuditMessage = ""
|
||||
if cxxml.Query[i].Result[j].Remark != "" {
|
||||
remarks := strings.Split(cxxml.Query[i].Result[j].Remark, "\n")
|
||||
messageCandidates := []string{}
|
||||
for cnd := 0; cnd < len(remarks); cnd++ {
|
||||
candidate := strings.Split(remarks[cnd], "]: ")
|
||||
if len(candidate) == 1 {
|
||||
if len(candidate[0]) != 0 {
|
||||
messageCandidates = append([]string{strings.Trim(candidate[0], "\r\n")}, messageCandidates...)
|
||||
}
|
||||
continue
|
||||
} else if len(candidate) == 0 {
|
||||
continue
|
||||
}
|
||||
messageCandidates = append([]string{strings.Trim(candidate[1], "\r\n")}, messageCandidates...) //Append in reverse order, trim to remove extra \r
|
||||
}
|
||||
props.ToolAuditMessage = strings.Join(messageCandidates, " \n ")
|
||||
}
|
||||
props.UnifiedAuditState = ""
|
||||
result.Properties = props
|
||||
|
||||
//Finalize
|
||||
sarif.Runs[0].Results = append(sarif.Runs[0].Results, result)
|
||||
}
|
||||
|
||||
//handle the rules array
|
||||
rule := *new(format.SarifRule)
|
||||
rule.ID = cxxml.Query[i].ID
|
||||
rule.Name = cxxml.Query[i].Name
|
||||
rule.HelpURI = baseURL + "queryID=" + cxxml.Query[i].ID + "&queryVersionCode=" + cxxml.Query[i].QueryVersionCode + "&queryTitle=" + cxxml.Query[i].Name
|
||||
rulesArray = append(rulesArray, rule)
|
||||
}
|
||||
|
||||
// Handle driver object
|
||||
tool := *new(format.Tool)
|
||||
tool.Driver = *new(format.Driver)
|
||||
tool.Driver.Name = "Checkmarx SCA"
|
||||
tool.Driver.Version = cxxml.CheckmarxVersion
|
||||
tool.Driver.Rules = rulesArray
|
||||
sarif.Runs[0].Tool = tool
|
||||
|
||||
//handle automationDetails
|
||||
sarif.Runs[0].AutomationDetails.Id = cxxml.DeepLink // Use deeplink to pass a maximum of information
|
||||
|
||||
//handle taxonomies
|
||||
//Only one exists apparently: CWE. It is fixed
|
||||
taxonomy := *new(format.Taxonomies)
|
||||
taxonomy.Name = "CWE"
|
||||
taxonomy.Organization = "MITRE"
|
||||
taxonomy.ShortDescription.Text = "The MITRE Common Weakness Enumeration"
|
||||
for key := range cweIdsForTaxonomies {
|
||||
taxa := *new(format.Taxa)
|
||||
taxa.Id = key
|
||||
taxonomy.Taxa = append(taxonomy.Taxa, taxa)
|
||||
}
|
||||
sarif.Runs[0].Taxonomies = append(sarif.Runs[0].Taxonomies, taxonomy)
|
||||
|
||||
return sarif, nil
|
||||
}
|
123
pkg/checkmarx/cxxml_to_sarif_test.go
Normal file
123
pkg/checkmarx/cxxml_to_sarif_test.go
Normal file
@ -0,0 +1,123 @@
|
||||
package checkmarx
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
|
||||
//Use a test CXXML doc
|
||||
testCxxml := `
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<CxXMLResults InitiatorName="Test" Owner="Tester" ScanId="1111111" ProjectId="11037" ProjectName="test-project" TeamFullPathOnReportDate="CxServer" DeepLink="https://cxtext.test/CxWebClient/ViewerMain.aspx?scanid=1111111&projectid=11037" ScanStart="Monday, March 7, 2022 1:58:49 PM" Preset="Checkmarx Default" ScanTime="00h:00m:22s" LinesOfCodeScanned="2682" FilesScanned="15" ReportCreationTime="Monday, March 7, 2022 1:59:25 PM" Team="SecurityTesting" CheckmarxVersion="9.4.3" ScanComments="Scan From Golang Script" ScanType="Incremental" SourceOrigin="LocalPath" Visibility="Public">
|
||||
<Query id="2415" categories="Dummy Categories" cweId="79" name="Dummy Vuln 1" group="JavaScript_High_Risk" Severity="High" Language="JavaScript" LanguageHash="9095271965336651" LanguageChangeDate="2022-01-16T00:00:00.0000000" SeverityIndex="3" QueryPath="JavaScript\Cx\JavaScript High Risk\Dummy Vuln 1:4" QueryVersionCode="14383421">
|
||||
<Result NodeId="143834211111" FileName="test/any.ts" Status="Recurrent" Line="7" Column="46" FalsePositive="False" Severity="High" AssignToUser="" state="0" Remark="" DeepLink="https://cxtext.test/CxWebClient/ViewerMain.aspx?" SeverityIndex="3" StatusIndex="1" DetectionDate="3/7/2022 12:21:30 PM">
|
||||
<Path ResultId="11037" PathId="4" SimilarityId="-1754124988" SourceMethod="function" DestinationMethod="function">
|
||||
<PathNode>
|
||||
<FileName>test/any.ts</FileName>
|
||||
<Line>7</Line>
|
||||
<Column>46</Column>
|
||||
<NodeId>1</NodeId>
|
||||
<Name>slice</Name>
|
||||
<Type></Type>
|
||||
<Length>5</Length>
|
||||
<Snippet>
|
||||
<Line>
|
||||
<Number>7</Number>
|
||||
<Code>dummy code</Code>
|
||||
</Line>
|
||||
</Snippet>
|
||||
</PathNode>
|
||||
<PathNode>
|
||||
<FileName>test/any.ts</FileName>
|
||||
<Line>7</Line>
|
||||
<Column>12</Column>
|
||||
<NodeId>2</NodeId>
|
||||
<Name>location</Name>
|
||||
<Type></Type>
|
||||
<Length>8</Length>
|
||||
<Snippet>
|
||||
<Line>
|
||||
<Number>7</Number>
|
||||
<Code>dummy code 2</Code>
|
||||
</Line>
|
||||
</Snippet>
|
||||
</PathNode>
|
||||
</Path>
|
||||
</Result>
|
||||
<Result NodeId="143834211112" FileName="html/ts.ts" Status="Recurrent" Line="7" Column="46" FalsePositive="False" Severity="High" AssignToUser="" state="0" Remark="" DeepLink="https://cxtext.test/CxWebClient/ViewerMain.aspx?" SeverityIndex="3" StatusIndex="1" DetectionDate="3/7/2022 12:21:30 PM">
|
||||
<Path ResultId="4845356468" PathId="5" SimilarityId="-1465173916" SourceMethod="function" DestinationMethod="function">
|
||||
<PathNode>
|
||||
<FileName>html/other.ts</FileName>
|
||||
<Line>7</Line>
|
||||
<Column>46</Column>
|
||||
<NodeId>1</NodeId>
|
||||
<Name>slice</Name>
|
||||
<Type></Type>
|
||||
<Length>5</Length>
|
||||
<Snippet>
|
||||
<Line>
|
||||
<Number>7</Number>
|
||||
<Code>dummycode</Code>
|
||||
</Line>
|
||||
</Snippet>
|
||||
</PathNode>
|
||||
<PathNode>
|
||||
<FileName>html/other.ts</FileName>
|
||||
<Line>7</Line>
|
||||
<Column>12</Column>
|
||||
<NodeId>2</NodeId>
|
||||
<Name>location</Name>
|
||||
<Type></Type>
|
||||
<Length>8</Length>
|
||||
<Snippet>
|
||||
<Line>
|
||||
<Number>7</Number>
|
||||
<Code>dummycode2</Code>
|
||||
</Line>
|
||||
</Snippet>
|
||||
</PathNode>
|
||||
</Path>
|
||||
</Result>
|
||||
</Query>
|
||||
<Query id="1111" categories="Dummy Categories" cweId="79" name="Dummy Vuln 2" group="JavaScript_High_Risk" Severity="High" Language="JavaScript" LanguageHash="9095271965336651" LanguageChangeDate="2022-01-16T00:00:00.0000000" SeverityIndex="3" QueryPath="JavaScript\Cx\JavaScript High Risk\Dummy Vuln 1:4" QueryVersionCode="14383421">
|
||||
<Result NodeId="143834211111" FileName="test/any.ts" Status="Recurrent" Line="7" Column="46" FalsePositive="False" Severity="High" AssignToUser="" state="2" Remark="Test-user Test-project, [Monday, March 7, 2022 1:57:26 PM]: Dummy comment
Test-user Test-project, [Monday, March 7, 2022 1:57:26 PM]: Changed status to Confirmed" DeepLink="https://cxtext.test/CxWebClient/ViewerMain.aspx?" SeverityIndex="3" StatusIndex="1" DetectionDate="3/7/2022 12:21:30 PM">
|
||||
<Path ResultId="11037" PathId="4" SimilarityId="-1754124988" SourceMethod="function" DestinationMethod="function">
|
||||
<PathNode>
|
||||
<FileName>test/any.ts</FileName>
|
||||
<Line>7</Line>
|
||||
<Column>46</Column>
|
||||
<NodeId>1</NodeId>
|
||||
<Name>slice</Name>
|
||||
<Type></Type>
|
||||
<Length>5</Length>
|
||||
<Snippet>
|
||||
<Line>
|
||||
<Number>7</Number>
|
||||
<Code>dummy code</Code>
|
||||
</Line>
|
||||
</Snippet>
|
||||
</PathNode>
|
||||
</Path>
|
||||
</Result>
|
||||
</Query>
|
||||
</CxXMLResults>
|
||||
`
|
||||
|
||||
t.Run("Valid config", func(t *testing.T) {
|
||||
sarif, err := Parse([]byte(testCxxml))
|
||||
assert.NoError(t, err, "error")
|
||||
assert.Equal(t, len(sarif.Runs[0].Results), 3)
|
||||
assert.Equal(t, len(sarif.Runs[0].Tool.Driver.Rules), 2)
|
||||
assert.Equal(t, sarif.Runs[0].Results[2].Properties.ToolState, "Confirmed")
|
||||
assert.Equal(t, sarif.Runs[0].Results[2].Properties.ToolAuditMessage, "Changed status to Confirmed \n Dummy comment")
|
||||
})
|
||||
|
||||
t.Run("Missing data", func(t *testing.T) {
|
||||
_, err := Parse([]byte{})
|
||||
assert.Error(t, err, "EOF")
|
||||
})
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package checkmarx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -9,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/format"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"github.com/SAP/jenkins-library/pkg/reporting"
|
||||
@ -182,6 +184,35 @@ func WriteJSONReport(jsonReport CheckmarxReportData) ([]piperutils.Path, error)
|
||||
return reportPaths, nil
|
||||
}
|
||||
|
||||
// WriteSarif writes a json file to disk as a .sarif if it respects the specification declared in format.SARIF
|
||||
func WriteSarif(sarif format.SARIF) ([]piperutils.Path, error) {
|
||||
utils := piperutils.Files{}
|
||||
reportPaths := []piperutils.Path{}
|
||||
|
||||
sarifReportPath := filepath.Join(ReportsDirectory, "result.sarif")
|
||||
// Ensure reporting directory exists
|
||||
if err := utils.MkdirAll(ReportsDirectory, 0777); err != nil {
|
||||
return reportPaths, errors.Wrapf(err, "failed to create report directory")
|
||||
}
|
||||
|
||||
// HTML characters will most likely be present: we need to use encode: create a buffer to hold JSON data
|
||||
buffer := new(bytes.Buffer)
|
||||
// create JSON encoder for buffer
|
||||
bufEncoder := json.NewEncoder(buffer)
|
||||
// set options
|
||||
bufEncoder.SetEscapeHTML(false)
|
||||
bufEncoder.SetIndent("", " ")
|
||||
//encode to buffer
|
||||
bufEncoder.Encode(sarif)
|
||||
if err := utils.FileWrite(sarifReportPath, buffer.Bytes(), 0666); err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return reportPaths, errors.Wrapf(err, "failed to write Checkmarx SARIF report")
|
||||
}
|
||||
reportPaths = append(reportPaths, piperutils.Path{Name: "Checkmarx SARIF Report", Target: sarifReportPath})
|
||||
|
||||
return reportPaths, nil
|
||||
}
|
||||
|
||||
func WriteCustomReports(scanReport reporting.ScanReport, projectName, projectID string) ([]piperutils.Path, error) {
|
||||
utils := piperutils.Files{}
|
||||
reportPaths := []piperutils.Path{}
|
||||
|
@ -76,17 +76,18 @@ type LogicalLocation struct {
|
||||
|
||||
// SarifProperties adding additional information/context to the finding
|
||||
type SarifProperties struct {
|
||||
InstanceID string `json:"instanceID,omitempty"`
|
||||
InstanceSeverity string `json:"instanceSeverity,omitempty"`
|
||||
Confidence string `json:"confidence,omitempty"`
|
||||
FortifyCategory string `json:"fortifyCategory,omitempty"`
|
||||
Audited bool `json:"audited"`
|
||||
ToolSeverity string `json:"toolSeverity"`
|
||||
ToolSeverityIndex int `json:"toolSeverityIndex"`
|
||||
ToolState string `json:"toolState"`
|
||||
ToolStateIndex int `json:"toolStateIndex"`
|
||||
ToolAuditMessage string `json:"toolAuditMessage"`
|
||||
UnifiedAuditState string `json:"unifiedAuditState"`
|
||||
InstanceID string `json:"instanceID,omitempty"`
|
||||
InstanceSeverity string `json:"instanceSeverity,omitempty"`
|
||||
Confidence string `json:"confidence,omitempty"`
|
||||
FortifyCategory string `json:"fortifyCategory,omitempty"`
|
||||
CheckmarxSimilarityID string `json:"checkmarxSimilarityID,omitempty"`
|
||||
Audited bool `json:"audited"`
|
||||
ToolSeverity string `json:"toolSeverity"`
|
||||
ToolSeverityIndex int `json:"toolSeverityIndex"`
|
||||
ToolState string `json:"toolState"`
|
||||
ToolStateIndex int `json:"toolStateIndex"`
|
||||
ToolAuditMessage string `json:"toolAuditMessage"`
|
||||
UnifiedAuditState string `json:"unifiedAuditState"`
|
||||
}
|
||||
|
||||
// Tool these structs are relevant to the Tool object
|
||||
@ -275,7 +276,7 @@ type AutomationDetails struct {
|
||||
|
||||
// Taxonomies These structs are relevant to the taxonomies object
|
||||
type Taxonomies struct {
|
||||
Guid string `json:"guid"`
|
||||
GUID string `json:"guid,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Organization string `json:"organization"`
|
||||
ShortDescription Message `json:"shortDescription"`
|
||||
|
@ -1015,7 +1015,7 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
||||
//handle taxonomies
|
||||
//Only one exists apparently: CWE. It is fixed
|
||||
taxonomy := *new(format.Taxonomies)
|
||||
taxonomy.Guid = "25F72D7E-8A92-459D-AD67-64853F788765"
|
||||
taxonomy.GUID = "25F72D7E-8A92-459D-AD67-64853F788765"
|
||||
taxonomy.Name = "CWE"
|
||||
taxonomy.Organization = "MITRE"
|
||||
taxonomy.ShortDescription.Text = "The MITRE Common Weakness Enumeration"
|
||||
|
@ -319,6 +319,13 @@ spec:
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: false
|
||||
- name: convertToSarif
|
||||
type: bool
|
||||
description: "[BETA] Convert the Checkmarx XML (Cxxml) scan results to the open SARIF standard. Uploaded through Cumulus later on."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
outputs:
|
||||
resources:
|
||||
- name: influx
|
||||
|
Loading…
x
Reference in New Issue
Block a user