1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-28 05:47:08 +02:00

Refinement of SARIF generation for BD and WS (#3942)

* Fix docs and format

* Assessment format added

* Added sample file

* Added parsing

* Added packageurl implementation

* Slight refinement

* Refactored assessment options

* Adapted sample file

* First attempt of ws sbom gen

* Reworked SBOM generation

* Fix test code

* Add assessment handling

* Update dependencies

* Added golden test

* Small fix

* feat(fortify): Added a check for fortify binary in $PATH (#3925)

* added check for fortifyupdate and sourceanalyzer bin

Co-authored-by: sumeet patil <sumeet.patil@sap.com>

* Modify SARIF

* Enhanced SARID contents

* Small refinement for hub detect

* Small adjustments

* Extend SARIF contents

* Consistency to Mend part

* Fix tests

* Fix merge

* Fix test

* Add debug log, enhance output

* Enhance meta info

* Fix libType for node

* Fix log entry

* Fix pointers and test

* Fix test

* Fix library types

* Fix test

* Extend libType mappings

Co-authored-by: Vinayak S <vinayaks439@gmail.com>
Co-authored-by: sumeet patil <sumeet.patil@sap.com>
This commit is contained in:
Sven Merk 2022-08-11 13:12:14 +02:00 committed by GitHub
parent ed4467282f
commit c81e741224
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 323 additions and 187 deletions

View File

@ -493,7 +493,7 @@ func isMajorVulnerability(v bd.Vulnerability) bool {
func postScanChecksAndReporting(ctx context.Context, config detectExecuteScanOptions, influx *detectExecuteScanInflux, utils detectUtils, sys *blackduckSystem) error {
errorsOccured := []string{}
vulns, _, err := getVulnsAndComponents(config, influx, sys)
vulns, components, err := getVulnsAndComponents(config, influx, sys)
if err != nil {
return errors.Wrap(err, "failed to fetch vulnerabilities")
}
@ -514,7 +514,7 @@ func postScanChecksAndReporting(ctx context.Context, config detectExecuteScanOpt
}
}
sarif := bd.CreateSarifResultFile(vulns)
sarif := bd.CreateSarifResultFile(vulns, components)
paths, err := bd.WriteSarifFile(sarif, utils)
if err != nil {
errorsOccured = append(errorsOccured, fmt.Sprint(err))

View File

@ -133,6 +133,7 @@ func newWhitesourceScan(config *ScanOptions) *ws.Scan {
return &ws.Scan{
AggregateProjectName: config.ProjectName,
ProductVersion: config.Version,
BuildTool: config.BuildTool,
}
}
@ -352,6 +353,8 @@ func resolveProjectIdentifiers(config *ScanOptions, scan *ws.Scan, utils whiteso
return errors.Wrap(err, "error resolving aggregate project token")
}
scan.ProductToken = config.ProductToken
return scan.UpdateProjects(config.ProductToken, sys)
}

View File

@ -623,8 +623,8 @@ func TestCheckProjectSecurityViolations(t *testing.T) {
t.Run("error - some vulnerabilities", func(t *testing.T) {
systemMock := ws.NewSystemMock("ignored")
systemMock.Alerts = []ws.Alert{
{Vulnerability: ws.Vulnerability{CVSS3Score: 7, Name: "CVE-2025-001"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "MAVEN_ARTIFACT"}},
{Vulnerability: ws.Vulnerability{CVSS3Score: 6, Name: "CVE-2025-002"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "MAVEN_ARTIFACT"}},
{Vulnerability: ws.Vulnerability{CVSS3Score: 7, Name: "CVE-2025-001"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "Java"}},
{Vulnerability: ws.Vulnerability{CVSS3Score: 6, Name: "CVE-2025-002"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "Java"}},
}
influx := whitesourceExecuteScanInflux{}
@ -637,8 +637,8 @@ func TestCheckProjectSecurityViolations(t *testing.T) {
t.Run("success - assessed vulnerabilities", func(t *testing.T) {
systemMock := ws.NewSystemMock("ignored")
systemMock.Alerts = []ws.Alert{
{Vulnerability: ws.Vulnerability{CVSS3Score: 7, Name: "CVE-2025-001"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "MAVEN_ARTIFACT"}},
{Vulnerability: ws.Vulnerability{CVSS3Score: 6, Name: "CVE-2025-002"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "MAVEN_ARTIFACT"}},
{Vulnerability: ws.Vulnerability{CVSS3Score: 7.8, Name: "CVE-2025-001"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "Java"}},
{Vulnerability: ws.Vulnerability{CVSS3Score: 6, Name: "CVE-2025-002"}, Library: ws.Library{KeyID: 42, Name: "test", GroupID: "com.sap", ArtifactID: "test", Version: "1.2.3", LibType: "Java"}},
}
influx := whitesourceExecuteScanInflux{}

View File

@ -12,6 +12,7 @@ import (
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/reporting"
"github.com/package-url/packageurl-go"
"github.com/pkg/errors"
)
@ -66,10 +67,16 @@ type Components struct {
}
type Component struct {
Name string `json:"componentName,omitempty"`
Version string `json:"componentVersionName,omitempty"`
PolicyStatus string `json:"policyStatus,omitempty"`
Metadata `json:"_meta,omitempty"`
Name string `json:"componentName,omitempty"`
Version string `json:"componentVersionName,omitempty"`
ComponentOriginName string `json:"componentVersionOriginName,omitempty"`
PrimaryLanguage string `json:"primaryLanguage,omitempty"`
PolicyStatus string `json:"policyStatus,omitempty"`
Metadata `json:"_meta,omitempty"`
}
func (c Component) ToPackageUrl() *packageurl.PackageURL {
return packageurl.NewPackageURL(transformComponentToPurlType(c.PrimaryLanguage), "", c.Name, c.Version, nil, "")
}
type Vulnerabilities struct {
@ -85,12 +92,15 @@ type Vulnerability struct {
}
type VulnerabilityWithRemediation struct {
VulnerabilityName string `json:"vulnerabilityName,omitempty"`
BaseScore float32 `json:"baseScore,omitempty"`
Severity string `json:"severity,omitempty"`
RemediationStatus string `json:"remediationStatus,omitempty"`
Description string `json:"description,omitempty"`
OverallScore float32 `json:"overallScore,omitempty"`
VulnerabilityName string `json:"vulnerabilityName,omitempty"`
BaseScore float32 `json:"baseScore,omitempty"`
Severity string `json:"severity,omitempty"`
RemediationStatus string `json:"remediationStatus,omitempty"`
Description string `json:"description,omitempty"`
OverallScore float32 `json:"overallScore,omitempty"`
CweID string `json:"cweId,omitempty"`
ExploitabilitySubscore float32 `json:"exploitabilitySubscore,omitempty"`
ImpactSubscore float32 `json:"impactSubscore,omitempty"`
}
// Title returns the issue title representation of the contents
@ -99,7 +109,7 @@ func (v Vulnerability) Title() string {
}
// ToMarkdown returns the markdown representation of the contents
func (v Vulnerability) ToMarkdown() ([]byte, error) {
func (v Vulnerability) ToMarkdown(component *Component) ([]byte, error) {
vul := reporting.VulnerabilityReport{
ArtifactID: v.Name,
@ -124,9 +134,10 @@ func (v Vulnerability) ToMarkdown() ([]byte, error) {
PublishDate: "",
Resolution: "",
Score: float64(v.VulnerabilityWithRemediation.BaseScore),
Severity: v.VulnerabilityWithRemediation.Severity,
Version: v.Version,
Score: float64(v.VulnerabilityWithRemediation.BaseScore),
Severity: v.VulnerabilityWithRemediation.Severity,
Version: v.Version,
PackageURL: component.ToPackageUrl().ToString(),
// no vulnerability link available, yet
VulnerabilityLink: "",
@ -137,13 +148,14 @@ func (v Vulnerability) ToMarkdown() ([]byte, error) {
}
// ToTxt returns the textual representation of the contents
func (v Vulnerability) ToTxt() string {
func (v Vulnerability) ToTxt(component *Component) string {
return fmt.Sprintf(`Vulnerability %v
Severity: %v
Base (NVD) Score: %v
Temporal Score: %v
Package: %v
Installed Version: %v
Package URL: %v
Description: %v
Fix Resolution: %v
Link: [%v](%v)`,
@ -153,6 +165,7 @@ Link: [%v](%v)`,
v.VulnerabilityWithRemediation.OverallScore,
v.Name,
v.Version,
component.ToPackageUrl().ToString(),
v.Description,
"",
"",
@ -479,7 +492,7 @@ func (b *Client) apiURL(apiEndpoint string) (*url.URL, error) {
}
func (b *Client) authenticationValid(now time.Time) bool {
// //check bearer token timeout
// check bearer token timeout
expiryTime := b.lastAuthentication.Add(time.Millisecond * time.Duration(b.BearerExpiresInMilliseconds))
return now.Sub(expiryTime) < 0
}
@ -488,3 +501,18 @@ func urlPath(fullUrl string) string {
theUrl, _ := url.Parse(fullUrl)
return theUrl.Path
}
func transformComponentToPurlType(primaryLanguage string) string {
// TODO verify possible relevant values
switch primaryLanguage {
case "Java":
return packageurl.TypeMaven
case "Javascript":
return packageurl.TypeNPM
case "Golang":
return packageurl.TypeGolang
case "Docker":
return packageurl.TypeDocker
}
return packageurl.TypeGeneric
}

View File

@ -1,9 +1,11 @@
package blackduck
import (
"encoding/base64"
"encoding/json"
"fmt"
"path/filepath"
"runtime"
"github.com/SAP/jenkins-library/pkg/format"
"github.com/SAP/jenkins-library/pkg/log"
@ -13,7 +15,13 @@ import (
)
// CreateSarifResultFile creates a SARIF result from the Vulnerabilities that were brought up by the scan
func CreateSarifResultFile(vulns *Vulnerabilities) *format.SARIF {
func CreateSarifResultFile(vulns *Vulnerabilities, components *Components) *format.SARIF {
// create component lookup map
componentLookup := map[string]Component{}
for _, comp := range components.Items {
componentLookup[fmt.Sprintf("%v/%v", comp.Name, comp.Version)] = comp
}
//Now, we handle the sarif
log.Entry().Debug("Creating SARIF file for data transfer")
var sarif format.SARIF
@ -30,15 +38,16 @@ func CreateSarifResultFile(vulns *Vulnerabilities) *format.SARIF {
tool.Driver.InformationUri = "https://community.synopsys.com/s/document-item?bundleId=integrations-detect&topicId=introduction.html&_LANG=enus"
// Handle results/vulnerabilities
collectedRules := []string{}
cweIdsForTaxonomies := []string{}
if vulns != nil && vulns.Items != nil {
for i := 0; i < len(vulns.Items); i++ {
v := vulns.Items[i]
for _, v := range vulns.Items {
component := componentLookup[fmt.Sprintf("%v/%v", v.Name, v.Version)]
result := *new(format.Results)
id := v.Title()
log.Entry().Debugf("Transforming alert %v into SARIF format", id)
result.RuleID = id
ruleId := v.Title()
log.Entry().Debugf("Transforming alert %v into SARIF format", ruleId)
result.RuleID = ruleId
result.Level = transformToLevel(v.VulnerabilityWithRemediation.Severity)
result.RuleIndex = i //Seems very abstract
result.Message = new(format.Message)
result.Message.Text = v.VulnerabilityWithRemediation.Description
result.AnalysisTarget = new(format.ArtifactLocation)
@ -46,40 +55,77 @@ func CreateSarifResultFile(vulns *Vulnerabilities) *format.SARIF {
result.AnalysisTarget.Index = 0
location := format.Location{PhysicalLocation: format.PhysicalLocation{ArtifactLocation: format.ArtifactLocation{URI: v.Name}}}
result.Locations = append(result.Locations, location)
//TODO add audit and tool related information, maybe fortifyCategory needs to become more general
//result.Properties = new(format.SarifProperties)
//result.Properties.ToolSeverity
//result.Properties.ToolAuditMessage
partialFingerprints := new(format.PartialFingerprints)
partialFingerprints.PackageURLPlusCVEHash = base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%v+%v", component.ToPackageUrl().ToString(), v.Title())))
result.PartialFingerprints = *partialFingerprints
cweIdsForTaxonomies = append(cweIdsForTaxonomies, v.VulnerabilityWithRemediation.CweID)
sarifRule := *new(format.SarifRule)
sarifRule.ID = id
sarifRule.ShortDescription = new(format.Message)
sarifRule.ShortDescription.Text = fmt.Sprintf("%v Package %v", v.VulnerabilityName, v.Name)
sarifRule.FullDescription = new(format.Message)
sarifRule.FullDescription.Text = v.VulnerabilityWithRemediation.Description
sarifRule.DefaultConfiguration = new(format.DefaultConfiguration)
sarifRule.DefaultConfiguration.Level = transformToLevel(v.VulnerabilityWithRemediation.Severity)
sarifRule.HelpURI = ""
markdown, _ := v.ToMarkdown()
sarifRule.Help = new(format.Help)
sarifRule.Help.Text = v.ToTxt()
sarifRule.Help.Markdown = string(markdown)
ruleProp := *new(format.SarifRuleProperties)
ruleProp.Tags = append(ruleProp.Tags, "SECURITY_VULNERABILITY")
ruleProp.Tags = append(ruleProp.Tags, v.VulnerabilityWithRemediation.Description)
ruleProp.Tags = append(ruleProp.Tags, v.Name)
ruleProp.Precision = "very-high"
sarifRule.Properties = &ruleProp
//Finalize: append the result and the rule
// append the result
sarif.Runs[0].Results = append(sarif.Runs[0].Results, result)
tool.Driver.Rules = append(tool.Driver.Rules, sarifRule)
// only create rule on new CVE
if !piperutils.ContainsString(collectedRules, ruleId) {
collectedRules = append(collectedRules, ruleId)
sarifRule := *new(format.SarifRule)
sarifRule.ID = ruleId
sarifRule.ShortDescription = new(format.Message)
sarifRule.ShortDescription.Text = fmt.Sprintf("%v Package %v", v.VulnerabilityName, component.Name)
sarifRule.FullDescription = new(format.Message)
sarifRule.FullDescription.Text = v.VulnerabilityWithRemediation.Description
sarifRule.DefaultConfiguration = new(format.DefaultConfiguration)
sarifRule.DefaultConfiguration.Level = transformToLevel(v.VulnerabilityWithRemediation.Severity)
sarifRule.HelpURI = ""
markdown, _ := v.ToMarkdown(&component)
sarifRule.Help = new(format.Help)
sarifRule.Help.Text = v.ToTxt(&component)
sarifRule.Help.Markdown = string(markdown)
ruleProp := *new(format.SarifRuleProperties)
ruleProp.Tags = append(ruleProp.Tags, "SECURITY_VULNERABILITY")
ruleProp.Tags = append(ruleProp.Tags, component.ToPackageUrl().ToString())
ruleProp.Tags = append(ruleProp.Tags, v.VulnerabilityWithRemediation.CweID)
ruleProp.Precision = "very-high"
ruleProp.Impact = fmt.Sprint(v.VulnerabilityWithRemediation.ImpactSubscore)
ruleProp.Probability = fmt.Sprint(v.VulnerabilityWithRemediation.ExploitabilitySubscore)
ruleProp.SecuritySeverity = fmt.Sprint(v.OverallScore)
sarifRule.Properties = &ruleProp
// append the rule
tool.Driver.Rules = append(tool.Driver.Rules, sarifRule)
}
}
}
//Finalize: tool
sarif.Runs[0].Tool = tool
// Threadflowlocations is no loger useful: voiding it will make for smaller reports
sarif.Runs[0].ThreadFlowLocations = []format.Locations{}
// Add a conversion object to highlight this isn't native SARIF
conversion := new(format.Conversion)
conversion.Tool.Driver.Name = "Piper FPR to SARIF converter"
conversion.Tool.Driver.InformationUri = "https://github.com/SAP/jenkins-library"
conversion.Invocation.ExecutionSuccessful = true
convInvocProp := new(format.InvocationProperties)
convInvocProp.Platform = runtime.GOOS
conversion.Invocation.Properties = convInvocProp
sarif.Runs[0].Conversion = conversion
//handle taxonomies
//Only one exists apparently: CWE. It is fixed
taxonomy := *new(format.Taxonomies)
taxonomy.GUID = "25F72D7E-8A92-459D-AD67-64853F788765"
taxonomy.Name = "CWE"
taxonomy.Organization = "MITRE"
taxonomy.ShortDescription.Text = "The MITRE Common Weakness Enumeration"
for key := range cweIdsForTaxonomies {
taxa := *new(format.Taxa)
taxa.Id = fmt.Sprint(key)
taxonomy.Taxa = append(taxonomy.Taxa, taxa)
}
sarif.Runs[0].Taxonomies = append(sarif.Runs[0].Taxonomies, taxonomy)
return &sarif
}

View File

@ -15,14 +15,20 @@ import (
func TestCreateSarifResultFile(t *testing.T) {
alerts := []Vulnerability{
{Name: "test1", Version: "1.2.3", VulnerabilityWithRemediation: VulnerabilityWithRemediation{VulnerabilityName: "CVE-45456543", Severity: "Critical", Description: "Some vulnerability that can be exploited by peeling the glue off.", BaseScore: 9.8, OverallScore: 10}},
{Name: "test2", Version: "1.2.4", VulnerabilityWithRemediation: VulnerabilityWithRemediation{VulnerabilityName: "CVE-45456542", Severity: "Critical", Description: "Some other vulnerability that can be exploited by filling the glass.", BaseScore: 9, OverallScore: 9}},
{Name: "test3", Version: "1.2.5", VulnerabilityWithRemediation: VulnerabilityWithRemediation{VulnerabilityName: "CVE-45456541", Severity: "Medium", Description: "Some vulnerability that can be exploited by turning it upside down.", BaseScore: 6.5, OverallScore: 7}},
{Name: "test1", Version: "1.2.3", VulnerabilityWithRemediation: VulnerabilityWithRemediation{VulnerabilityName: "CVE-45456542", Severity: "Critical", Description: "Some other vulnerability that can be exploited by filling the glass.", BaseScore: 9, OverallScore: 9}},
{Name: "test1", Version: "1.2.3", VulnerabilityWithRemediation: VulnerabilityWithRemediation{VulnerabilityName: "CVE-45456541", Severity: "Medium", Description: "Some vulnerability that can be exploited by turning it upside down.", BaseScore: 6.5, OverallScore: 7}},
}
vulns := Vulnerabilities{
Items: alerts,
}
components := []Component{
{Name: "test1", Version: "1.2.3", ComponentOriginName: "Maven"},
}
componentList := Components{
Items: components,
}
sarif := CreateSarifResultFile(&vulns)
sarif := CreateSarifResultFile(&vulns, &componentList)
assert.Equal(t, "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", sarif.Schema)
assert.Equal(t, "2.1.0", sarif.Version)

View File

@ -90,6 +90,7 @@ type PartialFingerprints struct {
FortifyInstanceID string `json:"fortifyInstanceID,omitempty"`
CheckmarxSimilarityID string `json:"checkmarxSimilarityID,omitempty"`
PrimaryLocationLineHash string `json:"primaryLocationLineHash,omitempty"`
PackageURLPlusCVEHash string `json:"packageUrlPlusCveHash,omitempty"`
}
// SarifProperties adding additional information/context to the finding

View File

@ -34,7 +34,7 @@ func (g *GitHub) UploadSingleReport(ctx context.Context, scanReport IssueDetail)
title := scanReport.Title()
markdownReport, _ := scanReport.ToMarkdown()
log.Entry().Debugf("Creating/updating GitHub issue with title %v in org %v and repo %v", title, g.Owner, g.Repository)
log.Entry().Debugf("Creating/updating GitHub issue with title %v in org %v and repo %v", title, &g.Owner, &g.Repository)
if err := g.createIssueOrUpdateIssueComment(ctx, title, string(markdownReport)); err != nil {
return fmt.Errorf("failed to upload results for '%v' into GitHub issue: %w", title, err)
}

View File

@ -28,6 +28,7 @@ type VulnerabilityReport struct {
DirectDependency string
Footer string
Group string
PackageURL string
PipelineName string
PipelineLink string
PublishDate string
@ -63,6 +64,7 @@ Pipeline run: [{{ .PipelineName }}]({{ .PipelineLink }})
{{if .ArtifactID}}**ArtifactId:** {{ .ArtifactID }}{{- end}}
{{if .Group}}**Group:** {{ .Group }}{{- end}}
{{if .Version}}**Version:** {{ .Version }}{{- end}}
{{if .PackageURL}}**Package URL:** {{ .PackageURL }}{{- end}}
{{if .PublishDate}}**Publishing date:** {{.PublishDate }}{{- end}}
## Description

View File

@ -35,6 +35,7 @@ func TestVulToMarkdown(t *testing.T) {
Score: 7.8,
Severity: "high",
Version: "1.2.3",
PackageURL: "pkg:generic/the.group/theArtifact@1.2.3",
VulnerabilityLink: "https://the.link/to/the/vulnerability",
VulnerabilityName: "CVE-Test-001",
}

View File

@ -20,6 +20,7 @@ Pipeline run: [thePipelineName](https://the.link.to.the.pipeline)
**ArtifactId:** theArtifact
**Group:** the.group
**Version:** 1.2.3
**Package URL:** pkg:generic/the.group/theArtifact@1.2.3
**Publishing date:** 2022-06-30
## Description

View File

@ -3,9 +3,11 @@ package whitesource
import (
"bytes"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"fmt"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
@ -189,17 +191,15 @@ func CreateSarifResultFile(scan *Scan, alerts *[]Alert) *format.SARIF {
tool.Driver = *new(format.Driver)
tool.Driver.Name = scan.AgentName
tool.Driver.Version = scan.AgentVersion
tool.Driver.InformationUri = "https://whitesource.atlassian.net/wiki/spaces/WD/pages/804814917/Unified+Agent+Overview"
tool.Driver.InformationUri = "https://mend.io"
// Handle results/vulnerabilities
for i := 0; i < len(*alerts); i++ {
alert := (*alerts)[i]
collectedRules := []string{}
for _, alert := range *alerts {
result := *new(format.Results)
id := fmt.Sprintf("%v/%v/%v", alert.Type, alert.Vulnerability.Name, alert.Library.ArtifactID)
log.Entry().Debugf("Transforming alert %v into SARIF format", id)
result.RuleID = id
result.Level = transformToLevel(alert.Vulnerability.Severity, alert.Vulnerability.CVSS3Severity)
result.RuleIndex = i //Seems very abstract
ruleId := alert.Vulnerability.Name
log.Entry().Debugf("Transforming alert %v into SARIF format", ruleId)
result.RuleID = ruleId
result.Message = new(format.Message)
result.Message.Text = alert.Vulnerability.Description
artLoc := new(format.ArtifactLocation)
@ -208,65 +208,88 @@ func CreateSarifResultFile(scan *Scan, alerts *[]Alert) *format.SARIF {
result.AnalysisTarget = artLoc
location := format.Location{PhysicalLocation: format.PhysicalLocation{ArtifactLocation: format.ArtifactLocation{URI: alert.Library.Filename}}}
result.Locations = append(result.Locations, location)
//TODO add audit and tool related information, maybe fortifyCategory needs to become more general
//result.Properties = new(format.SarifProperties)
//result.Properties.ToolSeverity
//result.Properties.ToolAuditMessage
sarifRule := *new(format.SarifRule)
sarifRule.ID = id
sd := new(format.Message)
sd.Text = fmt.Sprintf("%v Package %v", alert.Vulnerability.Name, alert.Library.ArtifactID)
sarifRule.ShortDescription = sd
fd := new(format.Message)
fd.Text = alert.Vulnerability.Description
sarifRule.FullDescription = fd
defaultConfig := new(format.DefaultConfiguration)
defaultConfig.Level = transformToLevel(alert.Vulnerability.Severity, alert.Vulnerability.CVSS3Severity)
sarifRule.DefaultConfiguration = defaultConfig
sarifRule.HelpURI = alert.Vulnerability.URL
markdown, _ := alert.ToMarkdown()
sarifRule.Help = new(format.Help)
sarifRule.Help.Text = alert.ToTxt()
sarifRule.Help.Markdown = string(markdown)
ruleProp := *new(format.SarifRuleProperties)
ruleProp.Tags = append(ruleProp.Tags, alert.Type)
ruleProp.Tags = append(ruleProp.Tags, alert.Description)
ruleProp.Tags = append(ruleProp.Tags, alert.Library.ArtifactID)
ruleProp.Precision = "very-high"
sarifRule.Properties = &ruleProp
//Finalize: append the result and the rule
partialFingerprints := new(format.PartialFingerprints)
partialFingerprints.PackageURLPlusCVEHash = base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%v+%v", alert.Library.ToPackageUrl().ToString(), alert.Vulnerability.Name)))
result.PartialFingerprints = *partialFingerprints
//append the result
sarif.Runs[0].Results = append(sarif.Runs[0].Results, result)
tool.Driver.Rules = append(tool.Driver.Rules, sarifRule)
// only create rule on new CVE
if !piperutils.ContainsString(collectedRules, ruleId) {
collectedRules = append(collectedRules, ruleId)
sarifRule := *new(format.SarifRule)
sarifRule.ID = ruleId
sarifRule.Name = alert.Vulnerability.Name
sd := new(format.Message)
sd.Text = fmt.Sprintf("%v Package %v", alert.Vulnerability.Name, alert.Library.ArtifactID)
sarifRule.ShortDescription = sd
fd := new(format.Message)
fd.Text = alert.Vulnerability.Description
sarifRule.FullDescription = fd
defaultConfig := new(format.DefaultConfiguration)
defaultConfig.Level = transformToLevel(alert.Vulnerability.Severity, alert.Vulnerability.CVSS3Severity)
sarifRule.DefaultConfiguration = defaultConfig
sarifRule.HelpURI = alert.Vulnerability.URL
markdown, _ := alert.ToMarkdown()
sarifRule.Help = new(format.Help)
sarifRule.Help.Text = alert.ToTxt()
sarifRule.Help.Markdown = string(markdown)
ruleProp := *new(format.SarifRuleProperties)
ruleProp.Tags = append(ruleProp.Tags, alert.Type)
ruleProp.Tags = append(ruleProp.Tags, alert.Library.ToPackageUrl().ToString())
ruleProp.Tags = append(ruleProp.Tags, alert.Vulnerability.URL)
ruleProp.SecuritySeverity = fmt.Sprint(consolidateScores(alert.Vulnerability.Score, alert.Vulnerability.CVSS3Score))
ruleProp.Precision = "very-high"
sarifRule.Properties = &ruleProp
// append the rule
tool.Driver.Rules = append(tool.Driver.Rules, sarifRule)
}
}
//Finalize: tool
sarif.Runs[0].Tool = tool
// Threadflowlocations is no loger useful: voiding it will make for smaller reports
sarif.Runs[0].ThreadFlowLocations = []format.Locations{}
// Add a conversion object to highlight this isn't native SARIF
conversion := new(format.Conversion)
conversion.Tool.Driver.Name = "Piper FPR to SARIF converter"
conversion.Tool.Driver.InformationUri = "https://github.com/SAP/jenkins-library"
conversion.Invocation.ExecutionSuccessful = true
convInvocProp := new(format.InvocationProperties)
convInvocProp.Platform = runtime.GOOS
conversion.Invocation.Properties = convInvocProp
sarif.Runs[0].Conversion = conversion
return &sarif
}
func transformToLevel(cvss2severity, cvss3severity string) string {
switch cvss3severity {
cvssseverity := consolidateSeverities(cvss2severity, cvss3severity)
switch cvssseverity {
case "low":
return "warning"
case "medium":
return "warning"
case "high":
return "error"
}
switch cvss2severity {
case "low":
return "warning"
case "medium":
return "warning"
case "high":
case "critical":
return "error"
}
return "none"
}
func consolidateSeverities(cvss2severity, cvss3severity string) string {
if len(cvss3severity) > 0 {
return cvss3severity
}
return cvss2severity
}
// WriteSarifFile write a JSON sarif format file for upload into e.g. GCP
func WriteSarifFile(sarif *format.SARIF, utils piperutils.FileUtils) ([]piperutils.Path, error) {
reportPaths := []piperutils.Path{}
@ -333,18 +356,23 @@ func CreateCycloneSBOM(scan *Scan, libraries *[]Library, alerts *[]Alert) ([]byt
// TODO check whether we can identify library vs. application
Component: &cdx.Component{
BOMRef: ppurl.ToString(),
Type: cdx.ComponentTypeLibrary,
Name: scan.Coordinates.ArtifactID,
Group: scan.Coordinates.GroupID,
Version: scan.Coordinates.Version,
BOMRef: ppurl.ToString(),
Type: cdx.ComponentTypeLibrary,
Name: scan.Coordinates.ArtifactID,
Group: scan.Coordinates.GroupID,
Version: scan.Coordinates.Version,
PackageURL: ppurl.ToString(),
},
// Use properties to include an internal identifier for this BOM
// https://cyclonedx.org/use-cases/#properties--name-value-store
Properties: &[]cdx.Property{
{
Name: "internal:bom-identifier",
Value: strings.Join(scan.ScannedProjectNames(), ", "),
Name: "internal:ws-product-identifier",
Value: scan.ProductToken,
},
{
Name: "internal:ws-project-identifier",
Value: strings.Join(scan.ScannedProjectTokens(), ", "),
},
},
}
@ -379,6 +407,16 @@ func CreateCycloneSBOM(scan *Scan, libraries *[]Library, alerts *[]Alert) ([]byt
// Define the vulnerabilities in VEX
// https://cyclonedx.org/use-cases/#vulnerability-exploitability
purl := alert.Library.ToPackageUrl()
advisories := []cdx.Advisory{}
for _, fix := range alert.Vulnerability.AllFixes {
advisory := cdx.Advisory{
Title: fix.Message,
URL: alert.Vulnerability.TopFix.URL,
}
advisories = append(advisories, advisory)
}
cvss3Score := alert.Vulnerability.CVSS3Score
cvssScore := alert.Vulnerability.Score
vuln := cdx.Vulnerability{
BOMRef: purl.ToString(),
ID: alert.Vulnerability.Name,
@ -397,26 +435,21 @@ func CreateCycloneSBOM(scan *Scan, libraries *[]Library, alerts *[]Alert) ([]byt
},
},
Recommendation: alert.Vulnerability.FixResolutionText,
Detail: alert.Vulnerability.Description,
Detail: alert.Vulnerability.URL,
Ratings: &[]cdx.VulnerabilityRating{
{
Score: &alert.Vulnerability.CVSS3Score,
Severity: transformToCdxSeverity(alert.Vulnerability.Severity),
Score: &cvss3Score,
Severity: transformToCdxSeverity(alert.Vulnerability.CVSS3Severity),
Method: cdx.ScoringMethodCVSSv3,
},
{
Score: &alert.Vulnerability.Score,
Score: &cvssScore,
Severity: transformToCdxSeverity(alert.Vulnerability.Severity),
Method: cdx.ScoringMethodCVSSv2,
},
},
Advisories: &[]cdx.Advisory{
{
Title: alert.Vulnerability.TopFix.Vulnerability,
URL: alert.Vulnerability.TopFix.Origin,
},
},
Description: alert.Description,
Advisories: &advisories,
Description: alert.Vulnerability.Description,
Created: alert.CreationDate,
Published: alert.Vulnerability.PublishDate,
Updated: alert.ModifiedDate,

View File

@ -108,21 +108,29 @@ func TestCreateCycloneSBOM(t *testing.T) {
scan := &Scan{
AgentName: "Mend Unified Agent",
AgentVersion: "3.3.3",
scannedProjects: map[string]Project{"testProject": {Name: "testProject", Token: "projectToken-567"}},
AggregateProjectName: config.ProjectName,
BuildTool: "maven",
ProductVersion: config.ProductVersion,
ProductToken: "productToken-123",
Coordinates: versioning.Coordinates{GroupID: "com.sap", ArtifactID: "myproduct", Version: "1.3.4"},
}
scan.AppendScannedProject("testProject")
lib3 := Library{KeyID: 43, Name: "commons-lang", GroupID: "apache-commons", ArtifactID: "commons-lang", Version: "2.4.30", LibType: "Java", Filename: "vul2"}
lib4 := Library{KeyID: 45, Name: "commons-lang", GroupID: "apache-commons", ArtifactID: "commons-lang", Version: "3.15", LibType: "Java", Filename: "novul"}
lib1 := Library{KeyID: 42, Name: "log4j", GroupID: "apache-logging", ArtifactID: "log4j", Version: "1.14", LibType: "Java", Filename: "vul1", Dependencies: []Library{lib3}}
lib2 := Library{KeyID: 44, Name: "log4j", GroupID: "apache-logging", ArtifactID: "log4j", Version: "3.25", LibType: "Java", Filename: "vul3", Dependencies: []Library{lib4}}
alerts := []Alert{
{Library: Library{KeyID: 42, Name: "log4j", GroupID: "apache-logging", ArtifactID: "log4j", Version: "1.14", LibType: "MAVEN_ARTIFACT", Filename: "vul1"}, Vulnerability: Vulnerability{Name: "CVE-2022-001", CVSS3Score: 7.0, Score: 6, Severity: "medium", PublishDate: "01.01.2022"}},
{Library: Library{KeyID: 43, Name: "commons-lang", GroupID: "apache-commons", ArtifactID: "commons-lang", Version: "2.4.30", LibType: "MAVEN_ARTIFACT", Filename: "vul2"}, Vulnerability: Vulnerability{Name: "CVE-2022-002", CVSS3Score: 8.0, Severity: "high", PublishDate: "02.01.2022", TopFix: Fix{Message: "this is the top fix"}}},
{Library: Library{KeyID: 42, Name: "log4j", GroupID: "apache-logging", ArtifactID: "log4j", Version: "3.25", LibType: "MAVEN_ARTIFACT", Filename: "vul3"}, Vulnerability: Vulnerability{Name: "CVE-2022-003", Score: 6, Severity: "medium", PublishDate: "03.01.2022"}},
{Library: lib1, Vulnerability: Vulnerability{Name: "CVE-2022-001", CVSS3Score: 7, Score: 6, CVSS3Severity: "high", Severity: "medium", PublishDate: "01.01.2022"}},
{Library: lib3, Vulnerability: Vulnerability{Name: "CVE-2022-002", CVSS3Score: 8, CVSS3Severity: "high", PublishDate: "02.01.2022", TopFix: Fix{Message: "this is the top fix"}}},
{Library: lib2, Vulnerability: Vulnerability{Name: "CVE-2022-003", Score: 6, Severity: "medium", PublishDate: "03.01.2022"}},
}
libraries := []Library{
{KeyID: 42, Name: "log4j", GroupID: "apache-logging", ArtifactID: "log4j", Version: "1.14", LibType: "MAVEN_ARTIFACT", Filename: "vul1", Dependencies: []Library{{KeyID: 43, Name: "commons-lang", GroupID: "apache-commons", ArtifactID: "commons-lang", Version: "2.4.30", LibType: "MAVEN_ARTIFACT", Filename: "vul2"}}},
{KeyID: 44, Name: "log4j", GroupID: "apache-logging", ArtifactID: "log4j", Version: "3.25", LibType: "MAVEN_ARTIFACT", Filename: "vul3", Dependencies: []Library{{KeyID: 45, Name: "commons-lang", GroupID: "apache-commons", ArtifactID: "commons-lang", Version: "3.15", LibType: "MAVEN_ARTIFACT", Filename: "vul2"}}},
lib1,
lib2,
}
contents, err := CreateCycloneSBOM(scan, &libraries, &alerts)
@ -160,9 +168,9 @@ func TestCreateSarifResultFile(t *testing.T) {
scan.AgentName = "Some test agent"
scan.AgentVersion = "1.2.6"
alerts := []Alert{
{Library: Library{Filename: "vul1", ArtifactID: "org.some.lib"}, Vulnerability: Vulnerability{CVSS3Score: 7.0, Score: 6}},
{Library: Library{Filename: "vul2", ArtifactID: "org.some.lib"}, Vulnerability: Vulnerability{CVSS3Score: 8.0, TopFix: Fix{Message: "this is the top fix"}}},
{Library: Library{Filename: "vul3", ArtifactID: "org.some.lib2"}, Vulnerability: Vulnerability{Score: 6}},
{Library: Library{Filename: "vul1", ArtifactID: "org.some.lib"}, Vulnerability: Vulnerability{Name: "CVE-2022-001", CVSS3Score: 7.0, Score: 6}},
{Library: Library{Filename: "vul2", ArtifactID: "org.some.lib"}, Vulnerability: Vulnerability{Name: "CVE-2022-002", CVSS3Score: 8.0, TopFix: Fix{Message: "this is the top fix"}}},
{Library: Library{Filename: "vul3", ArtifactID: "org.some.lib2"}, Vulnerability: Vulnerability{Name: "CVE-2022-003", Score: 6}},
}
sarif := CreateSarifResultFile(scan, &alerts)

View File

@ -18,6 +18,7 @@ type Scan struct {
AggregateProjectName string
// ProductVersion is the global version that is used across all Projects (modules) during the scan.
BuildTool string
ProductToken string
ProductVersion string
scannedProjects map[string]Project
scanTimes map[string]time.Time
@ -99,6 +100,20 @@ func (s *Scan) ScannedProjectNames() []string {
return projectNames
}
// ScannedProjectTokens returns a sorted list of all scanned project's tokens
func (s *Scan) ScannedProjectTokens() []string {
projectTokens := []string{}
for _, project := range s.ScannedProjects() {
if len(project.Token) > 0 {
projectTokens = append(projectTokens, project.Token)
}
}
// Sorting helps the list become stable across pipeline runs (and in the unit tests),
// as the order in which we travers map keys is not deterministic.
sort.Strings(projectTokens)
return projectTokens
}
// ScanTime returns the time at which the respective WhiteSource Project was scanned, or the the
// zero value of time.Time, if AppendScannedProject() was not called with that name.
func (s *Scan) ScanTime(projectName string) time.Time {

View File

@ -5,9 +5,11 @@
<group>com.sap</group>
<name>myproduct</name>
<version>1.3.4</version>
<purl>pkg:maven/com.sap/myproduct@1.3.4</purl>
</component>
<properties>
<property name="internal:bom-identifier">testProject - 1</property>
<property name="internal:ws-product-identifier">productToken-123</property>
<property name="internal:ws-project-identifier">projectToken-567</property>
</properties>
</metadata>
<components>
@ -55,8 +57,8 @@
<references></references>
<ratings>
<rating>
<score>0</score>
<severity>medium</severity>
<score>7</score>
<severity>high</severity>
<method>CVSSv3</method>
</rating>
<rating>
@ -65,11 +67,7 @@
<method>CVSSv2</method>
</rating>
</ratings>
<advisories>
<advisory>
<url></url>
</advisory>
</advisories>
<advisories></advisories>
<published>01.01.2022</published>
<tools>
<tool>
@ -101,21 +99,17 @@
<references></references>
<ratings>
<rating>
<score>0</score>
<score>8</score>
<severity>high</severity>
<method>CVSSv3</method>
</rating>
<rating>
<score>6</score>
<severity>high</severity>
<score>0</score>
<severity>none</severity>
<method>CVSSv2</method>
</rating>
</ratings>
<advisories>
<advisory>
<url></url>
</advisory>
</advisories>
<advisories></advisories>
<published>02.01.2022</published>
<tools>
<tool>
@ -148,7 +142,7 @@
<ratings>
<rating>
<score>0</score>
<severity>medium</severity>
<severity>none</severity>
<method>CVSSv3</method>
</rating>
<rating>
@ -157,11 +151,7 @@
<method>CVSSv2</method>
</rating>
</ratings>
<advisories>
<advisory>
<url></url>
</advisory>
</advisories>
<advisories></advisories>
<published>03.01.2022</published>
<tools>
<tool>

View File

@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/SAP/jenkins-library/pkg/format"
@ -88,41 +89,39 @@ func (a Alert) ContainedIn(assessments *[]format.Assessment) (bool, error) {
}
func transformLibToPurlType(libType string) string {
// TODO verify and complete, only maven is proven so far
switch libType {
case "MAVEN_ARTIFACT":
log.Entry().Debugf("LibType reported as %v", libType)
switch strings.ToLower(libType) {
case "java":
return packageurl.TypeMaven
case "NODE_ARTIFACT":
case "javascript/node.js":
return packageurl.TypeNPM
case "GOLANG_ARTIFACT":
case "javascript/bower":
return "bower"
case "go":
return packageurl.TypeGolang
case "DOCKER_ARTIFACT":
return packageurl.TypeGolang
case "UNKNOWN_ARTIFACT":
return packageurl.TypeGeneric
case "python":
return packageurl.TypePyPi
case "debian":
return packageurl.TypeDebian
case "docker":
return packageurl.TypeDocker
case "source library":
return "src"
case ".net":
return packageurl.TypeNuget
}
return packageurl.TypeGeneric
}
func consolidate(cvss2severity, cvss3severity string, cvss2score, cvss3score float64) string {
switch cvss3severity {
cvssseverity := consolidateSeverities(cvss2severity, cvss3severity)
switch cvssseverity {
case "low":
return "LOW"
case "medium":
return "MEDIUM"
case "high":
if cvss3score >= 9 {
return "CRITICAL"
}
return "HIGH"
}
switch cvss2severity {
case "low":
return "LOW"
case "medium":
return "MEDIUM"
case "high":
if cvss2score >= 9 {
if cvss3score >= 9 || cvss2score >= 9 {
return "CRITICAL"
}
return "HIGH"
@ -132,10 +131,7 @@ func consolidate(cvss2severity, cvss3severity string, cvss2score, cvss3score flo
// ToMarkdown returns the markdown representation of the contents
func (a Alert) ToMarkdown() ([]byte, error) {
score := a.Vulnerability.CVSS3Score
if score == 0 {
score = a.Vulnerability.Score
}
score := consolidateScores(a.Vulnerability.Score, a.Vulnerability.CVSS3Score)
vul := reporting.VulnerabilityReport{
ArtifactID: a.Library.ArtifactID,
@ -155,6 +151,7 @@ func (a Alert) ToMarkdown() ([]byte, error) {
Score: score,
Severity: consolidate(a.Vulnerability.Severity, a.Vulnerability.CVSS3Severity, a.Vulnerability.Score, a.Vulnerability.CVSS3Score),
Version: a.Library.Version,
PackageURL: a.Library.ToPackageUrl().ToString(),
VulnerabilityLink: a.Vulnerability.URL,
VulnerabilityName: a.Vulnerability.Name,
}
@ -164,25 +161,22 @@ func (a Alert) ToMarkdown() ([]byte, error) {
// ToTxt returns the textual representation of the contents
func (a Alert) ToTxt() string {
score := a.Vulnerability.CVSS3Score
if score == 0 {
score = a.Vulnerability.Score
}
score := consolidateScores(a.Vulnerability.Score, a.Vulnerability.CVSS3Score)
return fmt.Sprintf(`Vulnerability %v
Severity: %v
Base (NVD) Score: %v
Temporal Score: %v
Package: %v
Installed Version: %v
Package URL: %v
Description: %v
Fix Resolution: %v
Link: [%v](%v)`,
a.Vulnerability.Name,
a.Vulnerability.Severity,
score,
score,
a.Library.ArtifactID,
a.Library.Version,
a.Library.ToPackageUrl().ToString(),
a.Vulnerability.Description,
a.Vulnerability.TopFix.FixResolution,
a.Vulnerability.Name,
@ -190,6 +184,14 @@ Link: [%v](%v)`,
)
}
func consolidateScores(cvss2score, cvss3score float64) float64 {
score := cvss3score
if score == 0 {
score = cvss2score
}
return score
}
// Library
type Library struct {
KeyUUID string `json:"keyUuid,omitempty"`