You've already forked sap-jenkins-library
							
							
				mirror of
				https://github.com/SAP/jenkins-library.git
				synced 2025-10-30 23:57:50 +02:00 
			
		
		
		
	feat(fortifyExecuteScan): optimization of the SARIF conversion code (#3710)
* feat(fortifyExecuteScan): query SSC once for batch audit data * fix(fortifyExecuteScan): check audit data length in all cases * feat(fortifyExecuteScan): in fpr_to_sarif, better detection of error cases, unit tests * fix(log): comment useless error message * fix(fortifyExecuteScan): clarify log message * fix(fortifyExecuteScan): adapt unit tests
This commit is contained in:
		| @@ -278,6 +278,12 @@ func (f *fortifyMock) GenerateQGateReport(projectID, projectVersionID, reportTem | ||||
| func (f *fortifyMock) GetReportDetails(id int64) (*models.SavedReport, error) { | ||||
| 	return &models.SavedReport{Status: "PROCESS_COMPLETE"}, nil | ||||
| } | ||||
| func (f *fortifyMock) GetAllIssueDetails(projectVersionId int64) ([]*models.ProjectVersionIssue, error) { | ||||
| 	exploitable := "Exploitable" | ||||
| 	friority := "High" | ||||
| 	hascomments := true | ||||
| 	return []*models.ProjectVersionIssue{{ID: 1111, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}, {ID: 1112, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}}, nil | ||||
| } | ||||
| func (f *fortifyMock) GetIssueDetails(projectVersionId int64, issueInstanceId string) ([]*models.ProjectVersionIssue, error) { | ||||
| 	exploitable := "Exploitable" | ||||
| 	friority := "High" | ||||
|   | ||||
| @@ -66,6 +66,7 @@ type System interface { | ||||
| 	GenerateQGateReport(projectID, projectVersionID, reportTemplateID int64, projectName, projectVersionName, reportFormat string) (*models.SavedReport, error) | ||||
| 	GetReportDetails(id int64) (*models.SavedReport, error) | ||||
| 	GetIssueDetails(projectVersionId int64, issueInstanceId string) ([]*models.ProjectVersionIssue, error) | ||||
| 	GetAllIssueDetails(projectVersionId int64) ([]*models.ProjectVersionIssue, error) | ||||
| 	GetIssueComments(parentId int64) ([]*models.IssueAuditComment, error) | ||||
| 	UploadResultFile(endpoint, file string, projectVersionID int64) error | ||||
| 	DownloadReportFile(endpoint string, reportID int64) ([]byte, error) | ||||
| @@ -645,6 +646,19 @@ func (sys *SystemInstance) GetIssueDetails(projectVersionId int64, issueInstance | ||||
| 	return result.GetPayload().Data, nil | ||||
| } | ||||
|  | ||||
| // GetAllIssueDetails returns the details of all issues of the project with id projectVersionId | ||||
| func (sys *SystemInstance) GetAllIssueDetails(projectVersionId int64) ([]*models.ProjectVersionIssue, error) { | ||||
| 	var limit int32 | ||||
| 	limit = -1 | ||||
| 	params := &issue_of_project_version_controller.ListIssueOfProjectVersionParams{ParentID: projectVersionId, Limit: &limit} | ||||
| 	params.WithTimeout(sys.timeout) | ||||
| 	result, err := sys.client.IssueOfProjectVersionController.ListIssueOfProjectVersion(params, sys) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return result.GetPayload().Data, nil | ||||
| } | ||||
|  | ||||
| // GetIssueComments returns the details of an issue comments with its unique parentId | ||||
| func (sys *SystemInstance) GetIssueComments(parentId int64) ([]*models.IssueAuditComment, error) { | ||||
| 	params := &issue_audit_comment_of_issue_controller.ListIssueAuditCommentOfIssueParams{ParentID: parentId} | ||||
|   | ||||
| @@ -545,6 +545,23 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe | ||||
| 		return format.SARIF{}, err | ||||
| 	} | ||||
|  | ||||
| 	//Create an object containing all audit data | ||||
| 	log.Entry().Debug("Querying Fortify SSC for batch audit data") | ||||
| 	oneRequestPerIssueMode := false | ||||
| 	var auditData []*models.ProjectVersionIssue | ||||
| 	if sys != nil { | ||||
| 		auditData, err = sys.GetAllIssueDetails(projectVersion.ID) | ||||
| 		if err != nil { | ||||
| 			log.Entry().WithError(err).Error("failed to get all audit data, defaulting to one-request-per-issue basis") | ||||
| 			oneRequestPerIssueMode = true | ||||
| 		} else { | ||||
| 			log.Entry().Debug("Request successful, data frame size: ", len(auditData), " audits") | ||||
| 		} | ||||
| 	} else { | ||||
| 		log.Entry().Error("no system instance found, lookup impossible") | ||||
| 		oneRequestPerIssueMode = true | ||||
| 	} | ||||
|  | ||||
| 	//Now, we handle the 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" | ||||
| @@ -714,7 +731,7 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe | ||||
| 			prop.ToolState = "Not an Issue" | ||||
| 			prop.ToolStateIndex = 1 | ||||
| 		} else if sys != nil { | ||||
| 			if err := integrateAuditData(prop, fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceID, sys, project, projectVersion, filterSet); err != nil { | ||||
| 			if err := integrateAuditData(prop, fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceID, sys, project, projectVersion, auditData, filterSet, oneRequestPerIssueMode); err != nil { | ||||
| 				log.Entry().Debug(err) | ||||
| 				prop.Audited = false | ||||
| 				prop.ToolState = "Unknown" | ||||
| @@ -1029,7 +1046,7 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe | ||||
| 	return sarif, nil | ||||
| } | ||||
|  | ||||
| func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string, sys System, project *models.Project, projectVersion *models.ProjectVersion, filterSet *models.FilterSet) error { | ||||
| func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string, sys System, project *models.Project, projectVersion *models.ProjectVersion, auditData []*models.ProjectVersionIssue, filterSet *models.FilterSet, oneRequestPerIssue bool) error { | ||||
| 	if sys == nil { | ||||
| 		err := errors.New("no system instance, lookup impossible for " + issueInstanceID) | ||||
| 		return err | ||||
| @@ -1038,13 +1055,24 @@ func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string | ||||
| 		err := errors.New("project or projectVersion is undefined: lookup aborted for " + issueInstanceID) | ||||
| 		return err | ||||
| 	} | ||||
| 	data, err := sys.GetIssueDetails(projectVersion.ID, issueInstanceID) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	var data []*models.ProjectVersionIssue | ||||
| 	var err error | ||||
| 	if oneRequestPerIssue { | ||||
| 		log.Entry().Debug("operating in one-request-per-issue mode: looking up audit state of " + issueInstanceID) | ||||
| 		data, err = sys.GetIssueDetails(projectVersion.ID, issueInstanceID) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		for i := 0; i < len(auditData); i++ { | ||||
| 			if issueInstanceID == *auditData[i].IssueInstanceID { | ||||
| 				data = append(data, auditData[i]) | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	log.Entry().Debug("Looking up audit state of " + issueInstanceID) | ||||
| 	if len(data) != 1 { //issueInstanceID is supposedly unique so len(data) = 1 | ||||
| 		log.Entry().Error("not exactly 1 issue found, found " + fmt.Sprint(len(data))) | ||||
| 		//log.Entry().Error("not exactly 1 issue found, found " + fmt.Sprint(len(data))) | ||||
| 		return errors.New("not exactly 1 issue found, found " + fmt.Sprint(len(data))) | ||||
| 	} | ||||
| 	ruleProp.Audited = data[0].Audited | ||||
|   | ||||
| @@ -46,7 +46,7 @@ func TestParse(t *testing.T) { | ||||
|     <DefaultSeverity>5.0</DefaultSeverity> | ||||
|   </ClassInfo> | ||||
|   <InstanceInfo> | ||||
|     <InstanceID>DUMMY</InstanceID> | ||||
|     <InstanceID>DUMMYDUMMYDUMMY</InstanceID> | ||||
|     <InstanceSeverity>5.0</InstanceSeverity> | ||||
|     <Confidence>5.0</Confidence> | ||||
|   </InstanceInfo> | ||||
| @@ -77,7 +77,7 @@ func TestParse(t *testing.T) { | ||||
|     <DefaultSeverity>5.0</DefaultSeverity> | ||||
|   </ClassInfo> | ||||
|   <InstanceInfo> | ||||
|     <InstanceID>DUMMY</InstanceID> | ||||
|     <InstanceID>DUMMYDUMMYDUMMY</InstanceID> | ||||
|     <InstanceSeverity>5.0</InstanceSeverity> | ||||
|     <Confidence>5.0</Confidence> | ||||
|   </InstanceInfo> | ||||
| @@ -440,7 +440,8 @@ func TestIntegrateAuditData(t *testing.T) { | ||||
| 		ruleProp := *new(format.SarifProperties) | ||||
| 		project := models.Project{} | ||||
| 		projectVersion := models.ProjectVersion{ID: 11037} | ||||
| 		err := integrateAuditData(&ruleProp, "11037", sys, &project, &projectVersion, filterSet) | ||||
| 		auditData, _ := sys.GetAllIssueDetails(projectVersion.ID) | ||||
| 		err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, auditData, filterSet, false) | ||||
| 		assert.NoError(t, err, "error") | ||||
| 		assert.Equal(t, ruleProp.Audited, true) | ||||
| 		assert.Equal(t, ruleProp.ToolState, "Exploitable") | ||||
| @@ -454,14 +455,16 @@ func TestIntegrateAuditData(t *testing.T) { | ||||
| 	t.Run("Missing project", func(t *testing.T) { | ||||
| 		ruleProp := *new(format.SarifProperties) | ||||
| 		projectVersion := models.ProjectVersion{ID: 11037} | ||||
| 		err := integrateAuditData(&ruleProp, "11037", sys, nil, &projectVersion, filterSet) | ||||
| 		auditData, _ := sys.GetAllIssueDetails(projectVersion.ID) | ||||
| 		err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, nil, &projectVersion, auditData, filterSet, false) | ||||
| 		assert.Error(t, err, "project or projectVersion is undefined: lookup aborted for 11037") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Missing project version", func(t *testing.T) { | ||||
| 		ruleProp := *new(format.SarifProperties) | ||||
| 		project := models.Project{} | ||||
| 		err := integrateAuditData(&ruleProp, "11037", sys, &project, nil, filterSet) | ||||
| 		auditData, _ := sys.GetAllIssueDetails(11037) | ||||
| 		err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, nil, auditData, filterSet, false) | ||||
| 		assert.Error(t, err, "project or projectVersion is undefined: lookup aborted for 11037") | ||||
| 	}) | ||||
|  | ||||
| @@ -469,15 +472,41 @@ func TestIntegrateAuditData(t *testing.T) { | ||||
| 		ruleProp := *new(format.SarifProperties) | ||||
| 		project := models.Project{} | ||||
| 		projectVersion := models.ProjectVersion{ID: 11037} | ||||
| 		err := integrateAuditData(&ruleProp, "11037", nil, &project, &projectVersion, filterSet) | ||||
| 		assert.Error(t, err, "no system instance, lookup impossible for 11037") | ||||
| 		auditData, _ := sys.GetAllIssueDetails(projectVersion.ID) | ||||
| 		err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", nil, &project, &projectVersion, auditData, filterSet, false) | ||||
| 		assert.Error(t, err, "no system instance, lookup impossible for DUMMYDUMMYDUMMY") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Missing filterSet", func(t *testing.T) { | ||||
| 		ruleProp := *new(format.SarifProperties) | ||||
| 		project := models.Project{} | ||||
| 		projectVersion := models.ProjectVersion{ID: 11037} | ||||
| 		err := integrateAuditData(&ruleProp, "11037", sys, &project, &projectVersion, nil) | ||||
| 		auditData, _ := sys.GetAllIssueDetails(projectVersion.ID) | ||||
| 		err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, auditData, nil, false) | ||||
| 		assert.Error(t, err, "no filter set defined, category will be missing from 11037") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Missing Audit Data", func(t *testing.T) { | ||||
| 		ruleProp := *new(format.SarifProperties) | ||||
| 		project := models.Project{} | ||||
| 		projectVersion := models.ProjectVersion{ID: 11037} | ||||
| 		err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, nil, filterSet, false) | ||||
| 		assert.Error(t, err, "not exactly 1 issue found, found 0") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Successful lookup in oneRequestPerInstance mode", func(t *testing.T) { | ||||
| 		ruleProp := *new(format.SarifProperties) | ||||
| 		project := models.Project{} | ||||
| 		projectVersion := models.ProjectVersion{ID: 11037} | ||||
| 		err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, nil, filterSet, true) | ||||
| 		assert.NoError(t, err, "error") | ||||
| 		assert.Equal(t, ruleProp.Audited, true) | ||||
| 		assert.Equal(t, ruleProp.ToolState, "Exploitable") | ||||
| 		assert.Equal(t, ruleProp.ToolStateIndex, 5) | ||||
| 		assert.Equal(t, ruleProp.ToolSeverity, "High") | ||||
| 		assert.Equal(t, ruleProp.ToolSeverityIndex, 3) | ||||
| 		assert.Equal(t, ruleProp.ToolAuditMessage, "Dummy comment.") | ||||
| 		assert.Equal(t, ruleProp.FortifyCategory, "Audit All") | ||||
| 	}) | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user