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  }