github.com/jaylevin/jenkins-library@v1.230.4/cmd/checkmarxExecuteScan.go (about)

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