1
0
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:
xgoffin
2022-04-07 13:11:52 +02:00
committed by GitHub
parent 5a4b7c9925
commit fb9792ad71
4 changed files with 92 additions and 15 deletions

View File

@@ -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"

View File

@@ -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}

View File

@@ -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

View File

@@ -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")
})
}