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  }