From a2815c4567c4b80202ee9c5718757a468ddf4e8b Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Mon, 7 Nov 2022 11:16:07 +0100 Subject: [PATCH] fix(whitesourceExecuteScan): properly handle policy violations (#4089) * fix(whitesourceExecuteScan): properly handle policy violations * update files * update formatting Co-authored-by: Sven Merk <33895725+nevskrem@users.noreply.github.com> --- cmd/whitesourceExecuteScan.go | 41 +++++-- cmd/whitesourceExecuteScan_test.go | 18 ++- pkg/reporting/policyViolation.go | 93 +++++++++++++++ pkg/reporting/policyViolation_test.go | 43 +++++++ pkg/reporting/reporting.go | 104 +---------------- pkg/reporting/reporting_test.go | 42 ------- pkg/reporting/securityVulnerability.go | 108 ++++++++++++++++++ pkg/reporting/securityVulnerability_test.go | 49 ++++++++ .../testdata/markdownPolicyViolation.golden | 25 ++++ pkg/whitesource/configHelper.go | 2 +- pkg/whitesource/whitesource.go | 70 ++++++++---- 11 files changed, 411 insertions(+), 184 deletions(-) create mode 100644 pkg/reporting/policyViolation.go create mode 100644 pkg/reporting/policyViolation_test.go create mode 100644 pkg/reporting/securityVulnerability.go create mode 100644 pkg/reporting/securityVulnerability_test.go create mode 100644 pkg/reporting/testdata/markdownPolicyViolation.golden diff --git a/cmd/whitesourceExecuteScan.go b/cmd/whitesourceExecuteScan.go index b28ae1176..918e34f26 100644 --- a/cmd/whitesourceExecuteScan.go +++ b/cmd/whitesourceExecuteScan.go @@ -260,9 +260,14 @@ func checkAndReportScanResults(ctx context.Context, config *ScanOptions, scan *w checkErrors := []string{} - rPath, err := checkPolicyViolations(config, scan, sys, utils, reportPaths, influx) + rPath, err := checkPolicyViolations(ctx, config, scan, sys, utils, reportPaths, influx) + if err != nil { - checkErrors = append(checkErrors, fmt.Sprint(err)) + if !config.FailOnSevereVulnerabilities && log.GetErrorCategory() == log.ErrorCompliance { + log.Entry().Infof("policy violation(s) found - step will only create data but not fail due to setting failOnSevereVulnerabilities: false") + } else { + checkErrors = append(checkErrors, fmt.Sprint(err)) + } } reportPaths = append(reportPaths, rPath) @@ -270,7 +275,11 @@ func checkAndReportScanResults(ctx context.Context, config *ScanOptions, scan *w rPaths, err := checkSecurityViolations(ctx, config, scan, sys, utils, influx) reportPaths = append(reportPaths, rPaths...) if err != nil { - checkErrors = append(checkErrors, fmt.Sprint(err)) + if !config.FailOnSevereVulnerabilities && log.GetErrorCategory() == log.ErrorCompliance { + log.Entry().Infof("policy violation(s) found - step will only create data but not fail due to setting failOnSevereVulnerabilities: false") + } else { + checkErrors = append(checkErrors, fmt.Sprint(err)) + } } } @@ -480,14 +489,16 @@ func executeScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils) err return nil } -func checkPolicyViolations(config *ScanOptions, scan *ws.Scan, sys whitesource, utils whitesourceUtils, reportPaths []piperutils.Path, influx *whitesourceExecuteScanInflux) (piperutils.Path, error) { +func checkPolicyViolations(ctx context.Context, config *ScanOptions, scan *ws.Scan, sys whitesource, utils whitesourceUtils, reportPaths []piperutils.Path, influx *whitesourceExecuteScanInflux) (piperutils.Path, error) { policyViolationCount := 0 + allAlerts := []ws.Alert{} for _, project := range scan.ScannedProjects() { alerts, err := sys.GetProjectAlertsByType(project.Token, "REJECTED_BY_POLICY_RESOURCE") if err != nil { return piperutils.Path{}, fmt.Errorf("failed to retrieve project policy alerts from WhiteSource: %w", err) } policyViolationCount += len(alerts) + allAlerts = append(allAlerts, alerts...) } violations := struct { @@ -547,11 +558,24 @@ func checkPolicyViolations(config *ScanOptions, scan *ws.Scan, sys whitesource, if policyViolationCount > 0 { influx.whitesource_data.fields.policy_violations = policyViolationCount - if config.FailOnSevereVulnerabilities { - log.SetErrorCategory(log.ErrorCompliance) - return policyReport, fmt.Errorf("%v policy violation(s) found", policyViolationCount) + log.SetErrorCategory(log.ErrorCompliance) + + if config.CreateResultIssue && policyViolationCount > 0 && len(config.GithubToken) > 0 && len(config.GithubAPIURL) > 0 && len(config.Owner) > 0 && len(config.Repository) > 0 { + log.Entry().Debugf("Creating result issues for %v alert(s)", policyViolationCount) + issueDetails := make([]reporting.IssueDetail, len(allAlerts)) + piperutils.CopyAtoB(allAlerts, issueDetails) + gh := reporting.GitHub{ + Owner: &config.Owner, + Repository: &config.Repository, + Assignees: &config.Assignees, + IssueService: utils.GetIssueService(), + SearchService: utils.GetSearchService(), + } + if err := gh.UploadMultipleReports(ctx, &issueDetails); err != nil { + return policyReport, fmt.Errorf("failed to upload reports to GitHub for %v policy violations: %w", policyViolationCount, err) + } } - log.Entry().Infof("%v policy violation(s) found - step will only create data but not fail due to setting failOnSevereVulnerabilities: false", policyViolationCount) + return policyReport, fmt.Errorf("%v policy violation(s) found", policyViolationCount) } return policyReport, nil @@ -900,7 +924,6 @@ func persistScannedProjects(config *ScanOptions, scan *ws.Scan, commonPipelineEn } // create toolrecord file for whitesource -// func createToolRecordWhitesource(utils whitesourceUtils, workspace string, config *whitesourceExecuteScanOptions, scan *ws.Scan) (string, error) { record := toolrecord.New(utils, workspace, "whitesource", config.ServiceURL) wsUiRoot := "https://saas.whitesourcesoftware.com" diff --git a/cmd/whitesourceExecuteScan_test.go b/cmd/whitesourceExecuteScan_test.go index f9f3a9a4a..e224f3f6e 100644 --- a/cmd/whitesourceExecuteScan_test.go +++ b/cmd/whitesourceExecuteScan_test.go @@ -377,6 +377,7 @@ func TestCheckPolicyViolations(t *testing.T) { t.Parallel() t.Run("success - no violations", func(t *testing.T) { + ctx := context.Background() config := ScanOptions{ProductName: "mock-product", Version: "1"} scan := newWhitesourceScan(&config) if err := scan.AppendScannedProject("testProject1"); err != nil { @@ -391,7 +392,7 @@ func TestCheckPolicyViolations(t *testing.T) { } influx := whitesourceExecuteScanInflux{} - path, err := checkPolicyViolations(&config, scan, systemMock, utilsMock, reportPaths, &influx) + path, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) assert.NoError(t, err) assert.Equal(t, filepath.Join(ws.ReportsDirectory, "whitesource-ip.json"), path.Target) @@ -405,6 +406,7 @@ func TestCheckPolicyViolations(t *testing.T) { }) t.Run("success - no reports", func(t *testing.T) { + ctx := context.Background() config := ScanOptions{} scan := newWhitesourceScan(&config) if err := scan.AppendScannedProject("testProject1"); err != nil { @@ -416,7 +418,7 @@ func TestCheckPolicyViolations(t *testing.T) { reportPaths := []piperutils.Path{} influx := whitesourceExecuteScanInflux{} - path, err := checkPolicyViolations(&config, scan, systemMock, utilsMock, reportPaths, &influx) + path, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) assert.NoError(t, err) fileContent, _ := utilsMock.FileRead(path.Target) @@ -425,6 +427,7 @@ func TestCheckPolicyViolations(t *testing.T) { }) t.Run("error - policy violations", func(t *testing.T) { + ctx := context.Background() config := ScanOptions{FailOnSevereVulnerabilities: true} scan := newWhitesourceScan(&config) if err := scan.AppendScannedProject("testProject1"); err != nil { @@ -442,7 +445,7 @@ func TestCheckPolicyViolations(t *testing.T) { } influx := whitesourceExecuteScanInflux{} - path, err := checkPolicyViolations(&config, scan, systemMock, utilsMock, reportPaths, &influx) + path, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) assert.Contains(t, fmt.Sprint(err), "2 policy violation(s) found") fileContent, _ := utilsMock.FileRead(path.Target) @@ -452,6 +455,7 @@ func TestCheckPolicyViolations(t *testing.T) { }) t.Run("error - get alerts", func(t *testing.T) { + ctx := context.Background() config := ScanOptions{} scan := newWhitesourceScan(&config) if err := scan.AppendScannedProject("testProject1"); err != nil { @@ -463,11 +467,12 @@ func TestCheckPolicyViolations(t *testing.T) { reportPaths := []piperutils.Path{} influx := whitesourceExecuteScanInflux{} - _, err := checkPolicyViolations(&config, scan, systemMock, utilsMock, reportPaths, &influx) + _, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) assert.Contains(t, fmt.Sprint(err), "failed to retrieve project policy alerts from WhiteSource") }) t.Run("error - write file", func(t *testing.T) { + ctx := context.Background() config := ScanOptions{} scan := newWhitesourceScan(&config) if err := scan.AppendScannedProject("testProject1"); err != nil { @@ -480,11 +485,12 @@ func TestCheckPolicyViolations(t *testing.T) { reportPaths := []piperutils.Path{} influx := whitesourceExecuteScanInflux{} - _, err := checkPolicyViolations(&config, scan, systemMock, utilsMock, reportPaths, &influx) + _, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) assert.Contains(t, fmt.Sprint(err), "failed to write policy violation report:") }) t.Run("failed to write json report", func(t *testing.T) { + ctx := context.Background() config := ScanOptions{ProductName: "mock-product", Version: "1"} scan := newWhitesourceScan(&config) if err := scan.AppendScannedProject("testProject1"); err != nil { @@ -499,7 +505,7 @@ func TestCheckPolicyViolations(t *testing.T) { reportPaths := []piperutils.Path{} influx := whitesourceExecuteScanInflux{} - _, err := checkPolicyViolations(&config, scan, systemMock, utilsMock, reportPaths, &influx) + _, err := checkPolicyViolations(ctx, &config, scan, systemMock, utilsMock, reportPaths, &influx) assert.Contains(t, fmt.Sprint(err), "failed to write json report") }) } diff --git a/pkg/reporting/policyViolation.go b/pkg/reporting/policyViolation.go new file mode 100644 index 000000000..669ea6896 --- /dev/null +++ b/pkg/reporting/policyViolation.go @@ -0,0 +1,93 @@ +package reporting + +import ( + "bytes" + "fmt" + "text/template" + "time" + + "github.com/SAP/jenkins-library/pkg/orchestrator" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +type PolicyViolationReport struct { + ArtifactID string + Branch string + CommitID string + Description string + DirectDependency string + Footer string + Group string + PackageURL string + PipelineName string + PipelineLink string + Version string +} + +const policyViolationMdTemplate string = `# Policy Violation - {{ .PackageURL }} + +## Description + +{{ .Description }} + +## Context + +{{if .PipelineLink -}} +### Pipeline + +Pipeline run: [{{ .PipelineName }}]({{ .PipelineLink }}) +{{- end}} + +### Detected in + +{{if .Branch}}**Branch:** {{ .Branch }}{{- end}} +{{if .CommitID}}**CommitId:** {{ .CommitID }}{{- end}} +{{if .DirectDependency}}**Dependency:** {{if (eq .DirectDependency "true")}}direct{{ else }}indirect{{ end }}{{- end}} +{{if .ArtifactID}}**ArtifactId:** {{ .ArtifactID }}{{- end}} +{{if .Group}}**Group:** {{ .Group }}{{- end}} +{{if .Version}}**Version:** {{ .Version }}{{- end}} +{{if .PackageURL}}**Package URL:** {{ .PackageURL }}{{- end}} + +--- + +{{.Footer}} +` + +func (p *PolicyViolationReport) ToMarkdown() ([]byte, error) { + funcMap := template.FuncMap{ + "date": func(t time.Time) string { + return t.Format("2006-01-02") + }, + "title": func(s string) string { + caser := cases.Title(language.AmericanEnglish) + return caser.String(s) + }, + } + + // only fill with orchestrator information if orchestrator can be identified properly + if provider, err := orchestrator.NewOrchestratorSpecificConfigProvider(); err == nil { + // only add information if not yet provided + if len(p.CommitID) == 0 { + p.CommitID = provider.GetCommit() + } + if len(p.PipelineLink) == 0 { + p.PipelineLink = provider.GetJobURL() + p.PipelineName = provider.GetJobName() + } + } + + md := []byte{} + tmpl, err := template.New("report").Funcs(funcMap).Parse(policyViolationMdTemplate) + if err != nil { + return md, fmt.Errorf("failed to create markdown issue template: %w", err) + } + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, p) + if err != nil { + return md, fmt.Errorf("failed to execute markdown issue template: %w", err) + } + md = buf.Bytes() + return md, nil +} diff --git a/pkg/reporting/policyViolation_test.go b/pkg/reporting/policyViolation_test.go new file mode 100644 index 000000000..4e913df2e --- /dev/null +++ b/pkg/reporting/policyViolation_test.go @@ -0,0 +1,43 @@ +package reporting + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPolicyViolationToMarkdown(t *testing.T) { + t.Parallel() + t.Run("success - empty", func(t *testing.T) { + t.Parallel() + policyReport := PolicyViolationReport{} + _, err := policyReport.ToMarkdown() + assert.NoError(t, err) + }) + + t.Run("success - filled", func(t *testing.T) { + t.Parallel() + policyReport := PolicyViolationReport{ + ArtifactID: "theArtifact", + Branch: "main", + CommitID: "acb123", + Description: "This is the test description.", + DirectDependency: "true", + Footer: "This is the test footer", + Group: "the.group", + PipelineName: "thePipelineName", + PipelineLink: "https://the.link.to.the.pipeline", + Version: "1.2.3", + PackageURL: "pkg:generic/the.group/theArtifact@1.2.3", + } + goldenFilePath := filepath.Join("testdata", "markdownPolicyViolation.golden") + expected, err := os.ReadFile(goldenFilePath) + assert.NoError(t, err) + + res, err := policyReport.ToMarkdown() + assert.NoError(t, err) + assert.Equal(t, string(expected), string(res)) + }) +} diff --git a/pkg/reporting/reporting.go b/pkg/reporting/reporting.go index 6b1ecf52f..dc2e91acc 100644 --- a/pkg/reporting/reporting.go +++ b/pkg/reporting/reporting.go @@ -7,11 +7,7 @@ import ( "text/template" "time" - "github.com/SAP/jenkins-library/pkg/orchestrator" - "github.com/pkg/errors" - "golang.org/x/text/cases" - "golang.org/x/text/language" ) // IssueDetail represents any content that can be transformed into the body of a GitHub issue @@ -21,101 +17,6 @@ type IssueDetail interface { ToTxt() string } -// VulnerabilityReport represents metadata for a report on a vulnerability -type VulnerabilityReport struct { - ArtifactID string - Branch string - CommitID string - Description string - DirectDependency string - Footer string - Group string - PackageURL string - PipelineName string - PipelineLink string - PublishDate string - Resolution string - Score float64 - Severity string - Version string - VulnerabilityLink string - VulnerabilityName string -} - -const vulnerabilityMdTemplate string = `# {{title .Severity }} ({{ .Score }}) Vulnerability {{ .VulnerabilityName }} - {{ .ArtifactID }} - -**Vulnerability link:** [{{ .VulnerabilityLink }}]({{ .VulnerabilityLink }}) - -## Fix - -**{{ .Resolution }}** - -## Context - -{{if .PipelineLink -}} -### Pipeline - -Pipeline run: [{{ .PipelineName }}]({{ .PipelineLink }}) -{{- end}} - -### Detected in - -{{if .Branch}}**Branch:** {{ .Branch }}{{- end}} -{{if .CommitID}}**CommitId:** {{ .CommitID }}{{- end}} -{{if .DirectDependency}}**Dependency:** {{if (eq .DirectDependency "true")}}direct{{ else }}indirect{{ end }}{{- end}} -{{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 - -{{ .Description }} - ---- - -{{.Footer}} -` - -// ToMarkdown creates a vulnerability in markdown format which can be used in GitHub issues -func (v *VulnerabilityReport) ToMarkdown() ([]byte, error) { - funcMap := template.FuncMap{ - "date": func(t time.Time) string { - return t.Format("2006-01-02") - }, - "title": func(s string) string { - caser := cases.Title(language.AmericanEnglish) - return caser.String(s) - }, - } - - // only fill with orchestrator information if orchestrator can be identified properly - if provider, err := orchestrator.NewOrchestratorSpecificConfigProvider(); err == nil { - // only add information if not yet provided - if len(v.CommitID) == 0 { - v.CommitID = provider.GetCommit() - } - if len(v.PipelineLink) == 0 { - v.PipelineLink = provider.GetJobURL() - v.PipelineName = provider.GetJobName() - } - } - - md := []byte{} - tmpl, err := template.New("report").Funcs(funcMap).Parse(vulnerabilityMdTemplate) - if err != nil { - return md, fmt.Errorf("failed to create markdown issue template: %w", err) - } - buf := new(bytes.Buffer) - err = tmpl.Execute(buf, v) - if err != nil { - return md, fmt.Errorf("failed to execute markdown issue template: %w", err) - } - md = buf.Bytes() - return md, nil -} - // ScanReport defines the elements of a scan report used by various scan steps type ScanReport struct { StepName string `json:"stepName"` @@ -426,10 +327,7 @@ func drawCell(cell ScanCell) string { } func shouldDrawTable(table ScanDetailTable) bool { - if len(table.Headers) > 0 { - return true - } - return false + return len(table.Headers) > 0 } func drawOverviewRow(row OverviewRow) string { diff --git a/pkg/reporting/reporting_test.go b/pkg/reporting/reporting_test.go index 94a3f106b..1b0f0d379 100644 --- a/pkg/reporting/reporting_test.go +++ b/pkg/reporting/reporting_test.go @@ -1,54 +1,12 @@ package reporting import ( - "io/ioutil" - "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" ) -func TestVulToMarkdown(t *testing.T) { - t.Parallel() - t.Run("success - empty", func(t *testing.T) { - t.Parallel() - vulReport := VulnerabilityReport{} - _, err := vulReport.ToMarkdown() - assert.NoError(t, err) - }) - - t.Run("success - filled", func(t *testing.T) { - t.Parallel() - vulReport := VulnerabilityReport{ - ArtifactID: "theArtifact", - Branch: "main", - CommitID: "acb123", - Description: "This is the test description.", - DirectDependency: "true", - Footer: "This is the test footer", - Group: "the.group", - PipelineName: "thePipelineName", - PipelineLink: "https://the.link.to.the.pipeline", - PublishDate: "2022-06-30", - Resolution: "This is the test resolution.", - 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", - } - goldenFilePath := filepath.Join("testdata", "markdownVulnerability.golden") - expected, err := ioutil.ReadFile(goldenFilePath) - assert.NoError(t, err) - - res, err := vulReport.ToMarkdown() - assert.NoError(t, err) - assert.Equal(t, string(expected), string(res)) - }) -} - func TestToHTML(t *testing.T) { t.Run("empty table", func(t *testing.T) { report := ScanReport{ diff --git a/pkg/reporting/securityVulnerability.go b/pkg/reporting/securityVulnerability.go new file mode 100644 index 000000000..023443a75 --- /dev/null +++ b/pkg/reporting/securityVulnerability.go @@ -0,0 +1,108 @@ +package reporting + +import ( + "bytes" + "fmt" + "text/template" + "time" + + "github.com/SAP/jenkins-library/pkg/orchestrator" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// VulnerabilityReport represents metadata for a report on a vulnerability +type VulnerabilityReport struct { + ArtifactID string + Branch string + CommitID string + Description string + DirectDependency string + Footer string + Group string + PackageURL string + PipelineName string + PipelineLink string + PublishDate string + Resolution string + Score float64 + Severity string + Version string + VulnerabilityLink string + VulnerabilityName string +} + +const vulnerabilityMdTemplate string = `# {{title .Severity }} ({{ .Score }}) Vulnerability {{ .VulnerabilityName }} - {{ .ArtifactID }} + +**Vulnerability link:** [{{ .VulnerabilityLink }}]({{ .VulnerabilityLink }}) + +## Fix + +**{{ .Resolution }}** + +## Context + +{{if .PipelineLink -}} +### Pipeline + +Pipeline run: [{{ .PipelineName }}]({{ .PipelineLink }}) +{{- end}} + +### Detected in + +{{if .Branch}}**Branch:** {{ .Branch }}{{- end}} +{{if .CommitID}}**CommitId:** {{ .CommitID }}{{- end}} +{{if .DirectDependency}}**Dependency:** {{if (eq .DirectDependency "true")}}direct{{ else }}indirect{{ end }}{{- end}} +{{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 + +{{ .Description }} + +--- + +{{.Footer}} +` + +// ToMarkdown creates a vulnerability in markdown format which can be used in GitHub issues +func (v *VulnerabilityReport) ToMarkdown() ([]byte, error) { + funcMap := template.FuncMap{ + "date": func(t time.Time) string { + return t.Format("2006-01-02") + }, + "title": func(s string) string { + caser := cases.Title(language.AmericanEnglish) + return caser.String(s) + }, + } + + // only fill with orchestrator information if orchestrator can be identified properly + if provider, err := orchestrator.NewOrchestratorSpecificConfigProvider(); err == nil { + // only add information if not yet provided + if len(v.CommitID) == 0 { + v.CommitID = provider.GetCommit() + } + if len(v.PipelineLink) == 0 { + v.PipelineLink = provider.GetJobURL() + v.PipelineName = provider.GetJobName() + } + } + + md := []byte{} + tmpl, err := template.New("report").Funcs(funcMap).Parse(vulnerabilityMdTemplate) + if err != nil { + return md, fmt.Errorf("failed to create markdown issue template: %w", err) + } + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, v) + if err != nil { + return md, fmt.Errorf("failed to execute markdown issue template: %w", err) + } + md = buf.Bytes() + return md, nil +} diff --git a/pkg/reporting/securityVulnerability_test.go b/pkg/reporting/securityVulnerability_test.go new file mode 100644 index 000000000..0ee0130fb --- /dev/null +++ b/pkg/reporting/securityVulnerability_test.go @@ -0,0 +1,49 @@ +package reporting + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVulnerabilityReportToMarkdown(t *testing.T) { + t.Parallel() + t.Run("success - empty", func(t *testing.T) { + t.Parallel() + vulReport := VulnerabilityReport{} + _, err := vulReport.ToMarkdown() + assert.NoError(t, err) + }) + + t.Run("success - filled", func(t *testing.T) { + t.Parallel() + vulReport := VulnerabilityReport{ + ArtifactID: "theArtifact", + Branch: "main", + CommitID: "acb123", + Description: "This is the test description.", + DirectDependency: "true", + Footer: "This is the test footer", + Group: "the.group", + PipelineName: "thePipelineName", + PipelineLink: "https://the.link.to.the.pipeline", + PublishDate: "2022-06-30", + Resolution: "This is the test resolution.", + 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", + } + goldenFilePath := filepath.Join("testdata", "markdownVulnerability.golden") + expected, err := os.ReadFile(goldenFilePath) + assert.NoError(t, err) + + res, err := vulReport.ToMarkdown() + assert.NoError(t, err) + assert.Equal(t, string(expected), string(res)) + }) +} diff --git a/pkg/reporting/testdata/markdownPolicyViolation.golden b/pkg/reporting/testdata/markdownPolicyViolation.golden new file mode 100644 index 000000000..1572537dc --- /dev/null +++ b/pkg/reporting/testdata/markdownPolicyViolation.golden @@ -0,0 +1,25 @@ +# Policy Violation - pkg:generic/the.group/theArtifact@1.2.3 + +## Description + +This is the test description. + +## Context + +### Pipeline + +Pipeline run: [thePipelineName](https://the.link.to.the.pipeline) + +### Detected in + +**Branch:** main +**CommitId:** acb123 +**Dependency:** direct +**ArtifactId:** theArtifact +**Group:** the.group +**Version:** 1.2.3 +**Package URL:** pkg:generic/the.group/theArtifact@1.2.3 + +--- + +This is the test footer diff --git a/pkg/whitesource/configHelper.go b/pkg/whitesource/configHelper.go index a7e446fb3..ffa3e5de9 100644 --- a/pkg/whitesource/configHelper.go +++ b/pkg/whitesource/configHelper.go @@ -305,7 +305,7 @@ func mvnProjectExcludes(buildDescriptorExcludeList []string, utils Utils) []stri return []string{} } -//ToDo: Check if we want to optionally allow auto generation for unknown projects +// ToDo: Check if we want to optionally allow auto generation for unknown projects func autoGenerateWhitesourceConfig(config *ScanOptions, utils Utils) error { // TODO: Should we rely on -detect, or set the parameters manually? if err := utils.RunExecutable("java", "-jar", config.AgentFileName, "-d", ".", "-detect"); err != nil { diff --git a/pkg/whitesource/whitesource.go b/pkg/whitesource/whitesource.go index cf32ad189..6a3acf813 100644 --- a/pkg/whitesource/whitesource.go +++ b/pkg/whitesource/whitesource.go @@ -64,6 +64,8 @@ type Alert struct { func (a *Alert) Title() string { if a.Type == "SECURITY_VULNERABILITY" { return fmt.Sprintf("Security Vulnerability %v %v", a.Vulnerability.Name, a.Library.ArtifactID) + } else if a.Type == "REJECTED_BY_POLICY_RESOURCE" { + return fmt.Sprintf("Policy Violation %v %v", a.Vulnerability.Name, a.Library.ArtifactID) } return fmt.Sprintf("%v %v %v ", a.Type, a.Vulnerability.Name, a.Library.ArtifactID) } @@ -144,32 +146,54 @@ func consolidate(cvss2severity, cvss3severity string, cvss2score, cvss3score flo // ToMarkdown returns the markdown representation of the contents func (a *Alert) ToMarkdown() ([]byte, error) { - score := consolidateScores(a.Vulnerability.Score, a.Vulnerability.CVSS3Score) - vul := reporting.VulnerabilityReport{ - ArtifactID: a.Library.ArtifactID, - // no information available about branch and commit, yet - Branch: "", - CommitID: "", - Description: a.Vulnerability.Description, - DirectDependency: fmt.Sprint(a.DirectDependency), - // no information available about footer, yet - Footer: "", - Group: a.Library.GroupID, - // no information available about pipeline name and link, yet - PipelineName: "", - PipelineLink: "", - PublishDate: a.Vulnerability.PublishDate, - Resolution: a.Vulnerability.TopFix.FixResolution, - 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, + if a.Type == "SECURITY_VULNERABILITY" { + score := consolidateScores(a.Vulnerability.Score, a.Vulnerability.CVSS3Score) + + vul := reporting.VulnerabilityReport{ + ArtifactID: a.Library.ArtifactID, + // no information available about branch and commit, yet + Branch: "", + CommitID: "", + Description: a.Vulnerability.Description, + DirectDependency: fmt.Sprint(a.DirectDependency), + // no information available about footer, yet + Footer: "", + Group: a.Library.GroupID, + // no information available about pipeline name and link, yet + PipelineName: "", + PipelineLink: "", + PublishDate: a.Vulnerability.PublishDate, + Resolution: a.Vulnerability.TopFix.FixResolution, + 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, + } + return vul.ToMarkdown() + } else if a.Type == "REJECTED_BY_POLICY_RESOURCE" { + policyReport := reporting.PolicyViolationReport{ + ArtifactID: a.Library.ArtifactID, + // no information available about branch and commit, yet + Branch: "", + CommitID: "", + Description: a.Vulnerability.Description, + DirectDependency: fmt.Sprint(a.DirectDependency), + // no information available about footer, yet + Footer: "", + Group: a.Library.GroupID, + // no information available about pipeline name and link, yet + PipelineName: "", + PipelineLink: "", + Version: a.Library.Version, + PackageURL: a.Library.ToPackageUrl().ToString(), + } + return policyReport.ToMarkdown() } - return vul.ToMarkdown() + return []byte{}, nil } // ToTxt returns the textual representation of the contents