github.com/xgoffin/jenkins-library@v1.154.0/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 := strings.Split(strings.ReplaceAll(strings.ReplaceAll(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(xmlReportName) 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 for true { 412 stepDetail := "..." 413 stageDetail := "..." 414 var detail checkmarx.ScanStatusDetail 415 status, detail = sys.GetScanStatusAndDetail(scan.ID) 416 if status == "Finished" || status == "Canceled" || status == "Failed" { 417 break 418 } 419 if len(detail.Stage) > 0 { 420 stageDetail = detail.Stage 421 } 422 if len(detail.Step) > 0 { 423 stepDetail = detail.Step 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 return fmt.Errorf("scan failed, please check the Checkmarx UI for details") 440 } 441 return nil 442 } 443 444 func reportToInflux(results map[string]interface{}, influx *checkmarxExecuteScanInflux) { 445 influx.checkmarx_data.fields.high_issues = results["High"].(map[string]int)["Issues"] 446 influx.checkmarx_data.fields.high_not_false_postive = results["High"].(map[string]int)["NotFalsePositive"] 447 influx.checkmarx_data.fields.high_not_exploitable = results["High"].(map[string]int)["NotExploitable"] 448 influx.checkmarx_data.fields.high_confirmed = results["High"].(map[string]int)["Confirmed"] 449 influx.checkmarx_data.fields.high_urgent = results["High"].(map[string]int)["Urgent"] 450 influx.checkmarx_data.fields.high_proposed_not_exploitable = results["High"].(map[string]int)["ProposedNotExploitable"] 451 influx.checkmarx_data.fields.high_to_verify = results["High"].(map[string]int)["ToVerify"] 452 influx.checkmarx_data.fields.medium_issues = results["Medium"].(map[string]int)["Issues"] 453 influx.checkmarx_data.fields.medium_not_false_postive = results["Medium"].(map[string]int)["NotFalsePositive"] 454 influx.checkmarx_data.fields.medium_not_exploitable = results["Medium"].(map[string]int)["NotExploitable"] 455 influx.checkmarx_data.fields.medium_confirmed = results["Medium"].(map[string]int)["Confirmed"] 456 influx.checkmarx_data.fields.medium_urgent = results["Medium"].(map[string]int)["Urgent"] 457 influx.checkmarx_data.fields.medium_proposed_not_exploitable = results["Medium"].(map[string]int)["ProposedNotExploitable"] 458 influx.checkmarx_data.fields.medium_to_verify = results["Medium"].(map[string]int)["ToVerify"] 459 influx.checkmarx_data.fields.low_issues = results["Low"].(map[string]int)["Issues"] 460 influx.checkmarx_data.fields.low_not_false_postive = results["Low"].(map[string]int)["NotFalsePositive"] 461 influx.checkmarx_data.fields.low_not_exploitable = results["Low"].(map[string]int)["NotExploitable"] 462 influx.checkmarx_data.fields.low_confirmed = results["Low"].(map[string]int)["Confirmed"] 463 influx.checkmarx_data.fields.low_urgent = results["Low"].(map[string]int)["Urgent"] 464 influx.checkmarx_data.fields.low_proposed_not_exploitable = results["Low"].(map[string]int)["ProposedNotExploitable"] 465 influx.checkmarx_data.fields.low_to_verify = results["Low"].(map[string]int)["ToVerify"] 466 influx.checkmarx_data.fields.information_issues = results["Information"].(map[string]int)["Issues"] 467 influx.checkmarx_data.fields.information_not_false_postive = results["Information"].(map[string]int)["NotFalsePositive"] 468 influx.checkmarx_data.fields.information_not_exploitable = results["Information"].(map[string]int)["NotExploitable"] 469 influx.checkmarx_data.fields.information_confirmed = results["Information"].(map[string]int)["Confirmed"] 470 influx.checkmarx_data.fields.information_urgent = results["Information"].(map[string]int)["Urgent"] 471 influx.checkmarx_data.fields.information_proposed_not_exploitable = results["Information"].(map[string]int)["ProposedNotExploitable"] 472 influx.checkmarx_data.fields.information_to_verify = results["Information"].(map[string]int)["ToVerify"] 473 influx.checkmarx_data.fields.initiator_name = results["InitiatorName"].(string) 474 influx.checkmarx_data.fields.owner = results["Owner"].(string) 475 influx.checkmarx_data.fields.scan_id = results["ScanId"].(string) 476 influx.checkmarx_data.fields.project_id = results["ProjectId"].(string) 477 influx.checkmarx_data.fields.projectName = results["ProjectName"].(string) 478 influx.checkmarx_data.fields.team = results["Team"].(string) 479 influx.checkmarx_data.fields.team_full_path_on_report_date = results["TeamFullPathOnReportDate"].(string) 480 influx.checkmarx_data.fields.scan_start = results["ScanStart"].(string) 481 influx.checkmarx_data.fields.scan_time = results["ScanTime"].(string) 482 influx.checkmarx_data.fields.lines_of_code_scanned = results["LinesOfCodeScanned"].(int) 483 influx.checkmarx_data.fields.files_scanned = results["FilesScanned"].(int) 484 influx.checkmarx_data.fields.checkmarx_version = results["CheckmarxVersion"].(string) 485 influx.checkmarx_data.fields.scan_type = results["ScanType"].(string) 486 influx.checkmarx_data.fields.preset = results["Preset"].(string) 487 influx.checkmarx_data.fields.deep_link = results["DeepLink"].(string) 488 influx.checkmarx_data.fields.report_creation_time = results["ReportCreationTime"].(string) 489 } 490 491 func downloadAndSaveReport(sys checkmarx.System, reportFileName string, scanID int, utils checkmarxExecuteScanUtils) error { 492 report, err := generateAndDownloadReport(sys, scanID, "PDF") 493 if err != nil { 494 return errors.Wrap(err, "failed to download the report") 495 } 496 log.Entry().Debugf("Saving report to file %v...", reportFileName) 497 return utils.WriteFile(reportFileName, report, 0700) 498 } 499 500 func enforceThresholds(config checkmarxExecuteScanOptions, results map[string]interface{}) (bool, []string, []string) { 501 neutralResults := []string{} 502 insecureResults := []string{} 503 insecure := false 504 cxHighThreshold := config.VulnerabilityThresholdHigh 505 cxMediumThreshold := config.VulnerabilityThresholdMedium 506 cxLowThreshold := config.VulnerabilityThresholdLow 507 highValue := results["High"].(map[string]int)["NotFalsePositive"] 508 mediumValue := results["Medium"].(map[string]int)["NotFalsePositive"] 509 lowValue := results["Low"].(map[string]int)["NotFalsePositive"] 510 var unit string 511 highViolation := "" 512 mediumViolation := "" 513 lowViolation := "" 514 if config.VulnerabilityThresholdUnit == "percentage" { 515 unit = "%" 516 highAudited := results["High"].(map[string]int)["Issues"] - results["High"].(map[string]int)["NotFalsePositive"] 517 highOverall := results["High"].(map[string]int)["Issues"] 518 if highOverall == 0 { 519 highAudited = 1 520 highOverall = 1 521 } 522 mediumAudited := results["Medium"].(map[string]int)["Issues"] - results["Medium"].(map[string]int)["NotFalsePositive"] 523 mediumOverall := results["Medium"].(map[string]int)["Issues"] 524 if mediumOverall == 0 { 525 mediumAudited = 1 526 mediumOverall = 1 527 } 528 lowAudited := results["Low"].(map[string]int)["Confirmed"] + results["Low"].(map[string]int)["NotExploitable"] 529 lowOverall := results["Low"].(map[string]int)["Issues"] 530 if lowOverall == 0 { 531 lowAudited = 1 532 lowOverall = 1 533 } 534 highValue = int(float32(highAudited) / float32(highOverall) * 100.0) 535 mediumValue = int(float32(mediumAudited) / float32(mediumOverall) * 100.0) 536 lowValue = int(float32(lowAudited) / float32(lowOverall) * 100.0) 537 538 if highValue < cxHighThreshold { 539 insecure = true 540 highViolation = fmt.Sprintf("<-- %v %v deviation", cxHighThreshold-highValue, unit) 541 } 542 if mediumValue < cxMediumThreshold { 543 insecure = true 544 mediumViolation = fmt.Sprintf("<-- %v %v deviation", cxMediumThreshold-mediumValue, unit) 545 } 546 if lowValue < cxLowThreshold { 547 insecure = true 548 lowViolation = fmt.Sprintf("<-- %v %v deviation", cxLowThreshold-lowValue, unit) 549 } 550 } 551 if config.VulnerabilityThresholdUnit == "absolute" { 552 unit = " findings" 553 if highValue > cxHighThreshold { 554 insecure = true 555 highViolation = fmt.Sprintf("<-- %v%v deviation", highValue-cxHighThreshold, unit) 556 } 557 if mediumValue > cxMediumThreshold { 558 insecure = true 559 mediumViolation = fmt.Sprintf("<-- %v%v deviation", mediumValue-cxMediumThreshold, unit) 560 } 561 if lowValue > cxLowThreshold { 562 insecure = true 563 lowViolation = fmt.Sprintf("<-- %v%v deviation", lowValue-cxLowThreshold, unit) 564 } 565 } 566 567 highText := fmt.Sprintf("High %v%v %v", highValue, unit, highViolation) 568 mediumText := fmt.Sprintf("Medium %v%v %v", mediumValue, unit, mediumViolation) 569 lowText := fmt.Sprintf("Low %v%v %v", lowValue, unit, lowViolation) 570 if len(highViolation) > 0 { 571 insecureResults = append(insecureResults, highText) 572 } else { 573 neutralResults = append(neutralResults, highText) 574 } 575 if len(mediumViolation) > 0 { 576 insecureResults = append(insecureResults, mediumText) 577 } else { 578 neutralResults = append(neutralResults, mediumText) 579 } 580 if len(lowViolation) > 0 { 581 insecureResults = append(insecureResults, lowText) 582 } else { 583 neutralResults = append(neutralResults, lowText) 584 } 585 586 log.Entry().Infoln("") 587 log.Entry().Info(highText) 588 log.Entry().Info(mediumText) 589 log.Entry().Info(lowText) 590 log.Entry().Infoln("") 591 592 return insecure, insecureResults, neutralResults 593 } 594 595 func createAndConfigureNewProject(sys checkmarx.System, projectName, teamID string, presetIDValue int, presetValue, engineConfiguration string) (checkmarx.Project, error) { 596 if len(presetValue) == 0 { 597 log.SetErrorCategory(log.ErrorConfiguration) 598 return checkmarx.Project{}, fmt.Errorf("preset not specified, creation of project %v failed", projectName) 599 } 600 601 projectCreateResult, err := sys.CreateProject(projectName, teamID) 602 if err != nil { 603 return checkmarx.Project{}, errors.Wrapf(err, "cannot create project %v", projectName) 604 } 605 606 if err := setPresetForProject(sys, projectCreateResult.ID, presetIDValue, projectName, presetValue, engineConfiguration); err != nil { 607 return checkmarx.Project{}, errors.Wrapf(err, "failed to set preset %v for project", presetValue) 608 } 609 610 projects, err := sys.GetProjectsByNameAndTeam(projectName, teamID) 611 if err != nil || len(projects) == 0 { 612 return checkmarx.Project{}, errors.Wrapf(err, "failed to load newly created project %v", projectName) 613 } 614 log.Entry().Debugf("New Project %v created", projectName) 615 log.Entry().Debugf("Projects: %v", projects) 616 return projects[0], nil 617 } 618 619 // loadPreset finds a checkmarx.Preset that has either the ID or Name given by presetValue. 620 // presetValue is not expected to be empty. 621 func loadPreset(sys checkmarx.System, presetValue string) (checkmarx.Preset, error) { 622 presets := sys.GetPresets() 623 var preset checkmarx.Preset 624 var configuredPresetName string 625 preset = sys.FilterPresetByName(presets, presetValue) 626 configuredPresetName = presetValue 627 if len(configuredPresetName) > 0 && preset.Name == configuredPresetName { 628 log.Entry().Infof("Loaded preset %v", preset.Name) 629 return preset, nil 630 } 631 log.Entry().Infof("Preset '%s' not found. Available presets are:", presetValue) 632 for _, prs := range presets { 633 log.Entry().Infof("preset id: %v, name: '%v'", prs.ID, prs.Name) 634 } 635 return checkmarx.Preset{}, fmt.Errorf("preset %v not found", preset.Name) 636 } 637 638 // setPresetForProject is only called when it has already been established that the preset needs to be set. 639 // It will exit via the logging framework in case the preset could be found, or the project could not be updated. 640 func setPresetForProject(sys checkmarx.System, projectID, presetIDValue int, projectName, presetValue, engineConfiguration string) error { 641 presetID := presetIDValue 642 if presetID <= 0 { 643 preset, err := loadPreset(sys, presetValue) 644 if err != nil { 645 return errors.Wrapf(err, "preset %v not found, configuration of project %v failed", presetValue, projectName) 646 } 647 presetID = preset.ID 648 } 649 err := sys.UpdateProjectConfiguration(projectID, presetID, engineConfiguration) 650 if err != nil { 651 return errors.Wrapf(err, "updating configuration of project %v failed", projectName) 652 } 653 log.Entry().Debugf("Configuration of project %v updated", projectName) 654 return nil 655 } 656 657 func generateAndDownloadReport(sys checkmarx.System, scanID int, reportType string) ([]byte, error) { 658 report, err := sys.RequestNewReport(scanID, reportType) 659 if err != nil { 660 return []byte{}, errors.Wrap(err, "failed to request new report") 661 } 662 finalStatus := 1 663 for { 664 reportStatus, err := sys.GetReportStatus(report.ReportID) 665 if err != nil { 666 return []byte{}, errors.Wrap(err, "failed to get report status") 667 } 668 finalStatus = reportStatus.Status.ID 669 if finalStatus != 1 { 670 break 671 } 672 time.Sleep(10 * time.Second) 673 } 674 if finalStatus == 2 { 675 return sys.DownloadReport(report.ReportID) 676 } 677 return []byte{}, fmt.Errorf("unexpected status %v recieved", finalStatus) 678 } 679 680 func getNumCoherentIncrementalScans(scans []checkmarx.ScanStatus) int { 681 count := 0 682 for _, scan := range scans { 683 if !scan.IsIncremental { 684 break 685 } 686 count++ 687 } 688 return count 689 } 690 691 func getDetailedResults(sys checkmarx.System, reportFileName string, scanID int, utils checkmarxExecuteScanUtils) (map[string]interface{}, error) { 692 resultMap := map[string]interface{}{} 693 data, err := generateAndDownloadReport(sys, scanID, "XML") 694 if err != nil { 695 return resultMap, errors.Wrap(err, "failed to download xml report") 696 } 697 if len(data) > 0 { 698 err = utils.WriteFile(reportFileName, data, 0700) 699 if err != nil { 700 return resultMap, errors.Wrap(err, "failed to write file") 701 } 702 var xmlResult checkmarx.DetailedResult 703 err := xml.Unmarshal(data, &xmlResult) 704 if err != nil { 705 return resultMap, errors.Wrapf(err, "failed to unmarshal XML report for scan %v", scanID) 706 } 707 resultMap["InitiatorName"] = xmlResult.InitiatorName 708 resultMap["Owner"] = xmlResult.Owner 709 resultMap["ScanId"] = xmlResult.ScanID 710 resultMap["ProjectId"] = xmlResult.ProjectID 711 resultMap["ProjectName"] = xmlResult.ProjectName 712 resultMap["Team"] = xmlResult.Team 713 resultMap["TeamFullPathOnReportDate"] = xmlResult.TeamFullPathOnReportDate 714 resultMap["ScanStart"] = xmlResult.ScanStart 715 resultMap["ScanTime"] = xmlResult.ScanTime 716 resultMap["LinesOfCodeScanned"] = xmlResult.LinesOfCodeScanned 717 resultMap["FilesScanned"] = xmlResult.FilesScanned 718 resultMap["CheckmarxVersion"] = xmlResult.CheckmarxVersion 719 resultMap["ScanType"] = xmlResult.ScanType 720 resultMap["Preset"] = xmlResult.Preset 721 resultMap["DeepLink"] = xmlResult.DeepLink 722 resultMap["ReportCreationTime"] = xmlResult.ReportCreationTime 723 resultMap["High"] = map[string]int{} 724 resultMap["Medium"] = map[string]int{} 725 resultMap["Low"] = map[string]int{} 726 resultMap["Information"] = map[string]int{} 727 for _, query := range xmlResult.Queries { 728 for _, result := range query.Results { 729 key := result.Severity 730 var submap map[string]int 731 if resultMap[key] == nil { 732 submap = map[string]int{} 733 resultMap[key] = submap 734 } else { 735 submap = resultMap[key].(map[string]int) 736 } 737 submap["Issues"]++ 738 739 auditState := "ToVerify" 740 switch result.State { 741 case "1": 742 auditState = "NotExploitable" 743 break 744 case "2": 745 auditState = "Confirmed" 746 break 747 case "3": 748 auditState = "Urgent" 749 break 750 case "4": 751 auditState = "ProposedNotExploitable" 752 break 753 case "0": 754 default: 755 auditState = "ToVerify" 756 break 757 } 758 submap[auditState]++ 759 760 if result.FalsePositive != "True" { 761 submap["NotFalsePositive"]++ 762 } 763 } 764 } 765 } 766 return resultMap, nil 767 } 768 769 func zipFolder(source string, zipFile io.Writer, patterns []string, utils checkmarxExecuteScanUtils) error { 770 archive := zip.NewWriter(zipFile) 771 defer archive.Close() 772 773 info, err := utils.Stat(source) 774 if err != nil { 775 return nil 776 } 777 778 var baseDir string 779 if info.IsDir() { 780 baseDir = filepath.Base(source) 781 } 782 783 fileCount := 0 784 err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 785 if err != nil { 786 return err 787 } 788 789 noMatch, err := isFileNotMatchingPattern(patterns, path, info, utils) 790 if err != nil || noMatch { 791 return err 792 } 793 794 header, err := utils.FileInfoHeader(info) 795 if err != nil { 796 return err 797 } 798 799 if baseDir != "" { 800 header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source)) 801 } 802 803 adaptHeader(info, header) 804 805 writer, err := archive.CreateHeader(header) 806 if err != nil || info.IsDir() { 807 return err 808 } 809 810 file, err := utils.Open(path) 811 if err != nil { 812 return err 813 } 814 defer file.Close() 815 _, err = io.Copy(writer, file) 816 fileCount++ 817 return err 818 }) 819 log.Entry().Infof("Zipped %d files", fileCount) 820 err = handleZeroFilesZipped(source, err, fileCount) 821 return err 822 } 823 824 func adaptHeader(info os.FileInfo, header *zip.FileHeader) { 825 if info.IsDir() { 826 header.Name += "/" 827 } else { 828 header.Method = zip.Deflate 829 } 830 } 831 832 func handleZeroFilesZipped(source string, err error, fileCount int) error { 833 if err == nil && fileCount == 0 { 834 log.SetErrorCategory(log.ErrorConfiguration) 835 err = fmt.Errorf("filterPattern matched no files or workspace directory '%s' was empty", source) 836 } 837 return err 838 } 839 840 // isFileNotMatchingPattern checks if file path does not match one of the patterns. 841 // If it matches a negative pattern (starting with '!') then true is returned. 842 // 843 // If it is a directory, false is returned. 844 // If no patterns are provided, false is returned. 845 func isFileNotMatchingPattern(patterns []string, path string, info os.FileInfo, utils checkmarxExecuteScanUtils) (bool, error) { 846 if len(patterns) == 0 || info.IsDir() { 847 return false, nil 848 } 849 850 for _, pattern := range patterns { 851 negative := false 852 if strings.HasPrefix(pattern, "!") { 853 pattern = strings.TrimLeft(pattern, "!") 854 negative = true 855 } 856 match, err := utils.PathMatch(pattern, path) 857 if err != nil { 858 return false, errors.Wrapf(err, "Pattern %v could not get executed", pattern) 859 } 860 861 if match { 862 return negative, nil 863 } 864 } 865 return true, nil 866 } 867 868 func createToolRecordCx(workspace string, config checkmarxExecuteScanOptions, results map[string]interface{}) (string, error) { 869 record := toolrecord.New(workspace, "checkmarx", config.ServerURL) 870 // Todo TeamId - see run_scan() 871 // record.AddKeyData("team", XXX, resultMap["Team"], "") 872 // Project 873 err := record.AddKeyData("project", 874 results["ProjectId"].(string), 875 results["ProjectName"].(string), 876 "") 877 if err != nil { 878 return "", err 879 } 880 // Scan 881 err = record.AddKeyData("scanid", 882 results["ScanId"].(string), 883 results["ScanId"].(string), 884 results["DeepLink"].(string)) 885 if err != nil { 886 return "", err 887 } 888 err = record.Persist() 889 if err != nil { 890 return "", err 891 } 892 return record.GetFileName(), nil 893 }