github.com/SAP/jenkins-library@v1.362.0/cmd/checkmarxOneExecuteScan.go (about)

     1  package cmd
     2  
     3  import (
     4  	"archive/zip"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"math"
     9  	"os"
    10  	"path/filepath"
    11  	"regexp"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	checkmarxOne "github.com/SAP/jenkins-library/pkg/checkmarxone"
    18  	piperGithub "github.com/SAP/jenkins-library/pkg/github"
    19  	piperHttp "github.com/SAP/jenkins-library/pkg/http"
    20  	"github.com/SAP/jenkins-library/pkg/log"
    21  	"github.com/SAP/jenkins-library/pkg/piperutils"
    22  	"github.com/SAP/jenkins-library/pkg/reporting"
    23  	"github.com/SAP/jenkins-library/pkg/telemetry"
    24  	"github.com/SAP/jenkins-library/pkg/toolrecord"
    25  	"github.com/bmatcuk/doublestar"
    26  	"github.com/google/go-github/v45/github"
    27  	"github.com/pkg/errors"
    28  )
    29  
    30  type checkmarxOneExecuteScanUtils interface {
    31  	FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error)
    32  	Stat(name string) (os.FileInfo, error)
    33  	Open(name string) (*os.File, error)
    34  	WriteFile(filename string, data []byte, perm os.FileMode) error
    35  	MkdirAll(path string, perm os.FileMode) error
    36  	PathMatch(pattern, name string) (bool, error)
    37  	GetWorkspace() string
    38  	GetIssueService() *github.IssuesService
    39  	GetSearchService() *github.SearchService
    40  }
    41  
    42  type checkmarxOneExecuteScanHelper struct {
    43  	ctx     context.Context
    44  	config  checkmarxOneExecuteScanOptions
    45  	sys     checkmarxOne.System
    46  	influx  *checkmarxOneExecuteScanInflux
    47  	utils   checkmarxOneExecuteScanUtils
    48  	Project *checkmarxOne.Project
    49  	Group   *checkmarxOne.Group
    50  	App     *checkmarxOne.Application
    51  	reports []piperutils.Path
    52  }
    53  
    54  type checkmarxOneExecuteScanUtilsBundle struct {
    55  	workspace string
    56  	issues    *github.IssuesService
    57  	search    *github.SearchService
    58  }
    59  
    60  func checkmarxOneExecuteScan(config checkmarxOneExecuteScanOptions, _ *telemetry.CustomData, influx *checkmarxOneExecuteScanInflux) {
    61  	// TODO: Setup connection with Splunk, influxDB?
    62  	cx1sh, err := Authenticate(config, influx)
    63  	if err != nil {
    64  		log.Entry().WithError(err).Fatalf("failed to create Cx1 client: %s", err)
    65  	}
    66  
    67  	err = runStep(config, influx, &cx1sh)
    68  	if err != nil {
    69  		log.Entry().WithError(err).Fatalf("Failed to run CheckmarxOne scan.")
    70  	}
    71  	influx.step_data.fields.checkmarxOne = true
    72  }
    73  
    74  func runStep(config checkmarxOneExecuteScanOptions, influx *checkmarxOneExecuteScanInflux, cx1sh *checkmarxOneExecuteScanHelper) error {
    75  	err := error(nil)
    76  	cx1sh.Project, err = cx1sh.GetProjectByName()
    77  	if err != nil && err.Error() != "project not found" {
    78  		return fmt.Errorf("failed to get project: %s", err)
    79  	}
    80  
    81  	cx1sh.Group, err = cx1sh.GetGroup() // used when creating a project and when generating a SARIF report
    82  	if err != nil {
    83  		log.Entry().WithError(err).Warnf("failed to get group")
    84  	}
    85  
    86  	if cx1sh.Project == nil {
    87  		cx1sh.App, err = cx1sh.GetApplication() // read application name from piper config (optional) and get ID from CxONE API
    88  		if err != nil {
    89  			log.Entry().WithError(err).Warnf("Failed to get application - will attempt to create the project on the Tenant level")
    90  		}
    91  		cx1sh.Project, err = cx1sh.CreateProject() // requires groups, repoUrl, mainBranch, origin, tags, criticality
    92  		if err != nil {
    93  			return fmt.Errorf("failed to create project: %s", err)
    94  		}
    95  	} else {
    96  		cx1sh.Project, err = cx1sh.GetProjectByID(cx1sh.Project.ProjectID)
    97  		if err != nil {
    98  			return fmt.Errorf("failed to get project by ID: %s", err)
    99  		} else {
   100  			if len(cx1sh.Project.Applications) > 0 {
   101  				appId := cx1sh.Project.Applications[0]
   102  				cx1sh.App, err = cx1sh.GetApplicationByID(cx1sh.Project.Applications[0])
   103  				if err != nil {
   104  					return fmt.Errorf("failed to retrieve information for project's assigned application %v", appId)
   105  				}
   106  			}
   107  		}
   108  	}
   109  
   110  	err = cx1sh.SetProjectPreset()
   111  	if err != nil {
   112  		return fmt.Errorf("failed to set preset: %s", err)
   113  	}
   114  
   115  	scans, err := cx1sh.GetLastScans(10)
   116  	if err != nil {
   117  		log.Entry().WithError(err).Warnf("failed to get last 10 scans")
   118  	}
   119  
   120  	if config.VerifyOnly {
   121  		if len(scans) > 0 {
   122  			results, err := cx1sh.ParseResults(&scans[0]) // incl report-gen
   123  			if err != nil {
   124  				return fmt.Errorf("failed to get scan results: %s", err)
   125  			}
   126  
   127  			err = cx1sh.CheckCompliance(&scans[0], &results)
   128  			if err != nil {
   129  				log.SetErrorCategory(log.ErrorCompliance)
   130  				return fmt.Errorf("project %v not compliant: %s", cx1sh.Project.Name, err)
   131  			}
   132  
   133  			return nil
   134  		} else {
   135  			log.Entry().Warnf("Cannot load scans for project %v, verification only mode aborted", cx1sh.Project.Name)
   136  		}
   137  	}
   138  
   139  	incremental, err := cx1sh.IncrementalOrFull(scans) // requires: scan list
   140  	if err != nil {
   141  		return fmt.Errorf("failed to determine incremental or full scan configuration: %s", err)
   142  	}
   143  
   144  	if config.Incremental {
   145  		log.Entry().Warnf("If you change your file filter pattern it is recommended to run a Full scan instead of an incremental, to ensure full code coverage.")
   146  	}
   147  
   148  	zipFile, err := cx1sh.ZipFiles()
   149  	if err != nil {
   150  		return fmt.Errorf("failed to create zip file: %s", err)
   151  	}
   152  
   153  	uploadLink, err := cx1sh.UploadScanContent(zipFile) // POST /api/uploads + PUT /{uploadLink}
   154  	if err != nil {
   155  		return fmt.Errorf("failed to get upload URL: %s", err)
   156  	}
   157  
   158  	// TODO : The step structure should allow to enable different scanners: SAST, KICKS, SCA
   159  	scan, err := cx1sh.CreateScanRequest(incremental, uploadLink)
   160  	if err != nil {
   161  		return fmt.Errorf("failed to create scan: %s", err)
   162  	}
   163  
   164  	// TODO: how to provide other scan parameters like engineConfiguration?
   165  	// TODO: potential to persist file exclusions for git?
   166  	err = cx1sh.PollScanStatus(scan)
   167  	if err != nil {
   168  		return fmt.Errorf("failed while polling scan status: %s", err)
   169  	}
   170  
   171  	results, err := cx1sh.ParseResults(scan) // incl report-gen
   172  	if err != nil {
   173  		return fmt.Errorf("failed to get scan results: %s", err)
   174  	}
   175  	err = cx1sh.CheckCompliance(scan, &results)
   176  	if err != nil {
   177  		log.SetErrorCategory(log.ErrorCompliance)
   178  		return fmt.Errorf("project %v not compliant: %s", cx1sh.Project.Name, err)
   179  	}
   180  	// TODO: upload logs to Splunk, influxDB?
   181  	return nil
   182  
   183  }
   184  
   185  func Authenticate(config checkmarxOneExecuteScanOptions, influx *checkmarxOneExecuteScanInflux) (checkmarxOneExecuteScanHelper, error) {
   186  	client := &piperHttp.Client{}
   187  
   188  	ctx, ghClient, err := piperGithub.NewClientBuilder(config.GithubToken, config.GithubAPIURL).Build()
   189  	if err != nil {
   190  		log.Entry().WithError(err).Warning("Failed to get GitHub client")
   191  	}
   192  	sys, err := checkmarxOne.NewSystemInstance(client, config.ServerURL, config.IamURL, config.Tenant, config.APIKey, config.ClientID, config.ClientSecret)
   193  	if err != nil {
   194  		return checkmarxOneExecuteScanHelper{}, fmt.Errorf("failed to create Checkmarx One client talking to URLs %v and %v with tenant %v: %s", config.ServerURL, config.IamURL, config.Tenant, err)
   195  	}
   196  	influx.step_data.fields.checkmarxOne = false
   197  
   198  	utils := newcheckmarxOneExecuteScanUtilsBundle("./", ghClient)
   199  
   200  	return checkmarxOneExecuteScanHelper{ctx, config, sys, influx, utils, nil, nil, nil, []piperutils.Path{}}, nil
   201  }
   202  
   203  func (c *checkmarxOneExecuteScanHelper) GetProjectByName() (*checkmarxOne.Project, error) {
   204  	if len(c.config.ProjectName) == 0 {
   205  		log.Entry().Fatalf("No project name set in the configuration")
   206  	}
   207  
   208  	// get the Project, if it exists
   209  	projects, err := c.sys.GetProjectsByName(c.config.ProjectName)
   210  	if err != nil {
   211  		return nil, fmt.Errorf("error when trying to load project: %s", err)
   212  	}
   213  
   214  	for _, p := range projects {
   215  		if p.Name == c.config.ProjectName {
   216  			return &p, nil
   217  		}
   218  	}
   219  	return nil, fmt.Errorf("project not found")
   220  }
   221  
   222  func (c *checkmarxOneExecuteScanHelper) GetProjectByID(projectId string) (*checkmarxOne.Project, error) {
   223  	project, err := c.sys.GetProjectByID(projectId)
   224  	return &project, err
   225  }
   226  
   227  func (c *checkmarxOneExecuteScanHelper) GetGroup() (*checkmarxOne.Group, error) {
   228  	if len(c.config.GroupName) > 0 {
   229  		group, err := c.sys.GetGroupByName(c.config.GroupName)
   230  		if err != nil {
   231  			return nil, fmt.Errorf("Failed to get Checkmarx One group by Name %v: %s", c.config.GroupName, err)
   232  		}
   233  		return &group, nil
   234  	}
   235  	return nil, fmt.Errorf("No group name specified in configuration")
   236  }
   237  
   238  func (c *checkmarxOneExecuteScanHelper) GetApplication() (*checkmarxOne.Application, error) {
   239  	if len(c.config.ApplicationName) > 0 {
   240  		app, err := c.sys.GetApplicationByName(c.config.ApplicationName)
   241  		if err != nil {
   242  			return nil, fmt.Errorf("Failed to get Checkmarx One application by Name %v: %s", c.config.ApplicationName, err)
   243  		}
   244  
   245  		return &app, nil
   246  	}
   247  	return nil, fmt.Errorf("No application name specified in configuration")
   248  }
   249  
   250  func (c *checkmarxOneExecuteScanHelper) GetApplicationByID(applicationId string) (*checkmarxOne.Application, error) {
   251  	app, err := c.sys.GetApplicationByID(applicationId)
   252  	if err != nil {
   253  		return nil, fmt.Errorf("Failed to get Checkmarx One application by Name %v: %s", c.config.ApplicationName, err)
   254  	}
   255  
   256  	return &app, nil
   257  }
   258  
   259  func (c *checkmarxOneExecuteScanHelper) CreateProject() (*checkmarxOne.Project, error) {
   260  	if len(c.config.Preset) == 0 {
   261  		return nil, fmt.Errorf("Preset is required to create a project")
   262  	}
   263  
   264  	var project checkmarxOne.Project
   265  	var err error
   266  	var groupIDs []string = []string{}
   267  	if c.Group != nil {
   268  		groupIDs = []string{c.Group.GroupID}
   269  	}
   270  
   271  	if c.App != nil {
   272  		project, err = c.sys.CreateProjectInApplication(c.config.ProjectName, c.App.ApplicationID, groupIDs)
   273  	} else {
   274  		project, err = c.sys.CreateProject(c.config.ProjectName, groupIDs)
   275  	}
   276  
   277  	if err != nil {
   278  		return nil, fmt.Errorf("Error when trying to create project: %s", err)
   279  	}
   280  	log.Entry().Infof("Project %v created", project.ProjectID)
   281  
   282  	// new project, set the defaults per pipeline config
   283  	err = c.sys.SetProjectPreset(project.ProjectID, c.config.Preset, true)
   284  	if err != nil {
   285  		return nil, fmt.Errorf("Unable to set preset for project %v to %v: %s", project.ProjectID, c.config.Preset, err)
   286  	}
   287  	log.Entry().Infof("Project preset updated to %v", c.config.Preset)
   288  
   289  	if len(c.config.LanguageMode) != 0 {
   290  		err = c.sys.SetProjectLanguageMode(project.ProjectID, c.config.LanguageMode, true)
   291  		if err != nil {
   292  
   293  			return nil, fmt.Errorf("Unable to set languageMode for project %v to %v: %s", project.ProjectID, c.config.LanguageMode, err)
   294  		}
   295  		log.Entry().Infof("Project languageMode updated to %v", c.config.LanguageMode)
   296  	}
   297  
   298  	return &project, nil
   299  }
   300  
   301  func (c *checkmarxOneExecuteScanHelper) SetProjectPreset() error {
   302  	projectConf, err := c.sys.GetProjectConfiguration(c.Project.ProjectID)
   303  
   304  	if err != nil {
   305  		return fmt.Errorf("Failed to retrieve current project configuration: %s", err)
   306  	}
   307  
   308  	currentPreset := ""
   309  	currentLanguageMode := "multi" // piper default
   310  	for _, conf := range projectConf {
   311  		if conf.Key == "scan.config.sast.presetName" {
   312  			currentPreset = conf.Value
   313  		}
   314  		if conf.Key == "scan.config.sast.languageMode" {
   315  			currentLanguageMode = conf.Value
   316  		}
   317  	}
   318  
   319  	if c.config.LanguageMode == "" || strings.EqualFold(c.config.LanguageMode, "multi") { // default multi if blank
   320  		if currentLanguageMode != "multi" {
   321  			log.Entry().Info("Pipeline yaml requests multi-language scan - updating project configuration")
   322  			c.sys.SetProjectLanguageMode(c.Project.ProjectID, "multi", true)
   323  
   324  			if c.config.Incremental {
   325  				log.Entry().Warn("Pipeline yaml requests incremental scan, but switching from 'primary' to 'multi' language mode requires a full scan - switching from incremental to full")
   326  				c.config.Incremental = false
   327  			}
   328  		}
   329  	} else { // primary language mode
   330  		if currentLanguageMode != "primary" {
   331  			log.Entry().Info("Pipeline yaml requests primary-language scan - updating project configuration")
   332  			c.sys.SetProjectLanguageMode(c.Project.ProjectID, "primary", true)
   333  			// no need to switch incremental to full here (multi-language scan includes single-language scan coverage)
   334  		}
   335  	}
   336  
   337  	if c.config.Preset == "" {
   338  		if currentPreset == "" {
   339  			return fmt.Errorf("must specify the preset in either the pipeline yaml or in the CheckmarxOne project configuration")
   340  		} else {
   341  			log.Entry().Infof("Pipeline yaml does not specify a preset, will use project configuration (%v).", currentPreset)
   342  		}
   343  		c.config.Preset = currentPreset
   344  	} else if currentPreset != c.config.Preset {
   345  		log.Entry().Infof("Project configured preset (%v) does not match pipeline yaml (%v) - updating project configuration.", currentPreset, c.config.Preset)
   346  		c.sys.SetProjectPreset(c.Project.ProjectID, c.config.Preset, true)
   347  
   348  		if c.config.Incremental {
   349  			log.Entry().Warn("Changing project settings requires a full scan to take effect - switching from incremental to full")
   350  			c.config.Incremental = false
   351  		}
   352  	} else {
   353  		log.Entry().Infof("Project is already configured to use pipeline preset %v", currentPreset)
   354  	}
   355  	return nil
   356  }
   357  
   358  func (c *checkmarxOneExecuteScanHelper) GetLastScans(count int) ([]checkmarxOne.Scan, error) {
   359  	scans, err := c.sys.GetLastScansByStatus(c.Project.ProjectID, count, []string{"Completed"})
   360  	if err != nil {
   361  		return []checkmarxOne.Scan{}, fmt.Errorf("Failed to get last %d Completed scans for project %v: %s", count, c.Project.ProjectID, err)
   362  	}
   363  	return scans, nil
   364  }
   365  
   366  func (c *checkmarxOneExecuteScanHelper) IncrementalOrFull(scans []checkmarxOne.Scan) (bool, error) {
   367  	incremental := c.config.Incremental
   368  	fullScanCycle, err := strconv.Atoi(c.config.FullScanCycle)
   369  	if err != nil {
   370  		log.SetErrorCategory(log.ErrorConfiguration)
   371  		return false, fmt.Errorf("invalid configuration value for fullScanCycle %v, must be a positive int", c.config.FullScanCycle)
   372  	}
   373  
   374  	coherentIncrementalScans := c.getNumCoherentIncrementalScans(scans)
   375  
   376  	if c.config.IsOptimizedAndScheduled {
   377  		incremental = false
   378  	} else if incremental && c.config.FullScansScheduled && fullScanCycle > 0 && (coherentIncrementalScans+1) >= fullScanCycle {
   379  		incremental = false
   380  	}
   381  
   382  	return incremental, nil
   383  }
   384  
   385  func (c *checkmarxOneExecuteScanHelper) ZipFiles() (*os.File, error) {
   386  	zipFile, err := c.zipWorkspaceFiles(c.config.FilterPattern, c.utils)
   387  	if err != nil {
   388  		return nil, fmt.Errorf("Failed to zip workspace files")
   389  	}
   390  	return zipFile, nil
   391  }
   392  
   393  func (c *checkmarxOneExecuteScanHelper) UploadScanContent(zipFile *os.File) (string, error) {
   394  	uploadUri, err := c.sys.UploadProjectSourceCode(c.Project.ProjectID, zipFile.Name())
   395  	if err != nil {
   396  		return "", fmt.Errorf("Failed to upload source code for project %v: %s", c.Project.ProjectID, err)
   397  	}
   398  
   399  	log.Entry().Debugf("Source code uploaded for project %v", c.Project.Name)
   400  	err = os.Remove(zipFile.Name())
   401  	if err != nil {
   402  		log.Entry().WithError(err).Warnf("Failed to delete zipped source code for project %v", c.Project.Name)
   403  	}
   404  	return uploadUri, nil
   405  }
   406  
   407  func (c *checkmarxOneExecuteScanHelper) CreateScanRequest(incremental bool, uploadLink string) (*checkmarxOne.Scan, error) {
   408  	sastConfig := checkmarxOne.ScanConfiguration{}
   409  	sastConfig.ScanType = "sast"
   410  
   411  	sastConfig.Values = make(map[string]string, 0)
   412  	sastConfig.Values["incremental"] = strconv.FormatBool(incremental)
   413  	sastConfig.Values["presetName"] = c.config.Preset // always set, either coming from config or coming from Cx1 configuration
   414  	sastConfigString := fmt.Sprintf("incremental %v, preset %v", strconv.FormatBool(incremental), c.config.Preset)
   415  
   416  	if len(c.config.LanguageMode) > 0 {
   417  		sastConfig.Values["languageMode"] = c.config.LanguageMode
   418  		sastConfigString = sastConfigString + fmt.Sprintf(", languageMode %v", c.config.LanguageMode)
   419  	}
   420  
   421  	branch := c.config.Branch
   422  	if len(branch) == 0 && len(c.config.GitBranch) > 0 {
   423  		branch = c.config.GitBranch
   424  	}
   425  	if len(c.config.PullRequestName) > 0 {
   426  		branch = fmt.Sprintf("%v-%v", c.config.PullRequestName, c.config.Branch)
   427  	}
   428  
   429  	sastConfigString = fmt.Sprintf("Cx1 Branch name %v, ", branch) + sastConfigString
   430  
   431  	log.Entry().Infof("Will run a scan with the following configuration: %v", sastConfigString)
   432  
   433  	configs := []checkmarxOne.ScanConfiguration{sastConfig}
   434  	// add more engines
   435  
   436  	scan, err := c.sys.ScanProjectZip(c.Project.ProjectID, uploadLink, branch, configs)
   437  
   438  	if err != nil {
   439  		return nil, fmt.Errorf("Failed to run scan on project %v: %s", c.Project.Name, err)
   440  	}
   441  
   442  	log.Entry().Debugf("Scanning project %v: %v ", c.Project.Name, scan.ScanID)
   443  
   444  	return &scan, nil
   445  }
   446  
   447  func (c *checkmarxOneExecuteScanHelper) PollScanStatus(scan *checkmarxOne.Scan) error {
   448  	statusDetails := "Scan phase: New"
   449  	pastStatusDetails := statusDetails
   450  	log.Entry().Info(statusDetails)
   451  	status := "New"
   452  	for {
   453  		scan_refresh, err := c.sys.GetScan(scan.ScanID)
   454  
   455  		if err != nil {
   456  			return fmt.Errorf("Error while polling scan %v: %s", scan.ScanID, err)
   457  		}
   458  
   459  		status = scan_refresh.Status
   460  		workflow, err := c.sys.GetScanWorkflow(scan.ScanID)
   461  		if err != nil {
   462  			return fmt.Errorf("Error while getting workflow for scan %v: %s", scan.ScanID, err)
   463  		}
   464  
   465  		statusDetails = workflow[len(workflow)-1].Info
   466  
   467  		if pastStatusDetails != statusDetails {
   468  			log.Entry().Info(statusDetails)
   469  			pastStatusDetails = statusDetails
   470  		}
   471  
   472  		if status == "Completed" || status == "Canceled" || status == "Failed" {
   473  			break
   474  		}
   475  
   476  		if pastStatusDetails != statusDetails {
   477  			log.Entry().Info(statusDetails)
   478  			pastStatusDetails = statusDetails
   479  		}
   480  
   481  		log.Entry().Debug("Polling for status: sleeping...")
   482  
   483  		time.Sleep(10 * time.Second)
   484  	}
   485  	if status == "Canceled" {
   486  		log.SetErrorCategory(log.ErrorCustom)
   487  		return fmt.Errorf("Scan %v canceled via web interface", scan.ScanID)
   488  	}
   489  	if status == "Failed" {
   490  		return fmt.Errorf("Checkmarx One scan failed with the following error: %v", statusDetails)
   491  	}
   492  	return nil
   493  }
   494  
   495  func (c *checkmarxOneExecuteScanHelper) CheckCompliance(scan *checkmarxOne.Scan, detailedResults *map[string]interface{}) error {
   496  
   497  	links := []piperutils.Path{{Target: (*detailedResults)["DeepLink"].(string), Name: "Checkmarx One Web UI"}}
   498  
   499  	insecure := false
   500  	var insecureResults []string
   501  	var neutralResults []string
   502  
   503  	if c.config.VulnerabilityThresholdEnabled {
   504  		insecure, insecureResults, neutralResults = c.enforceThresholds(detailedResults)
   505  		scanReport := checkmarxOne.CreateCustomReport(detailedResults, insecureResults, neutralResults)
   506  
   507  		if insecure && c.config.CreateResultIssue && len(c.config.GithubToken) > 0 && len(c.config.GithubAPIURL) > 0 && len(c.config.Owner) > 0 && len(c.config.Repository) > 0 {
   508  			log.Entry().Debug("Creating/updating GitHub issue with check results")
   509  			gh := reporting.GitHub{
   510  				Owner:         &c.config.Owner,
   511  				Repository:    &c.config.Repository,
   512  				Assignees:     &c.config.Assignees,
   513  				IssueService:  c.utils.GetIssueService(),
   514  				SearchService: c.utils.GetSearchService(),
   515  			}
   516  			if err := gh.UploadSingleReport(c.ctx, scanReport); err != nil {
   517  				return fmt.Errorf("failed to upload scan results into GitHub: %s", err)
   518  			}
   519  		}
   520  
   521  		paths, err := checkmarxOne.WriteCustomReports(scanReport, c.Project.Name, c.Project.ProjectID)
   522  		if err != nil {
   523  			// do not fail until we have a better idea to handle it
   524  			log.Entry().Warning("failed to write HTML/MarkDown report file ...", err)
   525  		} else {
   526  			c.reports = append(c.reports, paths...)
   527  		}
   528  	}
   529  
   530  	piperutils.PersistReportsAndLinks("checkmarxOneExecuteScan", c.utils.GetWorkspace(), c.utils, c.reports, links)
   531  
   532  	c.reportToInflux(detailedResults)
   533  
   534  	if insecure {
   535  		if c.config.VulnerabilityThresholdResult == "FAILURE" {
   536  			log.SetErrorCategory(log.ErrorCompliance)
   537  			return fmt.Errorf("the project is not compliant - see report for details")
   538  		}
   539  		log.Entry().Errorf("Checkmarx One scan result set to %v, some results are not meeting defined thresholds. For details see the archived report.", c.config.VulnerabilityThresholdResult)
   540  	} else {
   541  		log.Entry().Infoln("Checkmarx One scan finished successfully")
   542  	}
   543  	return nil
   544  }
   545  
   546  func (c *checkmarxOneExecuteScanHelper) GetReportPDF(scan *checkmarxOne.Scan) error {
   547  	if c.config.GeneratePdfReport {
   548  		pdfReportName := c.createReportName(c.utils.GetWorkspace(), "Cx1_SASTReport_%v.pdf")
   549  		err := c.downloadAndSaveReport(pdfReportName, scan, "pdf")
   550  		if err != nil {
   551  			return fmt.Errorf("Report download failed: %s", err)
   552  		} else {
   553  			c.reports = append(c.reports, piperutils.Path{Target: pdfReportName, Mandatory: true})
   554  		}
   555  	} else {
   556  		log.Entry().Debug("Report generation is disabled via configuration")
   557  	}
   558  
   559  	return nil
   560  }
   561  
   562  func (c *checkmarxOneExecuteScanHelper) GetReportSARIF(scan *checkmarxOne.Scan, scanmeta *checkmarxOne.ScanMetadata, results *[]checkmarxOne.ScanResult) error {
   563  	if c.config.ConvertToSarif {
   564  		log.Entry().Info("Calling conversion to SARIF function.")
   565  		sarif, err := checkmarxOne.ConvertCxJSONToSarif(c.sys, c.config.ServerURL, results, scanmeta, scan)
   566  		if err != nil {
   567  			return fmt.Errorf("Failed to generate SARIF: %s", err)
   568  		}
   569  		paths, err := checkmarxOne.WriteSarif(sarif)
   570  		if err != nil {
   571  			return fmt.Errorf("Failed to write SARIF: %s", err)
   572  		}
   573  		c.reports = append(c.reports, paths...)
   574  	}
   575  	return nil
   576  }
   577  
   578  func (c *checkmarxOneExecuteScanHelper) GetReportJSON(scan *checkmarxOne.Scan) error {
   579  	jsonReportName := c.createReportName(c.utils.GetWorkspace(), "Cx1_SASTReport_%v.json")
   580  	err := c.downloadAndSaveReport(jsonReportName, scan, "json")
   581  	if err != nil {
   582  		return fmt.Errorf("Report download failed: %s", err)
   583  	} else {
   584  		c.reports = append(c.reports, piperutils.Path{Target: jsonReportName, Mandatory: true})
   585  	}
   586  	return nil
   587  }
   588  
   589  func (c *checkmarxOneExecuteScanHelper) GetHeaderReportJSON(detailedResults *map[string]interface{}) error {
   590  	// This is for the SAP-piper-format short-form JSON report
   591  	jsonReport := checkmarxOne.CreateJSONHeaderReport(detailedResults)
   592  	paths, err := checkmarxOne.WriteJSONHeaderReport(jsonReport)
   593  	if err != nil {
   594  		return fmt.Errorf("Failed to write JSON header report: %s", err)
   595  	} else {
   596  		// add JSON report to archiving list
   597  		c.reports = append(c.reports, paths...)
   598  	}
   599  	return nil
   600  }
   601  
   602  func (c *checkmarxOneExecuteScanHelper) ParseResults(scan *checkmarxOne.Scan) (map[string]interface{}, error) {
   603  	var detailedResults map[string]interface{}
   604  
   605  	scanmeta, err := c.sys.GetScanMetadata(scan.ScanID)
   606  	if err != nil {
   607  		return detailedResults, fmt.Errorf("Unable to fetch scan metadata for scan %v: %s", scan.ScanID, err)
   608  	}
   609  
   610  	totalResultCount := uint64(0)
   611  
   612  	scansummary, err := c.sys.GetScanSummary(scan.ScanID)
   613  	if err != nil {
   614  		/* TODO: scansummary throws a 404 for 0-result scans, once the bug is fixed put this code back. */
   615  		// return detailedResults, fmt.Errorf("Unable to fetch scan summary for scan %v: %s", scan.ScanID, err)
   616  	} else {
   617  		totalResultCount = scansummary.TotalCount()
   618  	}
   619  
   620  	results, err := c.sys.GetScanResults(scan.ScanID, totalResultCount)
   621  	if err != nil {
   622  		return detailedResults, fmt.Errorf("Unable to fetch scan results for scan %v: %s", scan.ScanID, err)
   623  	}
   624  
   625  	detailedResults, err = c.getDetailedResults(scan, &scanmeta, &results)
   626  	if err != nil {
   627  		return detailedResults, fmt.Errorf("Unable to fetch detailed results for scan %v: %s", scan.ScanID, err)
   628  	}
   629  
   630  	err = c.GetReportJSON(scan)
   631  	if err != nil {
   632  		log.Entry().WithError(err).Warnf("Failed to get JSON report")
   633  	}
   634  	err = c.GetReportPDF(scan)
   635  	if err != nil {
   636  		log.Entry().WithError(err).Warnf("Failed to get PDF report")
   637  	}
   638  	err = c.GetReportSARIF(scan, &scanmeta, &results)
   639  	if err != nil {
   640  		log.Entry().WithError(err).Warnf("Failed to get SARIF report")
   641  	}
   642  	err = c.GetHeaderReportJSON(&detailedResults)
   643  	if err != nil {
   644  		log.Entry().WithError(err).Warnf("Failed to generate JSON Header report")
   645  	}
   646  
   647  	// create toolrecord
   648  	toolRecordFileName, err := c.createToolRecordCx(&detailedResults)
   649  	if err != nil {
   650  		// do not fail until the framework is well established
   651  		log.Entry().Warning("TR_CHECKMARXONE: Failed to create toolrecord file ...", err)
   652  	} else {
   653  		c.reports = append(c.reports, piperutils.Path{Target: toolRecordFileName})
   654  	}
   655  
   656  	return detailedResults, nil
   657  }
   658  
   659  func (c *checkmarxOneExecuteScanHelper) createReportName(workspace, reportFileNameTemplate string) string {
   660  	regExpFileName := regexp.MustCompile(`[^\w\d]`)
   661  	timeStamp, _ := time.Now().Local().MarshalText()
   662  	return filepath.Join(workspace, fmt.Sprintf(reportFileNameTemplate, regExpFileName.ReplaceAllString(string(timeStamp), "_")))
   663  }
   664  
   665  func (c *checkmarxOneExecuteScanHelper) downloadAndSaveReport(reportFileName string, scan *checkmarxOne.Scan, reportType string) error {
   666  	report, err := c.generateAndDownloadReport(scan, reportType)
   667  	if err != nil {
   668  		return errors.Wrap(err, "failed to download the report")
   669  	}
   670  	log.Entry().Debugf("Saving report to file %v...", reportFileName)
   671  	return c.utils.WriteFile(reportFileName, report, 0o700)
   672  }
   673  
   674  func (c *checkmarxOneExecuteScanHelper) generateAndDownloadReport(scan *checkmarxOne.Scan, reportType string) ([]byte, error) {
   675  	var finalStatus checkmarxOne.ReportStatus
   676  
   677  	report, err := c.sys.RequestNewReport(scan.ScanID, scan.ProjectID, scan.Branch, reportType)
   678  	if err != nil {
   679  		return []byte{}, errors.Wrap(err, "failed to request new report")
   680  	}
   681  	for {
   682  		finalStatus, err = c.sys.GetReportStatus(report)
   683  		if err != nil {
   684  			return []byte{}, errors.Wrap(err, "failed to get report status")
   685  		}
   686  
   687  		if finalStatus.Status == "completed" {
   688  			break
   689  		} else if finalStatus.Status == "failed" {
   690  			return []byte{}, fmt.Errorf("report generation failed")
   691  		}
   692  		time.Sleep(10 * time.Second)
   693  	}
   694  	if finalStatus.Status == "completed" {
   695  		return c.sys.DownloadReport(finalStatus.ReportURL)
   696  	}
   697  
   698  	return []byte{}, fmt.Errorf("unexpected status %v recieved", finalStatus.Status)
   699  }
   700  
   701  func (c *checkmarxOneExecuteScanHelper) getNumCoherentIncrementalScans(scans []checkmarxOne.Scan) int {
   702  	count := 0
   703  	for _, scan := range scans {
   704  		inc, err := scan.IsIncremental()
   705  		if !inc && err == nil {
   706  			break
   707  		}
   708  		count++
   709  	}
   710  	return count
   711  }
   712  
   713  func (c *checkmarxOneExecuteScanHelper) getDetailedResults(scan *checkmarxOne.Scan, scanmeta *checkmarxOne.ScanMetadata, results *[]checkmarxOne.ScanResult) (map[string]interface{}, error) {
   714  	// this converts the JSON format results from Cx1 into the "resultMap" structure used in other parts of this step (influx etc)
   715  
   716  	resultMap := map[string]interface{}{}
   717  	resultMap["InitiatorName"] = scan.Initiator
   718  	resultMap["Owner"] = "Cx1 Gap: no project owner" // TODO: check for functionality
   719  	resultMap["ScanId"] = scan.ScanID
   720  	resultMap["ProjectId"] = c.Project.ProjectID
   721  	resultMap["ProjectName"] = c.Project.Name
   722  
   723  	resultMap["Group"] = ""
   724  	resultMap["GroupFullPathOnReportDate"] = ""
   725  
   726  	if c.App != nil {
   727  		resultMap["Application"] = c.App.ApplicationID
   728  		resultMap["ApplicationFullPathOnReportDate"] = c.App.Name
   729  	} else {
   730  		resultMap["Application"] = ""
   731  		resultMap["ApplicationFullPathOnReportDate"] = ""
   732  	}
   733  
   734  	resultMap["ScanStart"] = scan.CreatedAt
   735  
   736  	scanCreated, err := time.Parse(time.RFC3339, scan.CreatedAt)
   737  	if err != nil {
   738  		log.Entry().Warningf("Failed to parse string %v into time: %s", scan.CreatedAt, err)
   739  		resultMap["ScanTime"] = "Error parsing scan.CreatedAt"
   740  	} else {
   741  		scanFinished, err := time.Parse(time.RFC3339, scan.UpdatedAt)
   742  		if err != nil {
   743  			log.Entry().Warningf("Failed to parse string %v into time: %s", scan.UpdatedAt, err)
   744  			resultMap["ScanTime"] = "Error parsing scan.UpdatedAt"
   745  		} else {
   746  			difference := scanFinished.Sub(scanCreated)
   747  			resultMap["ScanTime"] = difference.String()
   748  		}
   749  	}
   750  
   751  	resultMap["LinesOfCodeScanned"] = scanmeta.LOC
   752  	resultMap["FilesScanned"] = scanmeta.FileCount
   753  
   754  	version, err := c.sys.GetVersion()
   755  	if err != nil {
   756  		resultMap["ToolVersion"] = "Error fetching current version"
   757  	} else {
   758  		resultMap["ToolVersion"] = fmt.Sprintf("CxOne: %v, SAST: %v, KICS: %v", version.CxOne, version.SAST, version.KICS)
   759  	}
   760  
   761  	if scanmeta.IsIncremental {
   762  		resultMap["ScanType"] = "Incremental"
   763  	} else {
   764  		resultMap["ScanType"] = "Full"
   765  	}
   766  
   767  	resultMap["Preset"] = scanmeta.PresetName
   768  	resultMap["DeepLink"] = fmt.Sprintf("%v/projects/%v/overview?branch=%v", c.config.ServerURL, c.Project.ProjectID, scan.Branch)
   769  	resultMap["ReportCreationTime"] = time.Now().String()
   770  	resultMap["High"] = map[string]int{}
   771  	resultMap["Medium"] = map[string]int{}
   772  	resultMap["Low"] = map[string]int{}
   773  	resultMap["Information"] = map[string]int{}
   774  
   775  	if len(*results) > 0 {
   776  		for _, result := range *results {
   777  			key := "Information"
   778  			switch result.Severity {
   779  			case "HIGH":
   780  				key = "High"
   781  			case "MEDIUM":
   782  				key = "Medium"
   783  			case "LOW":
   784  				key = "Low"
   785  			case "INFORMATION":
   786  			default:
   787  				key = "Information"
   788  			}
   789  
   790  			var submap map[string]int
   791  			if resultMap[key] == nil {
   792  				submap = map[string]int{}
   793  				resultMap[key] = submap
   794  			} else {
   795  				submap = resultMap[key].(map[string]int)
   796  			}
   797  			submap["Issues"]++
   798  
   799  			auditState := "ToVerify"
   800  			switch result.State {
   801  			case "NOT_EXPLOITABLE":
   802  				auditState = "NotExploitable"
   803  			case "CONFIRMED":
   804  				auditState = "Confirmed"
   805  			case "URGENT", "URGENT ":
   806  				auditState = "Urgent"
   807  			case "PROPOSED_NOT_EXPLOITABLE":
   808  				auditState = "ProposedNotExploitable"
   809  			case "TO_VERIFY":
   810  			default:
   811  				auditState = "ToVerify"
   812  			}
   813  			submap[auditState]++
   814  
   815  			if auditState != "NotExploitable" {
   816  				submap["NotFalsePositive"]++
   817  			}
   818  
   819  		}
   820  
   821  		// if the flag is switched on, build the list  of Low findings per query
   822  		if c.config.VulnerabilityThresholdLowPerQuery {
   823  			var lowPerQuery = map[string]map[string]int{}
   824  
   825  			for _, result := range *results {
   826  				if result.Severity != "LOW" {
   827  					continue
   828  				}
   829  				key := result.Data.QueryName
   830  				var submap map[string]int
   831  				if lowPerQuery[key] == nil {
   832  					submap = map[string]int{}
   833  					lowPerQuery[key] = submap
   834  				} else {
   835  					submap = lowPerQuery[key]
   836  				}
   837  				submap["Issues"]++
   838  				auditState := "ToVerify"
   839  				switch result.State {
   840  				case "NOT_EXPLOITABLE":
   841  					auditState = "NotExploitable"
   842  				case "CONFIRMED":
   843  					auditState = "Confirmed"
   844  				case "URGENT", "URGENT ":
   845  					auditState = "Urgent"
   846  				case "PROPOSED_NOT_EXPLOITABLE":
   847  					auditState = "ProposedNotExploitable"
   848  				case "TO_VERIFY":
   849  				default:
   850  					auditState = "ToVerify"
   851  				}
   852  				submap[auditState]++
   853  
   854  				if auditState != "NotExploitable" {
   855  					submap["NotFalsePositive"]++
   856  				}
   857  			}
   858  
   859  			resultMap["LowPerQuery"] = lowPerQuery
   860  		}
   861  	}
   862  	return resultMap, nil
   863  }
   864  
   865  func (c *checkmarxOneExecuteScanHelper) zipWorkspaceFiles(filterPattern string, utils checkmarxOneExecuteScanUtils) (*os.File, error) {
   866  	zipFileName := filepath.Join(utils.GetWorkspace(), "workspace.zip")
   867  	patterns := piperutils.Trim(strings.Split(filterPattern, ","))
   868  	sort.Strings(patterns)
   869  	zipFile, err := os.Create(zipFileName)
   870  	if err != nil {
   871  		return zipFile, errors.Wrap(err, "failed to create archive of project sources")
   872  	}
   873  	defer zipFile.Close()
   874  
   875  	err = c.zipFolder(utils.GetWorkspace(), zipFile, patterns, utils)
   876  	if err != nil {
   877  		return nil, errors.Wrap(err, "failed to compact folder")
   878  	}
   879  	return zipFile, nil
   880  }
   881  
   882  func (c *checkmarxOneExecuteScanHelper) zipFolder(source string, zipFile io.Writer, patterns []string, utils checkmarxOneExecuteScanUtils) error {
   883  	archive := zip.NewWriter(zipFile)
   884  	defer archive.Close()
   885  
   886  	log.Entry().Infof("Zipping %v into workspace.zip", source)
   887  
   888  	info, err := utils.Stat(source)
   889  	if err != nil {
   890  		return nil
   891  	}
   892  
   893  	var baseDir string
   894  	if info.IsDir() {
   895  		baseDir = filepath.Base(source)
   896  	}
   897  
   898  	fileCount := 0
   899  	err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
   900  		if err != nil {
   901  			return err
   902  		}
   903  
   904  		if !info.Mode().IsRegular() || info.Size() == 0 {
   905  			return nil
   906  		}
   907  
   908  		noMatch, err := c.isFileNotMatchingPattern(patterns, path, info, utils)
   909  		if err != nil || noMatch {
   910  			return err
   911  		}
   912  
   913  		fileName := strings.TrimPrefix(path, baseDir)
   914  		writer, err := archive.Create(fileName)
   915  		if err != nil {
   916  			return err
   917  		}
   918  
   919  		file, err := utils.Open(path)
   920  		if err != nil {
   921  			return err
   922  		}
   923  		defer file.Close()
   924  		_, err = io.Copy(writer, file)
   925  		fileCount++
   926  		return err
   927  	})
   928  	log.Entry().Infof("Zipped %d files", fileCount)
   929  	err = c.handleZeroFilesZipped(source, err, fileCount)
   930  	return err
   931  }
   932  
   933  func (c *checkmarxOneExecuteScanHelper) adaptHeader(info os.FileInfo, header *zip.FileHeader) {
   934  	if info.IsDir() {
   935  		header.Name += "/"
   936  	} else {
   937  		header.Method = zip.Deflate
   938  	}
   939  }
   940  
   941  func (c *checkmarxOneExecuteScanHelper) handleZeroFilesZipped(source string, err error, fileCount int) error {
   942  	if err == nil && fileCount == 0 {
   943  		log.SetErrorCategory(log.ErrorConfiguration)
   944  		err = fmt.Errorf("filterPattern matched no files or workspace directory '%s' was empty", source)
   945  	}
   946  	return err
   947  }
   948  
   949  // isFileNotMatchingPattern checks if file path does not match one of the patterns.
   950  // If it matches a negative pattern (starting with '!') then true is returned.
   951  //
   952  // If it is a directory, false is returned.
   953  // If no patterns are provided, false is returned.
   954  func (c *checkmarxOneExecuteScanHelper) isFileNotMatchingPattern(patterns []string, path string, info os.FileInfo, utils checkmarxOneExecuteScanUtils) (bool, error) {
   955  	if len(patterns) == 0 || info.IsDir() {
   956  		return false, nil
   957  	}
   958  
   959  	for _, pattern := range patterns {
   960  		negative := false
   961  		if strings.HasPrefix(pattern, "!") {
   962  			pattern = strings.TrimLeft(pattern, "!")
   963  			negative = true
   964  		}
   965  		match, err := utils.PathMatch(pattern, path)
   966  		if err != nil {
   967  			return false, errors.Wrapf(err, "Pattern %v could not get executed", pattern)
   968  		}
   969  
   970  		if match {
   971  			return negative, nil
   972  		}
   973  	}
   974  	return true, nil
   975  }
   976  
   977  func (c *checkmarxOneExecuteScanHelper) createToolRecordCx(results *map[string]interface{}) (string, error) {
   978  	workspace := c.utils.GetWorkspace()
   979  	record := toolrecord.New(c.utils, workspace, "checkmarxOne", c.config.ServerURL)
   980  
   981  	// Project
   982  	err := record.AddKeyData("project",
   983  		(*results)["ProjectId"].(string),
   984  		(*results)["ProjectName"].(string),
   985  		"")
   986  	if err != nil {
   987  		return "", err
   988  	}
   989  	// Scan
   990  	err = record.AddKeyData("scanid",
   991  		(*results)["ScanId"].(string),
   992  		(*results)["ScanId"].(string),
   993  		(*results)["DeepLink"].(string))
   994  	if err != nil {
   995  		return "", err
   996  	}
   997  	err = record.Persist()
   998  	if err != nil {
   999  		return "", err
  1000  	}
  1001  	return record.GetFileName(), nil
  1002  }
  1003  
  1004  func (c *checkmarxOneExecuteScanHelper) enforceThresholds(results *map[string]interface{}) (bool, []string, []string) {
  1005  	neutralResults := []string{}
  1006  	insecureResults := []string{}
  1007  	insecure := false
  1008  
  1009  	cxHighThreshold := c.config.VulnerabilityThresholdHigh
  1010  	cxMediumThreshold := c.config.VulnerabilityThresholdMedium
  1011  	cxLowThreshold := c.config.VulnerabilityThresholdLow
  1012  	cxLowThresholdPerQuery := c.config.VulnerabilityThresholdLowPerQuery
  1013  	cxLowThresholdPerQueryMax := c.config.VulnerabilityThresholdLowPerQueryMax
  1014  	highValue := (*results)["High"].(map[string]int)["NotFalsePositive"]
  1015  	mediumValue := (*results)["Medium"].(map[string]int)["NotFalsePositive"]
  1016  	lowValue := (*results)["Low"].(map[string]int)["NotFalsePositive"]
  1017  	var unit string
  1018  	highViolation := ""
  1019  	mediumViolation := ""
  1020  	lowViolation := ""
  1021  	if c.config.VulnerabilityThresholdUnit == "percentage" {
  1022  		unit = "%"
  1023  		highAudited := (*results)["High"].(map[string]int)["Issues"] - (*results)["High"].(map[string]int)["NotFalsePositive"]
  1024  		highOverall := (*results)["High"].(map[string]int)["Issues"]
  1025  		if highOverall == 0 {
  1026  			highAudited = 1
  1027  			highOverall = 1
  1028  		}
  1029  		mediumAudited := (*results)["Medium"].(map[string]int)["Issues"] - (*results)["Medium"].(map[string]int)["NotFalsePositive"]
  1030  		mediumOverall := (*results)["Medium"].(map[string]int)["Issues"]
  1031  		if mediumOverall == 0 {
  1032  			mediumAudited = 1
  1033  			mediumOverall = 1
  1034  		}
  1035  		lowAudited := (*results)["Low"].(map[string]int)["Confirmed"] + (*results)["Low"].(map[string]int)["NotExploitable"]
  1036  		lowOverall := (*results)["Low"].(map[string]int)["Issues"]
  1037  		if lowOverall == 0 {
  1038  			lowAudited = 1
  1039  			lowOverall = 1
  1040  		}
  1041  		highValue = int(float32(highAudited) / float32(highOverall) * 100.0)
  1042  		mediumValue = int(float32(mediumAudited) / float32(mediumOverall) * 100.0)
  1043  		lowValue = int(float32(lowAudited) / float32(lowOverall) * 100.0)
  1044  
  1045  		if highValue < cxHighThreshold {
  1046  			insecure = true
  1047  			highViolation = fmt.Sprintf("<-- %v %v deviation", cxHighThreshold-highValue, unit)
  1048  		}
  1049  		if mediumValue < cxMediumThreshold {
  1050  			insecure = true
  1051  			mediumViolation = fmt.Sprintf("<-- %v %v deviation", cxMediumThreshold-mediumValue, unit)
  1052  		}
  1053  		// if the flag is switched on, calculate the Low findings threshold per query
  1054  		if cxLowThresholdPerQuery {
  1055  			if (*results)["LowPerQuery"] != nil {
  1056  				lowPerQueryMap := (*results)["LowPerQuery"].(map[string]map[string]int)
  1057  
  1058  				for lowQuery, resultsLowQuery := range lowPerQueryMap {
  1059  					lowAuditedPerQuery := resultsLowQuery["Confirmed"] + resultsLowQuery["NotExploitable"]
  1060  					lowOverallPerQuery := resultsLowQuery["Issues"]
  1061  					lowAuditedRequiredPerQuery := int(math.Ceil(float64(lowOverallPerQuery) * float64(cxLowThreshold) / 100.0))
  1062  					if lowAuditedPerQuery < lowAuditedRequiredPerQuery && lowAuditedPerQuery < cxLowThresholdPerQueryMax {
  1063  						insecure = true
  1064  						msgSeperator := "|"
  1065  						if lowViolation == "" {
  1066  							msgSeperator = "<--"
  1067  						}
  1068  						lowViolation += fmt.Sprintf(" %v query: %v, audited: %v, required: %v ", msgSeperator, lowQuery, lowAuditedPerQuery, lowAuditedRequiredPerQuery)
  1069  					}
  1070  				}
  1071  			}
  1072  		} else { // calculate the Low findings threshold in total
  1073  			if lowValue < cxLowThreshold {
  1074  				insecure = true
  1075  				lowViolation = fmt.Sprintf("<-- %v %v deviation", cxLowThreshold-lowValue, unit)
  1076  			}
  1077  		}
  1078  
  1079  	}
  1080  	if c.config.VulnerabilityThresholdUnit == "absolute" {
  1081  		unit = " findings"
  1082  		if highValue > cxHighThreshold {
  1083  			insecure = true
  1084  			highViolation = fmt.Sprintf("<-- %v%v deviation", highValue-cxHighThreshold, unit)
  1085  		}
  1086  		if mediumValue > cxMediumThreshold {
  1087  			insecure = true
  1088  			mediumViolation = fmt.Sprintf("<-- %v%v deviation", mediumValue-cxMediumThreshold, unit)
  1089  		}
  1090  		if lowValue > cxLowThreshold {
  1091  			insecure = true
  1092  			lowViolation = fmt.Sprintf("<-- %v%v deviation", lowValue-cxLowThreshold, unit)
  1093  		}
  1094  	}
  1095  
  1096  	highText := fmt.Sprintf("High %v%v %v", highValue, unit, highViolation)
  1097  	mediumText := fmt.Sprintf("Medium %v%v %v", mediumValue, unit, mediumViolation)
  1098  	lowText := fmt.Sprintf("Low %v%v %v", lowValue, unit, lowViolation)
  1099  	if len(highViolation) > 0 {
  1100  		insecureResults = append(insecureResults, highText)
  1101  		log.Entry().Error(highText)
  1102  	} else {
  1103  		neutralResults = append(neutralResults, highText)
  1104  		log.Entry().Info(highText)
  1105  	}
  1106  	if len(mediumViolation) > 0 {
  1107  		insecureResults = append(insecureResults, mediumText)
  1108  		log.Entry().Error(mediumText)
  1109  	} else {
  1110  		neutralResults = append(neutralResults, mediumText)
  1111  		log.Entry().Info(mediumText)
  1112  	}
  1113  	if len(lowViolation) > 0 {
  1114  		insecureResults = append(insecureResults, lowText)
  1115  		log.Entry().Error(lowText)
  1116  	} else {
  1117  		neutralResults = append(neutralResults, lowText)
  1118  		log.Entry().Info(lowText)
  1119  	}
  1120  
  1121  	return insecure, insecureResults, neutralResults
  1122  }
  1123  
  1124  func (c *checkmarxOneExecuteScanHelper) reportToInflux(results *map[string]interface{}) {
  1125  
  1126  	c.influx.checkmarxOne_data.fields.high_issues = (*results)["High"].(map[string]int)["Issues"]
  1127  	c.influx.checkmarxOne_data.fields.high_not_false_postive = (*results)["High"].(map[string]int)["NotFalsePositive"]
  1128  	c.influx.checkmarxOne_data.fields.high_not_exploitable = (*results)["High"].(map[string]int)["NotExploitable"]
  1129  	c.influx.checkmarxOne_data.fields.high_confirmed = (*results)["High"].(map[string]int)["Confirmed"]
  1130  	c.influx.checkmarxOne_data.fields.high_urgent = (*results)["High"].(map[string]int)["Urgent"]
  1131  	c.influx.checkmarxOne_data.fields.high_proposed_not_exploitable = (*results)["High"].(map[string]int)["ProposedNotExploitable"]
  1132  	c.influx.checkmarxOne_data.fields.high_to_verify = (*results)["High"].(map[string]int)["ToVerify"]
  1133  	c.influx.checkmarxOne_data.fields.medium_issues = (*results)["Medium"].(map[string]int)["Issues"]
  1134  	c.influx.checkmarxOne_data.fields.medium_not_false_postive = (*results)["Medium"].(map[string]int)["NotFalsePositive"]
  1135  	c.influx.checkmarxOne_data.fields.medium_not_exploitable = (*results)["Medium"].(map[string]int)["NotExploitable"]
  1136  	c.influx.checkmarxOne_data.fields.medium_confirmed = (*results)["Medium"].(map[string]int)["Confirmed"]
  1137  	c.influx.checkmarxOne_data.fields.medium_urgent = (*results)["Medium"].(map[string]int)["Urgent"]
  1138  	c.influx.checkmarxOne_data.fields.medium_proposed_not_exploitable = (*results)["Medium"].(map[string]int)["ProposedNotExploitable"]
  1139  	c.influx.checkmarxOne_data.fields.medium_to_verify = (*results)["Medium"].(map[string]int)["ToVerify"]
  1140  	c.influx.checkmarxOne_data.fields.low_issues = (*results)["Low"].(map[string]int)["Issues"]
  1141  	c.influx.checkmarxOne_data.fields.low_not_false_postive = (*results)["Low"].(map[string]int)["NotFalsePositive"]
  1142  	c.influx.checkmarxOne_data.fields.low_not_exploitable = (*results)["Low"].(map[string]int)["NotExploitable"]
  1143  	c.influx.checkmarxOne_data.fields.low_confirmed = (*results)["Low"].(map[string]int)["Confirmed"]
  1144  	c.influx.checkmarxOne_data.fields.low_urgent = (*results)["Low"].(map[string]int)["Urgent"]
  1145  	c.influx.checkmarxOne_data.fields.low_proposed_not_exploitable = (*results)["Low"].(map[string]int)["ProposedNotExploitable"]
  1146  	c.influx.checkmarxOne_data.fields.low_to_verify = (*results)["Low"].(map[string]int)["ToVerify"]
  1147  	c.influx.checkmarxOne_data.fields.information_issues = (*results)["Information"].(map[string]int)["Issues"]
  1148  	c.influx.checkmarxOne_data.fields.information_not_false_postive = (*results)["Information"].(map[string]int)["NotFalsePositive"]
  1149  	c.influx.checkmarxOne_data.fields.information_not_exploitable = (*results)["Information"].(map[string]int)["NotExploitable"]
  1150  	c.influx.checkmarxOne_data.fields.information_confirmed = (*results)["Information"].(map[string]int)["Confirmed"]
  1151  	c.influx.checkmarxOne_data.fields.information_urgent = (*results)["Information"].(map[string]int)["Urgent"]
  1152  	c.influx.checkmarxOne_data.fields.information_proposed_not_exploitable = (*results)["Information"].(map[string]int)["ProposedNotExploitable"]
  1153  	c.influx.checkmarxOne_data.fields.information_to_verify = (*results)["Information"].(map[string]int)["ToVerify"]
  1154  	c.influx.checkmarxOne_data.fields.initiator_name = (*results)["InitiatorName"].(string)
  1155  	c.influx.checkmarxOne_data.fields.owner = (*results)["Owner"].(string)
  1156  	c.influx.checkmarxOne_data.fields.scan_id = (*results)["ScanId"].(string)
  1157  	c.influx.checkmarxOne_data.fields.project_id = (*results)["ProjectId"].(string)
  1158  	c.influx.checkmarxOne_data.fields.projectName = (*results)["ProjectName"].(string)
  1159  	c.influx.checkmarxOne_data.fields.group = (*results)["Group"].(string)
  1160  	c.influx.checkmarxOne_data.fields.group_full_path_on_report_date = (*results)["GroupFullPathOnReportDate"].(string)
  1161  	c.influx.checkmarxOne_data.fields.scan_start = (*results)["ScanStart"].(string)
  1162  	c.influx.checkmarxOne_data.fields.scan_time = (*results)["ScanTime"].(string)
  1163  	c.influx.checkmarxOne_data.fields.lines_of_code_scanned = (*results)["LinesOfCodeScanned"].(int)
  1164  	c.influx.checkmarxOne_data.fields.files_scanned = (*results)["FilesScanned"].(int)
  1165  	c.influx.checkmarxOne_data.fields.tool_version = (*results)["ToolVersion"].(string)
  1166  
  1167  	c.influx.checkmarxOne_data.fields.scan_type = (*results)["ScanType"].(string)
  1168  	c.influx.checkmarxOne_data.fields.preset = (*results)["Preset"].(string)
  1169  	c.influx.checkmarxOne_data.fields.deep_link = (*results)["DeepLink"].(string)
  1170  	c.influx.checkmarxOne_data.fields.report_creation_time = (*results)["ReportCreationTime"].(string)
  1171  }
  1172  
  1173  // Utils Bundle
  1174  // various utilities to set up or work with the workspace and prepare data to send to Cx1
  1175  
  1176  func (c *checkmarxOneExecuteScanUtilsBundle) PathMatch(pattern, name string) (bool, error) {
  1177  	return doublestar.PathMatch(pattern, name)
  1178  }
  1179  
  1180  func (c *checkmarxOneExecuteScanUtilsBundle) GetWorkspace() string {
  1181  	return c.workspace
  1182  }
  1183  
  1184  func (c *checkmarxOneExecuteScanUtilsBundle) WriteFile(filename string, data []byte, perm os.FileMode) error {
  1185  	return os.WriteFile(filename, data, perm)
  1186  }
  1187  
  1188  func (c *checkmarxOneExecuteScanUtilsBundle) MkdirAll(path string, perm os.FileMode) error {
  1189  	return os.MkdirAll(path, perm)
  1190  }
  1191  
  1192  func (c *checkmarxOneExecuteScanUtilsBundle) FileInfoHeader(fi os.FileInfo) (*zip.FileHeader, error) {
  1193  	return zip.FileInfoHeader(fi)
  1194  }
  1195  
  1196  func (c *checkmarxOneExecuteScanUtilsBundle) Stat(name string) (os.FileInfo, error) {
  1197  	return os.Stat(name)
  1198  }
  1199  
  1200  func (c *checkmarxOneExecuteScanUtilsBundle) Open(name string) (*os.File, error) {
  1201  	return os.Open(name)
  1202  }
  1203  
  1204  func (c *checkmarxOneExecuteScanUtilsBundle) CreateIssue(ghCreateIssueOptions *piperGithub.CreateIssueOptions) error {
  1205  	_, err := piperGithub.CreateIssue(ghCreateIssueOptions)
  1206  	return err
  1207  }
  1208  
  1209  func (c *checkmarxOneExecuteScanUtilsBundle) GetIssueService() *github.IssuesService {
  1210  	return c.issues
  1211  }
  1212  
  1213  func (c *checkmarxOneExecuteScanUtilsBundle) GetSearchService() *github.SearchService {
  1214  	return c.search
  1215  }
  1216  
  1217  func newcheckmarxOneExecuteScanUtilsBundle(workspace string, client *github.Client) checkmarxOneExecuteScanUtils {
  1218  	utils := checkmarxOneExecuteScanUtilsBundle{
  1219  		workspace: workspace,
  1220  	}
  1221  	if client != nil {
  1222  		utils.issues = client.Issues
  1223  		utils.search = client.Search
  1224  	}
  1225  	return &utils
  1226  }