diff --git a/cmd/checkmarxExecuteScan.go b/cmd/checkmarxExecuteScan.go index e84aa1c79..d8defee98 100644 --- a/cmd/checkmarxExecuteScan.go +++ b/cmd/checkmarxExecuteScan.go @@ -325,7 +325,7 @@ func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx // generate sarif report if config.ConvertToSarif { log.Entry().Info("Calling conversion to SARIF function.") - sarif, err := checkmarx.ConvertCxxmlToSarif(xmlReportName) + sarif, err := checkmarx.ConvertCxxmlToSarif(sys, xmlReportName, scanID) if err != nil { return fmt.Errorf("failed to generate SARIF") } diff --git a/cmd/checkmarxExecuteScan_test.go b/cmd/checkmarxExecuteScan_test.go index ad16f06f7..4709c5482 100644 --- a/cmd/checkmarxExecuteScan_test.go +++ b/cmd/checkmarxExecuteScan_test.go @@ -144,6 +144,9 @@ func (sys *systemMock) CreateProject(string, string) (checkmarx.ProjectCreateRes func (sys *systemMock) CreateBranch(int, string) int { return 18 } +func (sys *systemMock) GetShortDescription(int, int) (checkmarx.ShortDescription, error) { + return checkmarx.ShortDescription{Text: "dummyText"}, nil +} func (sys *systemMock) GetPresets() []checkmarx.Preset { sys.getPresetsCalled = true return []checkmarx.Preset{{ID: 10078, Name: "SAP Java Default", OwnerName: "16"}, {ID: 10048, Name: "SAP JS Default", OwnerName: "16"}, {ID: 16, Name: "CX_Default", OwnerName: "16"}} @@ -199,6 +202,9 @@ func (sys *systemMockForExistingProject) GetResults(int) checkmarx.ResultsStatis func (sys *systemMockForExistingProject) GetScans(int) ([]checkmarx.ScanStatus, error) { return []checkmarx.ScanStatus{{IsIncremental: true}, {IsIncremental: true}, {IsIncremental: true}, {IsIncremental: false}}, nil } +func (sys *systemMockForExistingProject) GetShortDescription(int, int) (checkmarx.ShortDescription, error) { + return checkmarx.ShortDescription{Text: "dummyText"}, nil +} func (sys *systemMockForExistingProject) GetScanStatusAndDetail(int) (string, checkmarx.ScanStatusDetail) { return "Finished", checkmarx.ScanStatusDetail{Stage: "", Step: ""} } diff --git a/pkg/checkmarx/checkmarx.go b/pkg/checkmarx/checkmarx.go index e29c50cf4..fb6dba546 100644 --- a/pkg/checkmarx/checkmarx.go +++ b/pkg/checkmarx/checkmarx.go @@ -161,6 +161,10 @@ type SourceSettingsLink struct { URI string `json:"uri"` } +type ShortDescription struct { + Text string `json:"shortDescription"` +} + //DetailedResult - DetailedResult Structure type DetailedResult struct { XMLName xml.Name `xml:"CxXMLResults"` @@ -231,6 +235,7 @@ type System interface { GetProjectByID(projectID int) (Project, error) GetProjectsByNameAndTeam(projectName, teamID string) ([]Project, error) GetProjects() ([]Project, error) + GetShortDescription(scanID int, pathID int) (ShortDescription, error) GetTeams() []Team } @@ -652,6 +657,20 @@ func (sys *SystemInstance) GetReportStatus(reportID int) (ReportStatusResponse, return response, nil } +// GetShortDescription returns the short description for an issue with a scanID and pathID +func (sys *SystemInstance) GetShortDescription(scanID int, pathID int) (ShortDescription, error) { + var shortDescription ShortDescription + + data, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("/sast/scans/%v/results/%v/shortDescription", scanID, pathID), nil, nil) + if err != nil { + sys.logger.Errorf("Failed to get short description for scanID %v and pathID %v: %s", scanID, pathID, err) + return shortDescription, err + } + + json.Unmarshal(data, &shortDescription) + return shortDescription, nil +} + // DownloadReport downloads the report addressed by reportID and returns the XML contents func (sys *SystemInstance) DownloadReport(reportID int) ([]byte, error) { header := http.Header{} diff --git a/pkg/checkmarx/checkmarx_test.go b/pkg/checkmarx/checkmarx_test.go index 82132bd0f..e544c6b10 100644 --- a/pkg/checkmarx/checkmarx_test.go +++ b/pkg/checkmarx/checkmarx_test.go @@ -605,3 +605,20 @@ func TestGetProjectByName(t *testing.T) { assert.Equal(t, "Project1_PR-18", result[0].Name, "Result incorrect") }) } + +func TestGetShortDescription(t *testing.T) { + logger := log.Entry().WithField("package", "SAP/jenkins-library/pkg/checkmarx_test") + opts := piperHttp.ClientOptions{} + t.Run("test success", func(t *testing.T) { + myTestClient := senderMock{responseBody: `{"shortDescription":"This is a dummy short description."}`, httpStatusCode: 200} + sys := SystemInstance{serverURL: "https://cx.server.com", client: &myTestClient, logger: logger} + myTestClient.SetOptions(opts) + + shortDescription, err := sys.GetShortDescription(11037, 1) + + assert.NoError(t, err) + assert.Equal(t, "https://cx.server.com/cxrestapi/sast/scans/11037/results/1/shortDescription", myTestClient.urlCalled, "Called url incorrect") + assert.Equal(t, "GET", myTestClient.httpMethod, "HTTP method incorrect") + assert.Equal(t, "This is a dummy short description.", shortDescription.Text, "Description incorrect") + }) +} diff --git a/pkg/checkmarx/cxxml_to_sarif.go b/pkg/checkmarx/cxxml_to_sarif.go index 50e888905..1c9da38b5 100644 --- a/pkg/checkmarx/cxxml_to_sarif.go +++ b/pkg/checkmarx/cxxml_to_sarif.go @@ -114,7 +114,7 @@ type Line struct { } // ConvertCxxmlToSarif is the entrypoint for the Parse function -func ConvertCxxmlToSarif(xmlReportName string) (format.SARIF, error) { +func ConvertCxxmlToSarif(sys System, xmlReportName string, scanID int) (format.SARIF, error) { var sarif format.SARIF log.Entry().Debug("Reading audit file.") data, err := ioutil.ReadFile(xmlReportName) @@ -128,11 +128,11 @@ func ConvertCxxmlToSarif(xmlReportName string) (format.SARIF, error) { } log.Entry().Debug("Calling Parse.") - return Parse(data) + return Parse(sys, data, scanID) } // Parse function -func Parse(data []byte) (format.SARIF, error) { +func Parse(sys System, data []byte, scanID int) (format.SARIF, error) { reader := bytes.NewReader(data) decoder := xml.NewDecoder(reader) @@ -153,18 +153,33 @@ func Parse(data []byte) (format.SARIF, error) { baseURL := "https://" + strings.Split(cxxml.DeepLink, "/")[2] + "/CxWebClient/ScanQueryDescription.aspx?" cweIdsForTaxonomies := make(map[string]int) //use a map to avoid duplicates cweCounter := 0 + var apiDescription string //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") log.Entry().Debug("[SARIF] Now handling results.") for i := 0; i < len(cxxml.Query); i++ { + descriptionFetched := false //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) + // For rules later, fetch description + if !descriptionFetched { + if sys != nil { + apiShortDescription, err := sys.GetShortDescription(scanID, cxxml.Query[i].Result[j].Path.PathID) + if err != nil { + log.Entry().Error(err) + } else { + descriptionFetched = true + apiDescription = apiShortDescription.Text + } + } + } + //General result.RuleID = "checkmarx-" + cxxml.Query[i].ID result.RuleIndex = cweIdsForTaxonomies[cxxml.Query[i].CweID] @@ -294,7 +309,10 @@ func Parse(data []byte) (format.SARIF, error) { rule.Help.Text = rule.HelpURI rule.ShortDescription = new(format.Message) rule.ShortDescription.Text = cxxml.Query[i].Name - if cxxml.Query[i].Categories != "" { + if apiDescription != "" { + rule.FullDescription = new(format.Message) + rule.FullDescription.Text = apiDescription + } else if cxxml.Query[i].Categories != "" { rule.FullDescription = new(format.Message) rule.FullDescription.Text = cxxml.Query[i].Categories } diff --git a/pkg/checkmarx/cxxml_to_sarif_test.go b/pkg/checkmarx/cxxml_to_sarif_test.go index 80220feba..7684c4691 100644 --- a/pkg/checkmarx/cxxml_to_sarif_test.go +++ b/pkg/checkmarx/cxxml_to_sarif_test.go @@ -3,6 +3,8 @@ package checkmarx import ( "testing" + piperHttp "github.com/SAP/jenkins-library/pkg/http" + "github.com/SAP/jenkins-library/pkg/log" "github.com/stretchr/testify/assert" ) @@ -107,16 +109,34 @@ func TestParse(t *testing.T) { ` t.Run("Valid config", func(t *testing.T) { - sarif, err := Parse([]byte(testCxxml)) + opts := piperHttp.ClientOptions{} + logger := log.Entry().WithField("package", "SAP/jenkins-library/pkg/checkmarx_test") + myTestClient := senderMock{responseBody: `{"shortDescription":"This is a dummy short description."}`, httpStatusCode: 200} + sys := SystemInstance{serverURL: "https://cx.server.com", client: &myTestClient, logger: logger} + myTestClient.SetOptions(opts) + + sarif, err := Parse(&sys, []byte(testCxxml), 11037) 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") + assert.Equal(t, "This is a dummy short description.", sarif.Runs[0].Tool.Driver.Rules[0].FullDescription.Text) + }) + + t.Run("Missing sys", func(t *testing.T) { + + sarif, err := Parse(nil, []byte(testCxxml), 11037) + 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") + assert.Equal(t, "Dummy Categories", sarif.Runs[0].Tool.Driver.Rules[0].FullDescription.Text) }) t.Run("Missing data", func(t *testing.T) { - _, err := Parse([]byte{}) + _, err := Parse(nil, []byte{}, 11037) assert.Error(t, err, "EOF") })