mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
SBOM creation for Mend (#3934)
* 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 Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
parent
a46f796bcd
commit
b3f37650a2
@ -385,6 +385,7 @@ func checkmarxExecuteScanMetadata() config.StepData {
|
||||
Inputs: config.StepInputs{
|
||||
Secrets: []config.StepSecrets{
|
||||
{Name: "checkmarxCredentialsId", Description: "Jenkins 'Username with password' credentials ID containing username and password to communicate with the Checkmarx backend.", Type: "jenkins"},
|
||||
{Name: "githubTokenCredentialsId", Description: "Jenkins 'Secret text' credentials ID containing token to authenticate to GitHub.", Type: "jenkins"},
|
||||
},
|
||||
Resources: []config.StepResources{
|
||||
{Name: "checkmarx", Type: "stash"},
|
||||
|
@ -298,6 +298,7 @@ func detectExecuteScanMetadata() config.StepData {
|
||||
Inputs: config.StepInputs{
|
||||
Secrets: []config.StepSecrets{
|
||||
{Name: "detectTokenCredentialsId", Description: "Jenkins 'Secret text' credentials ID containing the API token used to authenticate with the Synopsis Detect (formerly BlackDuck) Server.", Type: "jenkins", Aliases: []config.Alias{{Name: "apiTokenCredentialsId", Deprecated: false}}},
|
||||
{Name: "githubTokenCredentialsId", Description: "Jenkins 'Secret text' credentials ID containing token to authenticate to GitHub.", Type: "jenkins"},
|
||||
},
|
||||
Resources: []config.StepResources{
|
||||
{Name: "buildDescriptor", Type: "stash"},
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
ws "github.com/SAP/jenkins-library/pkg/whitesource"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/command"
|
||||
"github.com/SAP/jenkins-library/pkg/format"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/npm"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
@ -46,6 +47,7 @@ type whitesource interface {
|
||||
GetProjectAlerts(projectToken string) ([]ws.Alert, error)
|
||||
GetProjectAlertsByType(projectToken, alertType string) ([]ws.Alert, error)
|
||||
GetProjectLibraryLocations(projectToken string) ([]ws.Library, error)
|
||||
GetProjectHierarchy(projectToken string, includeInHouse bool) ([]ws.Library, error)
|
||||
}
|
||||
|
||||
type whitesourceUtils interface {
|
||||
@ -323,6 +325,7 @@ func resolveProjectIdentifiers(config *ScanOptions, scan *ws.Scan, utils whiteso
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get build artifact description")
|
||||
}
|
||||
scan.Coordinates = coordinates
|
||||
|
||||
if len(config.Version) > 0 {
|
||||
log.Entry().Infof("Resolving product version from default provided '%s' with versioning '%s'", config.Version, config.VersioningModel)
|
||||
@ -562,24 +565,33 @@ func checkSecurityViolations(ctx context.Context, config *ScanOptions, scan *ws.
|
||||
"as floating point number: %w", config.CvssSeverityLimit, err)
|
||||
}
|
||||
|
||||
// inhale assessments from file system
|
||||
assessments := readAssessmentsFromFile(config.AssessmentFile, utils)
|
||||
|
||||
if config.ProjectToken != "" {
|
||||
project := ws.Project{Name: config.ProjectName, Token: config.ProjectToken}
|
||||
// ToDo: see if HTML report generation is really required here
|
||||
// we anyway need to do some refactoring here since config.ProjectToken != "" essentially indicates an aggregated project
|
||||
if _, _, err := checkProjectSecurityViolations(config, cvssSeverityLimit, project, sys, influx); err != nil {
|
||||
if _, _, err := checkProjectSecurityViolations(config, cvssSeverityLimit, project, sys, assessments, influx); err != nil {
|
||||
return reportPaths, err
|
||||
}
|
||||
} else {
|
||||
vulnerabilitiesCount := 0
|
||||
var errorsOccured []string
|
||||
allAlerts := []ws.Alert{}
|
||||
allLibraries := []ws.Library{}
|
||||
for _, project := range scan.ScannedProjects() {
|
||||
// collect errors and aggregate vulnerabilities from all projects
|
||||
if vulCount, alerts, err := checkProjectSecurityViolations(config, cvssSeverityLimit, project, sys, influx); err != nil {
|
||||
if vulCount, alerts, err := checkProjectSecurityViolations(config, cvssSeverityLimit, project, sys, assessments, influx); err != nil {
|
||||
allAlerts = append(allAlerts, alerts...)
|
||||
vulnerabilitiesCount += vulCount
|
||||
errorsOccured = append(errorsOccured, fmt.Sprint(err))
|
||||
}
|
||||
// collect all libraries detected in all related projects and errors
|
||||
if libraries, err := sys.GetProjectHierarchy(project.Token, true); err != nil {
|
||||
allLibraries = append(allLibraries, libraries...)
|
||||
errorsOccured = append(errorsOccured, fmt.Sprint(err))
|
||||
}
|
||||
}
|
||||
log.Entry().Debugf("Aggregated %v alerts for scanned projects", len(allAlerts))
|
||||
|
||||
@ -613,6 +625,16 @@ func checkSecurityViolations(ctx context.Context, config *ScanOptions, scan *ws.
|
||||
}
|
||||
reportPaths = append(reportPaths, paths...)
|
||||
|
||||
sbom, err := ws.CreateCycloneSBOM(scan, &allLibraries, &allAlerts)
|
||||
if err != nil {
|
||||
errorsOccured = append(errorsOccured, fmt.Sprint(err))
|
||||
}
|
||||
paths, err = ws.WriteCycloneSBOM(sbom, utils)
|
||||
if err != nil {
|
||||
errorsOccured = append(errorsOccured, fmt.Sprint(err))
|
||||
}
|
||||
reportPaths = append(reportPaths, paths...)
|
||||
|
||||
if len(errorsOccured) > 0 {
|
||||
if vulnerabilitiesCount > 0 {
|
||||
log.SetErrorCategory(log.ErrorCompliance)
|
||||
@ -623,14 +645,49 @@ func checkSecurityViolations(ctx context.Context, config *ScanOptions, scan *ws.
|
||||
return reportPaths, nil
|
||||
}
|
||||
|
||||
// read assessments from file and expose them to match alerts and filter them before processing
|
||||
func readAssessmentsFromFile(assessmentFilePath string, utils whitesourceUtils) *[]format.Assessment {
|
||||
exists, err := utils.FileExists(assessmentFilePath)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
log.Entry().Errorf("unable to check existence of assessment file at '%s'", assessmentFilePath)
|
||||
}
|
||||
assessmentFile, err := utils.Open(assessmentFilePath)
|
||||
if exists && err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
log.Entry().Errorf("unable to open assessment file at '%s'", assessmentFilePath)
|
||||
}
|
||||
assessments := &[]format.Assessment{}
|
||||
if exists {
|
||||
defer assessmentFile.Close()
|
||||
assessments, err = format.ReadAssessments(assessmentFile)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
log.Entry().Errorf("unable to parse assessment file at '%s'", assessmentFilePath)
|
||||
}
|
||||
}
|
||||
return assessments
|
||||
}
|
||||
|
||||
// checkSecurityViolations checks security violations and returns an error if the configured severity limit is crossed.
|
||||
func checkProjectSecurityViolations(config *ScanOptions, cvssSeverityLimit float64, project ws.Project, sys whitesource, influx *whitesourceExecuteScanInflux) (int, []ws.Alert, error) {
|
||||
func checkProjectSecurityViolations(config *ScanOptions, cvssSeverityLimit float64, project ws.Project, sys whitesource, assessments *[]format.Assessment, influx *whitesourceExecuteScanInflux) (int, []ws.Alert, error) {
|
||||
// get project alerts (vulnerabilities)
|
||||
alerts, err := sys.GetProjectAlertsByType(project.Token, "SECURITY_VULNERABILITY")
|
||||
if err != nil {
|
||||
return 0, alerts, fmt.Errorf("failed to retrieve project alerts from WhiteSource: %w", err)
|
||||
}
|
||||
|
||||
// filter alerts related to existing assessments
|
||||
filteredAlerts := []ws.Alert{}
|
||||
if len(*assessments) > 0 {
|
||||
for _, alert := range alerts {
|
||||
if result, err := alert.ContainedIn(assessments); err == nil && result == false {
|
||||
filteredAlerts = append(filteredAlerts, alert)
|
||||
}
|
||||
}
|
||||
alerts = filteredAlerts
|
||||
}
|
||||
|
||||
severeVulnerabilities, nonSevereVulnerabilities := ws.CountSecurityVulnerabilities(&alerts, cvssSeverityLimit)
|
||||
influx.whitesource_data.fields.minor_vulnerabilities = nonSevereVulnerabilities
|
||||
influx.whitesource_data.fields.major_vulnerabilities = severeVulnerabilities
|
||||
|
@ -27,6 +27,7 @@ type whitesourceExecuteScanOptions struct {
|
||||
AgentParameters []string `json:"agentParameters,omitempty"`
|
||||
AgentURL string `json:"agentUrl,omitempty"`
|
||||
AggregateVersionWideReport bool `json:"aggregateVersionWideReport,omitempty"`
|
||||
AssessmentFile string `json:"assessmentFile,omitempty"`
|
||||
BuildDescriptorExcludeList []string `json:"buildDescriptorExcludeList,omitempty"`
|
||||
BuildDescriptorFile string `json:"buildDescriptorFile,omitempty"`
|
||||
BuildTool string `json:"buildTool,omitempty"`
|
||||
@ -310,6 +311,7 @@ func addWhitesourceExecuteScanFlags(cmd *cobra.Command, stepConfig *whitesourceE
|
||||
cmd.Flags().StringSliceVar(&stepConfig.AgentParameters, "agentParameters", []string{}, "[NOT IMPLEMENTED] List of additional parameters passed to the Unified Agent command line.")
|
||||
cmd.Flags().StringVar(&stepConfig.AgentURL, "agentUrl", `https://saas.whitesourcesoftware.com/agent`, "URL to the WhiteSource agent endpoint.")
|
||||
cmd.Flags().BoolVar(&stepConfig.AggregateVersionWideReport, "aggregateVersionWideReport", false, "This does not run a scan, instead just generated a report for all projects with projectVersion = config.ProductVersion")
|
||||
cmd.Flags().StringVar(&stepConfig.AssessmentFile, "assessmentFile", `hs-assessments.yaml`, "Explicit path to the assessment YAML file.")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.BuildDescriptorExcludeList, "buildDescriptorExcludeList", []string{`unit-tests/pom.xml`, `integration-tests/pom.xml`}, "List of build descriptors and therefore modules to exclude from the scan and assessment activities.")
|
||||
cmd.Flags().StringVar(&stepConfig.BuildDescriptorFile, "buildDescriptorFile", os.Getenv("PIPER_buildDescriptorFile"), "Explicit path to the build descriptor file.")
|
||||
cmd.Flags().StringVar(&stepConfig.BuildTool, "buildTool", os.Getenv("PIPER_buildTool"), "Defines the tool which is used for building the artifact.")
|
||||
@ -429,6 +431,15 @@ func whitesourceExecuteScanMetadata() config.StepData {
|
||||
Aliases: []config.Alias{},
|
||||
Default: false,
|
||||
},
|
||||
{
|
||||
Name: "assessmentFile",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: `hs-assessments.yaml`,
|
||||
},
|
||||
{
|
||||
Name: "buildDescriptorExcludeList",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/format"
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"github.com/SAP/jenkins-library/pkg/reporting"
|
||||
@ -613,7 +614,7 @@ func TestCheckProjectSecurityViolations(t *testing.T) {
|
||||
systemMock.Alerts = []ws.Alert{}
|
||||
influx := whitesourceExecuteScanInflux{}
|
||||
|
||||
severeVulnerabilities, alerts, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &influx)
|
||||
severeVulnerabilities, alerts, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &[]format.Assessment{}, &influx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, severeVulnerabilities)
|
||||
assert.Equal(t, 0, len(alerts))
|
||||
@ -622,23 +623,37 @@ 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}},
|
||||
{Vulnerability: ws.Vulnerability{CVSS3Score: 6}},
|
||||
{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"}},
|
||||
}
|
||||
influx := whitesourceExecuteScanInflux{}
|
||||
|
||||
severeVulnerabilities, alerts, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &influx)
|
||||
severeVulnerabilities, alerts, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &[]format.Assessment{}, &influx)
|
||||
assert.Contains(t, fmt.Sprint(err), "1 Open Source Software Security vulnerabilities")
|
||||
assert.Equal(t, 1, severeVulnerabilities)
|
||||
assert.Equal(t, 2, len(alerts))
|
||||
})
|
||||
|
||||
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"}},
|
||||
}
|
||||
influx := whitesourceExecuteScanInflux{}
|
||||
|
||||
severeVulnerabilities, alerts, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &[]format.Assessment{{Vulnerability: "CVE-2025-001", Purls: []format.Purl{{Purl: "pkg:/maven/com.sap/test@1.2.3"}}}, {Vulnerability: "CVE-2025-002", Purls: []format.Purl{{Purl: "pkg:/maven/com.sap/test@1.2.3"}}}}, &influx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, severeVulnerabilities)
|
||||
assert.Equal(t, 0, len(alerts))
|
||||
})
|
||||
|
||||
t.Run("error - WhiteSource failure", func(t *testing.T) {
|
||||
systemMock := ws.NewSystemMock("ignored")
|
||||
systemMock.AlertError = fmt.Errorf("failed to read alerts")
|
||||
influx := whitesourceExecuteScanInflux{}
|
||||
|
||||
_, _, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &influx)
|
||||
_, _, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &[]format.Assessment{}, &influx)
|
||||
assert.Contains(t, fmt.Sprint(err), "failed to retrieve project alerts from WhiteSource")
|
||||
})
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -40,6 +40,7 @@ require (
|
||||
github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5
|
||||
github.com/mitchellh/mapstructure v1.4.3
|
||||
github.com/motemen/go-nuts v0.0.0-20210915132349-615a782f2c69
|
||||
github.com/package-url/packageurl-go v0.1.0
|
||||
github.com/pelletier/go-toml v1.9.4
|
||||
github.com/piper-validation/fortify-client-go v0.0.0-20220126145513-7b3e9a72af01
|
||||
github.com/pkg/errors v0.9.1
|
||||
@ -81,6 +82,7 @@ require (
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/BurntSushi/toml v0.4.1 // indirect
|
||||
github.com/CycloneDX/cyclonedx-go v0.6.0
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
|
||||
github.com/Jeffail/gabs v1.1.1 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -146,6 +146,8 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
|
||||
github.com/CycloneDX/cyclonedx-go v0.6.0 h1:SizWGbZzFTC/O/1yh072XQBMxfvsoWqd//oKCIyzFyE=
|
||||
github.com/CycloneDX/cyclonedx-go v0.6.0/go.mod h1:nQCiF4Tvrg5Ieu8qPhYMvzPGMu5I7fANZkrSsJjl5mg=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4=
|
||||
@ -331,6 +333,8 @@ github.com/bndr/gojenkins v1.1.1-0.20210520222939-90ed82bfdff6 h1:yHK3nXjSRklq0S
|
||||
github.com/bndr/gojenkins v1.1.1-0.20210520222939-90ed82bfdff6/go.mod h1:QeskxN9F/Csz0XV/01IC8y37CapKKWvOHa0UHLLX1fM=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.7.0 h1:AT0vOjO68RcLyenLCHOGZzSNiuto7ziqzq6Q1/3xzMQ=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.7.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
|
||||
github.com/briankassouf/jose v0.9.2-0.20180619214549-d2569464773f h1:ZMEzE7R0WNqgbHplzSBaYJhJi5AZWTCK9baU0ebzG6g=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
|
||||
@ -1553,6 +1557,8 @@ github.com/oracle/oci-go-sdk v13.1.0+incompatible h1:inwbT0b/mMbnTfzYoW2xcU1cCMI
|
||||
github.com/oracle/oci-go-sdk v13.1.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA=
|
||||
github.com/ory/dockertest/v3 v3.8.0 h1:i5b0cJCd801qw0cVQUOH6dSpI9fT3j5tdWu0jKu90ks=
|
||||
github.com/package-url/packageurl-go v0.1.0 h1:efWBc98O/dBZRg1pw2xiDzovnlMjCa9NPnfaiBduh8I=
|
||||
github.com/package-url/packageurl-go v0.1.0/go.mod h1:C/ApiuWpmbpni4DIOECf6WCjFUZV7O1Fx7VAzrZHgBw=
|
||||
github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c h1:vwpFWvAO8DeIZfFeqASzZfsxuWPno9ncAebBEP0N3uE=
|
||||
github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
|
72
pkg/format/assessment.go
Normal file
72
pkg/format/assessment.go
Normal file
@ -0,0 +1,72 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/package-url/packageurl-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Assessment format related JSON structs
|
||||
type Assessments struct {
|
||||
List []Assessment `json:"ignore"`
|
||||
}
|
||||
|
||||
type Assessment struct {
|
||||
Vulnerability string `json:"vulnerability"`
|
||||
Status AssessmentStatus `json:"status"`
|
||||
Analysis AssessmentAnalysis `json:"analysis"`
|
||||
Purls []Purl `json:"purls"`
|
||||
}
|
||||
|
||||
type AssessmentStatus string
|
||||
|
||||
const (
|
||||
NotAssessed AssessmentStatus = "notAssessed" //"Not Assessed"
|
||||
Relevant AssessmentStatus = "relevant" //"Relevant (True Positive)"
|
||||
NotRelevant AssessmentStatus = "notRelevant" //"Not Relevant (False Positive)"
|
||||
InProcess AssessmentStatus = "inProcess" //"In Process"
|
||||
)
|
||||
|
||||
type AssessmentAnalysis string
|
||||
|
||||
const (
|
||||
WaitingForFix AssessmentAnalysis = "waitingForFix" //"Waiting for OSS community fix"
|
||||
RiskAccepted AssessmentAnalysis = "riskAccepted" //"Risk Accepted"
|
||||
//Others AssessmentAnalysis = "others" //"Others"
|
||||
NotPresent AssessmentAnalysis = "notPresent" //"Affected parts of the OSS library are not present"
|
||||
NotUsed AssessmentAnalysis = "notUsed" //"Affected parts of the OSS library are not used"
|
||||
AssessmentPropagation AssessmentAnalysis = "assessmentPropagation" //"Assessment Propagation"
|
||||
//BuildVersionOutdated AssessmentAnalysis = "buildVersionOutdated" //"Build Version is outdated"
|
||||
FixedByDevTeam AssessmentAnalysis = "fixedByDevTeam" //"OSS Component fixed by development team"
|
||||
Mitigated AssessmentAnalysis = "mitigated" //"Mitigated by the Application"
|
||||
WronglyReported AssessmentAnalysis = "wronglyReported" //"Wrongly reported CVE"
|
||||
)
|
||||
|
||||
type Purl struct {
|
||||
Purl string `json:"purl"`
|
||||
}
|
||||
|
||||
func (p Purl) ToPackageUrl() (packageurl.PackageURL, error) {
|
||||
return packageurl.FromString(p.Purl)
|
||||
}
|
||||
|
||||
// ReadAssessment loads the assessments and returns their contents
|
||||
func ReadAssessments(assessmentFile io.ReadCloser) (*[]Assessment, error) {
|
||||
defer assessmentFile.Close()
|
||||
assessments := &[]Assessment{}
|
||||
|
||||
content, err := ioutil.ReadAll(assessmentFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %v", assessmentFile)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(content, assessments)
|
||||
if err != nil {
|
||||
return nil, NewParseError(fmt.Sprintf("format of assessment file is invalid %q: %v", content, err))
|
||||
}
|
||||
return assessments, nil
|
||||
}
|
18
pkg/format/errors.go
Normal file
18
pkg/format/errors.go
Normal file
@ -0,0 +1,18 @@
|
||||
package format
|
||||
|
||||
// ParseError defines an error type for assessment file parsing errors
|
||||
type ParseError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// NewParseError creates a new ParseError
|
||||
func NewParseError(message string) *ParseError {
|
||||
return &ParseError{
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the message of the ParseError
|
||||
func (e *ParseError) Error() string {
|
||||
return e.message
|
||||
}
|
7
pkg/format/hs-assessment.yaml
Normal file
7
pkg/format/hs-assessment.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
ignore:
|
||||
# This is the full set of supported rule fields:
|
||||
- vulnerability: CVE-2008-4318
|
||||
status: notRelevant
|
||||
analysis: mitigated
|
||||
purls:
|
||||
- purl: "pkg:npm/observer@0.3.2"
|
18
pkg/piperutils/maps.go
Normal file
18
pkg/piperutils/maps.go
Normal file
@ -0,0 +1,18 @@
|
||||
package piperutils
|
||||
|
||||
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
|
||||
r := make([]K, 0, len(m))
|
||||
for k := range m {
|
||||
r = append(r, k)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
//Values returns the slice of values of the map provided
|
||||
func Values[M ~map[K]V, K comparable, V any](m M) []V {
|
||||
r := make([]V, 0, len(m))
|
||||
for _, v := range m {
|
||||
r = append(r, v)
|
||||
}
|
||||
return r
|
||||
}
|
31
pkg/piperutils/maps_test.go
Normal file
31
pkg/piperutils/maps_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
package piperutils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKeys(t *testing.T) {
|
||||
intStringMap := map[int]string{1: "eins", 2: "zwei", 3: "drei", 4: "vier"}
|
||||
|
||||
intList := Keys(intStringMap)
|
||||
|
||||
assert.Equal(t, 4, len(intList))
|
||||
assert.Equal(t, true, ContainsInt(intList, 1))
|
||||
assert.Equal(t, true, ContainsInt(intList, 2))
|
||||
assert.Equal(t, true, ContainsInt(intList, 3))
|
||||
assert.Equal(t, true, ContainsInt(intList, 4))
|
||||
}
|
||||
|
||||
func TestValues(t *testing.T) {
|
||||
intStringMap := map[int]string{1: "eins", 2: "zwei", 3: "drei", 4: "vier"}
|
||||
|
||||
intList := Values(intStringMap)
|
||||
|
||||
assert.Equal(t, 4, len(intList))
|
||||
assert.Equal(t, true, ContainsString(intList, "eins"))
|
||||
assert.Equal(t, true, ContainsString(intList, "zwei"))
|
||||
assert.Equal(t, true, ContainsString(intList, "drei"))
|
||||
assert.Equal(t, true, ContainsString(intList, "vier"))
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -9,6 +10,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/package-url/packageurl-go"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/format"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
@ -284,3 +288,229 @@ func WriteSarifFile(sarif *format.SARIF, utils piperutils.FileUtils) ([]piperuti
|
||||
|
||||
return reportPaths, nil
|
||||
}
|
||||
|
||||
func transformToCdxSeverity(severity string) cdx.Severity {
|
||||
switch severity {
|
||||
case "info":
|
||||
return cdx.SeverityInfo
|
||||
case "low":
|
||||
return cdx.SeverityLow
|
||||
case "medium":
|
||||
return cdx.SeverityMedium
|
||||
case "high":
|
||||
return cdx.SeverityHigh
|
||||
case "critical":
|
||||
return cdx.SeverityCritical
|
||||
case "":
|
||||
return cdx.SeverityNone
|
||||
}
|
||||
return cdx.SeverityUnknown
|
||||
}
|
||||
|
||||
func transformBuildToPurlType(buildType string) string {
|
||||
switch buildType {
|
||||
case "maven":
|
||||
return packageurl.TypeMaven
|
||||
case "npm":
|
||||
return packageurl.TypeNPM
|
||||
case "docker":
|
||||
return packageurl.TypeDocker
|
||||
case "kaniko":
|
||||
return packageurl.TypeDocker
|
||||
case "golang":
|
||||
return packageurl.TypeGolang
|
||||
case "mta":
|
||||
return packageurl.TypeComposer
|
||||
}
|
||||
return packageurl.TypeGeneric
|
||||
}
|
||||
|
||||
func CreateCycloneSBOM(scan *Scan, libraries *[]Library, alerts *[]Alert) ([]byte, error) {
|
||||
ppurl := packageurl.NewPackageURL(transformBuildToPurlType(scan.BuildTool), scan.Coordinates.GroupID, scan.Coordinates.ArtifactID, scan.Coordinates.Version, nil, "")
|
||||
metadata := cdx.Metadata{
|
||||
// Define metadata about the main component
|
||||
// (the component which the BOM will describe)
|
||||
|
||||
// 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,
|
||||
},
|
||||
// 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(), ", "),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
components := []cdx.Component{}
|
||||
flatUniqueLibrariesMap := map[string]Library{}
|
||||
transformToUniqueFlatList(libraries, &flatUniqueLibrariesMap)
|
||||
flatUniqueLibraries := piperutils.Values(flatUniqueLibrariesMap)
|
||||
sort.Slice(flatUniqueLibraries, func(i, j int) bool {
|
||||
return flatUniqueLibraries[i].ToPackageUrl().ToString() < flatUniqueLibraries[j].ToPackageUrl().ToString()
|
||||
})
|
||||
for _, lib := range flatUniqueLibraries {
|
||||
purl := lib.ToPackageUrl()
|
||||
// Define the components that the product ships with
|
||||
// https://cyclonedx.org/use-cases/#inventory
|
||||
component := cdx.Component{
|
||||
BOMRef: purl.ToString(),
|
||||
Type: cdx.ComponentTypeLibrary,
|
||||
Author: lib.GroupID,
|
||||
Name: lib.ArtifactID,
|
||||
Version: lib.Version,
|
||||
PackageURL: purl.ToString(),
|
||||
}
|
||||
components = append(components, component)
|
||||
}
|
||||
|
||||
dependencies := []cdx.Dependency{}
|
||||
declareDependency(ppurl, libraries, &dependencies)
|
||||
|
||||
vulnerabilities := []cdx.Vulnerability{}
|
||||
for _, alert := range *alerts {
|
||||
// Define the vulnerabilities in VEX
|
||||
// https://cyclonedx.org/use-cases/#vulnerability-exploitability
|
||||
purl := alert.Library.ToPackageUrl()
|
||||
vuln := cdx.Vulnerability{
|
||||
BOMRef: purl.ToString(),
|
||||
ID: alert.Vulnerability.Name,
|
||||
Source: &cdx.Source{URL: alert.Vulnerability.URL},
|
||||
Tools: &[]cdx.Tool{
|
||||
{
|
||||
Name: scan.AgentName,
|
||||
Version: scan.AgentVersion,
|
||||
Vendor: "Mend",
|
||||
ExternalReferences: &[]cdx.ExternalReference{
|
||||
{
|
||||
URL: "https://www.mend.io/",
|
||||
Type: cdx.ERTypeBuildMeta,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Recommendation: alert.Vulnerability.FixResolutionText,
|
||||
Detail: alert.Vulnerability.Description,
|
||||
Ratings: &[]cdx.VulnerabilityRating{
|
||||
{
|
||||
Score: &alert.Vulnerability.CVSS3Score,
|
||||
Severity: transformToCdxSeverity(alert.Vulnerability.Severity),
|
||||
Method: cdx.ScoringMethodCVSSv3,
|
||||
},
|
||||
{
|
||||
Score: &alert.Vulnerability.Score,
|
||||
Severity: transformToCdxSeverity(alert.Vulnerability.Severity),
|
||||
Method: cdx.ScoringMethodCVSSv2,
|
||||
},
|
||||
},
|
||||
Advisories: &[]cdx.Advisory{
|
||||
{
|
||||
Title: alert.Vulnerability.TopFix.Vulnerability,
|
||||
URL: alert.Vulnerability.TopFix.Origin,
|
||||
},
|
||||
},
|
||||
Description: alert.Description,
|
||||
Created: alert.CreationDate,
|
||||
Published: alert.Vulnerability.PublishDate,
|
||||
Updated: alert.ModifiedDate,
|
||||
Affects: &[]cdx.Affects{
|
||||
{
|
||||
Ref: purl.ToString(),
|
||||
Range: &[]cdx.AffectedVersions{
|
||||
{
|
||||
Version: alert.Library.Version,
|
||||
Status: cdx.VulnerabilityStatus(alert.Status),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
references := []cdx.VulnerabilityReference{}
|
||||
for _, ref := range alert.Vulnerability.References {
|
||||
reference := cdx.VulnerabilityReference{
|
||||
Source: &cdx.Source{Name: ref.Homepage, URL: ref.URL},
|
||||
ID: ref.GenericPackageIndex,
|
||||
}
|
||||
references = append(references, reference)
|
||||
}
|
||||
vuln.References = &references
|
||||
vulnerabilities = append(vulnerabilities, vuln)
|
||||
}
|
||||
|
||||
// Assemble the BOM
|
||||
bom := cdx.NewBOM()
|
||||
bom.Vulnerabilities = &vulnerabilities
|
||||
bom.Metadata = &metadata
|
||||
bom.Components = &components
|
||||
bom.Dependencies = &dependencies
|
||||
|
||||
// Encode the BOM
|
||||
var outputBytes []byte
|
||||
buffer := bytes.NewBuffer(outputBytes)
|
||||
encoder := cdx.NewBOMEncoder(buffer, cdx.BOMFileFormatXML)
|
||||
encoder.SetPretty(true)
|
||||
if err := encoder.Encode(bom); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func WriteCycloneSBOM(sbom []byte, utils piperutils.FileUtils) ([]piperutils.Path, error) {
|
||||
paths := []piperutils.Path{}
|
||||
if err := utils.MkdirAll(ReportsDirectory, 0777); err != nil {
|
||||
return paths, errors.Wrapf(err, "failed to create report directory")
|
||||
}
|
||||
|
||||
sbomPath := filepath.Join(ReportsDirectory, "piper_whitesource_sbom.xml")
|
||||
|
||||
// Write file
|
||||
if err := utils.FileWrite(sbomPath, sbom, 0666); err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return paths, errors.Wrapf(err, "failed to write SARIF file")
|
||||
}
|
||||
paths = append(paths, piperutils.Path{Name: "WhiteSource SBOM file", Target: sbomPath})
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func transformToUniqueFlatList(libraries *[]Library, flatMapRef *map[string]Library) {
|
||||
for _, lib := range *libraries {
|
||||
key := lib.ToPackageUrl().ToString()
|
||||
flatMap := *flatMapRef
|
||||
lookup := flatMap[key]
|
||||
if lookup.KeyID != lib.KeyID {
|
||||
flatMap[key] = lib
|
||||
if len(lib.Dependencies) > 0 {
|
||||
transformToUniqueFlatList(&lib.Dependencies, flatMapRef)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func declareDependency(parentPurl *packageurl.PackageURL, dependents *[]Library, collection *[]cdx.Dependency) {
|
||||
localDependencies := []cdx.Dependency{}
|
||||
for _, lib := range *dependents {
|
||||
purl := lib.ToPackageUrl()
|
||||
// Define the dependency graph
|
||||
// https://cyclonedx.org/use-cases/#dependency-graph
|
||||
localDependency := cdx.Dependency{Ref: purl.ToString()}
|
||||
localDependencies = append(localDependencies, localDependency)
|
||||
|
||||
if len(lib.Dependencies) > 0 {
|
||||
declareDependency(purl, &lib.Dependencies, collection)
|
||||
}
|
||||
}
|
||||
dependency := cdx.Dependency{
|
||||
Ref: parentPurl.ToString(),
|
||||
Dependencies: &localDependencies,
|
||||
}
|
||||
*collection = append(*collection, dependency)
|
||||
}
|
||||
|
@ -1,14 +1,19 @@
|
||||
package whitesource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/format"
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"github.com/SAP/jenkins-library/pkg/reporting"
|
||||
"github.com/SAP/jenkins-library/pkg/versioning"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -56,6 +61,99 @@ func TestCreateCustomVulnerabilityReport(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateCycloneSBOM(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
config := &ScanOptions{}
|
||||
scan := &Scan{
|
||||
AgentName: "Mend Unified Agent",
|
||||
AgentVersion: "3.3.3",
|
||||
AggregateProjectName: config.ProjectName,
|
||||
BuildTool: "maven",
|
||||
ProductVersion: config.ProductVersion,
|
||||
Coordinates: versioning.Coordinates{GroupID: "com.sap", ArtifactID: "myproduct", Version: "1.3.4"},
|
||||
}
|
||||
scan.AppendScannedProject("testProject")
|
||||
alerts := []Alert{
|
||||
{Library: Library{KeyID: 42, Name: "log4j", GroupID: "apache-logging", ArtifactID: "log4j", Filename: "vul1"}, Vulnerability: Vulnerability{CVSS3Score: 7.0, Score: 6}},
|
||||
{Library: Library{KeyID: 43, Name: "commons-lang", GroupID: "apache-commons", ArtifactID: "commons-lang", Filename: "vul2"}, Vulnerability: Vulnerability{CVSS3Score: 8.0, TopFix: Fix{Message: "this is the top fix"}}},
|
||||
{Library: Library{KeyID: 42, Name: "log4j", GroupID: "apache-logging", ArtifactID: "log4j", Filename: "vul3"}, Vulnerability: Vulnerability{Score: 6}},
|
||||
}
|
||||
|
||||
libraries := []Library{
|
||||
{KeyID: 42, Name: "log4j", GroupID: "apache-logging", ArtifactID: "log4j", Filename: "vul1", Dependencies: []Library{{KeyID: 43, Name: "commons-lang", GroupID: "apache-commons", ArtifactID: "commons-lang", Filename: "vul2"}}},
|
||||
{KeyID: 42, Name: "log4j", GroupID: "apache-logging", ArtifactID: "log4j", Filename: "vul3"},
|
||||
}
|
||||
|
||||
contents, err := CreateCycloneSBOM(scan, &libraries, &alerts)
|
||||
assert.NoError(t, err, "unexpected error")
|
||||
buffer := bytes.NewBuffer(contents)
|
||||
decoder := cdx.NewBOMDecoder(buffer, cdx.BOMFileFormatXML)
|
||||
bom := cdx.NewBOM()
|
||||
decoder.Decode(bom)
|
||||
|
||||
assert.NotNil(t, bom, "BOM was nil")
|
||||
assert.NotEmpty(t, bom.SpecVersion)
|
||||
|
||||
components := *bom.Components
|
||||
assert.Equal(t, 2, len(components))
|
||||
assert.Equal(t, true, components[0].Name == "log4j" || components[0].Name == "commons-lang")
|
||||
assert.Equal(t, true, components[1].Name == "log4j" || components[1].Name == "commons-lang")
|
||||
assert.Equal(t, true, components[0].Name != components[1].Name)
|
||||
})
|
||||
|
||||
t.Run("success - golden", func(t *testing.T) {
|
||||
config := &ScanOptions{ProjectName: "myproduct - 1.3.4", ProductVersion: "1"}
|
||||
scan := &Scan{
|
||||
AgentName: "Mend Unified Agent",
|
||||
AgentVersion: "3.3.3",
|
||||
AggregateProjectName: config.ProjectName,
|
||||
BuildTool: "maven",
|
||||
ProductVersion: config.ProductVersion,
|
||||
Coordinates: versioning.Coordinates{GroupID: "com.sap", ArtifactID: "myproduct", Version: "1.3.4"},
|
||||
}
|
||||
scan.AppendScannedProject("testProject")
|
||||
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"}},
|
||||
}
|
||||
|
||||
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"}}},
|
||||
}
|
||||
|
||||
contents, err := CreateCycloneSBOM(scan, &libraries, &alerts)
|
||||
assert.NoError(t, err, "unexpected error")
|
||||
|
||||
goldenFilePath := filepath.Join("testdata", "sbom.golden")
|
||||
expected, err := ioutil.ReadFile(goldenFilePath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, string(expected), string(contents))
|
||||
})
|
||||
}
|
||||
|
||||
func TestWriteCycloneSBOM(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var utilsMock piperutils.FileUtils
|
||||
utilsMock = &mock.FilesMock{}
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
paths, err := WriteCycloneSBOM([]byte{1, 2, 3, 4}, utilsMock)
|
||||
assert.NoError(t, err, "unexpexted error")
|
||||
assert.Equal(t, 1, len(paths))
|
||||
assert.Equal(t, "whitesource/piper_whitesource_sbom.xml", paths[0].Target)
|
||||
|
||||
exists, err := utilsMock.FileExists(paths[0].Target)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateSarifResultFile(t *testing.T) {
|
||||
scan := &Scan{ProductVersion: "1"}
|
||||
scan.AppendScannedProject("project1")
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/piperutils"
|
||||
"github.com/SAP/jenkins-library/pkg/versioning"
|
||||
)
|
||||
|
||||
// Scan stores information about scanned WhiteSource projects (modules).
|
||||
@ -16,11 +17,13 @@ type Scan struct {
|
||||
// It does not include the ProductVersion.
|
||||
AggregateProjectName string
|
||||
// ProductVersion is the global version that is used across all Projects (modules) during the scan.
|
||||
BuildTool string
|
||||
ProductVersion string
|
||||
scannedProjects map[string]Project
|
||||
scanTimes map[string]time.Time
|
||||
AgentName string
|
||||
AgentVersion string
|
||||
Coordinates versioning.Coordinates
|
||||
}
|
||||
|
||||
func (s *Scan) init() {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const whiteSourceConfig = "whitesource.config.json"
|
||||
@ -151,6 +152,9 @@ func getNpmProjectName(modulePath string, utils Utils) (string, error) {
|
||||
}
|
||||
var packageJSON = make(map[string]interface{})
|
||||
err = json.Unmarshal(fileContents, &packageJSON)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to unmarshall the file '%s'", modulePath)
|
||||
}
|
||||
|
||||
projectNameEntry, exists := packageJSON["name"]
|
||||
if !exists {
|
||||
|
@ -124,6 +124,11 @@ func (m *SystemMock) GetProjectLibraryLocations(projectToken string) ([]Library,
|
||||
return m.Libraries, nil
|
||||
}
|
||||
|
||||
// GetProjectHierarchy returns the libraries stored in the SystemMock.
|
||||
func (m *SystemMock) GetProjectHierarchy(projectToken string, inHouse bool) ([]Library, error) {
|
||||
return m.Libraries, nil
|
||||
}
|
||||
|
||||
// NewSystemMockWithProjectName returns a pointer to a new instance of SystemMock using a project with a defined name.
|
||||
func NewSystemMockWithProjectName(lastUpdateDate, projectName string) *SystemMock {
|
||||
mockLibrary := Library{
|
||||
|
191
pkg/whitesource/testdata/sbom.golden
vendored
Normal file
191
pkg/whitesource/testdata/sbom.golden
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" version="1">
|
||||
<metadata>
|
||||
<component bom-ref="pkg:maven/com.sap/myproduct@1.3.4" type="library">
|
||||
<group>com.sap</group>
|
||||
<name>myproduct</name>
|
||||
<version>1.3.4</version>
|
||||
</component>
|
||||
<properties>
|
||||
<property name="internal:bom-identifier">testProject - 1</property>
|
||||
</properties>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="pkg:maven/apache-commons/commons-lang@2.4.30" type="library">
|
||||
<author>apache-commons</author>
|
||||
<name>commons-lang</name>
|
||||
<version>2.4.30</version>
|
||||
<purl>pkg:maven/apache-commons/commons-lang@2.4.30</purl>
|
||||
</component>
|
||||
<component bom-ref="pkg:maven/apache-commons/commons-lang@3.15" type="library">
|
||||
<author>apache-commons</author>
|
||||
<name>commons-lang</name>
|
||||
<version>3.15</version>
|
||||
<purl>pkg:maven/apache-commons/commons-lang@3.15</purl>
|
||||
</component>
|
||||
<component bom-ref="pkg:maven/apache-logging/log4j@1.14" type="library">
|
||||
<author>apache-logging</author>
|
||||
<name>log4j</name>
|
||||
<version>1.14</version>
|
||||
<purl>pkg:maven/apache-logging/log4j@1.14</purl>
|
||||
</component>
|
||||
<component bom-ref="pkg:maven/apache-logging/log4j@3.25" type="library">
|
||||
<author>apache-logging</author>
|
||||
<name>log4j</name>
|
||||
<version>3.25</version>
|
||||
<purl>pkg:maven/apache-logging/log4j@3.25</purl>
|
||||
</component>
|
||||
</components>
|
||||
<dependencies>
|
||||
<dependency ref="pkg:maven/apache-logging/log4j@1.14">
|
||||
<dependency ref="pkg:maven/apache-commons/commons-lang@2.4.30"></dependency>
|
||||
</dependency>
|
||||
<dependency ref="pkg:maven/apache-logging/log4j@3.25">
|
||||
<dependency ref="pkg:maven/apache-commons/commons-lang@3.15"></dependency>
|
||||
</dependency>
|
||||
<dependency ref="pkg:maven/com.sap/myproduct@1.3.4">
|
||||
<dependency ref="pkg:maven/apache-logging/log4j@1.14"></dependency>
|
||||
<dependency ref="pkg:maven/apache-logging/log4j@3.25"></dependency>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<vulnerabilities>
|
||||
<vulnerability bom-ref="pkg:maven/apache-logging/log4j@1.14">
|
||||
<id>CVE-2022-001</id>
|
||||
<source></source>
|
||||
<references></references>
|
||||
<ratings>
|
||||
<rating>
|
||||
<score>0</score>
|
||||
<severity>medium</severity>
|
||||
<method>CVSSv3</method>
|
||||
</rating>
|
||||
<rating>
|
||||
<score>6</score>
|
||||
<severity>medium</severity>
|
||||
<method>CVSSv2</method>
|
||||
</rating>
|
||||
</ratings>
|
||||
<advisories>
|
||||
<advisory>
|
||||
<url></url>
|
||||
</advisory>
|
||||
</advisories>
|
||||
<published>01.01.2022</published>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>Mend</vendor>
|
||||
<name>Mend Unified Agent</name>
|
||||
<version>3.3.3</version>
|
||||
<externalReferences>
|
||||
<reference type="build-meta">
|
||||
<url>https://www.mend.io/</url>
|
||||
</reference>
|
||||
</externalReferences>
|
||||
</tool>
|
||||
</tools>
|
||||
<affects>
|
||||
<target>
|
||||
<ref>pkg:maven/apache-logging/log4j@1.14</ref>
|
||||
<versions>
|
||||
<version>
|
||||
<version>1.14</version>
|
||||
<status></status>
|
||||
</version>
|
||||
</versions>
|
||||
</target>
|
||||
</affects>
|
||||
</vulnerability>
|
||||
<vulnerability bom-ref="pkg:maven/apache-commons/commons-lang@2.4.30">
|
||||
<id>CVE-2022-002</id>
|
||||
<source></source>
|
||||
<references></references>
|
||||
<ratings>
|
||||
<rating>
|
||||
<score>0</score>
|
||||
<severity>high</severity>
|
||||
<method>CVSSv3</method>
|
||||
</rating>
|
||||
<rating>
|
||||
<score>6</score>
|
||||
<severity>high</severity>
|
||||
<method>CVSSv2</method>
|
||||
</rating>
|
||||
</ratings>
|
||||
<advisories>
|
||||
<advisory>
|
||||
<url></url>
|
||||
</advisory>
|
||||
</advisories>
|
||||
<published>02.01.2022</published>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>Mend</vendor>
|
||||
<name>Mend Unified Agent</name>
|
||||
<version>3.3.3</version>
|
||||
<externalReferences>
|
||||
<reference type="build-meta">
|
||||
<url>https://www.mend.io/</url>
|
||||
</reference>
|
||||
</externalReferences>
|
||||
</tool>
|
||||
</tools>
|
||||
<affects>
|
||||
<target>
|
||||
<ref>pkg:maven/apache-commons/commons-lang@2.4.30</ref>
|
||||
<versions>
|
||||
<version>
|
||||
<version>2.4.30</version>
|
||||
<status></status>
|
||||
</version>
|
||||
</versions>
|
||||
</target>
|
||||
</affects>
|
||||
</vulnerability>
|
||||
<vulnerability bom-ref="pkg:maven/apache-logging/log4j@3.25">
|
||||
<id>CVE-2022-003</id>
|
||||
<source></source>
|
||||
<references></references>
|
||||
<ratings>
|
||||
<rating>
|
||||
<score>0</score>
|
||||
<severity>medium</severity>
|
||||
<method>CVSSv3</method>
|
||||
</rating>
|
||||
<rating>
|
||||
<score>6</score>
|
||||
<severity>medium</severity>
|
||||
<method>CVSSv2</method>
|
||||
</rating>
|
||||
</ratings>
|
||||
<advisories>
|
||||
<advisory>
|
||||
<url></url>
|
||||
</advisory>
|
||||
</advisories>
|
||||
<published>03.01.2022</published>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>Mend</vendor>
|
||||
<name>Mend Unified Agent</name>
|
||||
<version>3.3.3</version>
|
||||
<externalReferences>
|
||||
<reference type="build-meta">
|
||||
<url>https://www.mend.io/</url>
|
||||
</reference>
|
||||
</externalReferences>
|
||||
</tool>
|
||||
</tools>
|
||||
<affects>
|
||||
<target>
|
||||
<ref>pkg:maven/apache-logging/log4j@3.25</ref>
|
||||
<versions>
|
||||
<version>
|
||||
<version>3.25</version>
|
||||
<status></status>
|
||||
</version>
|
||||
</versions>
|
||||
</target>
|
||||
</affects>
|
||||
</vulnerability>
|
||||
</vulnerabilities>
|
||||
</bom>
|
@ -8,9 +8,11 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/format"
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/reporting"
|
||||
"github.com/package-url/packageurl-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -64,6 +66,44 @@ func (a Alert) Title() string {
|
||||
return fmt.Sprintf("%v %v %v ", a.Type, a.Vulnerability.Name, a.Library.ArtifactID)
|
||||
}
|
||||
|
||||
func (a Alert) ContainedIn(assessments *[]format.Assessment) (bool, error) {
|
||||
localPurl := a.Library.ToPackageUrl().ToString()
|
||||
for _, assessment := range *assessments {
|
||||
if assessment.Vulnerability == a.Vulnerability.Name {
|
||||
for _, purl := range assessment.Purls {
|
||||
assessmentPurl, err := purl.ToPackageUrl()
|
||||
assessmentPurlStr := assessmentPurl.ToString()
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
log.Entry().WithError(err).Errorf("assessment from file ignored due to invalid packageUrl '%s'", purl)
|
||||
return false, err
|
||||
}
|
||||
if assessmentPurlStr == localPurl {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func transformLibToPurlType(libType string) string {
|
||||
// TODO verify and complete, only maven is proven so far
|
||||
switch libType {
|
||||
case "MAVEN_ARTIFACT":
|
||||
return packageurl.TypeMaven
|
||||
case "NODE_ARTIFACT":
|
||||
return packageurl.TypeNPM
|
||||
case "GOLANG_ARTIFACT":
|
||||
return packageurl.TypeGolang
|
||||
case "DOCKER_ARTIFACT":
|
||||
return packageurl.TypeGolang
|
||||
case "UNKNOWN_ARTIFACT":
|
||||
return packageurl.TypeGeneric
|
||||
}
|
||||
return packageurl.TypeGeneric
|
||||
}
|
||||
|
||||
func consolidate(cvss2severity, cvss3severity string, cvss2score, cvss3score float64) string {
|
||||
switch cvss3severity {
|
||||
case "low":
|
||||
@ -152,11 +192,22 @@ Link: [%v](%v)`,
|
||||
|
||||
// Library
|
||||
type Library struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Filename string `json:"filename,omitempty"`
|
||||
ArtifactID string `json:"artifactId,omitempty"`
|
||||
GroupID string `json:"groupId,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
KeyUUID string `json:"keyUuid,omitempty"`
|
||||
KeyID int `json:"keyId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Filename string `json:"filename,omitempty"`
|
||||
ArtifactID string `json:"artifactId,omitempty"`
|
||||
GroupID string `json:"groupId,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Sha1 string `json:"sha1,omitempty"`
|
||||
LibType string `json:"type,omitempty"`
|
||||
Coordinates string `json:"coordinates,omitempty"`
|
||||
Dependencies []Library `json:"dependencies,omitempty"`
|
||||
}
|
||||
|
||||
// ToPackageUrl constructs and returns the package URL of the library
|
||||
func (l Library) ToPackageUrl() *packageurl.PackageURL {
|
||||
return packageurl.NewPackageURL(transformLibToPurlType(l.LibType), l.GroupID, l.ArtifactID, l.Version, nil, "")
|
||||
}
|
||||
|
||||
// Vulnerability defines a vulnerability as returned by WhiteSource
|
||||
@ -221,6 +272,7 @@ type Request struct {
|
||||
AlertsEmailReceivers *Assignment `json:"alertsEmailReceivers,omitempty"`
|
||||
ProductApprovers *Assignment `json:"productApprovers,omitempty"`
|
||||
ProductIntegrators *Assignment `json:"productIntegrators,omitempty"`
|
||||
IncludeInHouseData bool `json:"includeInHouseData,omitempty"`
|
||||
}
|
||||
|
||||
// System defines a WhiteSource System including respective tokens (e.g. org token, user token)
|
||||
@ -346,6 +398,28 @@ func (s *System) GetProjectsMetaInfo(productToken string) ([]Project, error) {
|
||||
return wsResponse.ProjectVitals, nil
|
||||
}
|
||||
|
||||
// GetProjectHierarchy retrieves the full set of libraries that the project depends on
|
||||
func (s *System) GetProjectHierarchy(projectToken string, includeInHouse bool) ([]Library, error) {
|
||||
wsResponse := struct {
|
||||
Libraries []Library `json:"libraries"`
|
||||
}{
|
||||
Libraries: []Library{},
|
||||
}
|
||||
|
||||
req := Request{
|
||||
RequestType: "getProjectHierarchy",
|
||||
ProductToken: projectToken,
|
||||
IncludeInHouseData: includeInHouse,
|
||||
}
|
||||
|
||||
err := s.sendRequestAndDecodeJSON(req, &wsResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wsResponse.Libraries, nil
|
||||
}
|
||||
|
||||
// GetProjectToken returns the project token for a project with a given name
|
||||
func (s *System) GetProjectToken(productToken, projectName string) (string, error) {
|
||||
project, err := s.GetProjectByName(productToken, projectName)
|
||||
|
@ -55,6 +55,7 @@ func TestGetProductsMetaInfo(t *testing.T) {
|
||||
|
||||
sys := System{serverURL: "https://my.test.server", httpClient: &myTestClient, orgToken: "test_org_token", userToken: "test_user_token"}
|
||||
products, err := sys.GetProductsMetaInfo()
|
||||
assert.NoError(t, err)
|
||||
|
||||
requestBody, err := ioutil.ReadAll(myTestClient.requestBody)
|
||||
assert.NoError(t, err)
|
||||
@ -167,6 +168,7 @@ func TestGetProjectsMetaInfo(t *testing.T) {
|
||||
|
||||
sys := System{serverURL: "https://my.test.server", httpClient: &myTestClient, orgToken: "test_org_token", userToken: "test_user_token"}
|
||||
projects, err := sys.GetProjectsMetaInfo("test_product_token")
|
||||
assert.NoError(t, err)
|
||||
|
||||
requestBody, err := ioutil.ReadAll(myTestClient.requestBody)
|
||||
assert.NoError(t, err)
|
||||
@ -293,6 +295,138 @@ func TestGetProductName(t *testing.T) {
|
||||
assert.Equal(t, "Test Product", productName)
|
||||
}
|
||||
|
||||
func TestGetProjectHierarchy(t *testing.T) {
|
||||
myTestClient := whitesourceMockClient{
|
||||
responseBody: `{
|
||||
"libraries": [
|
||||
{
|
||||
"keyUuid": "1f9ee6ec-eded-45d3-8fdb-2d0d735e5b14",
|
||||
"keyId": 43,
|
||||
"filename": "log4j-1.2.17.jar",
|
||||
"name": "log4j",
|
||||
"groupId": "log4j",
|
||||
"artifactId": "log4j",
|
||||
"version": "1.2.17",
|
||||
"sha1": "5af35056b4d257e4b64b9e8069c0746e8b08629f",
|
||||
"type": "UNKNOWN_ARTIFACT",
|
||||
"coordinates": "log4j:log4j:1.2.17"
|
||||
},
|
||||
{
|
||||
"keyUuid": "f362c53f-ce25-4d0c-b53b-ee2768b32d1a",
|
||||
"keyId": 45,
|
||||
"filename": "akka-actor_2.11-2.5.2.jar",
|
||||
"name": "akka-actor",
|
||||
"groupId": "com.typesafe.akka",
|
||||
"artifactId": "akka-actor_2.11",
|
||||
"version": "2.5.2",
|
||||
"sha1": "183ccaed9002bfa10628a5df48e7bac6f1c03f7b",
|
||||
"type": "MAVEN_ARTIFACT",
|
||||
"coordinates": "com.typesafe.akka:akka-actor_2.11:2.5.2",
|
||||
"dependencies": [
|
||||
{
|
||||
"keyUuid": "49c6840d-bf96-470f-8892-6c2a536c91eb",
|
||||
"keyId": 44,
|
||||
"filename": "scala-library-2.11.11.jar",
|
||||
"name": "Scala Library",
|
||||
"groupId": "org.scala-lang",
|
||||
"artifactId": "scala-library",
|
||||
"version": "2.11.11",
|
||||
"sha1": "e283d2b7fde6504f6a86458b1f6af465353907cc",
|
||||
"type": "MAVEN_ARTIFACT",
|
||||
"coordinates": "org.scala-lang:scala-library:2.11.11"
|
||||
},
|
||||
{
|
||||
"keyUuid": "e5e730d1-8b41-4d2d-a8c5-610a374b6501",
|
||||
"keyId": 46,
|
||||
"filename": "scala-java8-compat_2.11-0.7.0.jar",
|
||||
"name": "scala-java8-compat_2.11",
|
||||
"groupId": "org.scala-lang.modules",
|
||||
"artifactId": "scala-java8-compat_2.11",
|
||||
"version": "0.7.0",
|
||||
"sha1": "a31b1b36bcf0d53657733b5d40c78d5f090a5dea",
|
||||
"type": "UNKNOWN_ARTIFACT",
|
||||
"coordinates": "org.scala-lang.modules:scala-java8-compat_2.11:0.7.0"
|
||||
},
|
||||
{
|
||||
"keyUuid": "426c0056-f180-4cac-a9dd-c266a76b32c9",
|
||||
"keyId": 47,
|
||||
"filename": "config-1.3.1.jar",
|
||||
"name": "config",
|
||||
"groupId": "com.typesafe",
|
||||
"artifactId": "config",
|
||||
"version": "1.3.1",
|
||||
"sha1": "2cf7a6cc79732e3bdf1647d7404279900ca63eb0",
|
||||
"type": "UNKNOWN_ARTIFACT",
|
||||
"coordinates": "com.typesafe:config:1.3.1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keyUuid": "25a8ceaa-4548-4fe4-9819-8658b8cbe9aa",
|
||||
"keyId": 48,
|
||||
"filename": "kafka-clients-0.10.2.1.jar",
|
||||
"name": "Apache Kafka",
|
||||
"groupId": "org.apache.kafka",
|
||||
"artifactId": "kafka-clients",
|
||||
"version": "0.10.2.1",
|
||||
"sha1": "3dd2aa4c9f87ac54175d017bcb63b4bb5dca63dd",
|
||||
"type": "MAVEN_ARTIFACT",
|
||||
"coordinates": "org.apache.kafka:kafka-clients:0.10.2.1",
|
||||
"dependencies": [
|
||||
{
|
||||
"keyUuid": "71065ffb-e509-4e2d-88bc-9184bc50888d",
|
||||
"keyId": 49,
|
||||
"filename": "lz4-1.3.0.jar",
|
||||
"name": "LZ4 and xxHash",
|
||||
"groupId": "net.jpountz.lz4",
|
||||
"artifactId": "lz4",
|
||||
"version": "1.3.0",
|
||||
"sha1": "c708bb2590c0652a642236ef45d9f99ff842a2ce",
|
||||
"type": "MAVEN_ARTIFACT",
|
||||
"coordinates": "net.jpountz.lz4:lz4:1.3.0"
|
||||
},
|
||||
{
|
||||
"keyUuid": "e44ab569-de95-4562-8efa-a2ebfe808471",
|
||||
"keyId": 50,
|
||||
"filename": "slf4j-api-1.7.21.jar",
|
||||
"name": "SLF4J API Module",
|
||||
"groupId": "org.slf4j",
|
||||
"artifactId": "slf4j-api",
|
||||
"version": "1.7.21",
|
||||
"sha1": "139535a69a4239db087de9bab0bee568bf8e0b70",
|
||||
"type": "MAVEN_ARTIFACT",
|
||||
"coordinates": "org.slf4j:slf4j-api:1.7.21"
|
||||
},
|
||||
{
|
||||
"keyUuid": "72ecad5e-9f35-466c-9ed8-0974e7ce4e29",
|
||||
"keyId": 51,
|
||||
"filename": "snappy-java-1.1.2.6.jar",
|
||||
"name": "snappy-java",
|
||||
"groupId": "org.xerial.snappy",
|
||||
"artifactId": "snappy-java",
|
||||
"version": "1.1.2.6",
|
||||
"sha1": "48d92871ca286a47f230feb375f0bbffa83b85f6",
|
||||
"type": "UNKNOWN_ARTIFACT",
|
||||
"coordinates": "org.xerial.snappy:snappy-java:1.1.2.6"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
}
|
||||
|
||||
sys := System{serverURL: "https://my.test.server", httpClient: &myTestClient, orgToken: "test_org_token", userToken: "test_user_token"}
|
||||
|
||||
libraries, err := sys.GetProjectHierarchy("test_project_token", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, len(libraries))
|
||||
assert.Nil(t, libraries[0].Dependencies)
|
||||
assert.NotNil(t, libraries[1].Dependencies)
|
||||
assert.Equal(t, 3, len(libraries[1].Dependencies))
|
||||
assert.NotNil(t, libraries[2].Dependencies)
|
||||
assert.Equal(t, 3, len(libraries[2].Dependencies))
|
||||
}
|
||||
|
||||
func TestGetProjectsByIDs(t *testing.T) {
|
||||
responseBody :=
|
||||
`{
|
||||
|
@ -19,6 +19,9 @@ spec:
|
||||
- name: checkmarxCredentialsId
|
||||
description: Jenkins 'Username with password' credentials ID containing username and password to communicate with the Checkmarx backend.
|
||||
type: jenkins
|
||||
- name: githubTokenCredentialsId
|
||||
description: Jenkins 'Secret text' credentials ID containing token to authenticate to GitHub.
|
||||
type: jenkins
|
||||
resources:
|
||||
- name: checkmarx
|
||||
type: stash
|
||||
|
@ -18,6 +18,9 @@ spec:
|
||||
- name: apiTokenCredentialsId
|
||||
description: Jenkins 'Secret text' credentials ID containing the API token used to authenticate with the Synopsis Detect (formerly BlackDuck) Server.
|
||||
type: jenkins
|
||||
- name: githubTokenCredentialsId
|
||||
description: Jenkins 'Secret text' credentials ID containing token to authenticate to GitHub.
|
||||
type: jenkins
|
||||
params:
|
||||
- name: token
|
||||
aliases:
|
||||
|
@ -81,6 +81,14 @@ spec:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: assessmentFile
|
||||
type: string
|
||||
description: "Explicit path to the assessment YAML file."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: "hs-assessments.yaml"
|
||||
- name: buildDescriptorExcludeList
|
||||
type: "[]string"
|
||||
description: "List of build descriptors and therefore modules to exclude from the scan and assessment activities."
|
||||
|
Loading…
Reference in New Issue
Block a user