You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-09-16 09:26:22 +02:00
feat(CxOne): Scan summary in PR (#5464)
This commit is contained in:
@@ -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) <br>", lowSeverityString, lowFinding.Total-lowFinding.Audited, lowFinding.QueryName, lowFinding.Audited, lowAuditedRequiredPerQuery)
|
||||
} else {
|
||||
lowSeverityString = fmt.Sprintf("%s%d %s<br>", 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 := "|"
|
||||
|
@@ -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{},
|
||||
|
@@ -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`
|
||||
|
@@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user