1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-10-30 23:57:50 +02:00

feat: improve vulnerability reporting via GitHub issues (#3924)

* feat: improve vulnerability reporting via GitHub issues

* feat: update reports

* chore: add tls cert links

* only write log on error

* chore: update formatting

* chore: update handling of direct dependencies

* chore: fix linting issue

* chore: minor updates
This commit is contained in:
Oliver Nocon
2022-08-02 08:26:26 +02:00
committed by GitHub
parent c8f069efb2
commit d640d72dc6
31 changed files with 1198 additions and 451 deletions

View File

@@ -2,6 +2,9 @@ package cmd
import (
"archive/zip"
"context"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
@@ -13,9 +16,6 @@ import (
"strings"
"time"
"encoding/json"
"encoding/xml"
"github.com/SAP/jenkins-library/pkg/checkmarx"
piperGithub "github.com/SAP/jenkins-library/pkg/github"
piperHttp "github.com/SAP/jenkins-library/pkg/http"
@@ -26,67 +26,96 @@ import (
"github.com/SAP/jenkins-library/pkg/toolrecord"
"github.com/bmatcuk/doublestar"
"github.com/pkg/errors"
"github.com/google/go-github/v45/github"
)
type checkmarxExecuteScanUtils interface {
CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error
FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error)
Stat(name string) (os.FileInfo, error)
Open(name string) (*os.File, error)
WriteFile(filename string, data []byte, perm os.FileMode) error
PathMatch(pattern, name string) (bool, error)
GetWorkspace() string
GetIssueService() *github.IssuesService
GetSearchService() *github.SearchService
}
type checkmarxExecuteScanUtilsBundle struct {
workspace string
issues *github.IssuesService
search *github.SearchService
}
func (checkmarxExecuteScanUtilsBundle) PathMatch(pattern, name string) (bool, error) {
func (c *checkmarxExecuteScanUtilsBundle) PathMatch(pattern, name string) (bool, error) {
return doublestar.PathMatch(pattern, name)
}
func (b checkmarxExecuteScanUtilsBundle) GetWorkspace() string {
return b.workspace
func (c *checkmarxExecuteScanUtilsBundle) GetWorkspace() string {
return c.workspace
}
func (checkmarxExecuteScanUtilsBundle) WriteFile(filename string, data []byte, perm os.FileMode) error {
func (c *checkmarxExecuteScanUtilsBundle) WriteFile(filename string, data []byte, perm os.FileMode) error {
return ioutil.WriteFile(filename, data, perm)
}
func (checkmarxExecuteScanUtilsBundle) FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) {
func (c *checkmarxExecuteScanUtilsBundle) FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) {
return zip.FileInfoHeader(fi)
}
func (checkmarxExecuteScanUtilsBundle) Stat(name string) (os.FileInfo, error) {
func (c *checkmarxExecuteScanUtilsBundle) Stat(name string) (os.FileInfo, error) {
return os.Stat(name)
}
func (checkmarxExecuteScanUtilsBundle) Open(name string) (*os.File, error) {
func (c *checkmarxExecuteScanUtilsBundle) Open(name string) (*os.File, error) {
return os.Open(name)
}
func (checkmarxExecuteScanUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error {
func (c *checkmarxExecuteScanUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error {
return piperGithub.CreateIssue(ghCreateIssueOptions)
}
func (c *checkmarxExecuteScanUtilsBundle) GetIssueService() *github.IssuesService {
return c.issues
}
func (c *checkmarxExecuteScanUtilsBundle) GetSearchService() *github.SearchService {
return c.search
}
func newCheckmarxExecuteScanUtilsBundle(workspace string, client *github.Client) checkmarxExecuteScanUtils {
utils := checkmarxExecuteScanUtilsBundle{
workspace: workspace,
}
if client != nil {
utils.issues = client.Issues
utils.search = client.Search
}
return &utils
}
func checkmarxExecuteScan(config checkmarxExecuteScanOptions, _ *telemetry.CustomData, influx *checkmarxExecuteScanInflux) {
client := &piperHttp.Client{}
options := piperHttp.ClientOptions{MaxRetries: config.MaxRetries}
client.SetOptions(options)
// TODO provide parameter for trusted certs
ctx, ghClient, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", []string{})
if err != nil {
log.Entry().WithError(err).Warning("Failed to get GitHub client")
}
sys, err := checkmarx.NewSystemInstance(client, config.ServerURL, config.Username, config.Password)
if err != nil {
log.Entry().WithError(err).Fatalf("Failed to create Checkmarx client talking to URL %v", config.ServerURL)
}
influx.step_data.fields.checkmarx = false
utils := checkmarxExecuteScanUtilsBundle{workspace: "./"}
if err := runScan(config, sys, influx, utils); err != nil {
utils := newCheckmarxExecuteScanUtilsBundle("./", ghClient)
if err := runScan(ctx, config, sys, influx, utils); err != nil {
log.Entry().WithError(err).Fatal("Failed to execute Checkmarx scan.")
}
influx.step_data.fields.checkmarx = true
}
func runScan(config checkmarxExecuteScanOptions, sys checkmarx.System, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error {
func runScan(ctx context.Context, config checkmarxExecuteScanOptions, sys checkmarx.System, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error {
teamID := config.TeamID
if len(teamID) == 0 {
readTeamID, err := loadTeamIDByTeamName(config, sys, teamID)
@@ -114,7 +143,7 @@ func runScan(config checkmarxExecuteScanOptions, sys checkmarx.System, influx *c
}
}
err = uploadAndScan(config, sys, project, influx, utils)
err = uploadAndScan(ctx, config, sys, project, influx, utils)
if err != nil {
return errors.Wrap(err, "scan, upload, and result validation returned an error")
}
@@ -240,13 +269,13 @@ func zipWorkspaceFiles(filterPattern string, utils checkmarxExecuteScanUtils) (*
return zipFile, nil
}
func uploadAndScan(config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error {
func uploadAndScan(ctx context.Context, config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error {
previousScans, err := sys.GetScans(project.ID)
if err != nil && config.VerifyOnly {
log.Entry().Warnf("Cannot load scans for project %v, verification only mode aborted", project.Name)
}
if len(previousScans) > 0 && config.VerifyOnly {
err := verifyCxProjectCompliance(config, sys, previousScans[0].ID, influx, utils)
err := verifyCxProjectCompliance(ctx, config, sys, previousScans[0].ID, influx, utils)
if err != nil {
log.SetErrorCategory(log.ErrorCompliance)
return errors.Wrapf(err, "project %v not compliant", project.Name)
@@ -280,12 +309,12 @@ func uploadAndScan(config checkmarxExecuteScanOptions, sys checkmarx.System, pro
incremental = false
}
return triggerScan(config, sys, project, incremental, influx, utils)
return triggerScan(ctx, config, sys, project, incremental, influx, utils)
}
return nil
}
func triggerScan(config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, incremental bool, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error {
func triggerScan(ctx context.Context, config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, incremental bool, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error {
scan, err := sys.ScanProject(project.ID, incremental, true, !config.AvoidDuplicateProjectScans)
if err != nil {
return errors.Wrapf(err, "cannot scan project %v", project.Name)
@@ -298,10 +327,10 @@ func triggerScan(config checkmarxExecuteScanOptions, sys checkmarx.System, proje
}
log.Entry().Debugln("Scan finished")
return verifyCxProjectCompliance(config, sys, scan.ID, influx, utils)
return verifyCxProjectCompliance(ctx, config, sys, scan.ID, influx, utils)
}
func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx.System, scanID int, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error {
func verifyCxProjectCompliance(ctx context.Context, config checkmarxExecuteScanOptions, sys checkmarx.System, scanID int, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error {
var reports []piperutils.Path
if config.GeneratePdfReport {
pdfReportName := createReportName(utils.GetWorkspace(), "CxSASTReport_%v.pdf")
@@ -354,11 +383,7 @@ func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx
// add JSON report to archiving list
reports = append(reports, paths...)
}
links := []piperutils.Path{{Target: results["DeepLink"].(string), Name: "Checkmarx Web UI"}}
piperutils.PersistReportsAndLinks("checkmarxExecuteScan", utils.GetWorkspace(), reports, links)
reportToInflux(results, influx)
insecure := false
var insecureResults []string
@@ -370,8 +395,14 @@ func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx
if insecure && config.CreateResultIssue && len(config.GithubToken) > 0 && len(config.GithubAPIURL) > 0 && len(config.Owner) > 0 && len(config.Repository) > 0 {
log.Entry().Debug("Creating/updating GitHub issue with check results")
err := reporting.UploadSingleReportToGithub(scanReport, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, utils)
if err != nil {
gh := reporting.GitHub{
Owner: &config.Owner,
Repository: &config.Repository,
Assignees: &config.Assignees,
IssueService: utils.GetIssueService(),
SearchService: utils.GetSearchService(),
}
if err := gh.UploadSingleReport(ctx, scanReport); err != nil {
return fmt.Errorf("failed to upload scan results into GitHub: %w", err)
}
}
@@ -385,6 +416,9 @@ func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx
}
}
piperutils.PersistReportsAndLinks("checkmarxExecuteScan", utils.GetWorkspace(), reports, links)
reportToInflux(results, influx)
if insecure {
if config.VulnerabilityThresholdResult == "FAILURE" {
log.SetErrorCategory(log.ErrorCompliance)
@@ -496,7 +530,7 @@ func downloadAndSaveReport(sys checkmarx.System, reportFileName string, scanID i
return errors.Wrap(err, "failed to download the report")
}
log.Entry().Debugf("Saving report to file %v...", reportFileName)
return utils.WriteFile(reportFileName, report, 0700)
return utils.WriteFile(reportFileName, report, 0o700)
}
func enforceThresholds(config checkmarxExecuteScanOptions, results map[string]interface{}) (bool, []string, []string) {
@@ -696,7 +730,7 @@ func getDetailedResults(sys checkmarx.System, reportFileName string, scanID int,
return resultMap, errors.Wrap(err, "failed to download xml report")
}
if len(data) > 0 {
err = utils.WriteFile(reportFileName, data, 0700)
err = utils.WriteFile(reportFileName, data, 0o700)
if err != nil {
return resultMap, errors.Wrap(err, "failed to write file")
}

View File

@@ -3,6 +3,7 @@ package cmd
import (
"archive/zip"
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
@@ -16,8 +17,9 @@ import (
"github.com/bmatcuk/doublestar"
"github.com/SAP/jenkins-library/pkg/checkmarx"
piperGithub "github.com/SAP/jenkins-library/pkg/github"
"github.com/stretchr/testify/assert"
"github.com/google/go-github/v45/github"
)
type fileInfo struct {
@@ -29,23 +31,28 @@ type fileInfo struct {
syss interface{} // underlying data source (can return nil)
}
func (fi fileInfo) IsDir() bool {
return fi.dir
func (f *fileInfo) IsDir() bool {
return f.dir
}
func (fi fileInfo) Name() string {
return fi.nam
func (f *fileInfo) Name() string {
return f.nam
}
func (fi fileInfo) Size() int64 {
return fi.siz
func (f *fileInfo) Size() int64 {
return f.siz
}
func (fi fileInfo) ModTime() time.Time {
return fi.modtime
func (f *fileInfo) ModTime() time.Time {
return f.modtime
}
func (fi fileInfo) Mode() os.FileMode {
return fi.mod
func (f *fileInfo) Mode() os.FileMode {
return f.mod
}
func (fi fileInfo) Sys() interface{} {
return fi.syss
func (f *fileInfo) Sys() interface{} {
return f.syss
}
type systemMock struct {
@@ -65,12 +72,15 @@ func (sys *systemMock) FilterPresetByName(_ []checkmarx.Preset, presetName strin
}
return checkmarx.Preset{ID: 10050, Name: "SAP_JS_Default", OwnerName: "16"}
}
func (sys *systemMock) FilterPresetByID([]checkmarx.Preset, int) checkmarx.Preset {
return checkmarx.Preset{ID: 10048, Name: "SAP_Default", OwnerName: "16"}
}
func (sys *systemMock) FilterProjectByName([]checkmarx.Project, string) checkmarx.Project {
return checkmarx.Project{ID: 1, Name: "Test", TeamID: "16", IsPublic: true}
}
func (sys *systemMock) GetProjectByID(projectID int) (checkmarx.Project, error) {
if projectID == 17 {
return checkmarx.Project{ID: 17, Name: "Test_PR-17", TeamID: "16", IsPublic: true}, nil
@@ -91,12 +101,14 @@ func (sys *systemMock) GetProjectsByNameAndTeam(projectName, teamID string) ([]c
sys.previousPName = projectName
return []checkmarx.Project{}, fmt.Errorf("no project error")
}
func (sys *systemMock) FilterTeamByName(_ []checkmarx.Team, teamName string) (checkmarx.Team, error) {
if teamName == "OpenSource/Cracks/16" {
return checkmarx.Team{ID: json.RawMessage(`"16"`), FullName: "OpenSource/Cracks/16"}, nil
}
return checkmarx.Team{ID: json.RawMessage(`15`), FullName: "OpenSource/Cracks/15"}, nil
}
func (sys *systemMock) FilterTeamByID(_ []checkmarx.Team, teamID json.RawMessage) checkmarx.Team {
teamIDBytes, _ := teamID.MarshalJSON()
if bytes.Equal(teamIDBytes, []byte(`"16"`)) {
@@ -104,56 +116,72 @@ func (sys *systemMock) FilterTeamByID(_ []checkmarx.Team, teamID json.RawMessage
}
return checkmarx.Team{ID: json.RawMessage(`15`), FullName: "OpenSource/Cracks/15"}
}
func (sys *systemMock) DownloadReport(int) ([]byte, error) {
return sys.response.([]byte), nil
}
func (sys *systemMock) GetReportStatus(int) (checkmarx.ReportStatusResponse, error) {
return checkmarx.ReportStatusResponse{Status: checkmarx.ReportStatus{ID: 2, Value: "Created"}}, nil
}
func (sys *systemMock) RequestNewReport(int, string) (checkmarx.Report, error) {
return checkmarx.Report{ReportID: 17}, nil
}
func (sys *systemMock) GetResults(int) checkmarx.ResultsStatistics {
return checkmarx.ResultsStatistics{}
}
func (sys *systemMock) GetScans(int) ([]checkmarx.ScanStatus, error) {
return []checkmarx.ScanStatus{{IsIncremental: true}, {IsIncremental: true}, {IsIncremental: true}, {IsIncremental: false}}, nil
}
func (sys *systemMock) GetScanStatusAndDetail(int) (string, checkmarx.ScanStatusDetail) {
return "Finished", checkmarx.ScanStatusDetail{Stage: "Step 1 of 25", Step: "Scan something"}
}
func (sys *systemMock) ScanProject(_ int, isIncrementalV, isPublicV, forceScanV bool) (checkmarx.Scan, error) {
sys.isIncremental = isIncrementalV
sys.isPublic = isPublicV
sys.forceScan = forceScanV
return checkmarx.Scan{ID: 16}, nil
}
func (sys *systemMock) UpdateProjectConfiguration(int, int, string) error {
sys.updateProjectConfigurationCalled = true
return nil
}
func (sys *systemMock) UpdateProjectExcludeSettings(int, string, string) error {
return nil
}
func (sys *systemMock) UploadProjectSourceCode(int, string) error {
return nil
}
func (sys *systemMock) CreateProject(string, string) (checkmarx.ProjectCreateResult, error) {
return checkmarx.ProjectCreateResult{ID: 20}, nil
}
func (sys *systemMock) CreateBranch(int, string) int {
return 18
}
func (sys *systemMock) GetShortDescription(int, int) (checkmarx.ShortDescription, error) {
return checkmarx.ShortDescription{Text: "dummyText"}, nil
}
func (sys *systemMock) GetPresets() []checkmarx.Preset {
sys.getPresetsCalled = true
return []checkmarx.Preset{{ID: 10078, Name: "SAP Java Default", OwnerName: "16"}, {ID: 10048, Name: "SAP JS Default", OwnerName: "16"}, {ID: 16, Name: "CX_Default", OwnerName: "16"}}
}
func (sys *systemMock) GetProjects() ([]checkmarx.Project, error) {
return []checkmarx.Project{{ID: 15, Name: "OtherTest", TeamID: "16"}, {ID: 1, Name: "Test", TeamID: "16"}}, nil
}
func (sys *systemMock) GetTeams() []checkmarx.Team {
return []checkmarx.Team{{ID: json.RawMessage(`"16"`), FullName: "OpenSource/Cracks/16"}, {ID: json.RawMessage(`15`), FullName: "OpenSource/Cracks/15"}}
}
@@ -169,45 +197,59 @@ type systemMockForExistingProject struct {
func (sys *systemMockForExistingProject) FilterPresetByName([]checkmarx.Preset, string) checkmarx.Preset {
return checkmarx.Preset{ID: 10050, Name: "SAP_JS_Default", OwnerName: "16"}
}
func (sys *systemMockForExistingProject) FilterPresetByID([]checkmarx.Preset, int) checkmarx.Preset {
return checkmarx.Preset{ID: 10048, Name: "SAP_Default", OwnerName: "16"}
}
func (sys *systemMockForExistingProject) FilterProjectByName([]checkmarx.Project, string) checkmarx.Project {
return checkmarx.Project{ID: 1, Name: "TestExisting", TeamID: "16", IsPublic: true}
}
func (sys *systemMockForExistingProject) GetProjectByID(int) (checkmarx.Project, error) {
return checkmarx.Project{}, nil
}
func (sys *systemMockForExistingProject) GetProjectsByNameAndTeam(projectName, teamID string) ([]checkmarx.Project, error) {
return []checkmarx.Project{{ID: 19, Name: projectName, TeamID: teamID, IsPublic: true}}, nil
}
func (sys *systemMockForExistingProject) FilterTeamByName([]checkmarx.Team, string) (checkmarx.Team, error) {
return checkmarx.Team{ID: json.RawMessage(`"16"`), FullName: "OpenSource/Cracks/16"}, nil
}
func (sys *systemMockForExistingProject) FilterTeamByID([]checkmarx.Team, json.RawMessage) checkmarx.Team {
return checkmarx.Team{ID: json.RawMessage(`"15"`), FullName: "OpenSource/Cracks/15"}
}
func (sys *systemMockForExistingProject) DownloadReport(int) ([]byte, error) {
return sys.response.([]byte), nil
}
func (sys *systemMockForExistingProject) GetReportStatus(int) (checkmarx.ReportStatusResponse, error) {
return checkmarx.ReportStatusResponse{Status: checkmarx.ReportStatus{ID: 2, Value: "Created"}}, nil
}
func (sys *systemMockForExistingProject) RequestNewReport(int, string) (checkmarx.Report, error) {
return checkmarx.Report{ReportID: 17}, nil
}
func (sys *systemMockForExistingProject) GetResults(int) checkmarx.ResultsStatistics {
return checkmarx.ResultsStatistics{}
}
func (sys *systemMockForExistingProject) GetScans(int) ([]checkmarx.ScanStatus, error) {
return []checkmarx.ScanStatus{{IsIncremental: true}, {IsIncremental: true}, {IsIncremental: true}, {IsIncremental: false}}, nil
}
func (sys *systemMockForExistingProject) GetShortDescription(int, int) (checkmarx.ShortDescription, error) {
return checkmarx.ShortDescription{Text: "dummyText"}, nil
}
func (sys *systemMockForExistingProject) GetScanStatusAndDetail(int) (string, checkmarx.ScanStatusDetail) {
return "Finished", checkmarx.ScanStatusDetail{Stage: "", Step: ""}
}
func (sys *systemMockForExistingProject) ScanProject(_ int, isIncrementalV, isPublicV, forceScanV bool) (checkmarx.Scan, error) {
sys.scanProjectCalled = true
sys.isIncremental = isIncrementalV
@@ -215,27 +257,35 @@ func (sys *systemMockForExistingProject) ScanProject(_ int, isIncrementalV, isPu
sys.forceScan = forceScanV
return checkmarx.Scan{ID: 16}, nil
}
func (sys *systemMockForExistingProject) UpdateProjectConfiguration(int, int, string) error {
return nil
}
func (sys *systemMockForExistingProject) UpdateProjectExcludeSettings(int, string, string) error {
return nil
}
func (sys *systemMockForExistingProject) UploadProjectSourceCode(int, string) error {
return nil
}
func (sys *systemMockForExistingProject) CreateProject(string, string) (checkmarx.ProjectCreateResult, error) {
return checkmarx.ProjectCreateResult{}, fmt.Errorf("create project error")
}
func (sys *systemMockForExistingProject) CreateBranch(int, string) int {
return 0
}
func (sys *systemMockForExistingProject) GetPresets() []checkmarx.Preset {
return []checkmarx.Preset{{ID: 10078, Name: "SAP_Java_Default", OwnerName: "16"}, {ID: 10048, Name: "SAP_JS_Default", OwnerName: "16"}}
}
func (sys *systemMockForExistingProject) GetProjects() ([]checkmarx.Project, error) {
return []checkmarx.Project{{ID: 1, Name: "TestExisting", TeamID: "16"}}, nil
}
func (sys *systemMockForExistingProject) GetTeams() []checkmarx.Team {
return []checkmarx.Team{{ID: json.RawMessage(`"16"`), FullName: "OpenSource/Cracks/16"}, {ID: json.RawMessage(`"15"`), FullName: "OpenSource/Cracks/15"}}
}
@@ -247,56 +297,56 @@ type checkmarxExecuteScanUtilsMock struct {
errorOnWriteFile bool
errorOnPathMatch bool
workspace string
ghCreateIssueError error
}
func newCheckmarxExecuteScanUtilsMock() checkmarxExecuteScanUtilsMock {
return checkmarxExecuteScanUtilsMock{}
func newCheckmarxExecuteScanUtilsMock() *checkmarxExecuteScanUtilsMock {
return &checkmarxExecuteScanUtilsMock{}
}
func (c checkmarxExecuteScanUtilsMock) GetWorkspace() string {
func (c *checkmarxExecuteScanUtilsMock) GetWorkspace() string {
return c.workspace
}
func (c checkmarxExecuteScanUtilsMock) PathMatch(pattern, name string) (bool, error) {
func (c *checkmarxExecuteScanUtilsMock) PathMatch(pattern, name string) (bool, error) {
if c.errorOnPathMatch {
return false, fmt.Errorf("error on PathMatch")
}
return doublestar.PathMatch(pattern, name)
}
func (c checkmarxExecuteScanUtilsMock) WriteFile(filename string, data []byte, perm os.FileMode) error {
func (c *checkmarxExecuteScanUtilsMock) WriteFile(filename string, data []byte, perm os.FileMode) error {
if c.errorOnWriteFile {
return fmt.Errorf("error on WriteFile")
}
return ioutil.WriteFile(filename, data, perm)
}
func (c checkmarxExecuteScanUtilsMock) FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) {
func (c *checkmarxExecuteScanUtilsMock) FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) {
if c.errorOnFileInfoHeader {
return nil, fmt.Errorf("error on FileInfoHeader")
}
return zip.FileInfoHeader(fi)
}
func (c checkmarxExecuteScanUtilsMock) Stat(name string) (os.FileInfo, error) {
func (c *checkmarxExecuteScanUtilsMock) Stat(name string) (os.FileInfo, error) {
if c.errorOnStat {
return nil, fmt.Errorf("error on Stat")
}
return os.Stat(name)
}
func (c checkmarxExecuteScanUtilsMock) Open(name string) (*os.File, error) {
func (c *checkmarxExecuteScanUtilsMock) Open(name string) (*os.File, error) {
if c.errorOnOpen {
return nil, fmt.Errorf("error on Open")
}
return os.Open(name)
}
func (c checkmarxExecuteScanUtilsMock) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error {
if c.ghCreateIssueError != nil {
return c.ghCreateIssueError
}
func (c *checkmarxExecuteScanUtilsMock) GetIssueService() *github.IssuesService {
return nil
}
func (c *checkmarxExecuteScanUtilsMock) GetSearchService() *github.SearchService {
return nil
}
@@ -318,7 +368,7 @@ func TestFilterFileGlob(t *testing.T) {
}
for k, v := range tt {
result, err := isFileNotMatchingPattern([]string{"!**/node_modules/**", "!**/.xmake/**", "!**/*_test.go", "!**/vendor/**/*.go", "**/*.go", "**/*.html", "*.test"}, v.input, v.fInfo, newCheckmarxExecuteScanUtilsMock())
result, err := isFileNotMatchingPattern([]string{"!**/node_modules/**", "!**/.xmake/**", "!**/*_test.go", "!**/vendor/**/*.go", "**/*.go", "**/*.html", "*.test"}, v.input, &v.fInfo, newCheckmarxExecuteScanUtilsMock())
assert.Equal(t, v.expected, result, fmt.Sprintf("wrong result for run %v", k))
assert.NoError(t, err, "no error expected in run %v", k)
}
@@ -330,8 +380,8 @@ func TestFilterFileGlob_errorOnPathMatch(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.errorOnPathMatch = true
result, err := isFileNotMatchingPattern([]string{"!**/node_modules/**", "!**/.xmake/**", "!**/*_test.go", "!**/vendor/**/*.go", "**/*.go", "**/*.html", "*.test"}, filepath.Join("a", "b", "c"), fileInfo{}, utilsMock)
assert.Equal(t, false, result, fmt.Sprintf("wrong result"))
result, err := isFileNotMatchingPattern([]string{"!**/node_modules/**", "!**/.xmake/**", "!**/*_test.go", "!**/vendor/**/*.go", "**/*.go", "**/*.html", "*.test"}, filepath.Join("a", "b", "c"), &fileInfo{}, utilsMock)
assert.Equal(t, false, result, "wrong result")
assert.EqualError(t, err, "Pattern **/node_modules/** could not get executed: error on PathMatch")
}
@@ -342,17 +392,17 @@ func TestZipFolder(t *testing.T) {
t.Parallel()
dir := t.TempDir()
err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "abcd.yaml"), []byte("abcd.yaml"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "abcd.yaml"), []byte("abcd.yaml"), 0o700)
assert.NoError(t, err)
err = os.Mkdir(filepath.Join(dir, "somepath"), 0700)
err = os.Mkdir(filepath.Join(dir, "somepath"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0o700)
assert.NoError(t, err)
var zipFileMock bytes.Buffer
@@ -361,7 +411,7 @@ func TestZipFolder(t *testing.T) {
zipString := zipFileMock.String()
//assert.Equal(t, 724, zipFileMock.Len(), "Expected length of 724, but got %v", zipFileMock.Len())
// assert.Equal(t, 724, zipFileMock.Len(), "Expected length of 724, but got %v", zipFileMock.Len())
assert.True(t, strings.Contains(zipString, "abcd.go"), "Expected 'abcd.go' contained")
assert.True(t, strings.Contains(zipString, filepath.Join("somepath", "abcd.txt")), "Expected 'somepath/abcd.txt' contained")
assert.False(t, strings.Contains(zipString, "abcd_test.go"), "Not expected 'abcd_test.go' contained")
@@ -373,15 +423,15 @@ func TestZipFolder(t *testing.T) {
t.Parallel()
dir := t.TempDir()
err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
err = os.Mkdir(filepath.Join(dir, "somepath"), 0700)
err = os.Mkdir(filepath.Join(dir, "somepath"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0o700)
assert.NoError(t, err)
var zipFileMock bytes.Buffer
@@ -396,15 +446,15 @@ func TestZipFolder(t *testing.T) {
t.Parallel()
dir := t.TempDir()
err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
err = os.Mkdir(filepath.Join(dir, "somepath"), 0700)
err = os.Mkdir(filepath.Join(dir, "somepath"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0o700)
assert.NoError(t, err)
var zipFileMock bytes.Buffer
@@ -419,15 +469,15 @@ func TestZipFolder(t *testing.T) {
t.Parallel()
dir := t.TempDir()
err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
err = os.Mkdir(filepath.Join(dir, "somepath"), 0700)
err = os.Mkdir(filepath.Join(dir, "somepath"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte("somepath/abcd.txt"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte("abcd_test.go"), 0o700)
assert.NoError(t, err)
err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0700)
err = ioutil.WriteFile(filepath.Join(dir, "abc_test.go"), []byte("abc_test.go"), 0o700)
assert.NoError(t, err)
var zipFileMock bytes.Buffer
@@ -507,11 +557,12 @@ func TestGetDetailedResults(t *testing.T) {
func TestRunScan(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)}
options := checkmarxExecuteScanOptions{ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
workspace := t.TempDir()
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
options.FilterPattern = "**/abcd.go"
@@ -520,7 +571,7 @@ func TestRunScan(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.workspace = workspace
err = runScan(options, sys, &influx, utilsMock)
err = runScan(ctx, options, sys, &influx, utilsMock)
assert.NoError(t, err, "error occurred but none expected")
assert.Equal(t, false, sys.isIncremental, "isIncremental has wrong value")
assert.Equal(t, true, sys.isPublic, "isPublic has wrong value")
@@ -530,11 +581,12 @@ func TestRunScan(t *testing.T) {
func TestRunScan_nonNumeralPreset(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)}
options := checkmarxExecuteScanOptions{ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "SAP_JS_Default", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
workspace := t.TempDir()
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
options.FilterPattern = "**/abcd.go"
@@ -543,17 +595,18 @@ func TestRunScan_nonNumeralPreset(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.workspace = workspace
err = runScan(options, sys, &influx, utilsMock)
err = runScan(ctx, options, sys, &influx, utilsMock)
assert.NoError(t, err, "error occurred but none expected")
}
func TestRunOptimizedScan(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)}
options := checkmarxExecuteScanOptions{IsOptimizedAndScheduled: true, ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "1", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
workspace := t.TempDir()
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
options.FilterPattern = "**/abcd.go"
@@ -562,7 +615,7 @@ func TestRunOptimizedScan(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.workspace = workspace
err = runScan(options, sys, &influx, utilsMock)
err = runScan(ctx, options, sys, &influx, utilsMock)
assert.NoError(t, err, "error occurred but none expected")
assert.Equal(t, false, sys.isIncremental, "isIncremental has wrong value")
assert.Equal(t, true, sys.isPublic, "isPublic has wrong value")
@@ -593,6 +646,7 @@ func TestSetPresetForProjectWithNameProvided(t *testing.T) {
func TestVerifyOnly(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)}
options := checkmarxExecuteScanOptions{VerifyOnly: true, ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamName: "OpenSource/Cracks/15", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
@@ -603,13 +657,14 @@ func TestVerifyOnly(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.workspace = workspace
err := runScan(options, sys, &influx, utilsMock)
err := runScan(ctx, options, sys, &influx, utilsMock)
assert.NoError(t, err, "error occurred but none expected")
assert.Equal(t, false, sys.scanProjectCalled, "ScanProject was invoked but shouldn't")
}
func TestVerifyOnly_errorOnWriteFileDoesNotBlock(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMockForExistingProject{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)}
options := checkmarxExecuteScanOptions{VerifyOnly: true, ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamName: "OpenSource/Cracks/15", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
@@ -621,17 +676,18 @@ func TestVerifyOnly_errorOnWriteFileDoesNotBlock(t *testing.T) {
utilsMock.workspace = workspace
utilsMock.errorOnWriteFile = true
err := runScan(options, sys, &influx, utilsMock)
err := runScan(ctx, options, sys, &influx, utilsMock)
assert.EqualError(t, err, "scan, upload, and result validation returned an error: project TestExisting not compliant: failed to get detailed results: failed to write file: error on WriteFile")
}
func TestRunScanWOtherCycle(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`), createProject: true}
options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "123", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
workspace := t.TempDir()
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
options.FilterPattern = "**/abcd.go"
@@ -640,7 +696,7 @@ func TestRunScanWOtherCycle(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.workspace = workspace
err = runScan(options, sys, &influx, utilsMock)
err = runScan(ctx, options, sys, &influx, utilsMock)
assert.NoError(t, err, "error occurred but none expected")
assert.Equal(t, true, sys.isIncremental, "isIncremental has wrong value")
assert.Equal(t, true, sys.isPublic, "isPublic has wrong value")
@@ -649,6 +705,7 @@ func TestRunScanWOtherCycle(t *testing.T) {
func TestRunScanErrorInZip(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`), createProject: true}
options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "123", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
@@ -660,17 +717,18 @@ func TestRunScanErrorInZip(t *testing.T) {
utilsMock.workspace = workspace
utilsMock.errorOnFileInfoHeader = true
err := runScan(options, sys, &influx, utilsMock)
err := runScan(ctx, options, sys, &influx, utilsMock)
assert.EqualError(t, err, "scan, upload, and result validation returned an error: failed to zip workspace files: failed to compact folder: error on FileInfoHeader")
}
func TestRunScanForPullRequest(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`)}
options := checkmarxExecuteScanOptions{PullRequestName: "PR-19", ProjectName: "Test", VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "123", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true, AvoidDuplicateProjectScans: false}
workspace := t.TempDir()
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
options.FilterPattern = "**/abcd.go"
@@ -679,7 +737,7 @@ func TestRunScanForPullRequest(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.workspace = workspace
err = runScan(options, sys, &influx, utilsMock)
err = runScan(ctx, options, sys, &influx, utilsMock)
assert.Equal(t, true, sys.isIncremental, "isIncremental has wrong value")
assert.Equal(t, true, sys.isPublic, "isPublic has wrong value")
assert.Equal(t, true, sys.forceScan, "forceScan has wrong value")
@@ -687,11 +745,12 @@ func TestRunScanForPullRequest(t *testing.T) {
func TestRunScanForPullRequestProjectNew(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`), createProject: true}
options := checkmarxExecuteScanOptions{PullRequestName: "PR-17", ProjectName: "Test", AvoidDuplicateProjectScans: true, VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamName: "OpenSource/Cracks/15", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
workspace := t.TempDir()
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
options.FilterPattern = "**/abcd.go"
@@ -700,7 +759,7 @@ func TestRunScanForPullRequestProjectNew(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.workspace = workspace
err = runScan(options, sys, &influx, utilsMock)
err = runScan(ctx, options, sys, &influx, utilsMock)
assert.NoError(t, err, "Unexpected error caught")
assert.Equal(t, true, sys.isIncremental, "isIncremental has wrong value")
assert.Equal(t, true, sys.isPublic, "isPublic has wrong value")
@@ -709,11 +768,12 @@ func TestRunScanForPullRequestProjectNew(t *testing.T) {
func TestRunScanForPullRequestProjectNew_nonNumeralPreset(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?><CxXMLResults />`), createProject: true}
options := checkmarxExecuteScanOptions{PullRequestName: "PR-17", ProjectName: "Test", AvoidDuplicateProjectScans: true, VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "SAP_JS_Default", TeamName: "OpenSource/Cracks/15", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
workspace := t.TempDir()
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
options.FilterPattern = "**/abcd.go"
@@ -722,12 +782,13 @@ func TestRunScanForPullRequestProjectNew_nonNumeralPreset(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.workspace = workspace
err = runScan(options, sys, &influx, utilsMock)
err = runScan(ctx, options, sys, &influx, utilsMock)
assert.NoError(t, err, "error occurred but none expected")
}
func TestRunScanHighViolationPercentage(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?>
<CxXMLResults InitiatorName="admin" Owner="admin" ScanId="1000005" ProjectId="2" ProjectName="Project 1" TeamFullPathOnReportDate="CxServer" DeepLink="http://WIN2K12-TEMP/CxWebClient/ViewerMain.aspx?scanid=1000005&amp;projectid=2" ScanStart="Sunday, December 3, 2017 4:50:34 PM" Preset="Checkmarx Default" ScanTime="00h:03m:18s" LinesOfCodeScanned="6838" FilesScanned="34" ReportCreationTime="Sunday, December 3, 2017 6:13:45 PM" Team="CxServer" CheckmarxVersion="8.6.0" ScanComments="" ScanType="Incremental" SourceOrigin="LocalPath" Visibility="Public">
@@ -751,7 +812,7 @@ func TestRunScanHighViolationPercentage(t *testing.T) {
</CxXMLResults>`)}
options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", VulnerabilityThresholdResult: "FAILURE", VulnerabilityThresholdHigh: 100, FullScanCycle: "10", FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
workspace := t.TempDir()
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
options.FilterPattern = "**/abcd.go"
@@ -760,12 +821,13 @@ func TestRunScanHighViolationPercentage(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.workspace = workspace
err = runScan(options, sys, &influx, utilsMock)
err = runScan(ctx, options, sys, &influx, utilsMock)
assert.Contains(t, fmt.Sprint(err), "the project is not compliant", "Expected different error")
}
func TestRunScanHighViolationAbsolute(t *testing.T) {
t.Parallel()
ctx := context.Background()
sys := &systemMock{response: []byte(`<?xml version="1.0" encoding="utf-8"?>
<CxXMLResults InitiatorName="admin" Owner="admin" ScanId="1000005" ProjectId="2" ProjectName="Project 1" TeamFullPathOnReportDate="CxServer" DeepLink="http://WIN2K12-TEMP/CxWebClient/ViewerMain.aspx?scanid=1000005&amp;projectid=2" ScanStart="Sunday, December 3, 2017 4:50:34 PM" Preset="Checkmarx Default" ScanTime="00h:03m:18s" LinesOfCodeScanned="6838" FilesScanned="34" ReportCreationTime="Sunday, December 3, 2017 6:13:45 PM" Team="CxServer" CheckmarxVersion="8.6.0" ScanComments="" ScanType="Incremental" SourceOrigin="LocalPath" Visibility="Public">
@@ -789,7 +851,7 @@ func TestRunScanHighViolationAbsolute(t *testing.T) {
</CxXMLResults>`)}
options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "absolute", VulnerabilityThresholdResult: "FAILURE", VulnerabilityThresholdLow: 1, FullScanCycle: "10", FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true}
workspace := t.TempDir()
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0700)
err := ioutil.WriteFile(filepath.Join(workspace, "abcd.go"), []byte("abcd.go"), 0o700)
assert.NoError(t, err)
options.FilterPattern = "**/abcd.go"
@@ -798,7 +860,7 @@ func TestRunScanHighViolationAbsolute(t *testing.T) {
utilsMock := newCheckmarxExecuteScanUtilsMock()
utilsMock.workspace = workspace
err = runScan(options, sys, &influx, utilsMock)
err = runScan(ctx, options, sys, &influx, utilsMock)
assert.Contains(t, fmt.Sprint(err), "the project is not compliant", "Expected different error")
}

View File

@@ -1,6 +1,7 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"io"
@@ -25,6 +26,8 @@ import (
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/toolrecord"
"github.com/google/go-github/v45/github"
)
type detectUtils interface {
@@ -41,25 +44,31 @@ type detectUtils interface {
DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error
CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error
GetIssueService() *github.IssuesService
GetSearchService() *github.SearchService
}
type detectUtilsBundle struct {
*command.Command
*piperutils.Files
*piperhttp.Client
issues *github.IssuesService
search *github.SearchService
}
// CreateIssue supplies capability for GitHub issue creation
func (d *detectUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error {
return piperGithub.CreateIssue(ghCreateIssueOptions)
func (d *detectUtilsBundle) GetIssueService() *github.IssuesService {
return d.issues
}
func (d *detectUtilsBundle) GetSearchService() *github.SearchService {
return d.search
}
type blackduckSystem struct {
Client bd.Client
}
func newDetectUtils() detectUtils {
func newDetectUtils(client *github.Client) detectUtils {
utils := detectUtilsBundle{
Command: &command.Command{
ErrorCategoryMapping: map[string][]string{
@@ -89,6 +98,10 @@ func newDetectUtils() detectUtils {
Files: &piperutils.Files{},
Client: &piperhttp.Client{},
}
if client != nil {
utils.issues = client.Issues
utils.search = client.Search
}
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
return &utils
@@ -103,10 +116,13 @@ func newBlackduckSystem(config detectExecuteScanOptions) *blackduckSystem {
func detectExecuteScan(config detectExecuteScanOptions, _ *telemetry.CustomData, influx *detectExecuteScanInflux) {
influx.step_data.fields.detect = false
utils := newDetectUtils()
err := runDetect(config, utils, influx)
ctx, client, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", config.CustomTLSCertificateLinks)
if err != nil {
log.Entry().WithError(err).Warning("Failed to get GitHub client")
}
utils := newDetectUtils(client)
if err := runDetect(ctx, config, utils, influx); err != nil {
log.Entry().
WithError(err).
Fatal("failed to execute detect scan")
@@ -115,7 +131,7 @@ func detectExecuteScan(config detectExecuteScanOptions, _ *telemetry.CustomData,
influx.step_data.fields.detect = true
}
func runDetect(config detectExecuteScanOptions, utils detectUtils, influx *detectExecuteScanInflux) error {
func runDetect(ctx context.Context, config detectExecuteScanOptions, utils detectUtils, influx *detectExecuteScanInflux) error {
// detect execution details, see https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/88440888/Sample+Synopsys+Detect+Scan+Configuration+Scenarios+for+Black+Duck
err := getDetectScript(config, utils)
if err != nil {
@@ -127,7 +143,7 @@ func runDetect(config detectExecuteScanOptions, utils detectUtils, influx *detec
log.Entry().Warnf("failed to delete 'detect.sh' script: %v", err)
}
}()
err = utils.Chmod("detect.sh", 0700)
err = utils.Chmod("detect.sh", 0o700)
if err != nil {
return err
}
@@ -158,7 +174,7 @@ func runDetect(config detectExecuteScanOptions, utils detectUtils, influx *detec
err = utils.RunShell("/bin/bash", script)
blackduckSystem := newBlackduckSystem(config)
reportingErr := postScanChecksAndReporting(config, influx, utils, blackduckSystem)
reportingErr := postScanChecksAndReporting(ctx, config, influx, utils, blackduckSystem)
if reportingErr != nil {
if strings.Contains(reportingErr.Error(), "License Policy Violations found") {
log.Entry().Errorf("License Policy Violations found")
@@ -194,8 +210,8 @@ func runDetect(config detectExecuteScanOptions, utils detectUtils, influx *detec
func mapErrorCategory(exitCodeKey int) {
switch exitCodeKey {
case 0:
//In case detect exits successfully, we rely on the function 'postScanChecksAndReporting' to determine the error category
//hence this method doesnt need to set an error category or go to 'default' case
// In case detect exits successfully, we rely on the function 'postScanChecksAndReporting' to determine the error category
// hence this method doesnt need to set an error category or go to 'default' case
break
case 1:
log.SetErrorCategory(log.ErrorInfrastructure)
@@ -230,7 +246,6 @@ func mapErrorCategory(exitCodeKey int) {
// Exit codes/error code mapping
func exitCodeMapping(exitCodeKey int) string {
exitCodes := map[int]string{
0: "Detect Scan completed successfully",
1: "FAILURE_BLACKDUCK_CONNECTIVITY => Detect was unable to connect to Black Duck. Check your configuration and connection.",
@@ -282,8 +297,8 @@ func getDetectScript(config detectExecuteScanOptions, utils detectUtils) error {
func addDetectArgs(args []string, config detectExecuteScanOptions, utils detectUtils) ([]string, error) {
detectVersionName := getVersionName(config)
//Split on spaces, the scanPropeties, so that each property is available as a single string
//instead of all properties being part of a single string
// Split on spaces, the scanPropeties, so that each property is available as a single string
// instead of all properties being part of a single string
config.ScanProperties = piperutils.SplitAndTrim(config.ScanProperties, " ")
if config.ScanOnChanges {
@@ -297,7 +312,7 @@ func addDetectArgs(args []string, config detectExecuteScanOptions, utils detectU
}
config.ScanProperties, _ = piperutils.RemoveAll(config.ScanProperties, "--detect.project.codelocation.unmap=false")
} else {
//When unmap is set to false, any occurances of unmap=true from scanProperties must be removed
// When unmap is set to false, any occurances of unmap=true from scanProperties must be removed
config.ScanProperties, _ = piperutils.RemoveAll(config.ScanProperties, "--detect.project.codelocation.unmap=true")
}
@@ -476,7 +491,7 @@ func isMajorVulnerability(v bd.Vulnerability) bool {
}
}
func postScanChecksAndReporting(config detectExecuteScanOptions, influx *detectExecuteScanInflux, utils detectUtils, sys *blackduckSystem) error {
func postScanChecksAndReporting(ctx context.Context, config detectExecuteScanOptions, influx *detectExecuteScanInflux, utils detectUtils, sys *blackduckSystem) error {
errorsOccured := []string{}
vulns, _, err := getVulnsAndComponents(config, influx, sys)
if err != nil {
@@ -487,8 +502,14 @@ func postScanChecksAndReporting(config detectExecuteScanOptions, influx *detectE
log.Entry().Debugf("Creating result issues for %v alert(s)", len(vulns.Items))
issueDetails := make([]reporting.IssueDetail, len(vulns.Items))
piperutils.CopyAtoB(vulns.Items, issueDetails)
err = reporting.UploadMultipleReportsToGithub(&issueDetails, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, config.CustomTLSCertificateLinks, utils)
if err != nil {
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 {
errorsOccured = append(errorsOccured, fmt.Sprint(err))
}
}
@@ -624,7 +645,7 @@ func writePolicyStatusReports(scanReport reporting.ScanReport, config detectExec
htmlReport, _ := scanReport.ToHTML()
htmlReportPath := "piper_detect_policy_violation_report.html"
if err := utils.FileWrite(htmlReportPath, htmlReport, 0666); err != nil {
if err := utils.FileWrite(htmlReportPath, htmlReport, 0o666); err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return reportPaths, errors.Wrapf(err, "failed to write html report")
}
@@ -632,12 +653,12 @@ func writePolicyStatusReports(scanReport reporting.ScanReport, config detectExec
jsonReport, _ := scanReport.ToJSON()
if exists, _ := utils.DirExists(reporting.StepReportDirectory); !exists {
err := utils.MkdirAll(reporting.StepReportDirectory, 0777)
err := utils.MkdirAll(reporting.StepReportDirectory, 0o777)
if err != nil {
return reportPaths, errors.Wrap(err, "failed to create reporting directory")
}
}
if err := utils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("detectExecuteScan_policy_%v.json", fmt.Sprintf("%v", time.Now()))), jsonReport, 0666); err != nil {
if err := utils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("detectExecuteScan_policy_%v.json", fmt.Sprintf("%v", time.Now()))), jsonReport, 0o666); err != nil {
return reportPaths, errors.Wrapf(err, "failed to write json report")
}
@@ -673,7 +694,7 @@ func writeIpPolicyJson(config detectExecuteScanOptions, utils detectUtils, paths
return fmt.Errorf("failed to marshal policy violation data: %w", err), violationCount
}
err = utils.FileWrite("blackduck-ip.json", violationContent, 0666)
err = utils.FileWrite("blackduck-ip.json", violationContent, 0o666)
if err != nil {
return fmt.Errorf("failed to write policy violation report: %w", err), violationCount
}

View File

@@ -2,6 +2,7 @@ package cmd
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
@@ -15,6 +16,7 @@ import (
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/google/go-github/v45/github"
"github.com/stretchr/testify/assert"
)
@@ -26,6 +28,14 @@ type detectTestUtilsBundle struct {
customEnv []string
}
func (d *detectTestUtilsBundle) GetIssueService() *github.IssuesService {
return nil
}
func (d *detectTestUtilsBundle) GetSearchService() *github.SearchService {
return nil
}
type httpMockClient struct {
responseBodyForURL map[string]string
errorMessageForURL map[string]string
@@ -164,7 +174,6 @@ func (c *detectTestUtilsBundle) RunExecutable(string, ...string) error {
}
func (c *detectTestUtilsBundle) SetOptions(piperhttp.ClientOptions) {
}
func (c *detectTestUtilsBundle) GetOsEnv() []string {
@@ -172,7 +181,6 @@ func (c *detectTestUtilsBundle) GetOsEnv() []string {
}
func (c *detectTestUtilsBundle) DownloadFile(url, filename string, _ http.Header, _ []*http.Cookie) error {
if c.expectedError != nil {
return c.expectedError
}
@@ -200,9 +208,10 @@ func TestRunDetect(t *testing.T) {
t.Parallel()
t.Run("success case", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
utilsMock := newDetectTestUtilsBundle()
utilsMock.AddFile("detect.sh", []byte(""))
err := runDetect(detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{})
err := runDetect(ctx, detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{})
assert.Equal(t, utilsMock.downloadedFiles["https://detect.synopsys.com/detect7.sh"], "detect.sh")
assert.True(t, utilsMock.HasRemovedFile("detect.sh"))
@@ -215,12 +224,13 @@ func TestRunDetect(t *testing.T) {
t.Run("success case detect 6", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
utilsMock := newDetectTestUtilsBundle()
utilsMock.AddFile("detect.sh", []byte(""))
options := detectExecuteScanOptions{
CustomEnvironmentVariables: []string{"DETECT_LATEST_RELEASE_VERSION=6.8.0"},
}
err := runDetect(options, utilsMock, &detectExecuteScanInflux{})
err := runDetect(ctx, options, utilsMock, &detectExecuteScanInflux{})
assert.Equal(t, utilsMock.downloadedFiles["https://detect.synopsys.com/detect.sh"], "detect.sh")
assert.True(t, utilsMock.HasRemovedFile("detect.sh"))
@@ -233,10 +243,11 @@ func TestRunDetect(t *testing.T) {
t.Run("success case detect 6 from OS env", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
utilsMock := newDetectTestUtilsBundle()
utilsMock.AddFile("detect.sh", []byte(""))
utilsMock.customEnv = []string{"DETECT_LATEST_RELEASE_VERSION=6.8.0"}
err := runDetect(detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{})
err := runDetect(ctx, detectExecuteScanOptions{}, utilsMock, &detectExecuteScanInflux{})
assert.Equal(t, utilsMock.downloadedFiles["https://detect.synopsys.com/detect.sh"], "detect.sh")
assert.True(t, utilsMock.HasRemovedFile("detect.sh"))
@@ -249,11 +260,12 @@ func TestRunDetect(t *testing.T) {
t.Run("failure case", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
utilsMock := newDetectTestUtilsBundle()
utilsMock.ShouldFailOnCommand = map[string]error{"./detect.sh --blackduck.url= --blackduck.api.token= \"--detect.project.name=''\" \"--detect.project.version.name=''\" \"--detect.code.location.name=''\" --detect.source.path='.'": fmt.Errorf("")}
utilsMock.ExitCode = 3
utilsMock.AddFile("detect.sh", []byte(""))
err := runDetect(detectExecuteScanOptions{FailOnSevereVulnerabilities: true}, utilsMock, &detectExecuteScanInflux{})
err := runDetect(ctx, detectExecuteScanOptions{FailOnSevereVulnerabilities: true}, utilsMock, &detectExecuteScanInflux{})
assert.Equal(t, utilsMock.ExitCode, 3)
assert.Contains(t, err.Error(), "FAILURE_POLICY_VIOLATION => Detect found policy violations.")
assert.True(t, utilsMock.HasRemovedFile("detect.sh"))
@@ -261,10 +273,11 @@ func TestRunDetect(t *testing.T) {
t.Run("maven parameters", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
utilsMock := newDetectTestUtilsBundle()
utilsMock.CurrentDir = "root_folder"
utilsMock.AddFile("detect.sh", []byte(""))
err := runDetect(detectExecuteScanOptions{
err := runDetect(ctx, detectExecuteScanOptions{
M2Path: ".pipeline/local_repo",
ProjectSettingsFile: "project-settings.xml",
GlobalSettingsFile: "global-settings.xml",
@@ -597,7 +610,6 @@ func TestAddDetectArgs(t *testing.T) {
// Testing exit code mapping method
func TestExitCodeMapping(t *testing.T) {
cases := []struct {
exitCode int
expected string
@@ -617,10 +629,11 @@ func TestExitCodeMapping(t *testing.T) {
func TestPostScanChecksAndReporting(t *testing.T) {
t.Parallel()
t.Run("Reporting after scan", func(t *testing.T) {
ctx := context.Background()
config := detectExecuteScanOptions{Token: "token", ServerURL: "https://my.blackduck.system", ProjectName: "SHC-PiperTest", Version: "", CustomScanVersion: "1.0"}
utils := newDetectTestUtilsBundle()
sys := newBlackduckMockSystem(config)
err := postScanChecksAndReporting(config, &detectExecuteScanInflux{}, utils, &sys)
err := postScanChecksAndReporting(ctx, config, &detectExecuteScanInflux{}, utils, &sys)
assert.EqualError(t, err, "License Policy Violations found")
content, err := utils.FileRead("blackduck-ip.json")

View File

@@ -18,7 +18,7 @@ import (
"github.com/bmatcuk/doublestar"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/google/uuid"
"github.com/piper-validation/fortify-client-go/models"
@@ -59,13 +59,16 @@ type fortifyUtils interface {
SetDir(d string)
GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error)
CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error
GetIssueService() *github.IssuesService
GetSearchService() *github.SearchService
}
type fortifyUtilsBundle struct {
*command.Command
*piperutils.Files
*piperhttp.Client
issues *github.IssuesService
search *github.SearchService
}
func (f *fortifyUtilsBundle) GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error) {
@@ -76,27 +79,46 @@ func (f *fortifyUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.Creat
return piperGithub.CreateIssue(ghCreateIssueOptions)
}
func newFortifyUtilsBundle() fortifyUtils {
func (f *fortifyUtilsBundle) GetIssueService() *github.IssuesService {
return f.issues
}
func (f *fortifyUtilsBundle) GetSearchService() *github.SearchService {
return f.search
}
func newFortifyUtilsBundle(client *github.Client) fortifyUtils {
utils := fortifyUtilsBundle{
Command: &command.Command{},
Files: &piperutils.Files{},
Client: &piperhttp.Client{},
}
if client != nil {
utils.issues = client.Issues
utils.search = client.Search
}
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
return &utils
}
const checkString = "<---CHECK FORTIFY---"
const classpathFileName = "fortify-execute-scan-cp.txt"
const (
checkString = "<---CHECK FORTIFY---"
classpathFileName = "fortify-execute-scan-cp.txt"
)
func fortifyExecuteScan(config fortifyExecuteScanOptions, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux) {
// TODO provide parameter for trusted certs
ctx, client, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", []string{})
if err != nil {
log.Entry().WithError(err).Warning("Failed to get GitHub client")
}
auditStatus := map[string]string{}
sys := fortify.NewSystemInstance(config.ServerURL, config.APIEndpoint, config.AuthToken, time.Minute*15)
utils := newFortifyUtilsBundle()
utils := newFortifyUtilsBundle(client)
influx.step_data.fields.fortify = false
reports, err := runFortifyScan(config, sys, utils, telemetryData, influx, auditStatus)
reports, err := runFortifyScan(ctx, config, sys, utils, telemetryData, influx, auditStatus)
piperutils.PersistReportsAndLinks("fortifyExecuteScan", config.ModulePath, reports, nil)
if err != nil {
log.Entry().WithError(err).Fatal("Fortify scan and check failed")
@@ -120,7 +142,7 @@ func determineArtifact(config fortifyExecuteScanOptions, utils fortifyUtils) (ve
return artifact, nil
}
func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils fortifyUtils, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux, auditStatus map[string]string) ([]piperutils.Path, error) {
func runFortifyScan(ctx context.Context, config fortifyExecuteScanOptions, sys fortify.System, utils fortifyUtils, telemetryData *telemetry.CustomData, influx *fortifyExecuteScanInflux, auditStatus map[string]string) ([]piperutils.Path, error) {
var reports []piperutils.Path
log.Entry().Debugf("Running Fortify scan against SSC at %v", config.ServerURL)
@@ -207,7 +229,7 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils
if config.VerifyOnly {
log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID)
err, paths := verifyFFProjectCompliance(config, utils, sys, project, projectVersion, filterSet, influx, auditStatus)
paths, err := verifyFFProjectCompliance(ctx, config, utils, sys, project, projectVersion, filterSet, influx, auditStatus)
reports = append(reports, paths...)
return reports, err
}
@@ -218,7 +240,9 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils
// Create sourceanalyzer command based on configuration
buildID := uuid.New().String()
utils.SetDir(config.ModulePath)
os.MkdirAll(fmt.Sprintf("%v/%v", config.ModulePath, "target"), os.ModePerm)
if err := os.MkdirAll(fmt.Sprintf("%v/%v", config.ModulePath, "target"), os.ModePerm); err != nil {
log.Entry().WithError(err).Error("failed to create directory")
}
if config.UpdateRulePack {
err := utils.RunExecutable("fortifyupdate", "-acceptKey", "-acceptSSLCertificate", "-url", config.ServerURL)
@@ -282,7 +306,7 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils
}
log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID)
err, paths := verifyFFProjectCompliance(config, utils, sys, project, projectVersion, filterSet, influx, auditStatus)
paths, err := verifyFFProjectCompliance(ctx, config, utils, sys, project, projectVersion, filterSet, influx, auditStatus)
reports = append(reports, paths...)
return reports, err
}
@@ -293,31 +317,35 @@ func classifyErrorOnLookup(err error) {
}
}
func verifyFFProjectCompliance(config fortifyExecuteScanOptions, utils fortifyUtils, sys fortify.System, project *models.Project, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) (error, []piperutils.Path) {
func verifyFFProjectCompliance(ctx context.Context, config fortifyExecuteScanOptions, utils fortifyUtils, sys fortify.System, project *models.Project, projectVersion *models.ProjectVersion, filterSet *models.FilterSet, influx *fortifyExecuteScanInflux, auditStatus map[string]string) ([]piperutils.Path, error) {
reports := []piperutils.Path{}
// Generate report
if config.Reporting {
resultURL := []byte(fmt.Sprintf("%v/html/ssc/version/%v/fix/null/", config.ServerURL, projectVersion.ID))
ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, "txt"), resultURL, 0700)
if err := ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, "txt"), resultURL, 0o700); err != nil {
log.Entry().WithError(err).Error("failed to write file")
}
data, err := generateAndDownloadQGateReport(config, sys, project, projectVersion)
if err != nil {
return err, reports
return reports, err
}
if err := ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, config.ReportType), data, 0o700); err != nil {
log.Entry().WithError(err).Warning("failed to write file")
}
ioutil.WriteFile(fmt.Sprintf("%vtarget/%v-%v.%v", config.ModulePath, *project.Name, *projectVersion.Name, config.ReportType), data, 0700)
}
// Perform audit compliance checks
issueFilterSelectorSet, err := sys.GetIssueFilterSelectorOfProjectVersionByName(projectVersion.ID, []string{"Analysis", "Folder", "Category"}, nil)
if err != nil {
return errors.Wrapf(err, "failed to fetch project version issue filter selector for project version ID %v", projectVersion.ID), reports
return reports, errors.Wrapf(err, "failed to fetch project version issue filter selector for project version ID %v", projectVersion.ID)
}
log.Entry().Debugf("initial filter selector set: %v", issueFilterSelectorSet)
spotChecksCountByCategory := []fortify.SpotChecksAuditCount{}
numberOfViolations, issueGroups, err := analyseUnauditedIssues(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus, &spotChecksCountByCategory)
if err != nil {
return errors.Wrap(err, "failed to analyze unaudited issues"), reports
return reports, errors.Wrap(err, "failed to analyze unaudited issues")
}
numberOfSuspiciousExploitable, issueGroupsSuspiciousExploitable := analyseSuspiciousExploitable(config, sys, projectVersion, filterSet, issueFilterSelectorSet, influx, auditStatus)
numberOfViolations += numberOfSuspiciousExploitable
@@ -335,7 +363,7 @@ func verifyFFProjectCompliance(config fortifyExecuteScanOptions, utils fortifyUt
scanReport := fortify.CreateCustomReport(fortifyReportingData, issueGroups)
paths, err := fortify.WriteCustomReports(scanReport)
if err != nil {
return errors.Wrap(err, "failed to write custom reports"), reports
return reports, errors.Wrap(err, "failed to write custom reports")
}
reports = append(reports, paths...)
@@ -343,24 +371,30 @@ func verifyFFProjectCompliance(config fortifyExecuteScanOptions, utils fortifyUt
log.Entry().Debugf("%v, %v, %v, %v, %v, %v", config.CreateResultIssue, numberOfViolations > 0, len(config.GithubToken) > 0, len(config.GithubAPIURL) > 0, len(config.Owner) > 0, len(config.Repository) > 0)
if config.CreateResultIssue && numberOfViolations > 0 && len(config.GithubToken) > 0 && len(config.GithubAPIURL) > 0 && len(config.Owner) > 0 && len(config.Repository) > 0 {
log.Entry().Debug("Creating/updating GitHub issue with scan results")
err = reporting.UploadSingleReportToGithub(scanReport, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, utils)
if err != nil {
return errors.Wrap(err, "failed to upload scan results into GitHub"), reports
gh := reporting.GitHub{
Owner: &config.Owner,
Repository: &config.Repository,
Assignees: &config.Assignees,
IssueService: utils.GetIssueService(),
SearchService: utils.GetSearchService(),
}
if err := gh.UploadSingleReport(ctx, scanReport); err != nil {
return reports, fmt.Errorf("failed to upload scan results into GitHub: %w", err)
}
}
jsonReport := fortify.CreateJSONReport(fortifyReportingData, spotChecksCountByCategory, config.ServerURL)
paths, err = fortify.WriteJSONReport(jsonReport)
if err != nil {
return errors.Wrap(err, "failed to write json report"), reports
return reports, errors.Wrap(err, "failed to write json report")
}
reports = append(reports, paths...)
if numberOfViolations > 0 {
log.SetErrorCategory(log.ErrorCompliance)
return errors.New("fortify scan failed, the project is not compliant. For details check the archived report"), reports
return reports, errors.New("fortify scan failed, the project is not compliant. For details check the archived report")
}
return nil, reports
return reports, nil
}
func prepareReportData(influx *fortifyExecuteScanInflux) fortify.FortifyReportData {
@@ -531,6 +565,9 @@ func analyseSuspiciousExploitable(config fortifyExecuteScanOptions, sys fortify.
log.Entry().Info("Analyzing suspicious and exploitable issues")
reducedFilterSelectorSet := sys.ReduceIssueFilterSelectorSet(issueFilterSelectorSet, []string{"Analysis"}, []string{})
fetchedGroups, err := sys.GetProjectIssuesByIDAndFilterSetGroupedBySelector(projectVersion.ID, "", filterSet.GUID, reducedFilterSelectorSet)
if err != nil {
log.Entry().WithError(err).Errorf("failed to get project issues")
}
suspiciousCount := 0
exploitableCount := 0
@@ -776,7 +813,8 @@ func generateMavenFortifyDefines(config *fortifyExecuteScanOptions, file string)
"-DincludeScope=compile",
"-DskipTests",
"-Dmaven.javadoc.skip=true",
"--fail-at-end"}
"--fail-at-end",
}
if len(config.BuildDescriptorExcludeList) > 0 {
// From the documentation, these are file paths to a module's pom.xml.
@@ -855,7 +893,7 @@ func removeDuplicates(contents, separator string) string {
}
func triggerFortifyScan(config fortifyExecuteScanOptions, utils fortifyUtils, buildID, buildLabel, buildProject string) error {
var err error = nil
var err error
// Do special Python related prep
pipVersion := "pip3"
if config.PythonVersion != "python3" {
@@ -899,10 +937,14 @@ func triggerFortifyScan(config fortifyExecuteScanOptions, utils fortifyUtils, bu
context := map[string]string{}
cmdTemplate := []string{pipVersion, "install", "--user", "-r", config.PythonRequirementsFile}
cmdTemplate = append(cmdTemplate, tokenize(config.PythonRequirementsInstallSuffix)...)
executeTemplatedCommand(utils, cmdTemplate, context)
if err := executeTemplatedCommand(utils, cmdTemplate, context); err != nil {
log.Entry().WithError(err).Error("failed to execute template command")
}
}
executeTemplatedCommand(utils, tokenize(config.PythonInstallCommand), map[string]string{"Pip": pipVersion})
if err := executeTemplatedCommand(utils, tokenize(config.PythonInstallCommand), map[string]string{"Pip": pipVersion}); err != nil {
log.Entry().WithError(err).Error("failed to execute template command")
}
config.Translate, err = populatePipTranslate(&config, classpath)
if err != nil {
@@ -1001,7 +1043,7 @@ func handleSingleTranslate(config *fortifyExecuteScanOptions, command fortifyUti
}
func scanProject(config *fortifyExecuteScanOptions, command fortifyUtils, buildID, buildLabel, buildProject string) error {
var scanOptions = []string{
scanOptions := []string{
"-verbose",
"-64",
"-b",
@@ -1013,9 +1055,7 @@ func scanProject(config *fortifyExecuteScanOptions, command fortifyUtils, buildI
scanOptions = append(scanOptions, "-quick")
}
if len(config.AdditionalScanParameters) > 0 {
for _, scanParameter := range config.AdditionalScanParameters {
scanOptions = append(scanOptions, scanParameter)
}
scanOptions = append(scanOptions, config.AdditionalScanParameters...)
}
if len(buildLabel) > 0 {
scanOptions = append(scanOptions, "-build-label", buildLabel)
@@ -1034,7 +1074,7 @@ func scanProject(config *fortifyExecuteScanOptions, command fortifyUtils, buildI
func determinePullRequestMerge(config fortifyExecuteScanOptions) (string, string) {
author := ""
//TODO provide parameter for trusted certs
// TODO provide parameter for trusted certs
ctx, client, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", []string{})
if err == nil && ctx != nil && client != nil {
prID, author, err := determinePullRequestMergeGithub(ctx, config, client.PullRequests)

View File

@@ -22,10 +22,9 @@ import (
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/versioning"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/stretchr/testify/assert"
piperGithub "github.com/SAP/jenkins-library/pkg/github"
"github.com/piper-validation/fortify-client-go/models"
)
@@ -35,26 +34,24 @@ type fortifyTestUtilsBundle struct {
*execRunnerMock
*mock.FilesMock
getArtifactShouldFail bool
ghCreateIssueOptions *piperGithub.CreateIssueOptions
ghCreateIssueError error
}
func (f fortifyTestUtilsBundle) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error {
func (f *fortifyTestUtilsBundle) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error {
panic("not expected to be called in tests")
}
func (f fortifyTestUtilsBundle) GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error) {
func (f *fortifyTestUtilsBundle) GetArtifact(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Artifact, error) {
if f.getArtifactShouldFail {
return nil, fmt.Errorf("build tool '%v' not supported", buildTool)
}
return artifactMock{Coordinates: newCoordinatesMock()}, nil
}
func (f fortifyTestUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error {
if f.ghCreateIssueError != nil {
return f.ghCreateIssueError
}
f.ghCreateIssueOptions = ghCreateIssueOptions
func (f *fortifyTestUtilsBundle) GetIssueService() *github.IssuesService {
return nil
}
func (cf *fortifyTestUtilsBundle) GetSearchService() *github.SearchService {
return nil
}
@@ -77,16 +74,20 @@ func newCoordinatesMock() versioning.Coordinates {
Version: "1.0.0",
}
}
func (a artifactMock) VersioningScheme() string {
return "full"
}
func (a artifactMock) GetVersion() (string, error) {
return a.Coordinates.Version, nil
}
func (a artifactMock) SetVersion(v string) error {
a.Coordinates.Version = v
return nil
}
func (a artifactMock) GetCoordinates() (versioning.Coordinates, error) {
return a.Coordinates, nil
}
@@ -100,21 +101,27 @@ type fortifyMock struct {
func (f *fortifyMock) GetProjectByName(name string, autoCreate bool, projectVersion string) (*models.Project, error) {
return &models.Project{Name: &name, ID: 64}, nil
}
func (f *fortifyMock) GetProjectVersionDetailsByProjectIDAndVersionName(id int64, name string, autoCreate bool, projectName string) (*models.ProjectVersion, error) {
return &models.ProjectVersion{ID: id, Name: &name, Project: &models.Project{Name: &projectName}}, nil
}
func (f *fortifyMock) GetProjectVersionAttributesByProjectVersionID(id int64) ([]*models.Attribute, error) {
return []*models.Attribute{}, nil
}
func (f *fortifyMock) SetProjectVersionAttributesByProjectVersionID(id int64, attributes []*models.Attribute) ([]*models.Attribute, error) {
return attributes, nil
}
func (f *fortifyMock) CreateProjectVersionIfNotExist(projectName, projectVersionName, description string) (*models.ProjectVersion, error) {
return &models.ProjectVersion{ID: 4711, Name: &projectVersionName, Project: &models.Project{Name: &projectName}}, nil
}
func (f *fortifyMock) LookupOrCreateProjectVersionDetailsForPullRequest(projectID int64, masterProjectVersion *models.ProjectVersion, pullRequestName string) (*models.ProjectVersion, error) {
return &models.ProjectVersion{ID: 4712, Name: &pullRequestName, Project: masterProjectVersion.Project}, nil
}
func (f *fortifyMock) CreateProjectVersion(version *models.ProjectVersion) (*models.ProjectVersion, error) {
return version, nil
}
@@ -122,19 +129,24 @@ func (f *fortifyMock) CreateProjectVersion(version *models.ProjectVersion) (*mod
func (f *fortifyMock) ProjectVersionCopyFromPartial(sourceID, targetID int64) error {
return nil
}
func (f *fortifyMock) ProjectVersionCopyCurrentState(sourceID, targetID int64) error {
return nil
}
func (f *fortifyMock) ProjectVersionCopyPermissions(sourceID, targetID int64) error {
return nil
}
func (f *fortifyMock) CommitProjectVersion(id int64) (*models.ProjectVersion, error) {
name := "Committed"
return &models.ProjectVersion{ID: id, Name: &name}, nil
}
func (f *fortifyMock) MergeProjectVersionStateOfPRIntoMaster(downloadEndpoint, uploadEndpoint string, masterProjectID, masterProjectVersionID int64, pullRequestName string) error {
return nil
}
func (f *fortifyMock) GetArtifactsOfProjectVersion(id int64) ([]*models.Artifact, error) {
switch id {
case 4711:
@@ -198,12 +210,15 @@ func (f *fortifyMock) GetArtifactsOfProjectVersion(id int64) ([]*models.Artifact
return []*models.Artifact{}, nil
}
}
func (f *fortifyMock) GetFilterSetOfProjectVersionByTitle(id int64, title string) (*models.FilterSet, error) {
return &models.FilterSet{}, nil
}
func (f *fortifyMock) GetIssueFilterSelectorOfProjectVersionByName(id int64, names []string, options []string) (*models.IssueFilterSelectorSet, error) {
return &models.IssueFilterSelectorSet{}, nil
}
func (f *fortifyMock) GetFilterSetByDisplayName(issueFilterSelectorSet *models.IssueFilterSelectorSet, name string) *models.IssueFilterSelector {
if issueFilterSelectorSet.FilterBySet != nil {
for _, filter := range issueFilterSelectorSet.FilterBySet {
@@ -214,6 +229,7 @@ func (f *fortifyMock) GetFilterSetByDisplayName(issueFilterSelectorSet *models.I
}
return &models.IssueFilterSelector{DisplayName: name}
}
func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64, filter, filterSetGUID string, issueFilterSelectorSet *models.IssueFilterSelectorSet) ([]*models.ProjectVersionIssueGroup, error) {
if filter == "ET1:abcd" {
group := "HTTP Verb tampering"
@@ -260,13 +276,16 @@ func (f *fortifyMock) GetProjectIssuesByIDAndFilterSetGroupedBySelector(id int64
{ID: &group3, CleanName: &group3, TotalCount: &total3, AuditedCount: &audited3},
}, nil
}
func (f *fortifyMock) ReduceIssueFilterSelectorSet(issueFilterSelectorSet *models.IssueFilterSelectorSet, names []string, options []string) *models.IssueFilterSelectorSet {
return issueFilterSelectorSet
}
func (f *fortifyMock) GetIssueStatisticsOfProjectVersion(id int64) ([]*models.IssueStatistics, error) {
suppressed := int32(6)
return []*models.IssueStatistics{{SuppressedCount: &suppressed}}, nil
}
func (f *fortifyMock) GenerateQGateReport(projectID, projectVersionID, reportTemplateID int64, projectName, projectVersionName, reportFormat string) (*models.SavedReport, error) {
if !f.Successive {
f.Successive = true
@@ -275,31 +294,38 @@ func (f *fortifyMock) GenerateQGateReport(projectID, projectVersionID, reportTem
f.Successive = false
return &models.SavedReport{Status: "PROCESS_COMPLETE"}, nil
}
func (f *fortifyMock) GetReportDetails(id int64) (*models.SavedReport, error) {
return &models.SavedReport{Status: "PROCESS_COMPLETE"}, nil
}
func (f *fortifyMock) GetAllIssueDetails(projectVersionId int64) ([]*models.ProjectVersionIssue, error) {
exploitable := "Exploitable"
friority := "High"
hascomments := true
return []*models.ProjectVersionIssue{{ID: 1111, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}, {ID: 1112, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}}, nil
}
func (f *fortifyMock) GetIssueDetails(projectVersionId int64, issueInstanceId string) ([]*models.ProjectVersionIssue, error) {
exploitable := "Exploitable"
friority := "High"
hascomments := true
return []*models.ProjectVersionIssue{{ID: 1111, Audited: true, PrimaryTag: &exploitable, HasComments: &hascomments, Friority: &friority}}, nil
}
func (f *fortifyMock) GetIssueComments(parentId int64) ([]*models.IssueAuditComment, error) {
comment := "Dummy"
return []*models.IssueAuditComment{{Comment: &comment}}, nil
}
func (f *fortifyMock) UploadResultFile(endpoint, file string, projectVersionID int64) error {
return nil
}
func (f *fortifyMock) DownloadReportFile(endpoint string, reportID int64) ([]byte, error) {
return []byte("abcd"), nil
}
func (f *fortifyMock) DownloadResultFile(endpoint string, projectVersionID int64) ([]byte, error) {
return []byte("defg"), nil
}
@@ -364,6 +390,7 @@ func (er *execRunnerMock) Stdout(out io.Writer) {
func (er *execRunnerMock) Stderr(err io.Writer) {
er.currentExecution().errWriter = err
}
func (er *execRunnerMock) RunExecutable(e string, p ...string) error {
er.numExecutions++
er.currentExecution().executable = e
@@ -383,7 +410,7 @@ func (er *execRunnerMock) RunExecutable(e string, p ...string) error {
}
} else if e == "mvn" {
path := strings.ReplaceAll(p[2], "-Dmdep.outputFile=", "")
err := ioutil.WriteFile(path, []byte(classpathMaven), 0644)
err := ioutil.WriteFile(path, []byte(classpathMaven), 0o644)
if err != nil {
return err
}
@@ -397,7 +424,7 @@ func TestDetermineArtifact(t *testing.T) {
utilsMock := newFortifyTestUtilsBundle()
utilsMock.getArtifactShouldFail = true
_, err := determineArtifact(fortifyExecuteScanOptions{}, utilsMock)
_, err := determineArtifact(fortifyExecuteScanOptions{}, &utilsMock)
assert.EqualError(t, err, "Unable to get artifact from descriptor : build tool '' not supported")
})
}
@@ -406,7 +433,6 @@ func TestExecutions(t *testing.T) {
type parameterTestData struct {
nameOfRun string
config fortifyExecuteScanOptions
expectedError string
expectedReportsLength int
expectedReports []string
}
@@ -433,11 +459,12 @@ func TestExecutions(t *testing.T) {
for _, data := range testData {
t.Run(data.nameOfRun, func(t *testing.T) {
ctx := context.Background()
ff := fortifyMock{}
utils := newFortifyTestUtilsBundle()
influx := fortifyExecuteScanInflux{}
auditStatus := map[string]string{}
reports, _ := runFortifyScan(data.config, &ff, utils, nil, &influx, auditStatus)
reports, _ := runFortifyScan(ctx, data.config, &ff, &utils, nil, &influx, auditStatus)
if len(data.expectedReports) != data.expectedReportsLength {
assert.Fail(t, fmt.Sprintf("Wrong number of reports detected, expected %v, actual %v", data.expectedReportsLength, len(data.expectedReports)))
}
@@ -578,7 +605,8 @@ func TestTriggerFortifyScan(t *testing.T) {
BuildDescriptorFile: "./pom.xml",
AdditionalScanParameters: []string{"-Dtest=property"},
Memory: "-Xmx4G -Xms2G",
Src: []string{"**/*.xml", "**/*.html", "**/*.jsp", "**/*.js", "src/main/resources/**/*", "src/main/java/**/*"}}
Src: []string{"**/*.xml", "**/*.html", "**/*.jsp", "**/*.js", "src/main/resources/**/*", "src/main/java/**/*"},
}
triggerFortifyScan(config, &utils, "test", "testLabel", "my.group-myartifact")
assert.Equal(t, 3, utils.numExecutions)
@@ -686,8 +714,10 @@ func TestGenerateAndDownloadQGateReport(t *testing.T) {
})
}
var defaultPollingDelay = 10 * time.Second
var defaultPollingTimeout = 0 * time.Minute
var (
defaultPollingDelay = 10 * time.Second
defaultPollingTimeout = 0 * time.Minute
)
func verifyScanResultsFinishedUploadingDefaults(config fortifyExecuteScanOptions, sys fortify.System, projectVersionID int64) error {
return verifyScanResultsFinishedUploading(config, sys, projectVersionID, "", &models.FilterSet{},
@@ -957,7 +987,6 @@ func TestPopulateMavenTranslate(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, `[{"classpath":""}]`, translate)
})
}
func TestPopulatePipTranslate(t *testing.T) {
@@ -997,7 +1026,8 @@ func TestPopulatePipTranslate(t *testing.T) {
config := fortifyExecuteScanOptions{
Translate: `[{"pythonPath":""}]`,
Src: []string{"./**/*"},
PythonAdditionalPath: []string{"./lib", "."}}
PythonAdditionalPath: []string{"./lib", "."},
}
translate, err := populatePipTranslate(&config, "ignored/path")
assert.NoError(t, err)
assert.Equal(t, `[{"pythonPath":""}]`, translate, "Expected different parameters")

View File

@@ -7,7 +7,7 @@ import (
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/pkg/errors"
@@ -19,7 +19,7 @@ type gitHubBranchProtectionRepositoriesService interface {
}
func githubCheckBranchProtection(config githubCheckBranchProtectionOptions, telemetryData *telemetry.CustomData) {
//TODO provide parameter for trusted certs
// TODO provide parameter for trusted certs
ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, "", []string{})
if err != nil {
log.Entry().WithError(err).Fatal("Failed to get GitHub client")

View File

@@ -7,7 +7,7 @@ import (
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/stretchr/testify/assert"
)
@@ -110,5 +110,4 @@ func TestRunGithubCheckBranchProtection(t *testing.T) {
err := runGithubCheckBranchProtection(ctx, &config, &telemetryData, &ghRepo)
assert.Contains(t, fmt.Sprint(err), "not enough mandatory reviewers")
})
}

View File

@@ -5,7 +5,7 @@ import (
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/pkg/errors"
piperGithub "github.com/SAP/jenkins-library/pkg/github"
@@ -16,7 +16,7 @@ type githubIssueCommentService interface {
}
func githubCommentIssue(config githubCommentIssueOptions, telemetryData *telemetry.CustomData) {
//TODO provide parameter for trusted certs
// TODO provide parameter for trusted certs
ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, "", []string{})
if err != nil {
log.Entry().WithError(err).Fatal("Failed to get GitHub client")

View File

@@ -6,7 +6,7 @@ import (
"net/http"
"testing"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/stretchr/testify/assert"
)

View File

@@ -5,7 +5,7 @@ import (
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/pkg/errors"
piperGithub "github.com/SAP/jenkins-library/pkg/github"
@@ -20,7 +20,7 @@ type githubIssueService interface {
}
func githubCreatePullRequest(config githubCreatePullRequestOptions, telemetryData *telemetry.CustomData) {
//TODO provide parameter for trusted certs
// TODO provide parameter for trusted certs
ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, "", []string{})
if err != nil {
log.Entry().WithError(err).Fatal("Failed to get GitHub client")
@@ -33,7 +33,6 @@ func githubCreatePullRequest(config githubCreatePullRequestOptions, telemetryDat
}
func runGithubCreatePullRequest(ctx context.Context, config *githubCreatePullRequestOptions, ghPRService githubPRService, ghIssueService githubIssueService) error {
prRequest := github.NewPullRequest{
Title: &config.Title,
Head: &config.Head,

View File

@@ -6,7 +6,7 @@ import (
"net/http"
"testing"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/stretchr/testify/assert"
)
@@ -107,7 +107,6 @@ func TestRunGithubCreatePullRequest(t *testing.T) {
err := runGithubCreatePullRequest(ctx, &myGithubPROptions, &ghPRService, &ghIssueService)
assert.EqualError(t, err, "Error occurred when creating pull request: Authentication failed", "Wrong error returned")
})
t.Run("Edit error", func(t *testing.T) {

View File

@@ -10,7 +10,7 @@ import (
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/pkg/errors"
piperGithub "github.com/SAP/jenkins-library/pkg/github"
@@ -30,7 +30,7 @@ type githubIssueClient interface {
}
func githubPublishRelease(config githubPublishReleaseOptions, telemetryData *telemetry.CustomData) {
//TODO provide parameter for trusted certs
// TODO provide parameter for trusted certs
ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, config.UploadURL, []string{})
if err != nil {
log.Entry().WithError(err).Fatal("Failed to get GitHub client.")
@@ -43,13 +43,12 @@ func githubPublishRelease(config githubPublishReleaseOptions, telemetryData *tel
}
func runGithubPublishRelease(ctx context.Context, config *githubPublishReleaseOptions, ghRepoClient GithubRepoClient, ghIssueClient githubIssueClient) error {
var publishedAt github.Timestamp
lastRelease, resp, err := ghRepoClient.GetLatestRelease(ctx, config.Owner, config.Repository)
if err != nil {
if resp != nil && resp.StatusCode == 404 {
//no previous release found -> first release
// no previous release found -> first release
config.AddDeltaToLastRelease = false
log.Entry().Debug("This is the first release.")
} else {
@@ -59,7 +58,7 @@ func runGithubPublishRelease(ctx context.Context, config *githubPublishReleaseOp
publishedAt = lastRelease.GetPublishedAt()
log.Entry().Debugf("Previous GitHub release published: '%v'", publishedAt)
//updating assets only supported on latest release
// updating assets only supported on latest release
if len(config.AssetPath) > 0 && config.Version == "latest" {
return uploadReleaseAsset(ctx, lastRelease.GetID(), config, ghRepoClient)
} else if len(config.AssetPathList) > 0 && config.Version == "latest" {
@@ -147,7 +146,7 @@ func getClosedIssuesText(ctx context.Context, publishedAt github.Timestamp, conf
func getReleaseDeltaText(config *githubPublishReleaseOptions, lastRelease *github.RepositoryRelease) string {
releaseDeltaText := ""
//add delta link to previous release
// add delta link to previous release
releaseDeltaText += "\n**Changes**\n"
releaseDeltaText += fmt.Sprintf(
"[%v...%v](%v/%v/%v/compare/%v...%v)\n",
@@ -174,7 +173,6 @@ func uploadReleaseAssetList(ctx context.Context, releaseID int64, config *github
}
func uploadReleaseAsset(ctx context.Context, releaseID int64, config *githubPublishReleaseOptions, ghRepoClient GithubRepoClient) error {
assets, _, err := ghRepoClient.ListReleaseAssets(ctx, config.Owner, config.Repository, releaseID, &github.ListOptions{})
if err != nil {
return errors.Wrap(err, "Failed to get list of release assets.")
@@ -187,7 +185,7 @@ func uploadReleaseAsset(ctx context.Context, releaseID int64, config *githubPubl
}
}
if assetID != 0 {
//asset needs to be deleted first since API does not allow for replacement
// asset needs to be deleted first since API does not allow for replacement
_, err := ghRepoClient.DeleteReleaseAsset(ctx, config.Owner, config.Repository, assetID)
if err != nil {
return errors.Wrap(err, "Failed to delete release asset.")
@@ -227,7 +225,7 @@ func uploadReleaseAsset(ctx context.Context, releaseID int64, config *githubPubl
}
func isExcluded(issue *github.Issue, excludeLabels []string) bool {
//issue.Labels[0].GetName()
// issue.Labels[0].GetName()
for _, ex := range excludeLabels {
for _, l := range issue.Labels {
if ex == l.GetName() {

View File

@@ -10,7 +10,7 @@ import (
"time"
"github.com/SAP/jenkins-library/cmd/mocks"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
@@ -151,7 +151,7 @@ func TestRunGithubPublishRelease(t *testing.T) {
t.Run("Success - subsequent releases & with body", func(t *testing.T) {
lastTag := "1.0"
lastPublishedAt := github.Timestamp{Time: time.Date(2019, 01, 01, 0, 0, 0, 0, time.UTC)}
lastPublishedAt := github.Timestamp{Time: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)}
ghRepoClient := ghRCMock{
createErr: nil,
latestRelease: &github.RepositoryRelease{
@@ -263,7 +263,7 @@ func TestRunGithubPublishRelease(t *testing.T) {
func TestGetClosedIssuesText(t *testing.T) {
ctx := context.Background()
publishedAt := github.Timestamp{Time: time.Date(2019, 01, 01, 0, 0, 0, 0, time.UTC)}
publishedAt := github.Timestamp{Time: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)}
t.Run("No issues", func(t *testing.T) {
ghIssueClient := ghICMock{}
@@ -278,7 +278,7 @@ func TestGetClosedIssuesText(t *testing.T) {
t.Run("All issues", func(t *testing.T) {
ctx := context.Background()
publishedAt := github.Timestamp{Time: time.Date(2019, 01, 01, 0, 0, 0, 0, time.UTC)}
publishedAt := github.Timestamp{Time: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)}
prHTMLURL := []string{"https://github.com/TEST/test/pull/1", "https://github.com/TEST/test/pull/2"}
prTitle := []string{"Pull1", "Pull2"}
@@ -311,7 +311,6 @@ func TestGetClosedIssuesText(t *testing.T) {
assert.Equal(t, "asc", ghIssueClient.options.Direction, "Sort direction not properly passed")
assert.Equal(t, publishedAt.Time, ghIssueClient.options.Since, "PublishedAt not properly passed")
})
}
func TestGetReleaseDeltaText(t *testing.T) {
@@ -459,7 +458,6 @@ func TestUploadReleaseAssetList(t *testing.T) {
}
func TestIsExcluded(t *testing.T) {
l1 := "label1"
l2 := "label2"
@@ -480,5 +478,4 @@ func TestIsExcluded(t *testing.T) {
for k, v := range tt {
assert.Equal(t, v.expected, isExcluded(v.issue, v.excludeLabels), fmt.Sprintf("Run %v failed", k))
}
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/pkg/errors"
@@ -19,7 +19,7 @@ type gitHubCommitStatusRepositoriesService interface {
}
func githubSetCommitStatus(config githubSetCommitStatusOptions, telemetryData *telemetry.CustomData) {
//TODO provide parameter for trusted certs
// TODO provide parameter for trusted certs
ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, "", []string{})
if err != nil {
log.Entry().WithError(err).Fatal("Failed to get GitHub client")

View File

@@ -7,7 +7,7 @@ import (
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/stretchr/testify/assert"
)

View File

@@ -5,7 +5,7 @@ package mocks
import (
context "context"
github "github.com/google/go-github/v32/github"
github "github.com/google/go-github/v45/github"
mock "github.com/stretchr/testify/mock"
os "os"

View File

@@ -1,6 +1,7 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"os"
@@ -24,6 +25,8 @@ import (
"github.com/SAP/jenkins-library/pkg/versioning"
"github.com/pkg/errors"
"github.com/xuri/excelize/v2"
"github.com/google/go-github/v45/github"
)
// ScanOptions is just used to make the lines less long
@@ -48,12 +51,10 @@ type whitesource interface {
type whitesourceUtils interface {
ws.Utils
piperutils.FileUtils
GetArtifactCoordinates(buildTool, buildDescriptorFile string,
options *versioning.Options) (versioning.Coordinates, error)
CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error
GetArtifactCoordinates(buildTool, buildDescriptorFile string, options *versioning.Options) (versioning.Coordinates, error)
Now() time.Time
GetIssueService() *github.IssuesService
GetSearchService() *github.SearchService
}
type whitesourceUtilsBundle struct {
@@ -61,11 +62,8 @@ type whitesourceUtilsBundle struct {
*command.Command
*piperutils.Files
npmExecutor npm.Executor
}
// CreateIssue supplies capability for GitHub issue creation
func (w *whitesourceUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error {
return piperGithub.CreateIssue(ghCreateIssueOptions)
issues *github.IssuesService
search *github.SearchService
}
func (w *whitesourceUtilsBundle) FileOpen(name string, flag int, perm os.FileMode) (ws.File, error) {
@@ -103,12 +101,24 @@ func (w *whitesourceUtilsBundle) Now() time.Time {
return time.Now()
}
func newWhitesourceUtils(config *ScanOptions) *whitesourceUtilsBundle {
func (w *whitesourceUtilsBundle) GetIssueService() *github.IssuesService {
return w.issues
}
func (w *whitesourceUtilsBundle) GetSearchService() *github.SearchService {
return w.search
}
func newWhitesourceUtils(config *ScanOptions, client *github.Client) *whitesourceUtilsBundle {
utils := whitesourceUtilsBundle{
Client: &piperhttp.Client{},
Command: &command.Command{},
Files: &piperutils.Files{},
}
if client != nil {
utils.issues = client.Issues
utils.search = client.Search
}
// Reroute cmd output to logging framework
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
@@ -125,18 +135,21 @@ func newWhitesourceScan(config *ScanOptions) *ws.Scan {
}
func whitesourceExecuteScan(config ScanOptions, _ *telemetry.CustomData, commonPipelineEnvironment *whitesourceExecuteScanCommonPipelineEnvironment, influx *whitesourceExecuteScanInflux) {
utils := newWhitesourceUtils(&config)
ctx, client, err := piperGithub.NewClient(config.GithubToken, config.GithubAPIURL, "", config.CustomTLSCertificateLinks)
if err != nil {
log.Entry().WithError(err).Warning("Failed to get GitHub client")
}
utils := newWhitesourceUtils(&config, client)
scan := newWhitesourceScan(&config)
sys := ws.NewSystem(config.ServiceURL, config.OrgToken, config.UserToken, time.Duration(config.Timeout)*time.Second)
influx.step_data.fields.whitesource = false
err := runWhitesourceExecuteScan(&config, scan, utils, sys, commonPipelineEnvironment, influx)
if err != nil {
if err := runWhitesourceExecuteScan(ctx, &config, scan, utils, sys, commonPipelineEnvironment, influx); err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
influx.step_data.fields.whitesource = true
}
func runWhitesourceExecuteScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, commonPipelineEnvironment *whitesourceExecuteScanCommonPipelineEnvironment, influx *whitesourceExecuteScanInflux) error {
func runWhitesourceExecuteScan(ctx context.Context, config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, commonPipelineEnvironment *whitesourceExecuteScanCommonPipelineEnvironment, influx *whitesourceExecuteScanInflux) error {
if err := resolveAggregateProjectName(config, scan, sys); err != nil {
return errors.Wrapf(err, "failed to resolve and aggregate project name")
}
@@ -160,14 +173,14 @@ func runWhitesourceExecuteScan(config *ScanOptions, scan *ws.Scan, utils whiteso
return errors.Wrapf(err, "failed to aggregate version wide vulnerabilities")
}
} else {
if err := runWhitesourceScan(config, scan, utils, sys, commonPipelineEnvironment, influx); err != nil {
if err := runWhitesourceScan(ctx, config, scan, utils, sys, commonPipelineEnvironment, influx); err != nil {
return errors.Wrapf(err, "failed to execute WhiteSource scan")
}
}
return nil
}
func runWhitesourceScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, commonPipelineEnvironment *whitesourceExecuteScanCommonPipelineEnvironment, influx *whitesourceExecuteScanInflux) error {
func runWhitesourceScan(ctx context.Context, config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, commonPipelineEnvironment *whitesourceExecuteScanCommonPipelineEnvironment, influx *whitesourceExecuteScanInflux) error {
// Download Docker image for container scan
// ToDo: move it to improve testability
if config.BuildTool == "docker" {
@@ -212,7 +225,7 @@ func runWhitesourceScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUti
}
log.Entry().Info("-----------------------------------------------------")
paths, err := checkAndReportScanResults(config, scan, utils, sys, influx)
paths, err := checkAndReportScanResults(ctx, config, scan, utils, sys, influx)
piperutils.PersistReportsAndLinks("whitesourceExecuteScan", "", paths, nil)
persistScannedProjects(config, scan, commonPipelineEnvironment)
if err != nil {
@@ -221,7 +234,7 @@ func runWhitesourceScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUti
return nil
}
func checkAndReportScanResults(config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, influx *whitesourceExecuteScanInflux) ([]piperutils.Path, error) {
func checkAndReportScanResults(ctx context.Context, config *ScanOptions, scan *ws.Scan, utils whitesourceUtils, sys whitesource, influx *whitesourceExecuteScanInflux) ([]piperutils.Path, error) {
reportPaths := []piperutils.Path{}
if !config.Reporting && !config.SecurityVulnerabilities {
return reportPaths, nil
@@ -251,7 +264,7 @@ func checkAndReportScanResults(config *ScanOptions, scan *ws.Scan, utils whiteso
reportPaths = append(reportPaths, rPath)
if config.SecurityVulnerabilities {
rPaths, err := checkSecurityViolations(config, scan, sys, utils, influx)
rPaths, err := checkSecurityViolations(ctx, config, scan, sys, utils, influx)
reportPaths = append(reportPaths, rPaths...)
if err != nil {
checkErrors = append(checkErrors, fmt.Sprint(err))
@@ -538,7 +551,7 @@ func checkPolicyViolations(config *ScanOptions, scan *ws.Scan, sys whitesource,
return policyReport, nil
}
func checkSecurityViolations(config *ScanOptions, scan *ws.Scan, sys whitesource, utils whitesourceUtils, influx *whitesourceExecuteScanInflux) ([]piperutils.Path, error) {
func checkSecurityViolations(ctx context.Context, config *ScanOptions, scan *ws.Scan, sys whitesource, utils whitesourceUtils, influx *whitesourceExecuteScanInflux) ([]piperutils.Path, error) {
var reportPaths []piperutils.Path
// Check for security vulnerabilities and fail the build if cvssSeverityLimit threshold is crossed
// convert config.CvssSeverityLimit to float64
@@ -574,8 +587,14 @@ func checkSecurityViolations(config *ScanOptions, scan *ws.Scan, sys whitesource
log.Entry().Debugf("Creating result issues for %v alert(s)", vulnerabilitiesCount)
issueDetails := make([]reporting.IssueDetail, len(allAlerts))
piperutils.CopyAtoB(allAlerts, issueDetails)
err = reporting.UploadMultipleReportsToGithub(&issueDetails, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, config.CustomTLSCertificateLinks, utils)
if err != nil {
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 {
errorsOccured = append(errorsOccured, fmt.Sprint(err))
}
}

View File

@@ -1,12 +1,12 @@
package cmd
import (
"context"
"fmt"
"path/filepath"
"testing"
"time"
piperGithub "github.com/SAP/jenkins-library/pkg/github"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/SAP/jenkins-library/pkg/piperutils"
@@ -14,6 +14,8 @@ import (
"github.com/SAP/jenkins-library/pkg/versioning"
ws "github.com/SAP/jenkins-library/pkg/whitesource"
"github.com/stretchr/testify/assert"
"github.com/google/go-github/v45/github"
)
type whitesourceUtilsMock struct {
@@ -25,17 +27,14 @@ type whitesourceUtilsMock struct {
}
func (w *whitesourceUtilsMock) GetArtifactCoordinates(buildTool, buildDescriptorFile string,
options *versioning.Options) (versioning.Coordinates, error) {
options *versioning.Options,
) (versioning.Coordinates, error) {
w.usedBuildTool = buildTool
w.usedBuildDescriptorFile = buildDescriptorFile
w.usedOptions = *options
return w.coordinates, nil
}
func (w *whitesourceUtilsMock) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error {
return nil
}
const wsTimeNow = "2010-05-10 00:15:42"
func (w *whitesourceUtilsMock) Now() time.Time {
@@ -43,6 +42,14 @@ func (w *whitesourceUtilsMock) Now() time.Time {
return now
}
func (w *whitesourceUtilsMock) GetIssueService() *github.IssuesService {
return nil
}
func (w *whitesourceUtilsMock) GetSearchService() *github.SearchService {
return nil
}
func newWhitesourceUtilsMock() *whitesourceUtilsMock {
return &whitesourceUtilsMock{
ScanUtilsMock: &ws.ScanUtilsMock{
@@ -60,7 +67,7 @@ func newWhitesourceUtilsMock() *whitesourceUtilsMock {
func TestNewWhitesourceUtils(t *testing.T) {
t.Parallel()
config := ScanOptions{}
utils := newWhitesourceUtils(&config)
utils := newWhitesourceUtils(&config, &github.Client{Issues: &github.IssuesService{}, Search: &github.SearchService{}})
assert.NotNil(t, utils.Client)
assert.NotNil(t, utils.Command)
@@ -70,6 +77,7 @@ func TestNewWhitesourceUtils(t *testing.T) {
func TestRunWhitesourceExecuteScan(t *testing.T) {
t.Parallel()
t.Run("fails for invalid configured project token", func(t *testing.T) {
ctx := context.Background()
// init
config := ScanOptions{
BuildDescriptorFile: "my-mta.yml",
@@ -86,13 +94,14 @@ func TestRunWhitesourceExecuteScan(t *testing.T) {
cpe := whitesourceExecuteScanCommonPipelineEnvironment{}
influx := whitesourceExecuteScanInflux{}
// test
err := runWhitesourceExecuteScan(&config, scan, utilsMock, systemMock, &cpe, &influx)
err := runWhitesourceExecuteScan(ctx, &config, scan, utilsMock, systemMock, &cpe, &influx)
// assert
assert.EqualError(t, err, "failed to resolve and aggregate project name: failed to get project by token: no project with token 'no-such-project-token' found in Whitesource")
assert.Equal(t, "", config.ProjectName)
assert.Equal(t, "", scan.AggregateProjectName)
})
t.Run("retrieves aggregate project name by configured token", func(t *testing.T) {
ctx := context.Background()
// init
config := ScanOptions{
BuildDescriptorFile: "my-mta.yml",
@@ -113,7 +122,7 @@ func TestRunWhitesourceExecuteScan(t *testing.T) {
cpe := whitesourceExecuteScanCommonPipelineEnvironment{}
influx := whitesourceExecuteScanInflux{}
// test
err := runWhitesourceExecuteScan(&config, scan, utilsMock, systemMock, &cpe, &influx)
err := runWhitesourceExecuteScan(ctx, &config, scan, utilsMock, systemMock, &cpe, &influx)
// assert
assert.NoError(t, err)
// Retrieved project name is stored in scan.AggregateProjectName, but not in config.ProjectName
@@ -137,6 +146,7 @@ func TestRunWhitesourceExecuteScan(t *testing.T) {
func TestCheckAndReportScanResults(t *testing.T) {
t.Parallel()
t.Run("no reports requested", func(t *testing.T) {
ctx := context.Background()
// init
config := &ScanOptions{
ProductToken: "mock-product-token",
@@ -148,7 +158,7 @@ func TestCheckAndReportScanResults(t *testing.T) {
system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout))
influx := whitesourceExecuteScanInflux{}
// test
_, err := checkAndReportScanResults(config, scan, utils, system, &influx)
_, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx)
// assert
assert.NoError(t, err)
vPath := filepath.Join(ws.ReportsDirectory, "mock-project-vulnerability-report.txt")
@@ -157,6 +167,7 @@ func TestCheckAndReportScanResults(t *testing.T) {
assert.False(t, utils.HasWrittenFile(rPath))
})
t.Run("check vulnerabilities - invalid limit", func(t *testing.T) {
ctx := context.Background()
// init
config := &ScanOptions{
SecurityVulnerabilities: true,
@@ -167,11 +178,12 @@ func TestCheckAndReportScanResults(t *testing.T) {
system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout))
influx := whitesourceExecuteScanInflux{}
// test
_, err := checkAndReportScanResults(config, scan, utils, system, &influx)
_, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx)
// assert
assert.EqualError(t, err, "failed to parse parameter cvssSeverityLimit (invalid) as floating point number: strconv.ParseFloat: parsing \"invalid\": invalid syntax")
})
t.Run("check vulnerabilities - limit not hit", func(t *testing.T) {
ctx := context.Background()
// init
config := &ScanOptions{
ProductToken: "mock-product-token",
@@ -185,11 +197,12 @@ func TestCheckAndReportScanResults(t *testing.T) {
system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout))
influx := whitesourceExecuteScanInflux{}
// test
_, err := checkAndReportScanResults(config, scan, utils, system, &influx)
_, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx)
// assert
assert.NoError(t, err)
})
t.Run("check vulnerabilities - limit exceeded", func(t *testing.T) {
ctx := context.Background()
// init
config := &ScanOptions{
ProductToken: "mock-product-token",
@@ -205,7 +218,7 @@ func TestCheckAndReportScanResults(t *testing.T) {
system := ws.NewSystemMock(time.Now().Format(ws.DateTimeLayout))
influx := whitesourceExecuteScanInflux{}
// test
_, err := checkAndReportScanResults(config, scan, utils, system, &influx)
_, err := checkAndReportScanResults(ctx, config, scan, utils, system, &influx)
// assert
assert.EqualError(t, err, "1 Open Source Software Security vulnerabilities with CVSS score greater or equal to 4.0 detected in project mock-project - 1")
})
@@ -389,7 +402,6 @@ func TestCheckPolicyViolations(t *testing.T) {
exists, err := utilsMock.FileExists(filepath.Join(reporting.StepReportDirectory, "whitesourceExecuteScan_ip_2d3120020f3f46393a54575a7f6f5675ad536721.json"))
assert.True(t, exists)
})
t.Run("success - no reports", func(t *testing.T) {
@@ -496,6 +508,7 @@ func TestCheckSecurityViolations(t *testing.T) {
t.Parallel()
t.Run("success - non-aggregated", func(t *testing.T) {
ctx := context.Background()
config := ScanOptions{
CvssSeverityLimit: "7",
}
@@ -510,7 +523,7 @@ func TestCheckSecurityViolations(t *testing.T) {
utilsMock := newWhitesourceUtilsMock()
influx := whitesourceExecuteScanInflux{}
reportPaths, err := checkSecurityViolations(&config, scan, systemMock, utilsMock, &influx)
reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx)
assert.NoError(t, err)
fileContent, err := utilsMock.FileRead(reportPaths[0].Target)
assert.NoError(t, err)
@@ -518,6 +531,7 @@ func TestCheckSecurityViolations(t *testing.T) {
})
t.Run("success - aggregated", func(t *testing.T) {
ctx := context.Background()
config := ScanOptions{
CvssSeverityLimit: "7",
ProjectToken: "theProjectToken",
@@ -530,24 +544,25 @@ func TestCheckSecurityViolations(t *testing.T) {
utilsMock := newWhitesourceUtilsMock()
influx := whitesourceExecuteScanInflux{}
reportPaths, err := checkSecurityViolations(&config, scan, systemMock, utilsMock, &influx)
reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx)
assert.NoError(t, err)
assert.Equal(t, 0, len(reportPaths))
})
t.Run("error - wrong limit", func(t *testing.T) {
ctx := context.Background()
config := ScanOptions{CvssSeverityLimit: "x"}
scan := newWhitesourceScan(&config)
systemMock := ws.NewSystemMock("ignored")
utilsMock := newWhitesourceUtilsMock()
influx := whitesourceExecuteScanInflux{}
_, err := checkSecurityViolations(&config, scan, systemMock, utilsMock, &influx)
_, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx)
assert.Contains(t, fmt.Sprint(err), "failed to parse parameter cvssSeverityLimit")
})
t.Run("error - non-aggregated", func(t *testing.T) {
ctx := context.Background()
config := ScanOptions{
CvssSeverityLimit: "5",
FailOnSevereVulnerabilities: true,
@@ -563,7 +578,7 @@ func TestCheckSecurityViolations(t *testing.T) {
utilsMock := newWhitesourceUtilsMock()
influx := whitesourceExecuteScanInflux{}
reportPaths, err := checkSecurityViolations(&config, scan, systemMock, utilsMock, &influx)
reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx)
assert.Contains(t, fmt.Sprint(err), "1 Open Source Software Security vulnerabilities")
fileContent, err := utilsMock.FileRead(reportPaths[0].Target)
assert.NoError(t, err)
@@ -571,6 +586,7 @@ func TestCheckSecurityViolations(t *testing.T) {
})
t.Run("error - aggregated", func(t *testing.T) {
ctx := context.Background()
config := ScanOptions{
CvssSeverityLimit: "5",
ProjectToken: "theProjectToken",
@@ -584,7 +600,7 @@ func TestCheckSecurityViolations(t *testing.T) {
utilsMock := newWhitesourceUtilsMock()
influx := whitesourceExecuteScanInflux{}
reportPaths, err := checkSecurityViolations(&config, scan, systemMock, utilsMock, &influx)
reportPaths, err := checkSecurityViolations(ctx, &config, scan, systemMock, utilsMock, &influx)
assert.Contains(t, fmt.Sprint(err), "1 Open Source Software Security vulnerabilities")
assert.Equal(t, 0, len(reportPaths))
})
@@ -626,7 +642,6 @@ func TestCheckProjectSecurityViolations(t *testing.T) {
_, _, err := checkProjectSecurityViolations(&ScanOptions{FailOnSevereVulnerabilities: true}, 7.0, project, systemMock, &influx)
assert.Contains(t, fmt.Sprint(err), "failed to retrieve project alerts from WhiteSource")
})
}
func TestAggregateVersionWideLibraries(t *testing.T) {

4
go.mod
View File

@@ -25,9 +25,9 @@ require (
github.com/go-playground/locales v0.14.0
github.com/go-playground/universal-translator v0.18.0
github.com/go-playground/validator/v10 v10.9.0
github.com/google/go-cmp v0.5.7
github.com/google/go-cmp v0.5.8
github.com/google/go-containerregistry v0.6.0
github.com/google/go-github/v32 v32.1.0
github.com/google/go-github/v45 v45.2.0
github.com/google/uuid v1.3.0
github.com/hashicorp/go-retryablehttp v0.7.0
github.com/hashicorp/vault v1.9.3

7
go.sum
View File

@@ -921,15 +921,16 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
github.com/google/go-containerregistry v0.5.2-0.20210604130445-3bfab55f3bd9/go.mod h1:R5WRYyTdQqTchlBhX4q+WICGh8HQIL5wDFoFZv7Jq6Q=
github.com/google/go-containerregistry v0.6.0 h1:niQ+8XD//kKgArIFwDVBXsWVWbde16LPdHMyNwSC8h4=
github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZcN5ttKpZLfa/wSo5iLw=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI=
github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28=
github.com/google/go-metrics-stackdriver v0.2.0 h1:rbs2sxHAPn2OtUj9JdR/Gij1YKGl0BTVD0augB+HEjE=
github.com/google/go-metrics-stackdriver v0.2.0/go.mod h1:KLcPyp3dWJAFD+yHisGlJSZktIsTjb50eB72U2YZ9K0=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=

View File

@@ -11,6 +11,7 @@ import (
"time"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/reporting"
"github.com/pkg/errors"
)
@@ -94,28 +95,45 @@ type VulnerabilityWithRemediation struct {
// Title returns the issue title representation of the contents
func (v Vulnerability) Title() string {
return fmt.Sprintf("%v/%v/%v/%v-%v", "SECURITY_VULNERABILITY", v.VulnerabilityWithRemediation.Severity, v.VulnerabilityName, v.Name, v.Version)
return fmt.Sprintf("Security Vulnerability %v %v", v.VulnerabilityName, v.Name)
}
// ToMarkdown returns the markdown representation of the contents
func (v Vulnerability) ToMarkdown() ([]byte, error) {
return []byte(fmt.Sprintf(
`**Vulnerability %v**
| Severity | Base (NVD) Score | Temporal Score | Package | Installed Version | Description | Fix Resolution | Link |
| --- | --- | --- | --- | --- | --- | --- | --- |
|%v|%v|%v|%v|%v|%v|%v|[%v](%v)|
`,
v.VulnerabilityWithRemediation.VulnerabilityName,
v.VulnerabilityWithRemediation.Severity,
v.VulnerabilityWithRemediation.BaseScore,
v.VulnerabilityWithRemediation.OverallScore,
v.Name,
v.Version,
v.Description,
"",
"",
"",
)), nil
vul := reporting.VulnerabilityReport{
ArtifactID: v.Name,
// no information available about branch and commit, yet
Branch: "",
CommitID: "",
Description: v.Description,
// no information available about direct/indirect dependency, yet
//DirectDependency: ... ,
// no information available about footer, yet
Footer: "",
// no information available about group, yet
Group: "",
// no information available about pipeline name and link, publish date and resolution yet
PipelineName: "",
PipelineLink: "",
PublishDate: "",
Resolution: "",
Score: float64(v.VulnerabilityWithRemediation.BaseScore),
Severity: v.VulnerabilityWithRemediation.Severity,
Version: v.Version,
// no vulnerability link available, yet
VulnerabilityLink: "",
VulnerabilityName: v.VulnerabilityName,
}
return vul.ToMarkdown()
}
// ToTxt returns the textual representation of the contents

View File

@@ -1,6 +1,7 @@
package github
import (
"github.com/google/go-github/v45/github"
"github.com/pkg/errors"
)
@@ -31,7 +32,7 @@ func FetchCommitStatistics(options *FetchCommitOptions) (FetchCommitResult, erro
return FetchCommitResult{}, errors.Wrap(err, "failed to get GitHub client")
}
// fetch commit by SAH
result, _, err := client.Repositories.GetCommit(ctx, options.Owner, options.Repository, options.SHA)
result, _, err := client.Repositories.GetCommit(ctx, options.Owner, options.Repository, options.SHA, &github.ListOptions{})
if err != nil {
return FetchCommitResult{}, errors.Wrap(err, "failed to get GitHub commit")
}

View File

@@ -8,7 +8,7 @@ import (
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)
@@ -38,7 +38,7 @@ type CreateIssueOptions struct {
TrustedCerts []string `json:"trustedCerts,omitempty"`
}
//NewClient creates a new GitHub client using an OAuth token for authentication
// NewClient creates a new GitHub client using an OAuth token for authentication
func NewClient(token, apiURL, uploadURL string, trustedCerts []string) (context.Context, *github.Client, error) {
httpClient := piperhttp.Client{}
httpClient.SetOptions(piperhttp.ClientOptions{
@@ -83,7 +83,6 @@ func CreateIssue(ghCreateIssueOptions *CreateIssueOptions) error {
}
func createIssueLocal(ctx context.Context, ghCreateIssueOptions *CreateIssueOptions, ghCreateIssueService githubCreateIssueService, ghSearchIssuesService githubSearchIssuesService, ghCreateCommentService githubCreateCommentService) error {
issue := github.IssueRequest{
Title: &ghCreateIssueOptions.Title,
}

View File

@@ -7,7 +7,7 @@ import (
"regexp"
"testing"
"github.com/google/go-github/v32/github"
"github.com/google/go-github/v45/github"
"github.com/stretchr/testify/assert"
)
@@ -47,7 +47,6 @@ type ghSearchIssuesMock struct {
}
func (g *ghSearchIssuesMock) Issues(ctx context.Context, query string, opts *github.SearchOptions) (*github.IssuesSearchResult, *github.Response, error) {
regex := regexp.MustCompile(`.*in:title (?P<Title>(.*))`)
matches := regex.FindStringSubmatch(query)

View File

@@ -1,62 +1,108 @@
package reporting
import (
"context"
"fmt"
piperGithub "github.com/SAP/jenkins-library/pkg/github"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/google/go-github/v45/github"
)
type Uploader interface {
CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error
type githubIssueService interface {
Create(ctx context.Context, owner string, repo string, issue *github.IssueRequest) (*github.Issue, *github.Response, error)
CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error)
Edit(ctx context.Context, owner string, repo string, number int, issue *github.IssueRequest) (*github.Issue, *github.Response, error)
}
// UploadSingleReportToGithub uploads a single report to GitHub
func UploadSingleReportToGithub(scanReport IssueDetail, token, APIURL, owner, repository string, assignees []string, uploader Uploader) error {
type githubSearchService interface {
Issues(ctx context.Context, query string, opts *github.SearchOptions) (*github.IssuesSearchResult, *github.Response, error)
}
// GitHub contains metadata for reporting towards GitHub
type GitHub struct {
Owner *string
Repository *string
Assignees *[]string
IssueService githubIssueService
SearchService githubSearchService
}
// UploadSingleReport uploads a single report to GitHub
func (g *GitHub) UploadSingleReport(ctx context.Context, scanReport IssueDetail) error {
// JSON reports are used by step pipelineCreateSummary in order to e.g. prepare an issue creation in GitHub
// ignore JSON errors since structure is in our hands
title := scanReport.Title()
markdownReport, _ := scanReport.ToMarkdown()
options := piperGithub.CreateIssueOptions{
Token: token,
APIURL: APIURL,
Owner: owner,
Repository: repository,
Title: scanReport.Title(),
Body: markdownReport,
Assignees: assignees,
UpdateExisting: true,
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)
}
err := uploader.CreateIssue(&options)
return nil
}
// UploadMultipleReports uploads a number of reports to GitHub, one per IssueDetail to create transparency
func (g *GitHub) UploadMultipleReports(ctx context.Context, scanReports *[]IssueDetail) error {
for _, scanReport := range *scanReports {
if err := g.UploadSingleReport(ctx, scanReport); err != nil {
return err
}
}
return nil
}
func (g *GitHub) createIssueOrUpdateIssueComment(ctx context.Context, title, issueContent string) error {
// check if issue is existing
issueNumber, issueBody, err := g.findExistingIssue(ctx, title)
if err != nil {
return fmt.Errorf("failed to upload results for '%v' into GitHub issue: %w", scanReport.Title(), err)
return fmt.Errorf("error when looking up issue: %w", err)
}
if issueNumber == 0 {
// issue not existing need to create it
issue := github.IssueRequest{Title: &title, Body: &issueContent, Assignees: g.Assignees}
if _, _, err := g.IssueService.Create(ctx, *g.Owner, *g.Repository, &issue); err != nil {
return fmt.Errorf("failed to create issue: %w", err)
}
return nil
}
// let's compare and only update in case an update is required
if issueContent != issueBody {
// update of issue required
issueRequest := github.IssueRequest{Body: &issueContent}
if _, _, err := g.IssueService.Edit(ctx, *g.Owner, *g.Repository, issueNumber, &issueRequest); err != nil {
return fmt.Errorf("failed to edit issue: %w", err)
}
// now add a small comment that the issue content has been updated
updateText := "issue content has been updated"
updateComment := github.IssueComment{Body: &updateText}
if _, _, err := g.IssueService.CreateComment(ctx, *g.Owner, *g.Repository, issueNumber, &updateComment); err != nil {
return fmt.Errorf("failed to create comment: %w", err)
}
}
return nil
}
// UploadMultipleReportsToGithub uploads a number of reports to GitHub, one per IssueDetail to create transparency
func UploadMultipleReportsToGithub(scanReports *[]IssueDetail, token, APIURL, owner, repository string, assignees, trustedCerts []string, uploader Uploader) error {
for i := 0; i < len(*scanReports); i++ {
vuln := (*scanReports)[i]
title := vuln.Title()
markdownReport, _ := vuln.ToMarkdown()
options := piperGithub.CreateIssueOptions{
Token: token,
APIURL: APIURL,
Owner: owner,
Repository: repository,
Title: title,
Body: markdownReport,
Assignees: assignees,
UpdateExisting: true,
TrustedCerts: trustedCerts,
}
log.Entry().Debugf("Creating/updating GitHub issue(s) with title %v in org %v and repo %v", title, owner, repository)
err := uploader.CreateIssue(&options)
if err != nil {
return fmt.Errorf("failed to upload results for '%v' into GitHub issue: %w", vuln.Title(), err)
func (g *GitHub) findExistingIssue(ctx context.Context, title string) (int, string, error) {
queryString := fmt.Sprintf("is:issue repo:%v/%v in:title %v", *g.Owner, *g.Repository, title)
searchResult, _, err := g.SearchService.Issues(ctx, queryString, nil)
if err != nil {
return 0, "", fmt.Errorf("error occurred when looking for existing issue: %w", err)
}
for _, i := range searchResult.Issues {
if i != nil && *i.Title == title {
if i.GetState() == "closed" {
// reopen issue
open := "open"
ir := github.IssueRequest{State: &open}
if _, _, err := g.IssueService.Edit(ctx, *g.Owner, *g.Repository, i.GetNumber(), &ir); err != nil {
return i.GetNumber(), "", fmt.Errorf("failed to re-open issue: %w", err)
}
}
return i.GetNumber(), i.GetBody(), nil
}
}
return nil
return 0, "", nil
}

View File

@@ -1,148 +1,440 @@
package reporting
import (
"context"
"fmt"
"testing"
piperGithub "github.com/SAP/jenkins-library/pkg/github"
"github.com/google/go-github/v45/github"
"github.com/stretchr/testify/assert"
)
type mockUploader struct {
issueOptions *piperGithub.CreateIssueOptions
uploadError error
type scanReportlMock struct {
markdown []byte
text string
title string
failToMarkdown bool
}
func (m *mockUploader) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error {
m.issueOptions = ghCreateIssueOptions
return m.uploadError
func (s *scanReportlMock) Title() string {
return s.title
}
type issueDetailMock struct {
vulnerabilityType string
vulnerabilityName string
libraryName string
vulnerabilitySeverity string
vulnerabilityScore float64
vulnerabilityCVSS3Score float64
func (s *scanReportlMock) ToMarkdown() ([]byte, error) {
if s.failToMarkdown {
return s.markdown, fmt.Errorf("toMarkdown failure")
}
return s.markdown, nil
}
func (idm issueDetailMock) Title() string {
return fmt.Sprintf("%v/%v/%v", idm.vulnerabilityType, idm.vulnerabilityName, idm.libraryName)
func (s *scanReportlMock) ToTxt() string {
return s.text
}
func (idm issueDetailMock) ToMarkdown() ([]byte, error) {
return []byte(fmt.Sprintf(`**Vulnerability %v**
| Severity | Package | Installed Version | Description | Fix Resolution | Link |
| --- | --- | --- | --- | --- | --- |
|%v|%v|%v|%v|%v|[%v](%v)|
`, idm.vulnerabilityName, idm.vulnerabilitySeverity, idm.libraryName, "", "", "", "", "")), nil
type ghServicesMock struct {
issues []*github.Issue
createError error
comment *github.IssueComment
createCommmentNumber int
createCommentError error
editError error
editNumber int
editRequest *github.IssueRequest
searchError error
searchOpts *github.SearchOptions
searchQuery string
searchResult []*github.Issue
}
func (idm issueDetailMock) ToTxt() string {
return fmt.Sprintf(`Vulnerability %v
Severity: %v
Package: %v
Installed Version: %v
Description: %v
Fix Resolution: %v
Link: %v
`, idm.vulnerabilityName, idm.vulnerabilitySeverity, idm.libraryName, "", "", "", "")
func (g *ghServicesMock) Create(ctx context.Context, owner string, repo string, issueRequest *github.IssueRequest) (*github.Issue, *github.Response, error) {
if g.issues == nil {
g.issues = []*github.Issue{}
}
number := len(g.issues) + 1
id := int64(number)
assignees := []*github.User{}
if issueRequest.Assignees != nil {
for _, userName := range *issueRequest.Assignees {
// cannot use userName directly since variable is re-used in the loop and thus name in assignees would be pointing to final value only.
user := userName
assignees = append(assignees, &github.User{Name: &user})
}
}
theIssue := github.Issue{
ID: &id,
Number: &number,
Title: issueRequest.Title,
Body: issueRequest.Body,
Assignees: assignees,
}
g.issues = append(g.issues, &theIssue)
if g.createError != nil {
return &theIssue, &github.Response{}, g.createError
}
return &theIssue, &github.Response{}, nil
}
func TestUploadSingleReportToGithub(t *testing.T) {
func (g *ghServicesMock) CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) {
g.createCommmentNumber = number
g.comment = comment
if g.createCommentError != nil {
return &github.IssueComment{}, &github.Response{}, g.createCommentError
}
return &github.IssueComment{}, &github.Response{}, nil
}
func (g *ghServicesMock) Edit(ctx context.Context, owner string, repo string, number int, issueRequest *github.IssueRequest) (*github.Issue, *github.Response, error) {
g.editNumber = number
g.editRequest = issueRequest
if g.editError != nil {
return &github.Issue{}, &github.Response{}, g.editError
}
return &github.Issue{}, &github.Response{}, nil
}
func (g *ghServicesMock) Issues(ctx context.Context, query string, opts *github.SearchOptions) (*github.IssuesSearchResult, *github.Response, error) {
g.searchOpts = opts
g.searchQuery = query
if g.searchError != nil {
return &github.IssuesSearchResult{Issues: g.searchResult}, &github.Response{}, g.searchError
}
return &github.IssuesSearchResult{Issues: g.searchResult}, &github.Response{}, nil
}
var (
owner string = "testOwner"
repository string = "testRepository"
)
func TestUploadSingleReport(t *testing.T) {
t.Parallel()
t.Run("success case", func(t *testing.T) {
t.Parallel()
testUploader := mockUploader{}
testData := struct {
scanReport ScanReport
token string
apiurl string
owner string
repository string
assignees []string
uploader Uploader
}{
scanReport: ScanReport{ReportTitle: "testReportTitle"},
token: "testToken",
apiurl: "testApiUrl",
owner: "testOwner",
repository: "testRepository",
assignees: []string{"testAssignee1", "testAssignee2"},
uploader: &testUploader,
ctx := context.Background()
ghMock := ghServicesMock{}
s := scanReportlMock{title: "The Title", markdown: []byte("# The Markdown")}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
err := UploadSingleReportToGithub(testData.scanReport, testData.token, testData.apiurl, testData.owner, testData.repository, testData.assignees, testData.uploader)
err := gh.UploadSingleReport(ctx, &s)
assert.NoError(t, err)
assert.Equal(t, testData.token, testUploader.issueOptions.Token)
assert.Equal(t, testData.apiurl, testUploader.issueOptions.APIURL)
assert.Equal(t, testData.owner, testUploader.issueOptions.Owner)
assert.Equal(t, testData.repository, testUploader.issueOptions.Repository)
assert.Equal(t, testData.scanReport.ReportTitle, testUploader.issueOptions.Title)
assert.Contains(t, string(testUploader.issueOptions.Body), "testReportTitle")
assert.Equal(t, testData.assignees, testUploader.issueOptions.Assignees)
assert.True(t, testUploader.issueOptions.UpdateExisting)
assert.Equal(t, s.title, ghMock.issues[0].GetTitle())
assert.Equal(t, string(s.markdown), ghMock.issues[0].GetBody())
})
t.Run("error case", func(t *testing.T) {
t.Parallel()
testUploader := mockUploader{uploadError: fmt.Errorf("upload failed")}
var report IssueDetail
report = ScanReport{}
err := UploadSingleReportToGithub(report, "", "", "", "", []string{}, &testUploader)
ctx := context.Background()
ghMock := ghServicesMock{createError: fmt.Errorf("create failed")}
s := scanReportlMock{title: "The Title"}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
assert.Contains(t, fmt.Sprint(err), "upload failed")
err := gh.UploadSingleReport(ctx, &s)
assert.EqualError(t, err, "failed to upload results for 'The Title' into GitHub issue: failed to create issue: create failed")
})
}
func TestUploadMultipleReportsToGithub(t *testing.T) {
func TestUploadMultipleReports(t *testing.T) {
t.Parallel()
t.Run("success case", func(t *testing.T) {
t.Parallel()
testUploader := mockUploader{}
testData := struct {
reports []IssueDetail
token string
apiurl string
owner string
repository string
assignees []string
uploader Uploader
}{
reports: []IssueDetail{issueDetailMock{vulnerabilityType: "SECURITY_VULNERABILITY", libraryName: "test-component", vulnerabilityName: "CVE-2022001", vulnerabilitySeverity: "MEDIUM", vulnerabilityScore: 5.3}},
token: "testToken",
apiurl: "testApiUrl",
owner: "testOwner",
repository: "testRepository",
assignees: []string{"testAssignee1", "testAssignee2"},
uploader: &testUploader,
ctx := context.Background()
ghMock := ghServicesMock{}
s1 := scanReportlMock{title: "The Title 1", markdown: []byte("# The Markdown 1")}
s2 := scanReportlMock{title: "The Title 2", markdown: []byte("# The Markdown 2")}
s := []IssueDetail{&s1, &s2}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
err := UploadMultipleReportsToGithub(&testData.reports, testData.token, testData.apiurl, testData.owner, testData.repository, testData.assignees, []string{}, testData.uploader)
err := gh.UploadMultipleReports(ctx, &s)
assert.NoError(t, err)
assert.Equal(t, testData.token, testUploader.issueOptions.Token)
assert.Equal(t, testData.apiurl, testUploader.issueOptions.APIURL)
assert.Equal(t, testData.owner, testUploader.issueOptions.Owner)
assert.Equal(t, testData.repository, testUploader.issueOptions.Repository)
assert.Equal(t, testData.reports[0].Title(), testUploader.issueOptions.Title)
assert.Contains(t, string(testUploader.issueOptions.Body), "CVE-2022001")
assert.Equal(t, testData.assignees, testUploader.issueOptions.Assignees)
assert.True(t, testUploader.issueOptions.UpdateExisting)
assert.Equal(t, s1.title, ghMock.issues[0].GetTitle())
assert.Equal(t, string(s1.markdown), ghMock.issues[0].GetBody())
assert.Equal(t, s2.title, ghMock.issues[1].GetTitle())
assert.Equal(t, string(s2.markdown), ghMock.issues[1].GetBody())
})
t.Run("error case", func(t *testing.T) {
t.Parallel()
testUploader := mockUploader{uploadError: fmt.Errorf("upload failed")}
reports := []IssueDetail{issueDetailMock{vulnerabilityType: "SECURITY_VULNERABILITY", libraryName: "test-component", vulnerabilityName: "CVE-2022001", vulnerabilitySeverity: "MEDIUM", vulnerabilityScore: 5.3}}
err := UploadMultipleReportsToGithub(&reports, "", "", "", "", []string{}, []string{}, &testUploader)
ctx := context.Background()
ghMock := ghServicesMock{createError: fmt.Errorf("create failed")}
s1 := scanReportlMock{title: "The Title 1", markdown: []byte("# The Markdown 1")}
s2 := scanReportlMock{title: "The Title 2", markdown: []byte("# The Markdown 2")}
s := []IssueDetail{&s1, &s2}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
assert.Contains(t, fmt.Sprint(err), "upload failed")
err := gh.UploadMultipleReports(ctx, &s)
assert.EqualError(t, err, "failed to upload results for 'The Title 1' into GitHub issue: failed to create issue: create failed")
})
}
func TestCreateIssueOrUpdateIssueComment(t *testing.T) {
t.Parallel()
title := "The Title"
assignees := []string{"assignee1", "assignee2"}
t.Run("success case - new issue", func(t *testing.T) {
ctx := context.Background()
ghMock := ghServicesMock{}
gh := GitHub{
Owner: &owner,
Repository: &repository,
Assignees: &assignees,
IssueService: &ghMock,
SearchService: &ghMock,
}
markdown := "# The Markdown"
err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown)
assert.NoError(t, err)
assert.Equal(t, title, ghMock.issues[0].GetTitle())
assert.Equal(t, markdown, ghMock.issues[0].GetBody())
assert.Equal(t, assignees[0], ghMock.issues[0].Assignees[0].GetName())
assert.Equal(t, assignees[1], ghMock.issues[0].Assignees[1].GetName())
})
t.Run("success case - update issue", func(t *testing.T) {
ctx := context.Background()
number := 1
state := "open"
title := "The Title"
body := "the body of the issue"
issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body}
ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
markdown := "# The Markdown"
err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown)
assert.NoError(t, err)
assert.Equal(t, number, ghMock.editNumber)
assert.Equal(t, markdown, ghMock.editRequest.GetBody())
assert.Equal(t, number, ghMock.createCommmentNumber)
assert.Equal(t, "issue content has been updated", ghMock.comment.GetBody())
})
t.Run("success case - no update", func(t *testing.T) {
ctx := context.Background()
number := 1
state := "open"
title := "The Title"
body := "the body of the issue"
issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body}
ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
markdown := "the body of the issue"
err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown)
assert.NoError(t, err)
assert.Nil(t, ghMock.editRequest)
assert.Nil(t, ghMock.comment)
})
t.Run("error case - lookup failed", func(t *testing.T) {
ctx := context.Background()
ghMock := ghServicesMock{searchError: fmt.Errorf("search failed")}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
markdown := "# The Markdown"
err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown)
assert.EqualError(t, err, "error when looking up issue: error occurred when looking for existing issue: search failed")
})
t.Run("error case - issue creation failed", func(t *testing.T) {
ctx := context.Background()
ghMock := ghServicesMock{createError: fmt.Errorf("creation failed")}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
markdown := "# The Markdown"
err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown)
assert.EqualError(t, err, "failed to create issue: creation failed")
})
t.Run("error case - issue editing failed", func(t *testing.T) {
ctx := context.Background()
number := 1
state := "open"
title := "The Title"
body := "the body of the issue"
issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body}
ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}, editError: fmt.Errorf("edit failed")}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
markdown := "# The Markdown"
err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown)
assert.EqualError(t, err, "failed to edit issue: edit failed")
})
t.Run("error case - edit comment creation failed", func(t *testing.T) {
ctx := context.Background()
number := 1
state := "open"
title := "The Title"
body := "the body of the issue"
issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body}
ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}, createCommentError: fmt.Errorf("comment failed")}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
markdown := "# The Markdown"
err := gh.createIssueOrUpdateIssueComment(ctx, title, markdown)
assert.EqualError(t, err, "failed to create comment: comment failed")
})
}
func TestFindExistingIssue(t *testing.T) {
t.Parallel()
t.Run("success case - issue found", func(t *testing.T) {
ctx := context.Background()
number := 1
state := "open"
title := "The Title"
body := "the body of the issue"
issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body}
ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
i, b, err := gh.findExistingIssue(ctx, title)
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("is:issue repo:%v/%v in:title %v", *gh.Owner, *gh.Repository, title), ghMock.searchQuery)
assert.Equal(t, 1, i)
assert.Equal(t, body, b)
})
t.Run("success case - issue found, reopen", func(t *testing.T) {
ctx := context.Background()
number := 1
state := "closed"
title := "The Title"
body := "the body of the issue"
issue := github.Issue{Number: &number, State: &state, Title: &title, Body: &body}
ghMock := ghServicesMock{searchResult: []*github.Issue{&issue}}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
i, b, err := gh.findExistingIssue(ctx, "The Title")
assert.NoError(t, err)
assert.Equal(t, 1, ghMock.editNumber)
assert.Equal(t, "open", ghMock.editRequest.GetState())
assert.Equal(t, 1, i)
assert.Equal(t, body, b)
})
t.Run("success case - no issue found", func(t *testing.T) {
ctx := context.Background()
ghMock := ghServicesMock{}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
i, body, err := gh.findExistingIssue(ctx, "The Title")
assert.NoError(t, err)
assert.Equal(t, 0, i)
assert.Equal(t, "", body)
})
t.Run("error case - search failed", func(t *testing.T) {
ctx := context.Background()
ghMock := ghServicesMock{searchError: fmt.Errorf("search failed")}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
i, _, err := gh.findExistingIssue(ctx, "The Title")
assert.EqualError(t, err, "error occurred when looking for existing issue: search failed")
assert.Equal(t, 0, i)
})
t.Run("error case - reopen failed", func(t *testing.T) {
ctx := context.Background()
number := 1
state := "closed"
title := "The Title"
issue := github.Issue{Number: &number, State: &state, Title: &title}
ghMock := ghServicesMock{editError: fmt.Errorf("reopen failed"), searchResult: []*github.Issue{&issue}}
gh := GitHub{
Owner: &owner,
Repository: &repository,
IssueService: &ghMock,
SearchService: &ghMock,
}
_, _, err := gh.findExistingIssue(ctx, "The Title")
assert.EqualError(t, err, "failed to re-open issue: reopen failed")
})
}

View File

@@ -8,6 +8,8 @@ import (
"time"
"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
@@ -17,6 +19,86 @@ 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
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 .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)
},
}
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"`
@@ -92,7 +174,7 @@ func (s *ScanReport) AddSubHeader(header, details string) {
s.Subheaders = append(s.Subheaders, Subheader{Description: header, Details: details})
}
//StepReportDirectory specifies the default directory for markdown reports which can later be collected by step pipelineCreateSummary
// StepReportDirectory specifies the default directory for markdown reports which can later be collected by step pipelineCreateSummary
const StepReportDirectory = ".pipeline/stepReports"
// ToJSON returns the report in JSON format

View File

@@ -1,12 +1,53 @@
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",
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{

View File

@@ -0,0 +1,31 @@
# High (7.8) Vulnerability CVE-Test-001 - theArtifact
**Vulnerability link:** [https://the.link/to/the/vulnerability](https://the.link/to/the/vulnerability)
## Fix
**This is the test resolution.**
## 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
**Publishing date:** 2022-06-30
## Description
This is the test description.
---
This is the test footer

View File

@@ -10,6 +10,7 @@ import (
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/reporting"
"github.com/pkg/errors"
)
@@ -57,7 +58,10 @@ type Alert struct {
// Title returns the issue title representation of the contents
func (a Alert) Title() string {
return fmt.Sprintf("%v/%v/%v/%v", a.Type, consolidate(a.Vulnerability.Severity, a.Vulnerability.CVSS3Severity, a.Vulnerability.Score, a.Vulnerability.CVSS3Score), a.Vulnerability.Name, a.Library.ArtifactID)
if a.Type == "SECURITY_VULNERABILITY" {
return fmt.Sprintf("Security Vulnerability %v %v", a.Vulnerability.Name, a.Library.ArtifactID)
}
return fmt.Sprintf("%v %v %v ", a.Type, a.Vulnerability.Name, a.Library.ArtifactID)
}
func consolidate(cvss2severity, cvss3severity string, cvss2score, cvss3score float64) string {
@@ -92,23 +96,30 @@ func (a Alert) ToMarkdown() ([]byte, error) {
if score == 0 {
score = a.Vulnerability.Score
}
return []byte(fmt.Sprintf(
`**Vulnerability %v**
| Severity | Base (NVD) Score | Temporal Score | Package | Installed Version | Description | Fix Resolution | Link |
| --- | --- | --- | --- | --- | --- | --- | --- |
|%v|%v|%v|%v|%v|%v|%v|[%v](%v)|
`,
a.Vulnerability.Name,
a.Vulnerability.Severity,
score,
score,
a.Library.ArtifactID,
a.Library.Version,
a.Vulnerability.Description,
a.Vulnerability.TopFix.FixResolution,
a.Vulnerability.Name,
a.Vulnerability.URL,
)), nil
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,
VulnerabilityLink: a.Vulnerability.URL,
VulnerabilityName: a.Vulnerability.Name,
}
return vul.ToMarkdown()
}
// ToTxt returns the textual representation of the contents