github.com/jaylevin/jenkins-library@v1.230.4/cmd/checkmarxExecuteScan.go (about) 1 package cmd 2 3 import ( 4 "archive/zip" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "regexp" 11 "sort" 12 "strconv" 13 "strings" 14 "time" 15 16 "encoding/json" 17 "encoding/xml" 18 19 "github.com/SAP/jenkins-library/pkg/checkmarx" 20 piperGithub "github.com/SAP/jenkins-library/pkg/github" 21 piperHttp "github.com/SAP/jenkins-library/pkg/http" 22 "github.com/SAP/jenkins-library/pkg/log" 23 "github.com/SAP/jenkins-library/pkg/piperutils" 24 "github.com/SAP/jenkins-library/pkg/reporting" 25 "github.com/SAP/jenkins-library/pkg/telemetry" 26 "github.com/SAP/jenkins-library/pkg/toolrecord" 27 "github.com/bmatcuk/doublestar" 28 "github.com/pkg/errors" 29 ) 30 31 type checkmarxExecuteScanUtils interface { 32 CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error 33 FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) 34 Stat(name string) (os.FileInfo, error) 35 Open(name string) (*os.File, error) 36 WriteFile(filename string, data []byte, perm os.FileMode) error 37 PathMatch(pattern, name string) (bool, error) 38 GetWorkspace() string 39 } 40 41 type checkmarxExecuteScanUtilsBundle struct { 42 workspace string 43 } 44 45 func (checkmarxExecuteScanUtilsBundle) PathMatch(pattern, name string) (bool, error) { 46 return doublestar.PathMatch(pattern, name) 47 } 48 49 func (b checkmarxExecuteScanUtilsBundle) GetWorkspace() string { 50 return b.workspace 51 } 52 53 func (checkmarxExecuteScanUtilsBundle) WriteFile(filename string, data []byte, perm os.FileMode) error { 54 return ioutil.WriteFile(filename, data, perm) 55 } 56 57 func (checkmarxExecuteScanUtilsBundle) FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) { 58 return zip.FileInfoHeader(fi) 59 } 60 61 func (checkmarxExecuteScanUtilsBundle) Stat(name string) (os.FileInfo, error) { 62 return os.Stat(name) 63 } 64 65 func (checkmarxExecuteScanUtilsBundle) Open(name string) (*os.File, error) { 66 return os.Open(name) 67 } 68 69 func (checkmarxExecuteScanUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { 70 return piperGithub.CreateIssue(ghCreateIssueOptions) 71 } 72 73 func checkmarxExecuteScan(config checkmarxExecuteScanOptions, _ *telemetry.CustomData, influx *checkmarxExecuteScanInflux) { 74 client := &piperHttp.Client{} 75 options := piperHttp.ClientOptions{MaxRetries: config.MaxRetries} 76 client.SetOptions(options) 77 sys, err := checkmarx.NewSystemInstance(client, config.ServerURL, config.Username, config.Password) 78 if err != nil { 79 log.Entry().WithError(err).Fatalf("Failed to create Checkmarx client talking to URL %v", config.ServerURL) 80 } 81 influx.step_data.fields.checkmarx = false 82 utils := checkmarxExecuteScanUtilsBundle{workspace: "./"} 83 if err := runScan(config, sys, influx, utils); err != nil { 84 log.Entry().WithError(err).Fatal("Failed to execute Checkmarx scan.") 85 } 86 influx.step_data.fields.checkmarx = true 87 } 88 89 func runScan(config checkmarxExecuteScanOptions, sys checkmarx.System, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { 90 teamID := config.TeamID 91 if len(teamID) == 0 { 92 readTeamID, err := loadTeamIDByTeamName(config, sys, teamID) 93 if err != nil { 94 return err 95 } 96 teamID = readTeamID 97 } 98 project, projectName, err := loadExistingProject(sys, config.ProjectName, config.PullRequestName, teamID) 99 if err != nil { 100 return errors.Wrap(err, "error when trying to load project") 101 } 102 if project.Name == projectName { 103 err = presetExistingProject(config, sys, projectName, project) 104 if err != nil { 105 return err 106 } 107 } else { 108 if len(teamID) == 0 { 109 return errors.Wrap(err, "TeamName or TeamID is required to create a new project") 110 } 111 project, err = createNewProject(config, sys, projectName, teamID) 112 if err != nil { 113 return err 114 } 115 } 116 117 err = uploadAndScan(config, sys, project, influx, utils) 118 if err != nil { 119 return errors.Wrap(err, "scan, upload, and result validation returned an error") 120 } 121 return nil 122 } 123 124 func loadTeamIDByTeamName(config checkmarxExecuteScanOptions, sys checkmarx.System, teamID string) (string, error) { 125 team, err := loadTeam(sys, config.TeamName) 126 if err != nil { 127 return "", errors.Wrap(err, "failed to load team") 128 } 129 teamIDBytes, _ := team.ID.MarshalJSON() 130 err = json.Unmarshal(teamIDBytes, &teamID) 131 if err != nil { 132 var teamIDInt int 133 err = json.Unmarshal(teamIDBytes, &teamIDInt) 134 if err != nil { 135 return "", errors.Wrap(err, "failed to unmarshall team.ID") 136 } 137 teamID = strconv.Itoa(teamIDInt) 138 } 139 return teamID, nil 140 } 141 142 func createNewProject(config checkmarxExecuteScanOptions, sys checkmarx.System, projectName string, teamID string) (checkmarx.Project, error) { 143 log.Entry().Infof("Project %v does not exist, starting to create it...", projectName) 144 presetID, _ := strconv.Atoi(config.Preset) 145 project, err := createAndConfigureNewProject(sys, projectName, teamID, presetID, config.Preset, config.SourceEncoding) 146 if err != nil { 147 return checkmarx.Project{}, errors.Wrapf(err, "failed to create and configure new project %v", projectName) 148 } 149 return project, nil 150 } 151 152 func presetExistingProject(config checkmarxExecuteScanOptions, sys checkmarx.System, projectName string, project checkmarx.Project) error { 153 log.Entry().Infof("Project %v exists...", projectName) 154 if len(config.Preset) > 0 { 155 presetID, _ := strconv.Atoi(config.Preset) 156 err := setPresetForProject(sys, project.ID, presetID, projectName, config.Preset, config.SourceEncoding) 157 if err != nil { 158 return errors.Wrapf(err, "failed to set preset %v for project %v", config.Preset, projectName) 159 } 160 } 161 return nil 162 } 163 164 func loadTeam(sys checkmarx.System, teamName string) (checkmarx.Team, error) { 165 teams := sys.GetTeams() 166 team := checkmarx.Team{} 167 var err error 168 if len(teams) > 0 && len(teamName) > 0 { 169 team, err = sys.FilterTeamByName(teams, teamName) 170 } 171 if err != nil { 172 return team, fmt.Errorf("failed to identify team by teamName %v", teamName) 173 } else { 174 return team, nil 175 } 176 } 177 178 func loadExistingProject(sys checkmarx.System, initialProjectName, pullRequestName, teamID string) (checkmarx.Project, string, error) { 179 var project checkmarx.Project 180 projectName := initialProjectName 181 if len(pullRequestName) > 0 { 182 projectName = fmt.Sprintf("%v_%v", initialProjectName, pullRequestName) 183 projects, err := sys.GetProjectsByNameAndTeam(projectName, teamID) 184 if err != nil || len(projects) == 0 { 185 projects, err = sys.GetProjectsByNameAndTeam(initialProjectName, teamID) 186 if err != nil { 187 return project, projectName, errors.Wrap(err, "failed getting projects") 188 } 189 if len(projects) == 0 { 190 return checkmarx.Project{}, projectName, nil 191 } 192 branchProject, err := sys.GetProjectByID(sys.CreateBranch(projects[0].ID, projectName)) 193 if err != nil { 194 return project, projectName, fmt.Errorf("failed to create branch %v for project %v", projectName, initialProjectName) 195 } 196 project = branchProject 197 } else { 198 project = projects[0] 199 log.Entry().Debugf("Loaded project with name %v", project.Name) 200 } 201 } else { 202 projects, err := sys.GetProjectsByNameAndTeam(projectName, teamID) 203 if err != nil { 204 return project, projectName, errors.Wrap(err, "failed getting projects") 205 } 206 if len(projects) == 0 { 207 return checkmarx.Project{}, projectName, nil 208 } 209 if len(projects) == 1 { 210 project = projects[0] 211 } else { 212 for _, current_project := range projects { 213 if projectName == current_project.Name { 214 project = current_project 215 break 216 } 217 } 218 if len(project.Name) == 0 { 219 return project, projectName, errors.New("Cannot find project " + projectName + ". You need to provide the teamName parameter if you want a new project to be created.") 220 } 221 } 222 log.Entry().Debugf("Loaded project with name %v", project.Name) 223 } 224 return project, projectName, nil 225 } 226 227 func zipWorkspaceFiles(filterPattern string, utils checkmarxExecuteScanUtils) (*os.File, error) { 228 zipFileName := filepath.Join(utils.GetWorkspace(), "workspace.zip") 229 patterns := piperutils.Trim(strings.Split(filterPattern, ",")) 230 sort.Strings(patterns) 231 zipFile, err := os.Create(zipFileName) 232 if err != nil { 233 return zipFile, errors.Wrap(err, "failed to create archive of project sources") 234 } 235 defer zipFile.Close() 236 err = zipFolder(utils.GetWorkspace(), zipFile, patterns, utils) 237 if err != nil { 238 return nil, errors.Wrap(err, "failed to compact folder") 239 } 240 return zipFile, nil 241 } 242 243 func uploadAndScan(config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { 244 previousScans, err := sys.GetScans(project.ID) 245 if err != nil && config.VerifyOnly { 246 log.Entry().Warnf("Cannot load scans for project %v, verification only mode aborted", project.Name) 247 } 248 if len(previousScans) > 0 && config.VerifyOnly { 249 err := verifyCxProjectCompliance(config, sys, previousScans[0].ID, influx, utils) 250 if err != nil { 251 log.SetErrorCategory(log.ErrorCompliance) 252 return errors.Wrapf(err, "project %v not compliant", project.Name) 253 } 254 } else { 255 zipFile, err := zipWorkspaceFiles(config.FilterPattern, utils) 256 if err != nil { 257 return errors.Wrap(err, "failed to zip workspace files") 258 } 259 err = sys.UploadProjectSourceCode(project.ID, zipFile.Name()) 260 if err != nil { 261 return errors.Wrapf(err, "failed to upload source code for project %v", project.Name) 262 } 263 264 log.Entry().Debugf("Source code uploaded for project %v", project.Name) 265 err = os.Remove(zipFile.Name()) 266 if err != nil { 267 log.Entry().WithError(err).Warnf("Failed to delete zipped source code for project %v", project.Name) 268 } 269 270 incremental := config.Incremental 271 fullScanCycle, err := strconv.Atoi(config.FullScanCycle) 272 if err != nil { 273 log.SetErrorCategory(log.ErrorConfiguration) 274 return errors.Wrapf(err, "invalid configuration value for fullScanCycle %v, must be a positive int", config.FullScanCycle) 275 } 276 277 if config.IsOptimizedAndScheduled { 278 incremental = false 279 } else if incremental && config.FullScansScheduled && fullScanCycle > 0 && (getNumCoherentIncrementalScans(previousScans)+1)%fullScanCycle == 0 { 280 incremental = false 281 } 282 283 return triggerScan(config, sys, project, incremental, influx, utils) 284 } 285 return nil 286 } 287 288 func triggerScan(config checkmarxExecuteScanOptions, sys checkmarx.System, project checkmarx.Project, incremental bool, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { 289 scan, err := sys.ScanProject(project.ID, incremental, true, !config.AvoidDuplicateProjectScans) 290 if err != nil { 291 return errors.Wrapf(err, "cannot scan project %v", project.Name) 292 } 293 294 log.Entry().Debugf("Scanning project %v ", project.Name) 295 err = pollScanStatus(sys, scan) 296 if err != nil { 297 return errors.Wrap(err, "polling scan status failed") 298 } 299 300 log.Entry().Debugln("Scan finished") 301 return verifyCxProjectCompliance(config, sys, scan.ID, influx, utils) 302 } 303 304 func verifyCxProjectCompliance(config checkmarxExecuteScanOptions, sys checkmarx.System, scanID int, influx *checkmarxExecuteScanInflux, utils checkmarxExecuteScanUtils) error { 305 var reports []piperutils.Path 306 if config.GeneratePdfReport { 307 pdfReportName := createReportName(utils.GetWorkspace(), "CxSASTReport_%v.pdf") 308 err := downloadAndSaveReport(sys, pdfReportName, scanID, utils) 309 if err != nil { 310 log.Entry().Warning("Report download failed - continue processing ...") 311 } else { 312 reports = append(reports, piperutils.Path{Target: pdfReportName, Mandatory: true}) 313 } 314 } else { 315 log.Entry().Debug("Report generation is disabled via configuration") 316 } 317 318 xmlReportName := createReportName(utils.GetWorkspace(), "CxSASTResults_%v.xml") 319 results, err := getDetailedResults(sys, xmlReportName, scanID, utils) 320 if err != nil { 321 return errors.Wrap(err, "failed to get detailed results") 322 } 323 reports = append(reports, piperutils.Path{Target: xmlReportName}) 324 325 // generate sarif report 326 if config.ConvertToSarif { 327 log.Entry().Info("Calling conversion to SARIF function.") 328 sarif, err := checkmarx.ConvertCxxmlToSarif(sys, xmlReportName, scanID) 329 if err != nil { 330 return fmt.Errorf("failed to generate SARIF") 331 } 332 paths, err := checkmarx.WriteSarif(sarif) 333 if err != nil { 334 return fmt.Errorf("failed to write sarif") 335 } 336 reports = append(reports, paths...) 337 } 338 339 // create toolrecord 340 toolRecordFileName, err := createToolRecordCx(utils.GetWorkspace(), config, results) 341 if err != nil { 342 // do not fail until the framework is well established 343 log.Entry().Warning("TR_CHECKMARX: Failed to create toolrecord file ...", err) 344 } else { 345 reports = append(reports, piperutils.Path{Target: toolRecordFileName}) 346 } 347 348 // create JSON report (regardless vulnerabilityThreshold enabled or not) 349 jsonReport := checkmarx.CreateJSONReport(results) 350 paths, err := checkmarx.WriteJSONReport(jsonReport) 351 if err != nil { 352 log.Entry().Warning("failed to write JSON report...", err) 353 } else { 354 // add JSON report to archiving list 355 reports = append(reports, paths...) 356 } 357 358 links := []piperutils.Path{{Target: results["DeepLink"].(string), Name: "Checkmarx Web UI"}} 359 piperutils.PersistReportsAndLinks("checkmarxExecuteScan", utils.GetWorkspace(), reports, links) 360 361 reportToInflux(results, influx) 362 363 insecure := false 364 insecureResults := []string{} 365 neutralResults := []string{} 366 367 err = nil 368 if config.VulnerabilityThresholdEnabled { 369 insecure, insecureResults, neutralResults = enforceThresholds(config, results) 370 scanReport := checkmarx.CreateCustomReport(results, insecureResults, neutralResults) 371 372 if insecure && config.CreateResultIssue && len(config.GithubToken) > 0 && len(config.GithubAPIURL) > 0 && len(config.Owner) > 0 && len(config.Repository) > 0 { 373 log.Entry().Debug("Creating/updating GitHub issue with check results") 374 err := reporting.UploadSingleReportToGithub(scanReport, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, utils) 375 if err != nil { 376 return fmt.Errorf("failed to upload scan results into GitHub: %w", err) 377 } 378 } 379 380 paths, err := checkmarx.WriteCustomReports(scanReport, fmt.Sprint(results["ProjectName"]), fmt.Sprint(results["ProjectID"])) 381 if err != nil { 382 // do not fail until we have a better idea to handle it 383 log.Entry().Warning("failed to write HTML/MarkDown report file ...", err) 384 } else { 385 reports = append(reports, paths...) 386 } 387 } 388 389 if insecure { 390 if config.VulnerabilityThresholdResult == "FAILURE" { 391 log.SetErrorCategory(log.ErrorCompliance) 392 return fmt.Errorf("the project is not compliant - see report for details") 393 } 394 log.Entry().Errorf("Checkmarx scan result set to %v, some results are not meeting defined thresholds. For details see the archived report.", config.VulnerabilityThresholdResult) 395 } else { 396 log.Entry().Infoln("Checkmarx scan finished successfully") 397 } 398 return nil 399 } 400 401 func createReportName(workspace, reportFileNameTemplate string) string { 402 regExpFileName := regexp.MustCompile(`[^\w\d]`) 403 timeStamp, _ := time.Now().Local().MarshalText() 404 return filepath.Join(workspace, fmt.Sprintf(reportFileNameTemplate, regExpFileName.ReplaceAllString(string(timeStamp), "_"))) 405 } 406 407 func pollScanStatus(sys checkmarx.System, scan checkmarx.Scan) error { 408 status := "Scan phase: New" 409 pastStatus := status 410 log.Entry().Info(status) 411 stepDetail := "..." 412 stageDetail := "..." 413 for true { 414 var detail checkmarx.ScanStatusDetail 415 status, detail = sys.GetScanStatusAndDetail(scan.ID) 416 if len(detail.Stage) > 0 { 417 stageDetail = detail.Stage 418 } 419 if len(detail.Step) > 0 { 420 stepDetail = detail.Step 421 } 422 if status == "Finished" || status == "Canceled" || status == "Failed" { 423 break 424 } 425 426 status = fmt.Sprintf("Scan phase: %v (%v / %v)", status, stageDetail, stepDetail) 427 if pastStatus != status { 428 log.Entry().Info(status) 429 pastStatus = status 430 } 431 log.Entry().Debug("Polling for status: sleeping...") 432 time.Sleep(10 * time.Second) 433 } 434 if status == "Canceled" { 435 log.SetErrorCategory(log.ErrorCustom) 436 return fmt.Errorf("scan canceled via web interface") 437 } 438 if status == "Failed" { 439 if strings.Contains(stageDetail, "<ErrorCode>17033</ErrorCode>") { // Translate a cryptic XML error into a human-readable message 440 stageDetail = "Failed to start scanning due to one of following reasons: source folder is empty, all source files are of an unsupported language or file format" 441 } 442 return fmt.Errorf("Checkmarx scan failed with the following error: %v", stageDetail) 443 } 444 return nil 445 } 446 447 func reportToInflux(results map[string]interface{}, influx *checkmarxExecuteScanInflux) { 448 influx.checkmarx_data.fields.high_issues = results["High"].(map[string]int)["Issues"] 449 influx.checkmarx_data.fields.high_not_false_postive = results["High"].(map[string]int)["NotFalsePositive"] 450 influx.checkmarx_data.fields.high_not_exploitable = results["High"].(map[string]int)["NotExploitable"] 451 influx.checkmarx_data.fields.high_confirmed = results["High"].(map[string]int)["Confirmed"] 452 influx.checkmarx_data.fields.high_urgent = results["High"].(map[string]int)["Urgent"] 453 influx.checkmarx_data.fields.high_proposed_not_exploitable = results["High"].(map[string]int)["ProposedNotExploitable"] 454 influx.checkmarx_data.fields.high_to_verify = results["High"].(map[string]int)["ToVerify"] 455 influx.checkmarx_data.fields.medium_issues = results["Medium"].(map[string]int)["Issues"] 456 influx.checkmarx_data.fields.medium_not_false_postive = results["Medium"].(map[string]int)["NotFalsePositive"] 457 influx.checkmarx_data.fields.medium_not_exploitable = results["Medium"].(map[string]int)["NotExploitable"] 458 influx.checkmarx_data.fields.medium_confirmed = results["Medium"].(map[string]int)["Confirmed"] 459 influx.checkmarx_data.fields.medium_urgent = results["Medium"].(map[string]int)["Urgent"] 460 influx.checkmarx_data.fields.medium_proposed_not_exploitable = results["Medium"].(map[string]int)["ProposedNotExploitable"] 461 influx.checkmarx_data.fields.medium_to_verify = results["Medium"].(map[string]int)["ToVerify"] 462 influx.checkmarx_data.fields.low_issues = results["Low"].(map[string]int)["Issues"] 463 influx.checkmarx_data.fields.low_not_false_postive = results["Low"].(map[string]int)["NotFalsePositive"] 464 influx.checkmarx_data.fields.low_not_exploitable = results["Low"].(map[string]int)["NotExploitable"] 465 influx.checkmarx_data.fields.low_confirmed = results["Low"].(map[string]int)["Confirmed"] 466 influx.checkmarx_data.fields.low_urgent = results["Low"].(map[string]int)["Urgent"] 467 influx.checkmarx_data.fields.low_proposed_not_exploitable = results["Low"].(map[string]int)["ProposedNotExploitable"] 468 influx.checkmarx_data.fields.low_to_verify = results["Low"].(map[string]int)["ToVerify"] 469 influx.checkmarx_data.fields.information_issues = results["Information"].(map[string]int)["Issues"] 470 influx.checkmarx_data.fields.information_not_false_postive = results["Information"].(map[string]int)["NotFalsePositive"] 471 influx.checkmarx_data.fields.information_not_exploitable = results["Information"].(map[string]int)["NotExploitable"] 472 influx.checkmarx_data.fields.information_confirmed = results["Information"].(map[string]int)["Confirmed"] 473 influx.checkmarx_data.fields.information_urgent = results["Information"].(map[string]int)["Urgent"] 474 influx.checkmarx_data.fields.information_proposed_not_exploitable = results["Information"].(map[string]int)["ProposedNotExploitable"] 475 influx.checkmarx_data.fields.information_to_verify = results["Information"].(map[string]int)["ToVerify"] 476 influx.checkmarx_data.fields.initiator_name = results["InitiatorName"].(string) 477 influx.checkmarx_data.fields.owner = results["Owner"].(string) 478 influx.checkmarx_data.fields.scan_id = results["ScanId"].(string) 479 influx.checkmarx_data.fields.project_id = results["ProjectId"].(string) 480 influx.checkmarx_data.fields.projectName = results["ProjectName"].(string) 481 influx.checkmarx_data.fields.team = results["Team"].(string) 482 influx.checkmarx_data.fields.team_full_path_on_report_date = results["TeamFullPathOnReportDate"].(string) 483 influx.checkmarx_data.fields.scan_start = results["ScanStart"].(string) 484 influx.checkmarx_data.fields.scan_time = results["ScanTime"].(string) 485 influx.checkmarx_data.fields.lines_of_code_scanned = results["LinesOfCodeScanned"].(int) 486 influx.checkmarx_data.fields.files_scanned = results["FilesScanned"].(int) 487 influx.checkmarx_data.fields.checkmarx_version = results["CheckmarxVersion"].(string) 488 influx.checkmarx_data.fields.scan_type = results["ScanType"].(string) 489 influx.checkmarx_data.fields.preset = results["Preset"].(string) 490 influx.checkmarx_data.fields.deep_link = results["DeepLink"].(string) 491 influx.checkmarx_data.fields.report_creation_time = results["ReportCreationTime"].(string) 492 } 493 494 func downloadAndSaveReport(sys checkmarx.System, reportFileName string, scanID int, utils checkmarxExecuteScanUtils) error { 495 report, err := generateAndDownloadReport(sys, scanID, "PDF") 496 if err != nil { 497 return errors.Wrap(err, "failed to download the report") 498 } 499 log.Entry().Debugf("Saving report to file %v...", reportFileName) 500 return utils.WriteFile(reportFileName, report, 0700) 501 } 502 503 func enforceThresholds(config checkmarxExecuteScanOptions, results map[string]interface{}) (bool, []string, []string) { 504 neutralResults := []string{} 505 insecureResults := []string{} 506 insecure := false 507 cxHighThreshold := config.VulnerabilityThresholdHigh 508 cxMediumThreshold := config.VulnerabilityThresholdMedium 509 cxLowThreshold := config.VulnerabilityThresholdLow 510 highValue := results["High"].(map[string]int)["NotFalsePositive"] 511 mediumValue := results["Medium"].(map[string]int)["NotFalsePositive"] 512 lowValue := results["Low"].(map[string]int)["NotFalsePositive"] 513 var unit string 514 highViolation := "" 515 mediumViolation := "" 516 lowViolation := "" 517 if config.VulnerabilityThresholdUnit == "percentage" { 518 unit = "%" 519 highAudited := results["High"].(map[string]int)["Issues"] - results["High"].(map[string]int)["NotFalsePositive"] 520 highOverall := results["High"].(map[string]int)["Issues"] 521 if highOverall == 0 { 522 highAudited = 1 523 highOverall = 1 524 } 525 mediumAudited := results["Medium"].(map[string]int)["Issues"] - results["Medium"].(map[string]int)["NotFalsePositive"] 526 mediumOverall := results["Medium"].(map[string]int)["Issues"] 527 if mediumOverall == 0 { 528 mediumAudited = 1 529 mediumOverall = 1 530 } 531 lowAudited := results["Low"].(map[string]int)["Confirmed"] + results["Low"].(map[string]int)["NotExploitable"] 532 lowOverall := results["Low"].(map[string]int)["Issues"] 533 if lowOverall == 0 { 534 lowAudited = 1 535 lowOverall = 1 536 } 537 highValue = int(float32(highAudited) / float32(highOverall) * 100.0) 538 mediumValue = int(float32(mediumAudited) / float32(mediumOverall) * 100.0) 539 lowValue = int(float32(lowAudited) / float32(lowOverall) * 100.0) 540 541 if highValue < cxHighThreshold { 542 insecure = true 543 highViolation = fmt.Sprintf("<-- %v %v deviation", cxHighThreshold-highValue, unit) 544 } 545 if mediumValue < cxMediumThreshold { 546 insecure = true 547 mediumViolation = fmt.Sprintf("<-- %v %v deviation", cxMediumThreshold-mediumValue, unit) 548 } 549 if lowValue < cxLowThreshold { 550 insecure = true 551 lowViolation = fmt.Sprintf("<-- %v %v deviation", cxLowThreshold-lowValue, unit) 552 } 553 } 554 if config.VulnerabilityThresholdUnit == "absolute" { 555 unit = " findings" 556 if highValue > cxHighThreshold { 557 insecure = true 558 highViolation = fmt.Sprintf("<-- %v%v deviation", highValue-cxHighThreshold, unit) 559 } 560 if mediumValue > cxMediumThreshold { 561 insecure = true 562 mediumViolation = fmt.Sprintf("<-- %v%v deviation", mediumValue-cxMediumThreshold, unit) 563 } 564 if lowValue > cxLowThreshold { 565 insecure = true 566 lowViolation = fmt.Sprintf("<-- %v%v deviation", lowValue-cxLowThreshold, unit) 567 } 568 } 569 570 highText := fmt.Sprintf("High %v%v %v", highValue, unit, highViolation) 571 mediumText := fmt.Sprintf("Medium %v%v %v", mediumValue, unit, mediumViolation) 572 lowText := fmt.Sprintf("Low %v%v %v", lowValue, unit, lowViolation) 573 if len(highViolation) > 0 { 574 insecureResults = append(insecureResults, highText) 575 } else { 576 neutralResults = append(neutralResults, highText) 577 } 578 if len(mediumViolation) > 0 { 579 insecureResults = append(insecureResults, mediumText) 580 } else { 581 neutralResults = append(neutralResults, mediumText) 582 } 583 if len(lowViolation) > 0 { 584 insecureResults = append(insecureResults, lowText) 585 } else { 586 neutralResults = append(neutralResults, lowText) 587 } 588 589 log.Entry().Infoln("") 590 log.Entry().Info(highText) 591 log.Entry().Info(mediumText) 592 log.Entry().Info(lowText) 593 log.Entry().Infoln("") 594 595 return insecure, insecureResults, neutralResults 596 } 597 598 func createAndConfigureNewProject(sys checkmarx.System, projectName, teamID string, presetIDValue int, presetValue, engineConfiguration string) (checkmarx.Project, error) { 599 if len(presetValue) == 0 { 600 log.SetErrorCategory(log.ErrorConfiguration) 601 return checkmarx.Project{}, fmt.Errorf("preset not specified, creation of project %v failed", projectName) 602 } 603 604 projectCreateResult, err := sys.CreateProject(projectName, teamID) 605 if err != nil { 606 return checkmarx.Project{}, errors.Wrapf(err, "cannot create project %v", projectName) 607 } 608 609 if err := setPresetForProject(sys, projectCreateResult.ID, presetIDValue, projectName, presetValue, engineConfiguration); err != nil { 610 return checkmarx.Project{}, errors.Wrapf(err, "failed to set preset %v for project", presetValue) 611 } 612 613 projects, err := sys.GetProjectsByNameAndTeam(projectName, teamID) 614 if err != nil || len(projects) == 0 { 615 return checkmarx.Project{}, errors.Wrapf(err, "failed to load newly created project %v", projectName) 616 } 617 log.Entry().Debugf("New Project %v created", projectName) 618 log.Entry().Debugf("Projects: %v", projects) 619 return projects[0], nil 620 } 621 622 // loadPreset finds a checkmarx.Preset that has either the ID or Name given by presetValue. 623 // presetValue is not expected to be empty. 624 func loadPreset(sys checkmarx.System, presetValue string) (checkmarx.Preset, error) { 625 presets := sys.GetPresets() 626 var preset checkmarx.Preset 627 var configuredPresetName string 628 preset = sys.FilterPresetByName(presets, presetValue) 629 configuredPresetName = presetValue 630 if len(configuredPresetName) > 0 && preset.Name == configuredPresetName { 631 log.Entry().Infof("Loaded preset %v", preset.Name) 632 return preset, nil 633 } 634 log.Entry().Infof("Preset '%s' not found. Available presets are:", presetValue) 635 for _, prs := range presets { 636 log.Entry().Infof("preset id: %v, name: '%v'", prs.ID, prs.Name) 637 } 638 return checkmarx.Preset{}, fmt.Errorf("preset %v not found", preset.Name) 639 } 640 641 // setPresetForProject is only called when it has already been established that the preset needs to be set. 642 // It will exit via the logging framework in case the preset could be found, or the project could not be updated. 643 func setPresetForProject(sys checkmarx.System, projectID, presetIDValue int, projectName, presetValue, engineConfiguration string) error { 644 presetID := presetIDValue 645 if presetID <= 0 { 646 preset, err := loadPreset(sys, presetValue) 647 if err != nil { 648 return errors.Wrapf(err, "preset %v not found, configuration of project %v failed", presetValue, projectName) 649 } 650 presetID = preset.ID 651 } 652 err := sys.UpdateProjectConfiguration(projectID, presetID, engineConfiguration) 653 if err != nil { 654 return errors.Wrapf(err, "updating configuration of project %v failed", projectName) 655 } 656 return nil 657 } 658 659 func generateAndDownloadReport(sys checkmarx.System, scanID int, reportType string) ([]byte, error) { 660 report, err := sys.RequestNewReport(scanID, reportType) 661 if err != nil { 662 return []byte{}, errors.Wrap(err, "failed to request new report") 663 } 664 finalStatus := 1 665 for { 666 reportStatus, err := sys.GetReportStatus(report.ReportID) 667 if err != nil { 668 return []byte{}, errors.Wrap(err, "failed to get report status") 669 } 670 finalStatus = reportStatus.Status.ID 671 if finalStatus != 1 { 672 break 673 } 674 time.Sleep(10 * time.Second) 675 } 676 if finalStatus == 2 { 677 return sys.DownloadReport(report.ReportID) 678 } 679 return []byte{}, fmt.Errorf("unexpected status %v recieved", finalStatus) 680 } 681 682 func getNumCoherentIncrementalScans(scans []checkmarx.ScanStatus) int { 683 count := 0 684 for _, scan := range scans { 685 if !scan.IsIncremental { 686 break 687 } 688 count++ 689 } 690 return count 691 } 692 693 func getDetailedResults(sys checkmarx.System, reportFileName string, scanID int, utils checkmarxExecuteScanUtils) (map[string]interface{}, error) { 694 resultMap := map[string]interface{}{} 695 data, err := generateAndDownloadReport(sys, scanID, "XML") 696 if err != nil { 697 return resultMap, errors.Wrap(err, "failed to download xml report") 698 } 699 if len(data) > 0 { 700 err = utils.WriteFile(reportFileName, data, 0700) 701 if err != nil { 702 return resultMap, errors.Wrap(err, "failed to write file") 703 } 704 var xmlResult checkmarx.DetailedResult 705 err := xml.Unmarshal(data, &xmlResult) 706 if err != nil { 707 return resultMap, errors.Wrapf(err, "failed to unmarshal XML report for scan %v", scanID) 708 } 709 resultMap["InitiatorName"] = xmlResult.InitiatorName 710 resultMap["Owner"] = xmlResult.Owner 711 resultMap["ScanId"] = xmlResult.ScanID 712 resultMap["ProjectId"] = xmlResult.ProjectID 713 resultMap["ProjectName"] = xmlResult.ProjectName 714 resultMap["Team"] = xmlResult.Team 715 resultMap["TeamFullPathOnReportDate"] = xmlResult.TeamFullPathOnReportDate 716 resultMap["ScanStart"] = xmlResult.ScanStart 717 resultMap["ScanTime"] = xmlResult.ScanTime 718 resultMap["LinesOfCodeScanned"] = xmlResult.LinesOfCodeScanned 719 resultMap["FilesScanned"] = xmlResult.FilesScanned 720 resultMap["CheckmarxVersion"] = xmlResult.CheckmarxVersion 721 resultMap["ScanType"] = xmlResult.ScanType 722 resultMap["Preset"] = xmlResult.Preset 723 resultMap["DeepLink"] = xmlResult.DeepLink 724 resultMap["ReportCreationTime"] = xmlResult.ReportCreationTime 725 resultMap["High"] = map[string]int{} 726 resultMap["Medium"] = map[string]int{} 727 resultMap["Low"] = map[string]int{} 728 resultMap["Information"] = map[string]int{} 729 for _, query := range xmlResult.Queries { 730 for _, result := range query.Results { 731 key := result.Severity 732 var submap map[string]int 733 if resultMap[key] == nil { 734 submap = map[string]int{} 735 resultMap[key] = submap 736 } else { 737 submap = resultMap[key].(map[string]int) 738 } 739 submap["Issues"]++ 740 741 auditState := "ToVerify" 742 switch result.State { 743 case "1": 744 auditState = "NotExploitable" 745 break 746 case "2": 747 auditState = "Confirmed" 748 break 749 case "3": 750 auditState = "Urgent" 751 break 752 case "4": 753 auditState = "ProposedNotExploitable" 754 break 755 case "0": 756 default: 757 auditState = "ToVerify" 758 break 759 } 760 submap[auditState]++ 761 762 if result.FalsePositive != "True" { 763 submap["NotFalsePositive"]++ 764 } 765 } 766 } 767 } 768 return resultMap, nil 769 } 770 771 func zipFolder(source string, zipFile io.Writer, patterns []string, utils checkmarxExecuteScanUtils) error { 772 archive := zip.NewWriter(zipFile) 773 defer archive.Close() 774 775 info, err := utils.Stat(source) 776 if err != nil { 777 return nil 778 } 779 780 var baseDir string 781 if info.IsDir() { 782 baseDir = filepath.Base(source) 783 } 784 785 fileCount := 0 786 err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 787 if err != nil { 788 return err 789 } 790 791 noMatch, err := isFileNotMatchingPattern(patterns, path, info, utils) 792 if err != nil || noMatch { 793 return err 794 } 795 796 header, err := utils.FileInfoHeader(info) 797 if err != nil { 798 return err 799 } 800 801 if baseDir != "" { 802 header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source)) 803 } 804 805 adaptHeader(info, header) 806 807 writer, err := archive.CreateHeader(header) 808 if err != nil || info.IsDir() { 809 return err 810 } 811 812 file, err := utils.Open(path) 813 if err != nil { 814 return err 815 } 816 defer file.Close() 817 _, err = io.Copy(writer, file) 818 fileCount++ 819 return err 820 }) 821 log.Entry().Infof("Zipped %d files", fileCount) 822 err = handleZeroFilesZipped(source, err, fileCount) 823 return err 824 } 825 826 func adaptHeader(info os.FileInfo, header *zip.FileHeader) { 827 if info.IsDir() { 828 header.Name += "/" 829 } else { 830 header.Method = zip.Deflate 831 } 832 } 833 834 func handleZeroFilesZipped(source string, err error, fileCount int) error { 835 if err == nil && fileCount == 0 { 836 log.SetErrorCategory(log.ErrorConfiguration) 837 err = fmt.Errorf("filterPattern matched no files or workspace directory '%s' was empty", source) 838 } 839 return err 840 } 841 842 // isFileNotMatchingPattern checks if file path does not match one of the patterns. 843 // If it matches a negative pattern (starting with '!') then true is returned. 844 // 845 // If it is a directory, false is returned. 846 // If no patterns are provided, false is returned. 847 func isFileNotMatchingPattern(patterns []string, path string, info os.FileInfo, utils checkmarxExecuteScanUtils) (bool, error) { 848 if len(patterns) == 0 || info.IsDir() { 849 return false, nil 850 } 851 852 for _, pattern := range patterns { 853 negative := false 854 if strings.HasPrefix(pattern, "!") { 855 pattern = strings.TrimLeft(pattern, "!") 856 negative = true 857 } 858 match, err := utils.PathMatch(pattern, path) 859 if err != nil { 860 return false, errors.Wrapf(err, "Pattern %v could not get executed", pattern) 861 } 862 863 if match { 864 return negative, nil 865 } 866 } 867 return true, nil 868 } 869 870 func createToolRecordCx(workspace string, config checkmarxExecuteScanOptions, results map[string]interface{}) (string, error) { 871 record := toolrecord.New(workspace, "checkmarx", config.ServerURL) 872 // Todo TeamId - see run_scan() 873 // record.AddKeyData("team", XXX, resultMap["Team"], "") 874 // Project 875 err := record.AddKeyData("project", 876 results["ProjectId"].(string), 877 results["ProjectName"].(string), 878 "") 879 if err != nil { 880 return "", err 881 } 882 // Scan 883 err = record.AddKeyData("scanid", 884 results["ScanId"].(string), 885 results["ScanId"].(string), 886 results["DeepLink"].(string)) 887 if err != nil { 888 return "", err 889 } 890 err = record.Persist() 891 if err != nil { 892 return "", err 893 } 894 return record.GetFileName(), nil 895 }