diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c638f67a..f72912cb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: pull_request: env: - GO_VERSION: '1.21.8' + GO_VERSION: '1.21.9' jobs: unit-tests: @@ -66,7 +66,7 @@ jobs: PR_GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} PR_GITHUB_NAMESPACE: "checkmarx" PR_GITHUB_REPO_NAME: "ast-cli" - PR_GITHUB_NUMBER: 418 + PR_GITHUB_NUMBER: 419 PR_GITLAB_TOKEN : ${{ secrets.PR_GITLAB_TOKEN }} PR_GITLAB_NAMESPACE: ${{ secrets.PR_GITLAB_NAMESPACE }} PR_GITLAB_REPO_NAME: ${{ secrets.PR_GITLAB_REPO_NAME }} diff --git a/go.mod b/go.mod index 4e16b4478..4601d6268 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/checkmarx/ast-cli -go 1.21.8 +go 1.21.9 require ( github.com/MakeNowJust/heredoc v1.0.0 @@ -43,4 +43,4 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect -) \ No newline at end of file +) diff --git a/go.sum b/go.sum index 6f08f88e2..0f3b60f16 100644 --- a/go.sum +++ b/go.sum @@ -81,20 +81,10 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= -golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.20.1-0.20240228204720-0d2316b26734 h1:HutZC8sRIg57ztz3rVaQYl4yxgM+UF0Jal0kAWUSeFU= -golang.org/x/crypto v0.20.1-0.20240228204720-0d2316b26734/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= diff --git a/internal/commands/cx_result_sonar.json b/internal/commands/cx_result_sonar.json index d4cf0d4a1..ddcfe2bdd 100644 --- a/internal/commands/cx_result_sonar.json +++ b/internal/commands/cx_result_sonar.json @@ -1 +1 @@ -{"issues":[{"engineId":"sast","type":"VULNERABILITY","primaryLocation":{"filePath":"dummy-file-name","textRange":{"startLine":10,"startColumn":9,"endColumn":10}},"secondaryLocations":[{"filePath":"dummy-file-name","textRange":{"startColumn":2,"endColumn":3}}]},{"engineId":"kics","type":"VULNERABILITY","primaryLocation":{"textRange":{"startColumn":1,"endColumn":2}},"secondaryLocations":null}]} +{"issues":[{"engineId":"sast","ruleId":"1","type":"VULNERABILITY","primaryLocation":{"message":"mock-query-name-1","filePath":"dummy-file-name-1","textRange":{"startLine":10,"startColumn":10,"endColumn":30}},"secondaryLocations":[{"message":"mock-query-name-1","filePath":"dummy-file-name-1","textRange":{"startLine":11,"startColumn":3,"endColumn":13}}]},{"engineId":"sast","ruleId":"2","type":"VULNERABILITY","primaryLocation":{"message":"mock-query-name-2","filePath":"dummy-file-name-2","textRange":{"startLine":10,"startColumn":10,"endColumn":30}},"secondaryLocations":[{"message":"mock-query-name-2","filePath":"dummy-file-name-2","textRange":{"startLine":11,"startColumn":3,"endColumn":13}}]},{"engineId":"sast","ruleId":"3","type":"VULNERABILITY","primaryLocation":{"message":"mock-query-name-2","filePath":"dummy-file-name-2","textRange":{"startLine":10,"startColumn":10,"endColumn":30}},"secondaryLocations":[{"message":"mock-query-name-2","filePath":"dummy-file-name-2","textRange":{"startLine":11,"startColumn":3,"endColumn":13}},{"message":"mock-query-name-2","filePath":"dummy-file-name-2","textRange":{"startLine":12,"startColumn":3,"endColumn":13}}]},{"engineId":"sast","ruleId":"4","type":"VULNERABILITY","primaryLocation":{"message":"mock-query-name-3","filePath":"dummy-file-name-3","textRange":{"startLine":10,"startColumn":10,"endColumn":30}},"secondaryLocations":[{"message":"mock-query-name-3","filePath":"dummy-file-name-3","textRange":{"startLine":11,"startColumn":3,"endColumn":13}}]},{"engineId":"sast","ruleId":"5","type":"VULNERABILITY","primaryLocation":{"message":"mock-query-name-3","filePath":"dummy-file-name-4","textRange":{"startLine":10,"startColumn":10,"endColumn":30}},"secondaryLocations":[{"message":"mock-query-name-3","filePath":"dummy-file-name-4","textRange":{"startLine":11,"startColumn":3,"endColumn":13}}]},{"engineId":"kics","type":"VULNERABILITY","primaryLocation":{"textRange":{"endColumn":1}},"secondaryLocations":null}]} diff --git a/internal/commands/result.go b/internal/commands/result.go index 224b02b79..597e9c8ec 100644 --- a/internal/commands/result.go +++ b/internal/commands/result.go @@ -73,13 +73,15 @@ const ( pendingStatus = "Pending" pdfToEmailFlagDescription = "Send the PDF report to the specified email address." + " Use \",\" as the delimiter for multiple emails" - pdfOptionsFlagDescription = "Sections to generate PDF report. Available options: Iac-Security,Sast,Sca," + - defaultPdfOptionsDataSections + pdfOptionsFlagDescription = "Sections to generate PDF report. Available options: Iac-Security,Sast,Sca and " + + defaultPdfOptionsDataSections + defaultPdfOprtionsImprovedDataSections sbomReportFlagDescription = "Sections to generate SBOM report. Available options: CycloneDxJson,CycloneDxXml,SpdxJson" delayValueForReport = 10 reportNameScanReport = "scan-report" + reportNameImprovedScanReport = "improved-scan-report" reportTypeEmail = "email" - defaultPdfOptionsDataSections = "ScanSummary,ExecutiveSummary,ScanResults" + defaultPdfOptionsDataSections = "ScanSummary,ExecutiveSummary,ScanResults with NEW_SAST_SCAN_REPORT_ENABLED feature flag disabled or " + defaultPdfOprtionsImprovedDataSections = "scan-information,results-overview,scan-results,categories,resolved-results,vulnerability-details with the flag enabled" defaultSbomOption = "CycloneDxJson" exploitablePathFlagDescription = "Enable or disable exploitable path in scan. Available options: true,false" scaLastScanTimeFlagDescription = "SCA last scan time. Available options: integer above 1" @@ -202,7 +204,7 @@ func resultShowSubCommand( ) resultShowCmd.PersistentFlags().String(commonParams.ReportFormatPdfToEmailFlag, "", pdfToEmailFlagDescription) resultShowCmd.PersistentFlags().String(commonParams.ReportSbomFormatFlag, defaultSbomOption, sbomReportFlagDescription) - resultShowCmd.PersistentFlags().String(commonParams.ReportFormatPdfOptionsFlag, defaultPdfOptionsDataSections, pdfOptionsFlagDescription) + resultShowCmd.PersistentFlags().String(commonParams.ReportFormatPdfOptionsFlag, "", pdfOptionsFlagDescription) resultShowCmd.PersistentFlags().String(commonParams.TargetFlag, "cx_result", "Output file") resultShowCmd.PersistentFlags().String(commonParams.TargetPathFlag, ".", "Output Path") resultShowCmd.PersistentFlags().StringSlice(commonParams.FilterFlag, []string{}, filterResultsListFlagUsage) @@ -1223,11 +1225,19 @@ func exportSbomResults(sbomWrapper wrappers.ResultsSbomWrapper, func exportPdfResults(pdfWrapper wrappers.ResultsPdfWrapper, summary *wrappers.ResultSummary, summaryRpt, formatPdfToEmail, pdfOptions string) error { pdfReportsPayload := &wrappers.PdfReportsPayload{} pollingResp := &wrappers.PdfPollingResponse{} - pdfOptionsSections, pdfOptionsEngines, err := parsePDFOptions(pdfOptions, summary.EnginesEnabled) + newScanReportEnabled := wrappers.FeatureFlags[wrappers.NewScanReportEnabled] + + if newScanReportEnabled { + pdfReportsPayload.ReportName = reportNameImprovedScanReport + } else { + pdfReportsPayload.ReportName = reportNameScanReport + } + + pdfOptionsSections, pdfOptionsEngines, err := parsePDFOptions(pdfOptions, summary.EnginesEnabled, pdfReportsPayload.ReportName) if err != nil { return err } - pdfReportsPayload.ReportName = reportNameScanReport + pdfReportsPayload.ReportType = "cli" pdfReportsPayload.FileFormat = printer.FormatPDF pdfReportsPayload.Data.ScanID = summary.ScanID @@ -1290,25 +1300,49 @@ func validateSbomOptions(sbomOption string) (string, error) { return "", errors.Errorf("invalid SBOM option: %s", sbomOption) } -func parsePDFOptions(pdfOptions string, enabledEngines []string) (pdfOptionsSections, pdfOptionsEngines []string, err error) { +func parsePDFOptions(pdfOptions string, enabledEngines []string, reportName string) (pdfOptionsSections, pdfOptionsEngines []string, err error) { var pdfOptionsSectionsMap = map[string]string{ "scansummary": "ScanSummary", "executivesummary": "ExecutiveSummary", "scanresults": "ScanResults", } + var pdfOptionsSectionsMapImproved = map[string]string{ + "scan-information": "scan-information", + "results-overview": "results-overview", + "scan-results": "scan-results", + "categories": "categories", + "resolved-results": "resolved-results", + "vulnerability-details": "vulnerability-details", + } var pdfOptionsEnginesMap = map[string]string{ commonParams.ScaType: "SCA", commonParams.SastType: "SAST", commonParams.KicsType: "KICS", commonParams.IacType: "KICS", } + + var pdfReportOptionsSections = map[string]map[string]string{ + reportNameImprovedScanReport: pdfOptionsSectionsMapImproved, + reportNameScanReport: pdfOptionsSectionsMap, + } + + var pdfReportOptionsEngines = map[string]map[string]string{ + reportNameImprovedScanReport: pdfOptionsEnginesMap, + reportNameScanReport: pdfOptionsEnginesMap, + } + pdfOptions = strings.ToLower(strings.ReplaceAll(pdfOptions, " ", "")) + // if no options are provided, report service defaults to all values + if pdfOptions == "" { + return pdfOptionsSections, pdfOptionsSections, nil + } + options := strings.Split(strings.ReplaceAll(pdfOptions, "\n", ""), ",") for _, s := range options { - if pdfOptionsEnginesMap[s] != "" { - pdfOptionsEngines = append(pdfOptionsEngines, pdfOptionsEnginesMap[s]) - } else if pdfOptionsSectionsMap[s] != "" { - pdfOptionsSections = append(pdfOptionsSections, pdfOptionsSectionsMap[s]) + if pdfReportOptionsEngines[reportName][s] != "" { + pdfOptionsEngines = append(pdfOptionsEngines, pdfReportOptionsEngines[reportName][s]) + } else if pdfReportOptionsSections[reportName][s] != "" { + pdfOptionsSections = append(pdfOptionsSections, pdfReportOptionsSections[reportName][s]) } else { return nil, nil, errors.Errorf("report option \"%s\" unavailable", s) } @@ -1316,7 +1350,7 @@ func parsePDFOptions(pdfOptions string, enabledEngines []string) (pdfOptionsSect if pdfOptionsEngines == nil { for _, engine := range enabledEngines { if pdfOptionsEnginesMap[engine] != "" { - pdfOptionsEngines = append(pdfOptionsEngines, pdfOptionsEnginesMap[engine]) + pdfOptionsEngines = append(pdfOptionsEngines, pdfReportOptionsEngines[reportName][engine]) } } } diff --git a/internal/commands/result_test.go b/internal/commands/result_test.go index 534d3cd47..6f2c6f157 100644 --- a/internal/commands/result_test.go +++ b/internal/commands/result_test.go @@ -10,6 +10,7 @@ import ( "github.com/checkmarx/ast-cli/internal/commands/util/printer" "github.com/checkmarx/ast-cli/internal/params" "github.com/checkmarx/ast-cli/internal/wrappers" + "github.com/checkmarx/ast-cli/internal/wrappers/mock" "gotest.tools/assert" ) @@ -249,6 +250,7 @@ func TestRunGetBFLByScanIdAndQueryIdWithFormatList(t *testing.T) { } func TestRunGetResultsGeneratingPdfReportWithInvalidEmail(t *testing.T) { + mock.Flags = wrappers.FeatureFlagsResponseModel{{Name: wrappers.NewScanReportEnabled, Status: false}} err := execCmdNotNilAssertion(t, "results", "show", "--report-format", "pdf", @@ -258,6 +260,7 @@ func TestRunGetResultsGeneratingPdfReportWithInvalidEmail(t *testing.T) { } func TestRunGetResultsGeneratingPdfReportWithInvalidOptions(t *testing.T) { + mock.Flags = wrappers.FeatureFlagsResponseModel{{Name: wrappers.NewScanReportEnabled, Status: false}} err := execCmdNotNilAssertion(t, "results", "show", "--report-format", "pdf", @@ -266,7 +269,18 @@ func TestRunGetResultsGeneratingPdfReportWithInvalidOptions(t *testing.T) { assert.Equal(t, err.Error(), "report option \"invalid\" unavailable", "Wrong expected error message") } +func TestRunGetResultsGeneratingPdfReportWithInvalidImprovedOptions(t *testing.T) { + mock.Flags = wrappers.FeatureFlagsResponseModel{{Name: wrappers.NewScanReportEnabled, Status: false}} + err := execCmdNotNilAssertion(t, + "results", "show", + "--report-format", "pdf", + "--scan-id", "MOCK", + "--report-pdf-options", "scan-information") + assert.Equal(t, err.Error(), "report option \"scan-information\" unavailable", "Wrong expected error message") +} + func TestRunGetResultsGeneratingPdfReportWithEmailAndOptions(t *testing.T) { + mock.Flags = wrappers.FeatureFlagsResponseModel{{Name: wrappers.NewScanReportEnabled, Status: false}} cmd := createASTTestCommand() err := executeTestCommand(cmd, "results", "show", @@ -277,7 +291,32 @@ func TestRunGetResultsGeneratingPdfReportWithEmailAndOptions(t *testing.T) { assert.NilError(t, err) } -func TestRunGetResultsGeneratingPdfReporWithOptions(t *testing.T) { +func TestRunGetResultsGeneratingPdfReportWithOptionsImproved(t *testing.T) { + mock.Flags = wrappers.FeatureFlagsResponseModel{{Name: wrappers.NewScanReportEnabled, Status: true}} + cmd := createASTTestCommand() + err := executeTestCommand(cmd, + "results", "show", + "--report-format", "pdf", + "--scan-id", "MOCK", + "--report-pdf-email", "ab@cd.pt,test@test.pt", + "--report-pdf-options", "Iac-Security,Sast,Sca,scan-information") + assert.NilError(t, err) +} + +func TestRunGetResultsGeneratingPdfReportWithInvalidOptionsImproved(t *testing.T) { + mock.Flags = wrappers.FeatureFlagsResponseModel{{Name: wrappers.NewScanReportEnabled, Status: true}} + cmd := createASTTestCommand() + err := executeTestCommand(cmd, + "results", "show", + "--report-format", "pdf", + "--scan-id", "MOCK", + "--report-pdf-email", "ab@cd.pt,test@test.pt", + "--report-pdf-options", "Iac-Security,Sast,Sca,ScanSummary") + assert.Error(t, err, "report option \"scansummary\" unavailable") +} + +func TestRunGetResultsGeneratingPdfReportWithOptions(t *testing.T) { + mock.Flags = wrappers.FeatureFlagsResponseModel{{Name: wrappers.NewScanReportEnabled, Status: false}} cmd := createASTTestCommand() err := executeTestCommand(cmd, "results", "show", diff --git a/internal/commands/scan.go b/internal/commands/scan.go index 464549c49..e614c2bf7 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -556,7 +556,7 @@ func scanCreateSubCommand( createScanCmd.PersistentFlags().String(commonParams.ScaPrivatePackageVersionFlag, "", scaPrivatePackageVersionFlagDescription) createScanCmd.PersistentFlags().String(commonParams.ReportFormatPdfToEmailFlag, "", pdfToEmailFlagDescription) createScanCmd.PersistentFlags().String(commonParams.ReportSbomFormatFlag, defaultSbomOption, sbomReportFlagDescription) - createScanCmd.PersistentFlags().String(commonParams.ReportFormatPdfOptionsFlag, defaultPdfOptionsDataSections, pdfOptionsFlagDescription) + createScanCmd.PersistentFlags().String(commonParams.ReportFormatPdfOptionsFlag, "", pdfOptionsFlagDescription) createScanCmd.PersistentFlags().String(commonParams.TargetFlag, "cx_result", "Output file") createScanCmd.PersistentFlags().String(commonParams.TargetPathFlag, ".", "Output Path") createScanCmd.PersistentFlags().StringSlice(commonParams.FilterFlag, []string{}, filterResultsListFlagUsage) diff --git a/internal/wrappers/feature-flags.go b/internal/wrappers/feature-flags.go index 21c5fe877..65f2b04d2 100644 --- a/internal/wrappers/feature-flags.go +++ b/internal/wrappers/feature-flags.go @@ -8,6 +8,7 @@ const tenantIDClaimKey = "tenant_id" const PackageEnforcementEnabled = "PACKAGE_ENFORCEMENT_ENABLED" const MinioEnabled = "MINIO_ENABLED" const ContainerEngineCLIEnabled = "CONTAINER_ENGINE_CLI_ENABLED" +const NewScanReportEnabled = "NEW_SAST_SCAN_REPORT_ENABLED" var FeatureFlagsBaseMap = []CommandFlags{ { @@ -26,6 +27,15 @@ var FeatureFlagsBaseMap = []CommandFlags{ { CommandName: "cx project create", }, + { + CommandName: "cx results show", + FeatureFlags: []FlagBase{ + { + Name: NewScanReportEnabled, + Default: false, + }, + }, + }, } var FeatureFlags = map[string]bool{} diff --git a/test/integration/result_test.go b/test/integration/result_test.go index b3711da84..78a9755be 100644 --- a/test/integration/result_test.go +++ b/test/integration/result_test.go @@ -210,7 +210,7 @@ func TestResultsGeneratingPdfReportWithPdfOptions(t *testing.T) { "results", "show", flag(params.ScanIDFlag), scanID, flag(params.TargetFormatFlag), "pdf", - flag(params.ReportFormatPdfOptionsFlag), "Iac-Security,ScanSummary,ExecutiveSummary,ScanResults", + flag(params.ReportFormatPdfOptionsFlag), "Iac-Security,scan-information", flag(params.TargetFlag), fileName, ) defer func() { @@ -220,7 +220,6 @@ func TestResultsGeneratingPdfReportWithPdfOptions(t *testing.T) { _, err := os.Stat(fmt.Sprintf("%s.%s", fileName, printer.FormatPDF)) assert.NilError(t, err, "Report file should exist: "+fileName+printer.FormatPDF) assert.Assert(t, outputBuffer != nil, "Scan must complete successfully") - } func TestResultsGeneratingPdfReportAndSendToEmail(t *testing.T) { @@ -230,7 +229,7 @@ func TestResultsGeneratingPdfReportAndSendToEmail(t *testing.T) { "results", "show", flag(params.ScanIDFlag), scanID, flag(params.TargetFormatFlag), "pdf", - flag(params.ReportFormatPdfOptionsFlag), "Iac-Security,ScanSummary,ExecutiveSummary,ScanResults", + flag(params.ReportFormatPdfOptionsFlag), "Iac-Security,scan-information", flag(params.ReportFormatPdfToEmailFlag), "test@checkmarx.com,test+2@checkmarx.com", ) assert.Assert(t, outputBuffer != nil, "Scan must complete successfully") diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index 67cec7b1b..90c3f3a40 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -1057,7 +1057,7 @@ func TestScanGeneratingPdfReportWithPdfOptions(t *testing.T) { flag(params.PresetName), "Checkmarx Default", flag(params.BranchFlag), "dummy_branch", flag(params.TargetFormatFlag), "pdf", - flag(params.ReportFormatPdfOptionsFlag), "Iac-Security,ScanSummary,ExecutiveSummary,ScanResults", + flag(params.ReportFormatPdfOptionsFlag), "Iac-Security,scan-information", flag(params.TargetFlag), fileName, ) defer func() {