diff --git a/cmd/checkmarxOneExecuteScan.go b/cmd/checkmarxOneExecuteScan.go
index 74070215d..73fa37fd3 100644
--- a/cmd/checkmarxOneExecuteScan.go
+++ b/cmd/checkmarxOneExecuteScan.go
@@ -21,6 +21,7 @@ import (
piperGithub "github.com/SAP/jenkins-library/pkg/github"
piperHttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
+ "github.com/SAP/jenkins-library/pkg/orchestrator"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/reporting"
"github.com/SAP/jenkins-library/pkg/telemetry"
@@ -451,11 +452,25 @@ func (c *checkmarxOneExecuteScanHelper) CreateScanRequest(incremental bool, uplo
}
branch := c.config.Branch
- if len(branch) == 0 && len(c.config.GitBranch) > 0 {
+ cicdOrch, err := orchestrator.GetOrchestratorConfigProvider(nil)
+ if err == nil {
+ log.Entry().Warn("Could not identify orchestrator")
+ }
+ if len(branch) == 0 && len(c.config.GitBranch) > 0 && c.config.GitBranch != "n/a" {
branch = c.config.GitBranch
+ } else if len(branch) == 0 && (len(c.config.GitBranch) == 0 || c.config.GitBranch == "n/a") { // use the branch from the orchestrator by default
+ cicdBranch := cicdOrch.Branch()
+ if cicdBranch != "n/a" {
+ branch = cicdBranch
+ log.Entry().Infof("CxOne scan branch was automatically set to : %v", branch)
+ } else {
+ log.Entry().Info("Could not retrieve branch name from orchestrator")
+ }
}
if len(c.config.PullRequestName) > 0 {
branch = fmt.Sprintf("%v-%v", c.config.PullRequestName, c.config.Branch)
+ } else if cicdOrch.IsPullRequest() && cicdOrch.PullRequestConfig().Branch != "n/a" {
+ branch = fmt.Sprintf("PR%v-%v", cicdOrch.PullRequestConfig().Key, cicdOrch.PullRequestConfig().Branch)
}
sastConfigString = fmt.Sprintf("Cx1 Branch name %v, ", branch) + sastConfigString
@@ -533,10 +548,107 @@ func (c *checkmarxOneExecuteScanHelper) PollScanStatus(scan *checkmarxOne.Scan)
return nil
}
+func (c *checkmarxOneExecuteScanHelper) PostScanSummaryInPullRequest(detailedResults *map[string]interface{}, insecure bool) error {
+ cicdOrch, err := orchestrator.GetOrchestratorConfigProvider(nil)
+ if err != nil {
+ return fmt.Errorf("Failed to get orchestrator config provider: %s", err)
+ }
+ isPullRequest := cicdOrch.IsPullRequest()
+ pullRequestId := cicdOrch.PullRequestConfig().Key
+ var owner, repository string
+ if len(c.config.Repository) == 0 || len(c.config.Owner) == 0 {
+ log.Entry().Debug("No repository or owner configured, trying to get it from orchestrator")
+ repoUrl := cicdOrch.RepoURL()
+ if repoUrl != "n/a" {
+ parsedURL, err := url.Parse(repoUrl)
+ if err != nil {
+ return fmt.Errorf("failed to parse repository URL %s: %s", repoUrl, err)
+ }
+ pathParts := strings.Split(strings.TrimSuffix(parsedURL.Path, ".git"), "/")
+ if len(pathParts) >= 2 {
+ if len(c.config.Owner) == 0 {
+ owner = pathParts[len(pathParts)-2]
+ }
+ if len(c.config.Repository) == 0 {
+ repository = pathParts[len(pathParts)-1]
+ }
+ log.Entry().Infof("Found repository %s and owner %s from orchestrator", c.config.Repository, c.config.Owner)
+ } else {
+ return fmt.Errorf("failed to extract owner and repository from URL %s", repoUrl)
+ }
+ } else {
+ log.Entry().Debug("Could not retrieve repository URL from orchestrator")
+ }
+ } else {
+ owner = c.config.Owner
+ repository = c.config.Repository
+ log.Entry().Debug("Using Owner and Repository from configuration: " + owner + "/" + repository)
+ }
+ log.Entry().Debugf("Parameters for PR summary: ScanSummaryInPullRequest: %t, isPullRequest: %t, pullRequestId: %s, PullRequestName: %s, GithubAPIURL: %s, GithubToken: %s, Owner: %s, Repository: %s", c.config.ScanSummaryInPullRequest, isPullRequest, pullRequestId, c.config.PullRequestName, c.config.GithubAPIURL, c.config.GithubToken, owner, repository)
+ if c.config.ScanSummaryInPullRequest && isPullRequest && pullRequestId != "n/a" && len(c.config.GithubToken) > 0 && len(c.config.GithubAPIURL) > 0 && len(owner) > 0 && len(repository) > 0 {
+ ghIssues := c.utils.GetIssueService()
+ log.Entry().Debugf("Creating/updating GitHub issue with check results with PR: %s, GithubAPIURL: %s, Owner: %s, Repository: %s", c.config.PullRequestName, c.config.GithubAPIURL, owner, repository)
+ scanReportOverview := checkmarxOne.CreateJSONHeaderReport(detailedResults)
+ var criticalSeverityString, highSeverityString, mediumSeverityString, lowSeverityString string
+ for _, finding := range *scanReportOverview.Findings {
+ switch finding.ClassificationName {
+ case "Critical":
+ criticalSeverityString = fmt.Sprintf("%d", finding.Total-*finding.Audited)
+ case "High":
+ highSeverityString = fmt.Sprintf("%d", finding.Total-*finding.Audited)
+ case "Medium":
+ mediumSeverityString = fmt.Sprintf("%d", finding.Total-*finding.Audited)
+ case "Low":
+ if finding.LowPerQuery != nil {
+ for _, lowFinding := range *finding.LowPerQuery {
+ if c.config.VulnerabilityThresholdLowPerQuery {
+ lowAuditedRequiredPerQuery := min(int(math.Ceil(float64(lowFinding.Total)*float64(c.config.VulnerabilityThresholdLow)/100.0)), c.config.VulnerabilityThresholdLowPerQueryMax)
+ lowSeverityString = fmt.Sprintf("%s%d %s (%d audits / %d required)
", lowSeverityString, lowFinding.Total-lowFinding.Audited, lowFinding.QueryName, lowFinding.Audited, lowAuditedRequiredPerQuery)
+ } else {
+ lowSeverityString = fmt.Sprintf("%s%d %s
", lowSeverityString, lowFinding.Total-lowFinding.Audited, lowFinding.QueryName)
+ }
+ }
+ }
+ }
+ }
+ var scanIcon string
+ if insecure {
+ scanIcon = ":x:"
+ } else {
+ scanIcon = ":white_check_mark:"
+ }
+ comment := &github.IssueComment{
+ Body: github.Ptr(fmt.Sprintf(`# %s Checkmarx %s scan completed
+**Project**: %s
+**ScanId**: %s
+**Preset**: %s
+Severity | Number of open findings
+--- | ---
+:bangbang: Critical | %s
+:red_circle: High | %s
+:orange_circle: Medium | %s
+:yellow_circle: Low | %s
+
+[Go to the scan results](%s)
+ `, scanIcon, strings.ToLower(scanReportOverview.ScanType), c.Project.Name, scanReportOverview.ScanID, scanReportOverview.Preset, criticalSeverityString, highSeverityString, mediumSeverityString, lowSeverityString, scanReportOverview.DeepLink)),
+ }
+ pullRequestNumber, err := strconv.Atoi(pullRequestId)
+ if err != nil {
+ return fmt.Errorf("failed to parse int from pull request name %s: %s", c.config.PullRequestName, err)
+ }
+ _, _, err = ghIssues.CreateComment(c.ctx, owner, repository, pullRequestNumber, comment)
+ if err != nil {
+ return fmt.Errorf("failed to create GitHub issue comment: %s", err)
+ }
+ log.Entry().Infof("Created GitHub issue comment for project %v", c.Project.Name)
+ } else {
+ log.Entry().Debug("Skipping GitHub issue comment creation, no pull request or GitHub configuration provided")
+ }
+ return nil
+}
+
func (c *checkmarxOneExecuteScanHelper) CheckCompliance(scan *checkmarxOne.Scan, detailedResults *map[string]interface{}) error {
-
links := []piperutils.Path{{Target: (*detailedResults)["DeepLink"].(string), Name: "Checkmarx One Web UI"}}
-
insecure := false
var insecureResults []string
var neutralResults []string
@@ -545,6 +657,14 @@ func (c *checkmarxOneExecuteScanHelper) CheckCompliance(scan *checkmarxOne.Scan,
insecure, insecureResults, neutralResults = c.enforceThresholds(detailedResults)
scanReport := checkmarxOne.CreateCustomReport(detailedResults, insecureResults, neutralResults)
+ // Create scan summary comment in PR
+ if c.config.ScanSummaryInPullRequest {
+ err := c.PostScanSummaryInPullRequest(detailedResults, insecure)
+ if err != nil {
+ log.Entry().Errorf("failed to post scan summary in pull request: %s", err)
+ }
+ }
+
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 {
log.Entry().Debug("Creating/updating GitHub issue with check results")
gh := reporting.GitHub{
@@ -1144,7 +1264,7 @@ func (c *checkmarxOneExecuteScanHelper) enforceThresholds(results *map[string]in
for lowQuery, resultsLowQuery := range lowPerQueryMap {
lowAuditedPerQuery := resultsLowQuery["Confirmed"] + resultsLowQuery["NotExploitable"]
lowOverallPerQuery := resultsLowQuery["Issues"]
- lowAuditedRequiredPerQuery := int(math.Ceil(float64(lowOverallPerQuery) * float64(cxLowThreshold) / 100.0))
+ lowAuditedRequiredPerQuery := min(int(math.Ceil(float64(lowOverallPerQuery)*float64(cxLowThreshold)/100.0)), cxLowThresholdPerQueryMax)
if lowAuditedPerQuery < lowAuditedRequiredPerQuery && lowAuditedPerQuery < cxLowThresholdPerQueryMax {
insecure = true
msgSeperator := "|"
diff --git a/cmd/checkmarxOneExecuteScan_generated.go b/cmd/checkmarxOneExecuteScan_generated.go
index 812c4a2b1..4b7bc8f20 100644
--- a/cmd/checkmarxOneExecuteScan_generated.go
+++ b/cmd/checkmarxOneExecuteScan_generated.go
@@ -31,6 +31,7 @@ type checkmarxOneExecuteScanOptions struct {
GeneratePdfReport bool `json:"generatePdfReport,omitempty"`
GithubAPIURL string `json:"githubApiUrl,omitempty"`
GithubToken string `json:"githubToken,omitempty"`
+ ScanSummaryInPullRequest bool `json:"scanSummaryInPullRequest,omitempty"`
Incremental bool `json:"incremental,omitempty"`
Owner string `json:"owner,omitempty"`
GitBranch string `json:"gitBranch,omitempty"`
@@ -403,6 +404,7 @@ func addCheckmarxOneExecuteScanFlags(cmd *cobra.Command, stepConfig *checkmarxOn
cmd.Flags().BoolVar(&stepConfig.GeneratePdfReport, "generatePdfReport", true, "Whether to generate a PDF report of the analysis results or not")
cmd.Flags().StringVar(&stepConfig.GithubAPIURL, "githubApiUrl", `https://api.github.com`, "Set the GitHub API URL.")
cmd.Flags().StringVar(&stepConfig.GithubToken, "githubToken", os.Getenv("PIPER_githubToken"), "GitHub personal access token as per https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line")
+ cmd.Flags().BoolVar(&stepConfig.ScanSummaryInPullRequest, "scanSummaryInPullRequest", false, "Whether the scan summary shall be added to the pull request as a comment or not. This is only applied if the step is executed in a pull request context. githubToken and githubApiUrl parameters must be set to allow the step to create the comment.")
cmd.Flags().BoolVar(&stepConfig.Incremental, "incremental", true, "Whether incremental scans are to be applied which optimizes the scan time but might reduce detection capabilities. Therefore full scans are still required from time to time and should be scheduled via `fullScansScheduled` and `fullScanCycle`")
cmd.Flags().StringVar(&stepConfig.Owner, "owner", os.Getenv("PIPER_owner"), "Set the GitHub organization.")
cmd.Flags().StringVar(&stepConfig.GitBranch, "gitBranch", os.Getenv("PIPER_gitBranch"), "Set the GitHub repository branch.")
@@ -551,6 +553,15 @@ func checkmarxOneExecuteScanMetadata() config.StepData {
Aliases: []config.Alias{{Name: "access_token"}},
Default: os.Getenv("PIPER_githubToken"),
},
+ {
+ Name: "scanSummaryInPullRequest",
+ ResourceRef: []config.ResourceReference{},
+ Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
+ Type: "bool",
+ Mandatory: false,
+ Aliases: []config.Alias{},
+ Default: false,
+ },
{
Name: "incremental",
ResourceRef: []config.ResourceReference{},
diff --git a/resources/metadata/checkmarxOneExecuteScan.yaml b/resources/metadata/checkmarxOneExecuteScan.yaml
index 314b18687..6b1f60f9c 100644
--- a/resources/metadata/checkmarxOneExecuteScan.yaml
+++ b/resources/metadata/checkmarxOneExecuteScan.yaml
@@ -106,6 +106,14 @@ spec:
- type: vaultSecret
default: github
name: githubVaultSecretName
+ - name: scanSummaryInPullRequest
+ description: Whether the scan summary shall be added to the pull request as a comment or not. This is only applied if the step is executed in a pull request context. githubToken and githubApiUrl parameters must be set to allow the step to create the comment.
+ type: bool
+ scope:
+ - PARAMETERS
+ - STAGES
+ - STEPS
+ default: false
- name: incremental
type: bool
description: Whether incremental scans are to be applied which optimizes the scan time but might reduce detection capabilities. Therefore full scans are still required from time to time and should be scheduled via `fullScansScheduled` and `fullScanCycle`
diff --git a/vars/checkmarxOneExecuteScan.groovy b/vars/checkmarxOneExecuteScan.groovy
index 88a6911e1..4045a5245 100644
--- a/vars/checkmarxOneExecuteScan.groovy
+++ b/vars/checkmarxOneExecuteScan.groovy
@@ -7,6 +7,7 @@ import groovy.transform.Field
void call(Map parameters = [:]) {
List credentials = [[type: 'usernamePassword', id: 'checkmarxOneCredentialsId', env: ['PIPER_clientId', 'PIPER_clientSecret']],
- [type: 'token', id: 'checkmarxOneAPIKey', env: ['PIPER_APIKey']]]
+ [type: 'token', id: 'checkmarxOneAPIKey', env: ['PIPER_APIKey']],
+ [type: 'token', id: 'githubTokenCredentialsId', env: ['PIPER_githubToken']]]
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
}