github.com/xgoffin/jenkins-library@v1.154.0/cmd/detectExecuteScan.go (about) 1 package cmd 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "path/filepath" 10 "regexp" 11 "sort" 12 "strconv" 13 "strings" 14 "time" 15 16 bd "github.com/SAP/jenkins-library/pkg/blackduck" 17 piperGithub "github.com/SAP/jenkins-library/pkg/github" 18 piperhttp "github.com/SAP/jenkins-library/pkg/http" 19 "github.com/SAP/jenkins-library/pkg/maven" 20 "github.com/SAP/jenkins-library/pkg/reporting" 21 "github.com/SAP/jenkins-library/pkg/versioning" 22 "github.com/pkg/errors" 23 24 "github.com/SAP/jenkins-library/pkg/command" 25 "github.com/SAP/jenkins-library/pkg/log" 26 "github.com/SAP/jenkins-library/pkg/piperutils" 27 "github.com/SAP/jenkins-library/pkg/telemetry" 28 "github.com/SAP/jenkins-library/pkg/toolrecord" 29 ) 30 31 type detectUtils interface { 32 Abs(path string) (string, error) 33 FileExists(filename string) (bool, error) 34 FileRemove(filename string) error 35 Copy(src, dest string) (int64, error) 36 Move(src, dest string) error 37 DirExists(dest string) (bool, error) 38 FileRead(path string) ([]byte, error) 39 FileWrite(path string, content []byte, perm os.FileMode) error 40 MkdirAll(path string, perm os.FileMode) error 41 Chmod(path string, mode os.FileMode) error 42 Glob(pattern string) (matches []string, err error) 43 Chdir(path string) error 44 TempDir(string, string) (string, error) 45 RemoveAll(string) error 46 FileRename(string, string) error 47 Getwd() (string, error) 48 Symlink(oldname string, newname string) error 49 SHA256(path string) (string, error) 50 CurrentTime(format string) string 51 52 GetExitCode() int 53 GetOsEnv() []string 54 Stdout(out io.Writer) 55 Stderr(err io.Writer) 56 SetDir(dir string) 57 SetEnv(env []string) 58 RunExecutable(e string, p ...string) error 59 RunShell(shell, script string) error 60 61 DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error 62 63 CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error 64 } 65 66 type detectUtilsBundle struct { 67 *command.Command 68 *piperutils.Files 69 *piperhttp.Client 70 } 71 72 // CreateIssue supplies capability for GitHub issue creation 73 func (d *detectUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error { 74 return piperGithub.CreateIssue(ghCreateIssueOptions) 75 } 76 77 type blackduckSystem struct { 78 Client bd.Client 79 } 80 81 func newDetectUtils() detectUtils { 82 utils := detectUtilsBundle{ 83 Command: &command.Command{ 84 ErrorCategoryMapping: map[string][]string{ 85 log.ErrorCompliance.String(): { 86 "FAILURE_POLICY_VIOLATION - Detect found policy violations.", 87 }, 88 log.ErrorConfiguration.String(): { 89 "FAILURE_CONFIGURATION - Detect was unable to start due to issues with it's configuration.", 90 "FAILURE_DETECTOR - Detect had one or more detector failures while extracting dependencies. Check that all projects build and your environment is configured correctly.", 91 "FAILURE_SCAN - Detect was unable to run the signature scanner against your source. Check your configuration.", 92 }, 93 log.ErrorInfrastructure.String(): { 94 "FAILURE_PROXY_CONNECTIVITY - Detect was unable to use the configured proxy. Check your configuration and connection.", 95 "FAILURE_BLACKDUCK_CONNECTIVITY - Detect was unable to connect to Black Duck. Check your configuration and connection.", 96 "FAILURE_POLARIS_CONNECTIVITY - Detect was unable to connect to Polaris. Check your configuration and connection.", 97 }, 98 log.ErrorService.String(): { 99 "FAILURE_TIMEOUT - Detect could not wait for actions to be completed on Black Duck. Check your Black Duck server or increase your timeout.", 100 "FAILURE_DETECTOR_REQUIRED - Detect did not run all of the required detectors. Fix detector issues or disable required detectors.", 101 "FAILURE_BLACKDUCK_VERSION_NOT_SUPPORTED - Detect attempted an operation that was not supported by your version of Black Duck. Ensure your Black Duck is compatible with this version of detect.", 102 "FAILURE_BLACKDUCK_FEATURE_ERROR - Detect encountered an error while attempting an operation on Black Duck. Ensure your Black Duck is compatible with this version of detect.", 103 "FAILURE_GENERAL_ERROR - Detect encountered a known error, details of the error are provided.", 104 "FAILURE_UNKNOWN_ERROR - Detect encountered an unknown error.", 105 }, 106 }, 107 }, 108 Files: &piperutils.Files{}, 109 Client: &piperhttp.Client{}, 110 } 111 utils.Stdout(log.Writer()) 112 utils.Stderr(log.Writer()) 113 return &utils 114 } 115 116 func newBlackduckSystem(config detectExecuteScanOptions) *blackduckSystem { 117 sys := blackduckSystem{ 118 Client: bd.NewClient(config.Token, config.ServerURL, &piperhttp.Client{}), 119 } 120 return &sys 121 } 122 123 func detectExecuteScan(config detectExecuteScanOptions, _ *telemetry.CustomData, influx *detectExecuteScanInflux) { 124 influx.step_data.fields.detect = false 125 utils := newDetectUtils() 126 err := runDetect(config, utils, influx) 127 128 if err != nil { 129 log.Entry(). 130 WithError(err). 131 Fatal("failed to execute detect scan") 132 } 133 134 influx.step_data.fields.detect = true 135 } 136 137 func runDetect(config detectExecuteScanOptions, utils detectUtils, influx *detectExecuteScanInflux) error { 138 // detect execution details, see https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/88440888/Sample+Synopsys+Detect+Scan+Configuration+Scenarios+for+Black+Duck 139 err := getDetectScript(config, utils) 140 if err != nil { 141 return fmt.Errorf("failed to download 'detect.sh' script: %w", err) 142 } 143 defer func() { 144 err := utils.FileRemove("detect.sh") 145 if err != nil { 146 log.Entry().Warnf("failed to delete 'detect.sh' script: %v", err) 147 } 148 }() 149 err = utils.Chmod("detect.sh", 0700) 150 if err != nil { 151 return err 152 } 153 154 if config.InstallArtifacts { 155 err := maven.InstallMavenArtifacts(&maven.EvaluateOptions{ 156 M2Path: config.M2Path, 157 ProjectSettingsFile: config.ProjectSettingsFile, 158 GlobalSettingsFile: config.GlobalSettingsFile, 159 }, utils) 160 if err != nil { 161 return err 162 } 163 } 164 165 args := []string{"./detect.sh"} 166 args, err = addDetectArgs(args, config, utils) 167 if err != nil { 168 return err 169 } 170 script := strings.Join(args, " ") 171 172 envs := []string{"BLACKDUCK_SKIP_PHONE_HOME=true"} 173 envs = append(envs, config.CustomEnvironmentVariables...) 174 175 utils.SetDir(".") 176 utils.SetEnv(envs) 177 178 err = utils.RunShell("/bin/bash", script) 179 blackduckSystem := newBlackduckSystem(config) 180 reportingErr := postScanChecksAndReporting(config, influx, utils, blackduckSystem) 181 if reportingErr != nil { 182 if strings.Contains(reportingErr.Error(), "License Policy Violations found") { 183 log.Entry().Errorf("License Policy Violations found") 184 log.SetErrorCategory(log.ErrorCompliance) 185 if err == nil && !piperutils.ContainsStringPart(config.FailOn, "NONE") { 186 err = errors.New("License Policy Violations found") 187 } 188 } else { 189 log.Entry().Warnf("Failed to generate reports: %v", reportingErr) 190 } 191 } 192 if err != nil { 193 // Setting error category based on exit code 194 mapErrorCategory(utils.GetExitCode()) 195 196 // Error code mapping with more human readable text 197 err = errors.Wrapf(err, exitCodeMapping(utils.GetExitCode())) 198 } 199 // create Toolrecord file 200 toolRecordFileName, toolRecordErr := createToolRecordDetect("./", config, blackduckSystem) 201 if toolRecordErr != nil { 202 // do not fail until the framework is well established 203 log.Entry().Warning("TR_DETECT: Failed to create toolrecord file "+toolRecordFileName, err) 204 } 205 return err 206 } 207 208 // Get proper error category 209 func mapErrorCategory(exitCodeKey int) { 210 switch exitCodeKey { 211 case 0: 212 //In case detect exits successfully, we rely on the function 'postScanChecksAndReporting' to determine the error category 213 //hence this method doesnt need to set an error category or go to 'default' case 214 break 215 case 1: 216 log.SetErrorCategory(log.ErrorInfrastructure) 217 case 2: 218 log.SetErrorCategory(log.ErrorService) 219 case 3: 220 log.SetErrorCategory(log.ErrorCompliance) 221 case 4: 222 log.SetErrorCategory(log.ErrorInfrastructure) 223 case 5: 224 log.SetErrorCategory(log.ErrorConfiguration) 225 case 6: 226 log.SetErrorCategory(log.ErrorConfiguration) 227 case 7: 228 log.SetErrorCategory(log.ErrorConfiguration) 229 case 9: 230 log.SetErrorCategory(log.ErrorService) 231 case 10: 232 log.SetErrorCategory(log.ErrorService) 233 case 11: 234 log.SetErrorCategory(log.ErrorService) 235 case 12: 236 log.SetErrorCategory(log.ErrorInfrastructure) 237 case 99: 238 log.SetErrorCategory(log.ErrorService) 239 case 100: 240 log.SetErrorCategory(log.ErrorUndefined) 241 default: 242 log.SetErrorCategory(log.ErrorUndefined) 243 } 244 } 245 246 // Exit codes/error code mapping 247 func exitCodeMapping(exitCodeKey int) string { 248 249 exitCodes := map[int]string{ 250 0: "Detect Scan completed successfully", 251 1: "FAILURE_BLACKDUCK_CONNECTIVITY => Detect was unable to connect to Black Duck. Check your configuration and connection.", 252 2: "FAILURE_TIMEOUT => Detect could not wait for actions to be completed on Black Duck. Check your Black Duck server or increase your timeout.", 253 3: "FAILURE_POLICY_VIOLATION => Detect found policy violations.", 254 4: "FAILURE_PROXY_CONNECTIVITY => Detect was unable to use the configured proxy. Check your configuration and connection.", 255 5: "FAILURE_DETECTOR => Detect had one or more detector failures while extracting dependencies. Check that all projects build and your environment is configured correctly.", 256 6: "FAILURE_SCAN => Detect was unable to run the signature scanner against your source. Check your configuration.", 257 7: "FAILURE_CONFIGURATION => Detect was unable to start because of a configuration issue. Check and fix your configuration.", 258 9: "FAILURE_DETECTOR_REQUIRED => Detect did not run all of the required detectors. Fix detector issues or disable required detectors.", 259 10: "FAILURE_BLACKDUCK_VERSION_NOT_SUPPORTED => Detect attempted an operation that was not supported by your version of Black Duck. Ensure your Black Duck is compatible with this version of detect.", 260 11: "FAILURE_BLACKDUCK_FEATURE_ERROR => Detect encountered an error while attempting an operation on Black Duck. Ensure your Black Duck is compatible with this version of detect.", 261 12: "FAILURE_POLARIS_CONNECTIVITY => Detect was unable to connect to Polaris. Check your configuration and connection.", 262 99: "FAILURE_GENERAL_ERROR => Detect encountered a known error, details of the error are provided.", 263 100: "FAILURE_UNKNOWN_ERROR => Detect encountered an unknown error.", 264 } 265 266 if _, isKeyExists := exitCodes[exitCodeKey]; isKeyExists { 267 return exitCodes[exitCodeKey] 268 } 269 270 return "[" + strconv.Itoa(exitCodeKey) + "]: Not known exit code key" 271 } 272 273 func getDetectScript(config detectExecuteScanOptions, utils detectUtils) error { 274 if config.ScanOnChanges { 275 log.Entry().Infof("Using Detect Rescan script") 276 return utils.DownloadFile("https://raw.githubusercontent.com/blackducksoftware/detect_rescan/master/detect_rescan.sh", "detect.sh", nil, nil) 277 } 278 env := utils.GetOsEnv() 279 env = append(env, config.CustomEnvironmentVariables...) 280 if piperutils.ContainsStringPart(env, "DETECT_LATEST_RELEASE_VERSION") { 281 releaseVersion := "" 282 for _, i := range env { 283 if strings.Contains(i, "DETECT_LATEST_RELEASE_VERSION") { 284 releaseVersion = strings.Split(i, "=")[1] 285 } 286 } 287 log.Entry().Infof("Using detect script Version %v ", releaseVersion) 288 detect6, _ := regexp.MatchString("6\\.\\d\\.\\d", releaseVersion) 289 if detect6 { 290 log.Entry().Infof("Downloading Detect 6.x") 291 return utils.DownloadFile("https://detect.synopsys.com/detect.sh", "detect.sh", nil, nil) 292 } 293 } 294 log.Entry().Infof("Downloading Detect7") 295 return utils.DownloadFile("https://detect.synopsys.com/detect7.sh", "detect.sh", nil, nil) 296 } 297 298 func addDetectArgs(args []string, config detectExecuteScanOptions, utils detectUtils) ([]string, error) { 299 detectVersionName := getVersionName(config) 300 //Split on spaces, the scanPropeties, so that each property is available as a single string 301 //instead of all properties being part of a single string 302 config.ScanProperties = piperutils.SplitAndTrim(config.ScanProperties, " ") 303 304 if config.ScanOnChanges { 305 args = append(args, "--report") 306 config.Unmap = false 307 } 308 309 if config.Unmap { 310 if !piperutils.ContainsString(config.ScanProperties, "--detect.project.codelocation.unmap=true") { 311 args = append(args, fmt.Sprintf("--detect.project.codelocation.unmap=true")) 312 } 313 config.ScanProperties, _ = piperutils.RemoveAll(config.ScanProperties, "--detect.project.codelocation.unmap=false") 314 } else { 315 //When unmap is set to false, any occurances of unmap=true from scanProperties must be removed 316 config.ScanProperties, _ = piperutils.RemoveAll(config.ScanProperties, "--detect.project.codelocation.unmap=true") 317 } 318 319 args = append(args, config.ScanProperties...) 320 321 args = append(args, fmt.Sprintf("--blackduck.url=%v", config.ServerURL)) 322 args = append(args, fmt.Sprintf("--blackduck.api.token=%v", config.Token)) 323 // ProjectNames, VersionName, GroupName etc can contain spaces and need to be escaped using double quotes in CLI 324 // Hence the string need to be surrounded by \" 325 args = append(args, fmt.Sprintf("\"--detect.project.name='%v'\"", config.ProjectName)) 326 args = append(args, fmt.Sprintf("\"--detect.project.version.name='%v'\"", detectVersionName)) 327 328 // Groups parameter is added only when there is atleast one non-empty groupname provided 329 if len(config.Groups) > 0 && len(config.Groups[0]) > 0 { 330 args = append(args, fmt.Sprintf("\"--detect.project.user.groups='%v'\"", strings.Join(config.Groups, ","))) 331 } 332 333 // Atleast 1, non-empty category to fail on must be provided 334 if len(config.FailOn) > 0 && len(config.FailOn[0]) > 0 { 335 args = append(args, fmt.Sprintf("--detect.policy.check.fail.on.severities=%v", strings.Join(config.FailOn, ","))) 336 } 337 338 codelocation := config.CodeLocation 339 if len(codelocation) == 0 && len(config.ProjectName) > 0 { 340 codelocation = fmt.Sprintf("%v/%v", config.ProjectName, detectVersionName) 341 } 342 args = append(args, fmt.Sprintf("\"--detect.code.location.name='%v'\"", codelocation)) 343 344 if len(config.ScanPaths) > 0 && len(config.ScanPaths[0]) > 0 { 345 args = append(args, fmt.Sprintf("--detect.blackduck.signature.scanner.paths=%v", strings.Join(config.ScanPaths, ","))) 346 } 347 348 if len(config.DependencyPath) > 0 { 349 args = append(args, fmt.Sprintf("--detect.source.path=%v", config.DependencyPath)) 350 } else { 351 args = append(args, fmt.Sprintf("--detect.source.path='.'")) 352 } 353 354 if len(config.IncludedPackageManagers) > 0 { 355 args = append(args, fmt.Sprintf("--detect.included.detector.types=%v", strings.ToUpper(strings.Join(config.IncludedPackageManagers, ",")))) 356 } 357 358 if len(config.ExcludedPackageManagers) > 0 { 359 args = append(args, fmt.Sprintf("--detect.excluded.detector.types=%v", strings.ToUpper(strings.Join(config.ExcludedPackageManagers, ",")))) 360 } 361 362 if len(config.MavenExcludedScopes) > 0 { 363 args = append(args, fmt.Sprintf("--detect.maven.excluded.scopes=%v", strings.ToLower(strings.Join(config.MavenExcludedScopes, ",")))) 364 } 365 366 if len(config.DetectTools) > 0 { 367 args = append(args, fmt.Sprintf("--detect.tools=%v", strings.Join(config.DetectTools, ","))) 368 } 369 370 mavenArgs, err := maven.DownloadAndGetMavenParameters(config.GlobalSettingsFile, config.ProjectSettingsFile, utils) 371 if err != nil { 372 return nil, err 373 } 374 375 if len(config.M2Path) > 0 { 376 absolutePath, err := utils.Abs(config.M2Path) 377 if err != nil { 378 return nil, err 379 } 380 mavenArgs = append(mavenArgs, fmt.Sprintf("-Dmaven.repo.local=%v", absolutePath)) 381 } 382 383 if len(mavenArgs) > 0 { 384 args = append(args, fmt.Sprintf("\"--detect.maven.build.command='%v'\"", strings.Join(mavenArgs, " "))) 385 } 386 387 return args, nil 388 } 389 390 func getVersionName(config detectExecuteScanOptions) string { 391 detectVersionName := config.CustomScanVersion 392 if len(detectVersionName) > 0 { 393 log.Entry().Infof("Using custom version: %v", detectVersionName) 394 } else { 395 detectVersionName = versioning.ApplyVersioningModel(config.VersioningModel, config.Version) 396 } 397 return detectVersionName 398 } 399 400 func createVulnerabilityReport(config detectExecuteScanOptions, vulns *bd.Vulnerabilities, influx *detectExecuteScanInflux, sys *blackduckSystem) reporting.ScanReport { 401 versionName := getVersionName(config) 402 versionUrl, _ := sys.Client.GetProjectVersionLink(config.ProjectName, versionName) 403 scanReport := reporting.ScanReport{ 404 ReportTitle: "BlackDuck Security Vulnerability Report", 405 Subheaders: []reporting.Subheader{ 406 {Description: "BlackDuck Project Name ", Details: config.ProjectName}, 407 {Description: "BlackDuck Project Version ", Details: fmt.Sprintf("<a href='%v'>%v</a>", versionUrl, versionName)}, 408 }, 409 Overview: []reporting.OverviewRow{ 410 {Description: "Total number of vulnerabilities ", Details: fmt.Sprint(influx.detect_data.fields.vulnerabilities)}, 411 {Description: "Total number of Critical/High vulnerabilties ", Details: fmt.Sprint(influx.detect_data.fields.major_vulnerabilities)}, 412 }, 413 SuccessfulScan: influx.detect_data.fields.major_vulnerabilities == 0, 414 ReportTime: time.Now(), 415 } 416 417 detailTable := reporting.ScanDetailTable{ 418 NoRowsMessage: "No publicly known vulnerabilities detected", 419 Headers: []string{ 420 "Vulnerability Name", 421 "Severity", 422 "Overall Score", 423 "Base Score", 424 "Component Name", 425 "Component Version", 426 "Description", 427 "Status", 428 }, 429 WithCounter: true, 430 CounterHeader: "Entry#", 431 } 432 433 vulnItems := vulns.Items 434 sort.Slice(vulnItems, func(i, j int) bool { 435 return vulnItems[i].OverallScore > vulnItems[j].OverallScore 436 }) 437 438 for _, vuln := range vulnItems { 439 row := reporting.ScanRow{} 440 row.AddColumn(vuln.VulnerabilityWithRemediation.VulnerabilityName, 0) 441 row.AddColumn(vuln.VulnerabilityWithRemediation.Severity, 0) 442 443 var scoreStyle reporting.ColumnStyle = reporting.Yellow 444 if isMajorVulnerability(vuln) { 445 scoreStyle = reporting.Red 446 } 447 if !isActiveVulnerability(vuln) { 448 scoreStyle = reporting.Grey 449 } 450 row.AddColumn(vuln.VulnerabilityWithRemediation.OverallScore, scoreStyle) 451 row.AddColumn(vuln.VulnerabilityWithRemediation.BaseScore, 0) 452 row.AddColumn(vuln.Name, 0) 453 row.AddColumn(vuln.Version, 0) 454 row.AddColumn(vuln.VulnerabilityWithRemediation.Description, 0) 455 row.AddColumn(vuln.VulnerabilityWithRemediation.RemediationStatus, 0) 456 457 detailTable.Rows = append(detailTable.Rows, row) 458 } 459 460 scanReport.DetailTable = detailTable 461 return scanReport 462 } 463 464 func isActiveVulnerability(v bd.Vulnerability) bool { 465 switch v.VulnerabilityWithRemediation.RemediationStatus { 466 case "NEW": 467 return true 468 case "REMEDIATION_REQUIRED": 469 return true 470 case "NEEDS_REVIEW": 471 return true 472 default: 473 return false 474 } 475 } 476 477 func isMajorVulnerability(v bd.Vulnerability) bool { 478 switch v.VulnerabilityWithRemediation.Severity { 479 case "CRITICAL": 480 return true 481 case "HIGH": 482 return true 483 default: 484 return false 485 } 486 } 487 488 func postScanChecksAndReporting(config detectExecuteScanOptions, influx *detectExecuteScanInflux, utils detectUtils, sys *blackduckSystem) error { 489 errorsOccured := []string{} 490 vulns, _, err := getVulnsAndComponents(config, influx, sys) 491 if err != nil { 492 return errors.Wrap(err, "failed to fetch vulnerabilities") 493 } 494 495 if config.CreateResultIssue && len(config.GithubToken) > 0 && len(config.GithubAPIURL) > 0 && len(config.Owner) > 0 && len(config.Repository) > 0 { 496 log.Entry().Debugf("Creating result issues for %v alert(s)", len(vulns.Items)) 497 issueDetails := make([]reporting.IssueDetail, len(vulns.Items)) 498 piperutils.CopyAtoB(vulns.Items, issueDetails) 499 err = reporting.UploadMultipleReportsToGithub(&issueDetails, config.GithubToken, config.GithubAPIURL, config.Owner, config.Repository, config.Assignees, config.CustomTLSCertificateLinks, utils) 500 if err != nil { 501 errorsOccured = append(errorsOccured, fmt.Sprint(err)) 502 } 503 } 504 505 sarif := bd.CreateSarifResultFile(vulns) 506 paths, err := bd.WriteSarifFile(sarif, utils) 507 if err != nil { 508 errorsOccured = append(errorsOccured, fmt.Sprint(err)) 509 } 510 511 scanReport := createVulnerabilityReport(config, vulns, influx, sys) 512 vulnerabilityReportPaths, err := bd.WriteVulnerabilityReports(scanReport, utils) 513 if err != nil { 514 errorsOccured = append(errorsOccured, fmt.Sprint(err)) 515 } 516 paths = append(paths, vulnerabilityReportPaths...) 517 518 policyStatus, err := getPolicyStatus(config, influx, sys) 519 policyReport := createPolicyStatusReport(config, policyStatus, influx, sys) 520 policyReportPaths, err := writePolicyStatusReports(policyReport, config, utils) 521 if err != nil { 522 errorsOccured = append(errorsOccured, fmt.Sprint(err)) 523 } 524 paths = append(paths, policyReportPaths...) 525 526 piperutils.PersistReportsAndLinks("detectExecuteScan", "", paths, nil) 527 if err != nil { 528 errorsOccured = append(errorsOccured, fmt.Sprint(err)) 529 } 530 531 err, violationCount := writeIpPolicyJson(config, utils, paths, sys) 532 if err != nil { 533 errorsOccured = append(errorsOccured, fmt.Sprint(err)) 534 } 535 536 if violationCount > 0 { 537 log.SetErrorCategory(log.ErrorCompliance) 538 errorsOccured = append(errorsOccured, fmt.Sprint("License Policy Violations found")) 539 } 540 541 if len(errorsOccured) > 0 { 542 return fmt.Errorf(strings.Join(errorsOccured, ": ")) 543 } 544 545 return nil 546 } 547 548 func getVulnsAndComponents(config detectExecuteScanOptions, influx *detectExecuteScanInflux, sys *blackduckSystem) (*bd.Vulnerabilities, *bd.Components, error) { 549 detectVersionName := getVersionName(config) 550 vulns, err := sys.Client.GetVulnerabilities(config.ProjectName, detectVersionName) 551 if err != nil { 552 return nil, nil, err 553 } 554 555 majorVulns := 0 556 activeVulns := 0 557 for _, vuln := range vulns.Items { 558 if isActiveVulnerability(vuln) { 559 activeVulns++ 560 if isMajorVulnerability(vuln) { 561 majorVulns++ 562 } 563 } 564 } 565 influx.detect_data.fields.vulnerabilities = activeVulns 566 influx.detect_data.fields.major_vulnerabilities = majorVulns 567 influx.detect_data.fields.minor_vulnerabilities = activeVulns - majorVulns 568 569 components, err := sys.Client.GetComponents(config.ProjectName, detectVersionName) 570 if err != nil { 571 return vulns, nil, err 572 } 573 influx.detect_data.fields.components = components.TotalCount 574 575 return vulns, components, nil 576 } 577 578 func getPolicyStatus(config detectExecuteScanOptions, influx *detectExecuteScanInflux, sys *blackduckSystem) (*bd.PolicyStatus, error) { 579 policyStatus, err := sys.Client.GetPolicyStatus(config.ProjectName, getVersionName(config)) 580 if err != nil { 581 return nil, err 582 } 583 584 totalViolations := 0 585 for _, level := range policyStatus.SeverityLevels { 586 totalViolations += level.Value 587 } 588 influx.detect_data.fields.policy_violations = totalViolations 589 590 return policyStatus, nil 591 } 592 593 func createPolicyStatusReport(config detectExecuteScanOptions, policyStatus *bd.PolicyStatus, influx *detectExecuteScanInflux, sys *blackduckSystem) reporting.ScanReport { 594 versionName := getVersionName(config) 595 versionUrl, _ := sys.Client.GetProjectVersionLink(config.ProjectName, versionName) 596 policyReport := reporting.ScanReport{ 597 ReportTitle: "BlackDuck Policy Violations Report", 598 Subheaders: []reporting.Subheader{ 599 {Description: "BlackDuck project name ", Details: config.ProjectName}, 600 {Description: "BlackDuck project version name", Details: fmt.Sprintf("<a href='%v'>%v</a>", versionUrl, versionName)}, 601 }, 602 Overview: []reporting.OverviewRow{ 603 {Description: "Overall Policy Violation Status", Details: policyStatus.OverallStatus}, 604 {Description: "Total Number of Policy Vioaltions", Details: fmt.Sprint(influx.detect_data.fields.policy_violations)}, 605 }, 606 SuccessfulScan: influx.detect_data.fields.policy_violations > 0, 607 ReportTime: time.Now(), 608 } 609 610 detailTable := reporting.ScanDetailTable{ 611 Headers: []string{ 612 "Policy Severity Level", "Number of Components in Violation", 613 }, 614 WithCounter: false, 615 } 616 617 for _, level := range policyStatus.SeverityLevels { 618 row := reporting.ScanRow{} 619 row.AddColumn(level.Name, 0) 620 row.AddColumn(level.Value, 0) 621 detailTable.Rows = append(detailTable.Rows, row) 622 } 623 policyReport.DetailTable = detailTable 624 625 return policyReport 626 } 627 628 func writePolicyStatusReports(scanReport reporting.ScanReport, config detectExecuteScanOptions, utils detectUtils) ([]piperutils.Path, error) { 629 reportPaths := []piperutils.Path{} 630 631 htmlReport, _ := scanReport.ToHTML() 632 htmlReportPath := "piper_detect_policy_violation_report.html" 633 if err := utils.FileWrite(htmlReportPath, htmlReport, 0666); err != nil { 634 log.SetErrorCategory(log.ErrorConfiguration) 635 return reportPaths, errors.Wrapf(err, "failed to write html report") 636 } 637 reportPaths = append(reportPaths, piperutils.Path{Name: "BlackDuck Policy Violation Report", Target: htmlReportPath}) 638 639 jsonReport, _ := scanReport.ToJSON() 640 if exists, _ := utils.DirExists(reporting.StepReportDirectory); !exists { 641 err := utils.MkdirAll(reporting.StepReportDirectory, 0777) 642 if err != nil { 643 return reportPaths, errors.Wrap(err, "failed to create reporting directory") 644 } 645 } 646 if err := utils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("detectExecuteScan_policy_%v.json", fmt.Sprintf("%v", time.Now()))), jsonReport, 0666); err != nil { 647 return reportPaths, errors.Wrapf(err, "failed to write json report") 648 } 649 650 return reportPaths, nil 651 } 652 653 func writeIpPolicyJson(config detectExecuteScanOptions, utils detectUtils, paths []piperutils.Path, sys *blackduckSystem) (error, int) { 654 components, err := sys.Client.GetComponentsWithLicensePolicyRule(config.ProjectName, getVersionName(config)) 655 if err != nil { 656 errors.Wrapf(err, "failed to get License Policy Violations") 657 return err, 0 658 } 659 660 violationCount := getActivePolicyViolations(components) 661 violations := struct { 662 PolicyViolations int `json:"policyViolations"` 663 Reports []string `json:"reports"` 664 }{ 665 PolicyViolations: violationCount, 666 Reports: []string{}, 667 } 668 669 for _, path := range paths { 670 violations.Reports = append(violations.Reports, path.Target) 671 } 672 if files, err := utils.Glob("**/*BlackDuck_RiskReport.pdf"); err == nil && len(files) > 0 { 673 // there should only be one RiskReport thus only taking the first one 674 _, reportFile := filepath.Split(files[0]) 675 violations.Reports = append(violations.Reports, reportFile) 676 } 677 678 violationContent, err := json.Marshal(violations) 679 if err != nil { 680 return fmt.Errorf("failed to marshal policy violation data: %w", err), violationCount 681 } 682 683 err = utils.FileWrite("blackduck-ip.json", violationContent, 0666) 684 if err != nil { 685 return fmt.Errorf("failed to write policy violation report: %w", err), violationCount 686 } 687 return nil, violationCount 688 } 689 690 func getActivePolicyViolations(components *bd.Components) int { 691 if components.TotalCount == 0 { 692 return 0 693 } 694 activeViolations := 0 695 for _, component := range components.Items { 696 if isActivePolicyViolation(component.PolicyStatus) { 697 activeViolations++ 698 } 699 } 700 return activeViolations 701 } 702 703 func isActivePolicyViolation(status string) bool { 704 if status == "IN_VIOLATION" { 705 return true 706 } 707 return false 708 } 709 710 // create toolrecord file for detectExecute 711 func createToolRecordDetect(workspace string, config detectExecuteScanOptions, sys *blackduckSystem) (string, error) { 712 record := toolrecord.New(workspace, "detectExecute", config.ServerURL) 713 project, err := sys.Client.GetProject(config.ProjectName) 714 if err != nil { 715 return "", fmt.Errorf("TR_DETECT: GetProject failed %v", err) 716 } 717 metadata := project.Metadata 718 projectURL := metadata.Href 719 if projectURL == "" { 720 return "", fmt.Errorf("TR_DETECT: no project URL") 721 } 722 // project UUID comes as last part of the URL 723 parts := strings.Split(projectURL, "/") 724 projectId := parts[len(parts)-1] 725 if projectId == "" { 726 return "", fmt.Errorf("TR_DETECT: no project id in %v", projectURL) 727 } 728 err = record.AddKeyData("project", 729 projectId, 730 config.ProjectName, 731 projectURL) 732 if err != nil { 733 return "", err 734 } 735 record.AddContext("DetectTools", config.DetectTools) 736 err = record.Persist() 737 if err != nil { 738 return "", err 739 } 740 return record.GetFileName(), nil 741 }