You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-09-16 09:26:22 +02:00
feat(fortifyExecuteScan): implement a system to limit the number of API calls upon request failures (#3818)
* feat(fortifyExecuteScan): add a max number of retries for API calls in SARIF conversion * feat(checkmarxExecuteScan): implement max number of retries on API call for descriptions in SARIF processing * feat(checkmarx/fortify): extra logging line when failing an API request in SARIF conversion * fix(fortifyExecuteScan): panic if undefined projectversion in sarif * fix(fortifyExecuteScan): logging improvement * fix(fortifyExecuteScan): wrong if condition caused crash * fix(fortifyExecuteScan): do not log if retries hit -1, adjust logging * fix(SARIF): commenting API calls for Checkmarx until a solution can be found for the API issues * feat(SARIF): add omitempty to extensions
This commit is contained in:
@@ -153,32 +153,40 @@ func Parse(sys System, data []byte, scanID int) (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
|
||||
//maxretries := 5
|
||||
|
||||
//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++ {
|
||||
var apiDescription string
|
||||
result := *new(format.Results)
|
||||
|
||||
// COMMENTED UNTIL CHECKMARX API WORK AS INTENDED
|
||||
// For rules later, fetch description
|
||||
if !descriptionFetched {
|
||||
/*if maxretries == 0 { // Don't spam logfile: only enter the loop if maxretries is positive, and only display the error if it hits 0
|
||||
log.Entry().Error("request failed: maximum number of retries reached, descriptions will no longer be fetched")
|
||||
maxretries = maxretries - 1
|
||||
} else if maxretries > 0 {
|
||||
if sys != nil {
|
||||
apiShortDescription, err := sys.GetShortDescription(scanID, cxxml.Query[i].Result[j].Path.PathID)
|
||||
if err != nil {
|
||||
maxretries = maxretries - 1
|
||||
log.Entry().Debug("request failed: remaining retries ", maxretries)
|
||||
log.Entry().Error(err)
|
||||
} else {
|
||||
descriptionFetched = true
|
||||
apiDescription = apiShortDescription.Text
|
||||
}
|
||||
} else {
|
||||
maxretries = maxretries - 1
|
||||
log.Entry().Debug("request failed: no system instance, remaining retries ", maxretries)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
//General
|
||||
result.RuleID = "checkmarx-" + cxxml.Query[i].ID
|
||||
@@ -186,13 +194,13 @@ func Parse(sys System, data []byte, scanID int) (format.SARIF, error) {
|
||||
result.Level = "none"
|
||||
msg := new(format.Message)
|
||||
//msg.Text = cxxml.Query[i].Name + ": " + cxxml.Query[i].Categories
|
||||
msg.Text = cxxml.Query[i].Name
|
||||
if apiDescription != "" {
|
||||
msg.Text = apiDescription
|
||||
} else {
|
||||
msg.Text = cxxml.Query[i].Name
|
||||
}
|
||||
result.Message = msg
|
||||
//analysisTarget := new(format.ArtifactLocation)
|
||||
//analysisTarget.URI = cxxml.Query[i].Result[j].FileName
|
||||
//analysisTarget.Index = index
|
||||
|
||||
//result.AnalysisTarget = analysisTarget
|
||||
if cxxml.Query[i].Name != "" {
|
||||
msg := new(format.Message)
|
||||
msg.Text = cxxml.Query[i].Name
|
||||
@@ -200,22 +208,6 @@ func Parse(sys System, data []byte, scanID int) (format.SARIF, error) {
|
||||
//Locations
|
||||
for k := 0; k < len(cxxml.Query[i].Result[j].Path.PathNode); k++ {
|
||||
loc := *new(format.Location)
|
||||
/*index := 0
|
||||
//Check if that artifact has been added
|
||||
added := false
|
||||
for file := 0; file < len(sarif.Runs[0].Artifacts); file++ {
|
||||
if sarif.Runs[0].Artifacts[file].Location.Uri == cxxml.Query[i].Result[j].FileName {
|
||||
added = true
|
||||
index = file
|
||||
break
|
||||
}
|
||||
}
|
||||
if !added {
|
||||
artifact := format.Artifact{Location: format.SarifLocation{Uri: cxxml.Query[i].Result[j].FileName}}
|
||||
sarif.Runs[0].Artifacts = append(sarif.Runs[0].Artifacts, artifact)
|
||||
index = len(sarif.Runs[0].Artifacts) - 1
|
||||
}
|
||||
loc.PhysicalLocation.ArtifactLocation.Index = index */
|
||||
loc.PhysicalLocation.ArtifactLocation.URI = cxxml.Query[i].Result[j].FileName
|
||||
loc.PhysicalLocation.Region.StartLine = cxxml.Query[i].Result[j].Path.PathNode[k].Line
|
||||
loc.PhysicalLocation.Region.EndLine = cxxml.Query[i].Result[j].Path.PathNode[k].Line
|
||||
@@ -223,9 +215,6 @@ func Parse(sys System, data []byte, scanID int) (format.SARIF, error) {
|
||||
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
|
||||
@@ -309,14 +298,21 @@ func Parse(sys System, data []byte, scanID int) (format.SARIF, error) {
|
||||
rule.Help.Text = rule.HelpURI
|
||||
rule.ShortDescription = new(format.Message)
|
||||
rule.ShortDescription.Text = cxxml.Query[i].Name
|
||||
if apiDescription != "" {
|
||||
rule.Properties = new(format.SarifRuleProperties)
|
||||
/*if apiDescription != "" {
|
||||
rule.FullDescription = new(format.Message)
|
||||
rule.FullDescription.Text = apiDescription
|
||||
} else if cxxml.Query[i].Categories != "" {
|
||||
} else */if cxxml.Query[i].Categories != "" {
|
||||
rule.FullDescription = new(format.Message)
|
||||
rule.FullDescription.Text = cxxml.Query[i].Categories
|
||||
|
||||
//split categories on ;
|
||||
cats := strings.Split(cxxml.Query[i].Categories, ";")
|
||||
for cat := 0; cat < len(cats); cat++ {
|
||||
rule.Properties.Tags = append(rule.Properties.Tags, cats[cat])
|
||||
}
|
||||
}
|
||||
rule.Properties = new(format.SarifRuleProperties)
|
||||
|
||||
if cxxml.Query[i].CweID != "" {
|
||||
rule.Properties.Tags = append(rule.Properties.Tags, "external/cwe/cwe-"+cxxml.Query[i].CweID)
|
||||
}
|
||||
|
@@ -121,7 +121,7 @@ func TestParse(t *testing.T) {
|
||||
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)
|
||||
//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) {
|
||||
|
@@ -103,7 +103,7 @@ type SarifProperties struct {
|
||||
// Tool these structs are relevant to the Tool object
|
||||
type Tool struct {
|
||||
Driver Driver `json:"driver"`
|
||||
Extensions []Driver `json:"extensions"`
|
||||
Extensions []Driver `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Driver meta information for the scan and tool context
|
||||
|
@@ -542,17 +542,21 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
||||
log.Entry().Debug("Querying Fortify SSC for batch audit data")
|
||||
oneRequestPerIssueMode := false
|
||||
var auditData []*models.ProjectVersionIssue
|
||||
if sys != nil {
|
||||
maxretries := 5 // Maximum number of requests allowed to fail before stopping them
|
||||
if sys != nil && projectVersion != nil {
|
||||
auditData, err = sys.GetAllIssueDetails(projectVersion.ID)
|
||||
if err != nil {
|
||||
if err != nil || len(auditData) == 0 { // It's reasonable to admit that with a length of 0, something went wrong
|
||||
log.Entry().WithError(err).Error("failed to get all audit data, defaulting to one-request-per-issue basis")
|
||||
oneRequestPerIssueMode = true
|
||||
// We do not lower maxretries here in case a "real" bug happened
|
||||
} else {
|
||||
log.Entry().Debug("Request successful, data frame size: ", len(auditData), " audits")
|
||||
}
|
||||
} else {
|
||||
log.Entry().Error("no system instance found, lookup impossible")
|
||||
log.Entry().Error("no system instance or project version found, lookup impossible")
|
||||
oneRequestPerIssueMode = true
|
||||
maxretries = 1 // Set to 1 if the sys instance isn't defined: chances are it couldn't be created, we'll live a chance if there was an unknown bug
|
||||
log.Entry().Debug("request failed: remaining retries ", maxretries)
|
||||
}
|
||||
|
||||
//Now, we handle the sarif
|
||||
@@ -785,17 +789,12 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
||||
prop.Confidence = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.Confidence
|
||||
prop.InstanceID = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceID
|
||||
//Get the audit data
|
||||
if sys != 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"
|
||||
prop.ToolAuditMessage = "Error fetching audit state"
|
||||
if err := integrateAuditData(prop, fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceID, sys, project, projectVersion, auditData, filterSet, oneRequestPerIssueMode, maxretries); err != nil {
|
||||
log.Entry().Debug(err)
|
||||
maxretries = maxretries - 1
|
||||
if maxretries >= 0 {
|
||||
log.Entry().Debug("request failed: remaining retries ", maxretries)
|
||||
}
|
||||
} else {
|
||||
prop.Audited = false
|
||||
prop.ToolState = "Unknown"
|
||||
prop.ToolAuditMessage = "Cannot fetch audit state"
|
||||
}
|
||||
result.Properties = prop
|
||||
|
||||
@@ -1157,16 +1156,25 @@ 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, auditData []*models.ProjectVersionIssue, filterSet *models.FilterSet, oneRequestPerIssue bool) error {
|
||||
func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string, sys System, project *models.Project, projectVersion *models.ProjectVersion, auditData []*models.ProjectVersionIssue, filterSet *models.FilterSet, oneRequestPerIssue bool, maxretries int) error {
|
||||
// Set default values
|
||||
ruleProp.Audited = false
|
||||
ruleProp.FortifyCategory = "Unknown"
|
||||
ruleProp.ToolSeverity = "Unknown"
|
||||
ruleProp.ToolState = "Unreviewed"
|
||||
ruleProp.ToolState = "Unknown"
|
||||
ruleProp.ToolAuditMessage = "Error fetching audit state" // We set this as default for the error phase, then reset it to nothing
|
||||
ruleProp.ToolSeverityIndex = 0
|
||||
ruleProp.ToolStateIndex = 0
|
||||
// These default values allow for the property bag to be filled even if an error happens later. They all should be overwritten by a normal course of the progrma.
|
||||
if maxretries == 0 {
|
||||
// Max retries reached, we stop there to avoid a longer execution time
|
||||
err := errors.New("request failed: maximum number of retries reached, placeholder values will be set from now on for audit data")
|
||||
return err
|
||||
} else if maxretries < 0 {
|
||||
return nil // Avoid spamming logfile
|
||||
}
|
||||
if sys == nil {
|
||||
ruleProp.ToolAuditMessage = "Cannot fetch audit state: no sys instance"
|
||||
err := errors.New("no system instance, lookup impossible for " + issueInstanceID)
|
||||
return err
|
||||
}
|
||||
@@ -1174,6 +1182,8 @@ func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string
|
||||
err := errors.New("project or projectVersion is undefined: lookup aborted for " + issueInstanceID)
|
||||
return err
|
||||
}
|
||||
// Reset the audit message
|
||||
ruleProp.ToolAuditMessage = ""
|
||||
var data []*models.ProjectVersionIssue
|
||||
var err error
|
||||
if oneRequestPerIssue {
|
||||
|
@@ -385,7 +385,7 @@ If you are concerned about leaking system data via NFC on an Android device, you
|
||||
assert.Equal(t, len(sarif.Runs[0].Results), 2)
|
||||
assert.Equal(t, len(sarif.Runs[0].Tool.Driver.Rules), 1)
|
||||
assert.Equal(t, sarif.Runs[0].Results[0].Properties.ToolState, "Unknown")
|
||||
assert.Equal(t, sarif.Runs[0].Results[0].Properties.ToolAuditMessage, "Cannot fetch audit state")
|
||||
assert.Equal(t, sarif.Runs[0].Results[0].Properties.ToolAuditMessage, "Cannot fetch audit state: no sys instance")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -441,7 +441,7 @@ func TestIntegrateAuditData(t *testing.T) {
|
||||
project := models.Project{}
|
||||
projectVersion := models.ProjectVersion{ID: 11037}
|
||||
auditData, _ := sys.GetAllIssueDetails(projectVersion.ID)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, auditData, filterSet, false)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, auditData, filterSet, false, 5)
|
||||
assert.NoError(t, err, "error")
|
||||
assert.Equal(t, ruleProp.Audited, true)
|
||||
assert.Equal(t, ruleProp.ToolState, "Exploitable")
|
||||
@@ -456,7 +456,7 @@ func TestIntegrateAuditData(t *testing.T) {
|
||||
ruleProp := *new(format.SarifProperties)
|
||||
projectVersion := models.ProjectVersion{ID: 11037}
|
||||
auditData, _ := sys.GetAllIssueDetails(projectVersion.ID)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, nil, &projectVersion, auditData, filterSet, false)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, nil, &projectVersion, auditData, filterSet, false, 5)
|
||||
assert.Error(t, err, "project or projectVersion is undefined: lookup aborted for 11037")
|
||||
})
|
||||
|
||||
@@ -464,7 +464,7 @@ func TestIntegrateAuditData(t *testing.T) {
|
||||
ruleProp := *new(format.SarifProperties)
|
||||
project := models.Project{}
|
||||
auditData, _ := sys.GetAllIssueDetails(11037)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, nil, auditData, filterSet, false)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, nil, auditData, filterSet, false, 5)
|
||||
assert.Error(t, err, "project or projectVersion is undefined: lookup aborted for 11037")
|
||||
})
|
||||
|
||||
@@ -473,7 +473,7 @@ func TestIntegrateAuditData(t *testing.T) {
|
||||
project := models.Project{}
|
||||
projectVersion := models.ProjectVersion{ID: 11037}
|
||||
auditData, _ := sys.GetAllIssueDetails(projectVersion.ID)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", nil, &project, &projectVersion, auditData, filterSet, false)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", nil, &project, &projectVersion, auditData, filterSet, false, 5)
|
||||
assert.Error(t, err, "no system instance, lookup impossible for DUMMYDUMMYDUMMY")
|
||||
})
|
||||
|
||||
@@ -482,7 +482,7 @@ func TestIntegrateAuditData(t *testing.T) {
|
||||
project := models.Project{}
|
||||
projectVersion := models.ProjectVersion{ID: 11037}
|
||||
auditData, _ := sys.GetAllIssueDetails(projectVersion.ID)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, auditData, nil, false)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, auditData, nil, false, 5)
|
||||
assert.Error(t, err, "no filter set defined, category will be missing from 11037")
|
||||
})
|
||||
|
||||
@@ -490,7 +490,7 @@ func TestIntegrateAuditData(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)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, nil, filterSet, false, 5)
|
||||
assert.Error(t, err, "not exactly 1 issue found for instance ID 11037, found 0")
|
||||
})
|
||||
|
||||
@@ -498,7 +498,7 @@ func TestIntegrateAuditData(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)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, nil, filterSet, true, 5)
|
||||
assert.NoError(t, err, "error")
|
||||
assert.Equal(t, ruleProp.Audited, true)
|
||||
assert.Equal(t, ruleProp.ToolState, "Exploitable")
|
||||
@@ -509,4 +509,21 @@ func TestIntegrateAuditData(t *testing.T) {
|
||||
assert.Equal(t, ruleProp.FortifyCategory, "Audit All")
|
||||
})
|
||||
|
||||
t.Run("Max retries set to 0: error raised", func(t *testing.T) {
|
||||
ruleProp := *new(format.SarifProperties)
|
||||
project := models.Project{}
|
||||
projectVersion := models.ProjectVersion{ID: 11037}
|
||||
auditData, _ := sys.GetAllIssueDetails(11037)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, auditData, filterSet, false, 0)
|
||||
assert.Error(t, err, "request failed: maximum number of retries reached, placeholder values will be set from now on for audit data")
|
||||
})
|
||||
|
||||
t.Run("Max retries set to -1: fail silently", func(t *testing.T) {
|
||||
ruleProp := *new(format.SarifProperties)
|
||||
project := models.Project{}
|
||||
projectVersion := models.ProjectVersion{ID: 11037}
|
||||
auditData, _ := sys.GetAllIssueDetails(11037)
|
||||
err := integrateAuditData(&ruleProp, "DUMMYDUMMYDUMMY", sys, &project, &projectVersion, auditData, filterSet, false, -1)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user