package cmd import ( "bytes" "fmt" "io/ioutil" "os" "os/exec" "path/filepath" "testing" "time" "github.com/SAP/jenkins-library/pkg/checkmarx" "github.com/stretchr/testify/assert" ) type fileInfo struct { nam string // base name of the file siz int64 // length in bytes for regular files; system-dependent for others mod os.FileMode // file mode bits modtime time.Time // modification time dir bool // abbreviation for Mode().IsDir() syss interface{} // underlying data source (can return nil) } func (fi fileInfo) IsDir() bool { return fi.dir } func (fi fileInfo) Name() string { return fi.nam } func (fi fileInfo) Size() int64 { return fi.siz } func (fi fileInfo) ModTime() time.Time { return fi.modtime } func (fi fileInfo) Mode() os.FileMode { return fi.mod } func (fi fileInfo) Sys() interface{} { return fi.syss } type systemMock struct { response interface{} isIncremental bool isPublic bool forceScan bool createProject bool projectLoadCount int } func (sys *systemMock) FilterPresetByName(presets []checkmarx.Preset, presetName string) checkmarx.Preset { return checkmarx.Preset{ID: 10050, Name: "SAP_JS_Default", OwnerName: "16"} } func (sys *systemMock) FilterPresetByID(presets []checkmarx.Preset, presetID int) checkmarx.Preset { return checkmarx.Preset{ID: 10048, Name: "SAP_Default", OwnerName: "16"} } func (sys *systemMock) FilterProjectByName(projects []checkmarx.Project, projectName string) checkmarx.Project { return checkmarx.Project{ID: 1, Name: "Test", TeamID: "16", IsPublic: false} } func (sys *systemMock) GetProjectByID(projectID int) (bool, checkmarx.Project) { return true, checkmarx.Project{ID: 19, Name: "Test_PR-19", TeamID: "16", IsPublic: false} } func (sys *systemMock) GetProjectsByNameAndTeam(projectName, teamID string) []checkmarx.Project { sys.projectLoadCount++ if !sys.createProject || sys.projectLoadCount%2 == 0 { return []checkmarx.Project{checkmarx.Project{ID: 19, Name: projectName, TeamID: teamID, IsPublic: false}} } return []checkmarx.Project{} } func (sys *systemMock) FilterTeamByName(teams []checkmarx.Team, teamName string) checkmarx.Team { return checkmarx.Team{ID: "16", FullName: "OpenSource/Cracks/16"} } func (sys *systemMock) FilterTeamByID(teams []checkmarx.Team, teamID string) checkmarx.Team { return checkmarx.Team{ID: "15", FullName: "OpenSource/Cracks/15"} } func (sys *systemMock) DownloadReport(reportID int) (bool, []byte) { return true, sys.response.([]byte) } func (sys *systemMock) GetReportStatus(reportID int) checkmarx.ReportStatusResponse { return checkmarx.ReportStatusResponse{Status: checkmarx.ReportStatus{ID: 2, Value: "Created"}} } func (sys *systemMock) RequestNewReport(scanID int, reportType string) (bool, checkmarx.Report) { return true, checkmarx.Report{ReportID: 17} } func (sys *systemMock) GetResults(scanID int) checkmarx.ResultsStatistics { return checkmarx.ResultsStatistics{} } func (sys *systemMock) GetScans(projectID int) (bool, []checkmarx.ScanStatus) { return true, []checkmarx.ScanStatus{checkmarx.ScanStatus{IsIncremental: true}, checkmarx.ScanStatus{IsIncremental: true}, checkmarx.ScanStatus{IsIncremental: true}, checkmarx.ScanStatus{IsIncremental: false}} } func (sys *systemMock) GetScanStatusAndDetail(scanID int) (string, checkmarx.ScanStatusDetail) { return "Finished", checkmarx.ScanStatusDetail{Stage: "Step 1 of 25", Step: "Scan something"} } func (sys *systemMock) ScanProject(projectID int, isIncrementalV, isPublicV, forceScanV bool) (bool, checkmarx.Scan) { sys.isIncremental = isIncrementalV sys.isPublic = isPublicV sys.forceScan = forceScanV return true, checkmarx.Scan{ID: 16} } func (sys *systemMock) UpdateProjectConfiguration(projectID int, presetID int, engineConfigurationID string) bool { return true } func (sys *systemMock) UpdateProjectExcludeSettings(projectID int, excludeFolders string, excludeFiles string) bool { return true } func (sys *systemMock) UploadProjectSourceCode(projectID int, zipFile string) bool { return true } func (sys *systemMock) CreateProject(projectName string, teamID string) (bool, checkmarx.ProjectCreateResult) { return true, checkmarx.ProjectCreateResult{ID: 20} } func (sys *systemMock) CreateBranch(projectID int, branchName string) int { return 18 } func (sys *systemMock) GetPresets() []checkmarx.Preset { return []checkmarx.Preset{checkmarx.Preset{ID: 10078, Name: "SAP Java Default", OwnerName: "16"}, checkmarx.Preset{ID: 10048, Name: "SAP JS Default", OwnerName: "16"}} } func (sys *systemMock) GetProjects() []checkmarx.Project { return []checkmarx.Project{checkmarx.Project{ID: 15, Name: "OtherTest", TeamID: "16"}, checkmarx.Project{ID: 1, Name: "Test", TeamID: "16"}} } func (sys *systemMock) GetTeams() []checkmarx.Team { sys.projectLoadCount = 0 return []checkmarx.Team{checkmarx.Team{ID: "16", FullName: "OpenSource/Cracks/16"}, checkmarx.Team{ID: "15", FullName: "OpenSource/Cracks/15"}} } type systemMockForExistingProject struct { response interface{} isIncremental bool isPublic bool forceScan bool } func (sys *systemMockForExistingProject) FilterPresetByName(presets []checkmarx.Preset, presetName string) checkmarx.Preset { return checkmarx.Preset{ID: 10050, Name: "SAP_JS_Default", OwnerName: "16"} } func (sys *systemMockForExistingProject) FilterPresetByID(presets []checkmarx.Preset, presetID int) checkmarx.Preset { return checkmarx.Preset{ID: 10048, Name: "SAP_Default", OwnerName: "16"} } func (sys *systemMockForExistingProject) FilterProjectByName(projects []checkmarx.Project, projectName string) checkmarx.Project { return checkmarx.Project{ID: 1, Name: "TestExisting", TeamID: "16", IsPublic: false} } func (sys *systemMockForExistingProject) GetProjectByID(projectID int) (bool, checkmarx.Project) { return false, checkmarx.Project{} } func (sys *systemMockForExistingProject) GetProjectsByNameAndTeam(projectName, teamID string) []checkmarx.Project { return []checkmarx.Project{checkmarx.Project{ID: 19, Name: projectName, TeamID: teamID, IsPublic: false}} } func (sys *systemMockForExistingProject) FilterTeamByName(teams []checkmarx.Team, teamName string) checkmarx.Team { return checkmarx.Team{ID: "16", FullName: "OpenSource/Cracks/16"} } func (sys *systemMockForExistingProject) FilterTeamByID(teams []checkmarx.Team, teamID string) checkmarx.Team { return checkmarx.Team{ID: "15", FullName: "OpenSource/Cracks/15"} } func (sys *systemMockForExistingProject) DownloadReport(reportID int) (bool, []byte) { return true, sys.response.([]byte) } func (sys *systemMockForExistingProject) GetReportStatus(reportID int) checkmarx.ReportStatusResponse { return checkmarx.ReportStatusResponse{Status: checkmarx.ReportStatus{ID: 2, Value: "Created"}} } func (sys *systemMockForExistingProject) RequestNewReport(scanID int, reportType string) (bool, checkmarx.Report) { return true, checkmarx.Report{ReportID: 17} } func (sys *systemMockForExistingProject) GetResults(scanID int) checkmarx.ResultsStatistics { return checkmarx.ResultsStatistics{} } func (sys *systemMockForExistingProject) GetScans(projectID int) (bool, []checkmarx.ScanStatus) { return true, []checkmarx.ScanStatus{checkmarx.ScanStatus{IsIncremental: true}, checkmarx.ScanStatus{IsIncremental: true}, checkmarx.ScanStatus{IsIncremental: true}, checkmarx.ScanStatus{IsIncremental: false}} } func (sys *systemMockForExistingProject) GetScanStatusAndDetail(scanID int) (string, checkmarx.ScanStatusDetail) { return "Finished", checkmarx.ScanStatusDetail{Stage: "", Step: ""} } func (sys *systemMockForExistingProject) ScanProject(projectID int, isIncrementalV, isPublicV, forceScanV bool) (bool, checkmarx.Scan) { sys.isIncremental = isIncrementalV sys.isPublic = isPublicV sys.forceScan = forceScanV return true, checkmarx.Scan{ID: 16} } func (sys *systemMockForExistingProject) UpdateProjectConfiguration(projectID int, presetID int, engineConfigurationID string) bool { return true } func (sys *systemMockForExistingProject) UpdateProjectExcludeSettings(projectID int, excludeFolders string, excludeFiles string) bool { return true } func (sys *systemMockForExistingProject) UploadProjectSourceCode(projectID int, zipFile string) bool { return true } func (sys *systemMockForExistingProject) CreateProject(projectName string, teamID string) (bool, checkmarx.ProjectCreateResult) { return false, checkmarx.ProjectCreateResult{} } func (sys *systemMockForExistingProject) CreateBranch(projectID int, branchName string) int { return 0 } func (sys *systemMockForExistingProject) GetPresets() []checkmarx.Preset { return []checkmarx.Preset{checkmarx.Preset{ID: 10078, Name: "SAP Java Default", OwnerName: "16"}, checkmarx.Preset{ID: 10048, Name: "SAP JS Default", OwnerName: "16"}} } func (sys *systemMockForExistingProject) GetProjects() []checkmarx.Project { return []checkmarx.Project{checkmarx.Project{ID: 1, Name: "TestExisting", TeamID: "16"}} } func (sys *systemMockForExistingProject) GetTeams() []checkmarx.Team { return []checkmarx.Team{checkmarx.Team{ID: "16", FullName: "OpenSource/Cracks/16"}, checkmarx.Team{ID: "15", FullName: "OpenSource/Cracks/15"}} } func TestFilterFileGlob(t *testing.T) { tt := []struct { input string fInfo fileInfo expected bool }{ {input: "somepath/node_modules/someOther/some.file", fInfo: fileInfo{}, expected: true}, {input: "somepath/non_modules/someOther/some.go", fInfo: fileInfo{}, expected: false}, {input: ".xmake/someOther/some.go", fInfo: fileInfo{}, expected: true}, {input: "another/vendor/some.html", fInfo: fileInfo{}, expected: false}, {input: "another/vendor/some.pdf", fInfo: fileInfo{}, expected: true}, {input: "another/vendor/some.test", fInfo: fileInfo{}, expected: true}, {input: "some.test", fInfo: fileInfo{}, expected: false}, {input: "a/b/c", fInfo: fileInfo{dir: true}, expected: false}, } for k, v := range tt { assert.Equal(t, v.expected, filterFileGlob([]string{"!**/node_modules/**", "!**/.xmake/**", "!**/*_test.go", "!**/vendor/**/*.go", "**/*.go", "**/*.html", "*.test"}, v.input, v.fInfo), fmt.Sprintf("wrong long name for run %v", k)) } } func TestZipFolder(t *testing.T) { t.Run("zip files", func(t *testing.T) { dir, err := ioutil.TempDir("", "test zip files") if err != nil { t.Fatal("Failed to create temporary directory") } // clean up tmp dir defer os.RemoveAll(dir) ioutil.WriteFile(filepath.Join(dir, "abcd.go"), []byte{byte(1), byte(2), byte(3)}, 0700) ioutil.WriteFile(filepath.Join(dir, "somepath", "abcd.txt"), []byte{byte(1), byte(2), byte(3)}, 0700) ioutil.WriteFile(filepath.Join(dir, "abcd_test.go"), []byte{byte(1), byte(2), byte(3)}, 0700) var zipFileMock bytes.Buffer zipFolder(dir, &zipFileMock, []string{"!abc_test.go", "**/abcd.txt", "**/abc.go"}) got := zipFileMock.Len() want := 164 if got != want { t.Errorf("Zipping test failed expected %v but got %v", want, got) } }) } func TestGetDetailedResults(t *testing.T) { t.Run("success case", func(t *testing.T) { sys := &systemMock{response: []byte(` `)} dir, err := ioutil.TempDir("", "test detailed results") if err != nil { t.Fatal("Failed to create temporary directory") } // clean up tmp dir defer os.RemoveAll(dir) result := getDetailedResults(sys, filepath.Join(dir, "abc.xml"), 2635) assert.Equal(t, "2", result["ProjectId"], "Project ID incorrect") assert.Equal(t, "Project 1", result["ProjectName"], "Project name incorrect") assert.Equal(t, 2, result["High"].(map[string]int)["Issues"], "Number of High issues incorrect") assert.Equal(t, 2, result["High"].(map[string]int)["NotFalsePositive"], "Number of High NotFalsePositive issues incorrect") assert.Equal(t, 1, result["Medium"].(map[string]int)["Issues"], "Number of Medium issues incorrect") assert.Equal(t, 0, result["Medium"].(map[string]int)["NotFalsePositive"], "Number of Medium NotFalsePositive issues incorrect") }) } func TestRunScan(t *testing.T) { sys := &systemMockForExistingProject{response: []byte(``)} options := checkmarxExecuteScanOptions{ProjectName: "TestExisting", VulnerabilityThresholdUnit: "absolute", FullScanCycle: "2", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} workspace, err := ioutil.TempDir("", "workspace1") if err != nil { t.Fatal("Failed to create temporary workspace directory") } // clean up tmp dir defer os.RemoveAll(workspace) influx := checkmarxExecuteScanInflux{} runScan(options, sys, workspace, &influx) assert.Equal(t, false, sys.isIncremental, "isIncremental has wrong value") assert.Equal(t, false, sys.isPublic, "isPublic has wrong value") assert.Equal(t, true, sys.forceScan, "forceScan has wrong value") } func TestRunScanWOtherCycle(t *testing.T) { sys := &systemMock{response: []byte(``), createProject: true} options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "SAP_JS_Default", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} workspace, err := ioutil.TempDir("", "workspace2") if err != nil { t.Fatal("Failed to create temporary workspace directory") } // clean up tmp dir defer os.RemoveAll(workspace) influx := checkmarxExecuteScanInflux{} runScan(options, sys, workspace, &influx) assert.Equal(t, true, sys.isIncremental, "isIncremental has wrong value") assert.Equal(t, false, sys.isPublic, "isPublic has wrong value") assert.Equal(t, true, sys.forceScan, "forceScan has wrong value") } func TestRunScanForPullRequest(t *testing.T) { sys := &systemMock{response: []byte(``)} options := checkmarxExecuteScanOptions{PullRequestName: "Test_PR-19", ProjectName: "Test_PR-19", VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "SAP_JS_Default", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true, AvoidDuplicateProjectScans: false} workspace, err := ioutil.TempDir("", "workspace3") if err != nil { t.Fatal("Failed to create temporary workspace directory") } // clean up tmp dir defer os.RemoveAll(workspace) influx := checkmarxExecuteScanInflux{} runScan(options, sys, workspace, &influx) assert.Equal(t, true, sys.isIncremental, "isIncremental has wrong value") assert.Equal(t, false, sys.isPublic, "isPublic has wrong value") assert.Equal(t, true, sys.forceScan, "forceScan has wrong value") } func TestRunScanForPullRequestProjectNew(t *testing.T) { sys := &systemMock{response: []byte(``), createProject: true} options := checkmarxExecuteScanOptions{PullRequestName: "PR-17", ProjectName: "Test_PR-19", VulnerabilityThresholdUnit: "percentage", FullScanCycle: "3", Incremental: true, FullScansScheduled: true, Preset: "10048", TeamName: "OpenSource/Cracks/15", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} workspace, err := ioutil.TempDir("", "workspace4") if err != nil { t.Fatal("Failed to create temporary workspace directory") } // clean up tmp dir defer os.RemoveAll(workspace) influx := checkmarxExecuteScanInflux{} runScan(options, sys, workspace, &influx) assert.Equal(t, true, sys.isIncremental, "isIncremental has wrong value") assert.Equal(t, false, sys.isPublic, "isPublic has wrong value") assert.Equal(t, true, sys.forceScan, "forceScan has wrong value") } func TestRunScanHighViolationPercentage(t *testing.T) { if os.Getenv("BE_CRASHER") == "1" { sys := &systemMock{response: []byte(` `)} options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", VulnerabilityThresholdResult: "FAILURE", VulnerabilityThresholdHigh: 100, FullScanCycle: "10", FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} workspace, err := ioutil.TempDir("", "workspace5") if err != nil { t.Fatal("Failed to create temporary workspace directory") } // clean up tmp dir defer os.RemoveAll(workspace) influx := checkmarxExecuteScanInflux{} runScan(options, sys, workspace, &influx) return } cmd := exec.Command(os.Args[0], "-test.run=TestRunScanHighViolationPercentage") cmd.Env = append(os.Environ(), "BE_CRASHER=1") err := cmd.Run() if e, ok := err.(*exec.ExitError); ok && !e.Success() { return } t.Fatalf("process ran with err %v, want exit status 1", err) } func TestRunScanHighViolationAbsolute(t *testing.T) { if os.Getenv("BE_CRASHER") == "1" { sys := &systemMock{response: []byte(` `)} options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "absolute", VulnerabilityThresholdResult: "FAILURE", VulnerabilityThresholdLow: 1, FullScanCycle: "10", FullScansScheduled: true, Preset: "10048", TeamID: "16", VulnerabilityThresholdEnabled: true, GeneratePdfReport: true} workspace, err := ioutil.TempDir("", "workspace6") if err != nil { t.Fatal("Failed to create temporary workspace directory") } // clean up tmp dir defer os.RemoveAll(workspace) influx := checkmarxExecuteScanInflux{} runScan(options, sys, workspace, &influx) return } cmd := exec.Command(os.Args[0], "-test.run=TestRunScanHighViolationAbsolute") cmd.Env = append(os.Environ(), "BE_CRASHER=1") err := cmd.Run() if e, ok := err.(*exec.ExitError); ok && !e.Success() { return } t.Fatalf("process ran with err %v, want exit status 1", err) } func TestEnforceThresholds(t *testing.T) { results := map[string]interface{}{} results["High"] = map[string]int{} results["Medium"] = map[string]int{} results["Low"] = map[string]int{} results["High"].(map[string]int)["NotFalsePositive"] = 10 results["Medium"].(map[string]int)["NotFalsePositive"] = 10 results["Low"].(map[string]int)["NotFalsePositive"] = 10 results["Low"].(map[string]int)["NotExploitable"] = 0 results["Low"].(map[string]int)["Confirmed"] = 0 results["High"].(map[string]int)["Issues"] = 10 results["Medium"].(map[string]int)["Issues"] = 10 results["Low"].(map[string]int)["Issues"] = 10 t.Run("percentage high violation", func(t *testing.T) { options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", VulnerabilityThresholdHigh: 100, VulnerabilityThresholdEnabled: true} insecure := enforceThresholds(options, results) assert.Equal(t, true, insecure, "Expected results to be insecure but where not") }) t.Run("absolute high violation", func(t *testing.T) { options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "absolute", VulnerabilityThresholdHigh: 5, VulnerabilityThresholdEnabled: true} insecure := enforceThresholds(options, results) assert.Equal(t, true, insecure, "Expected results to be insecure but where not") }) t.Run("percentage medium violation", func(t *testing.T) { options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", VulnerabilityThresholdMedium: 100, VulnerabilityThresholdEnabled: true} insecure := enforceThresholds(options, results) assert.Equal(t, true, insecure, "Expected results to be insecure but where not") }) t.Run("absolute medium violation", func(t *testing.T) { options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "absolute", VulnerabilityThresholdMedium: 5, VulnerabilityThresholdEnabled: true} insecure := enforceThresholds(options, results) assert.Equal(t, true, insecure, "Expected results to be insecure but where not") }) t.Run("percentage low violation", func(t *testing.T) { options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", VulnerabilityThresholdLow: 100, VulnerabilityThresholdEnabled: true} insecure := enforceThresholds(options, results) assert.Equal(t, true, insecure, "Expected results to be insecure but where not") }) t.Run("absolute low violation", func(t *testing.T) { options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "absolute", VulnerabilityThresholdLow: 5, VulnerabilityThresholdEnabled: true} insecure := enforceThresholds(options, results) assert.Equal(t, true, insecure, "Expected results to be insecure but where not") }) t.Run("percentage no violation", func(t *testing.T) { options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "percentage", VulnerabilityThresholdLow: 0, VulnerabilityThresholdEnabled: true} insecure := enforceThresholds(options, results) assert.Equal(t, false, insecure, "Expected results to be insecure but where not") }) t.Run("absolute no violation", func(t *testing.T) { options := checkmarxExecuteScanOptions{VulnerabilityThresholdUnit: "absolute", VulnerabilityThresholdLow: 15, VulnerabilityThresholdMedium: 15, VulnerabilityThresholdHigh: 15, VulnerabilityThresholdEnabled: true} insecure := enforceThresholds(options, results) assert.Equal(t, false, insecure, "Expected results to be insecure but where not") }) } func TestLoadPreset(t *testing.T) { sys := &systemMock{} t.Run("resolve via code", func(t *testing.T) { ok, preset := loadPreset(sys, "10048") assert.Equal(t, true, ok, "Expected success but failed") assert.Equal(t, 10048, preset.ID, "Expected result but got none") }) t.Run("resolve via name", func(t *testing.T) { ok, preset := loadPreset(sys, "SAP_JS_Default") assert.Equal(t, true, ok, "Expected success but failed") assert.Equal(t, "SAP_JS_Default", preset.Name, "Expected result but got none") }) t.Run("error case", func(t *testing.T) { ok, preset := loadPreset(sys, "") assert.Equal(t, false, ok, "Expected error but succeeded") assert.Equal(t, 0, preset.ID, "Expected result but got none") }) }