diff --git a/internal/commands/result.go b/internal/commands/result.go index 7a321ceef..c13aaf60d 100644 --- a/internal/commands/result.go +++ b/internal/commands/result.go @@ -1111,10 +1111,8 @@ func setIsSCSEnabled(featureFlagsWrapper wrappers.FeatureFlagsWrapper) { wrappers.IsSCSEnabled = scsEngineCLIEnabled.Status } -func setIsContainersEnabled(agent string, featureFlagsWrapper wrappers.FeatureFlagsWrapper) { - agentSupported := !containsIgnoreCase(containerEngineUnsupportedAgents, agent) - containerEngineCLIEnabled, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.ContainerEngineCLIEnabled) - wrappers.IsContainersEnabled = containerEngineCLIEnabled.Status && agentSupported +func setIsContainersEnabled(agent string) { + wrappers.IsContainersEnabled = !containsIgnoreCase(containerEngineUnsupportedAgents, agent) } func filterResultsByType(results *wrappers.ScanResultsCollection, excludedTypes map[string]struct{}) *wrappers.ScanResultsCollection { @@ -1173,7 +1171,7 @@ func CreateScanReport( reportList := strings.Split(reportTypes, ",") results := &wrappers.ScanResultsCollection{} setIsSCSEnabled(featureFlagsWrapper) - setIsContainersEnabled(agent, featureFlagsWrapper) + setIsContainersEnabled(agent) summary, err := convertScanToResultsSummary(scan, resultsWrapper) if err != nil { return nil, err diff --git a/internal/commands/result_test.go b/internal/commands/result_test.go index ad0aabccf..46a2c1d07 100644 --- a/internal/commands/result_test.go +++ b/internal/commands/result_test.go @@ -312,7 +312,6 @@ func TestRunGetResultsByScanIdSarifFormat(t *testing.T) { } func TestRunGetResultsByScanIdSarifFormatWithContainers(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "sarif") // Remove generated sarif file removeFileBySuffix(t, printer.FormatSarif) @@ -334,7 +333,6 @@ func TestRunGetResultsByScanIdSonarFormat(t *testing.T) { func TestRunGetResultsByScanIdSonarFormatWithContainers(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "sonar") // Remove generated sonar file removeFile(t, fileName+"_"+printer.FormatSonar, printer.FormatJSON) @@ -367,7 +365,6 @@ func TestDecodeHTMLEntitiesInResults(t *testing.T) { func TestRunGetResultsByScanIdJsonFormatWithContainers(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "json") // Remove generated json file @@ -390,7 +387,6 @@ func TestRunGetResultsByScanIdSummaryJsonFormat(t *testing.T) { func TestRunGetResultsByScanIdSummaryJsonFormatWithContainers(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "summaryJSON") // Remove generated json file @@ -406,7 +402,6 @@ func TestRunGetResultsByScanIdSummaryHtmlFormat(t *testing.T) { func TestRunGetResultsByScanIdSummaryHtmlFormatWithContainers(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "summaryHTML") // Remove generated html file @@ -425,13 +420,11 @@ func TestRunGetResultsByScanIdSummaryMarkdownFormatWithContainers(t *testing.T) func TestRunGetResultsByScanIdSummaryConsoleFormatWithContainers(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "summaryConsole") } func TestRunGetResultsByScanIdSummaryMarkdownFormat(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "markdown") // Remove generated md file removeFileBySuffix(t, "md") @@ -480,7 +473,6 @@ func TestRunGetResultsByScanIdPDFFormat(t *testing.T) { func TestRunGetResultsByScanIdPDFFormatWithContainers(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "pdf") _, err := os.Stat(fmt.Sprintf("%s.%s", fileName, printer.FormatPDF)) assert.NilError(t, err, "Report file should exist for extension "+printer.FormatPDF) @@ -759,7 +751,6 @@ func TestSBOMReportXML(t *testing.T) { func TestSBOMReportJsonWithContainers(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "sbom") _, err := os.Stat(fmt.Sprintf("%s.%s", fileName+"_"+printer.FormatSbom, printer.FormatJSON)) assert.NilError(t, err, "Report file should exist for extension "+printer.FormatJSON) @@ -769,7 +760,6 @@ func TestSBOMReportJsonWithContainers(t *testing.T) { func TestSBOMReportXMLWithContainers(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "sbom", "--report-sbom-format", "CycloneDxXml") _, err := os.Stat(fmt.Sprintf("%s.%s", fileName+"_"+printer.FormatSbom, printer.FormatXML)) assert.NilError(t, err, "Report file should exist for extension "+printer.FormatXML) @@ -782,26 +772,17 @@ func TestRunGetResultsByScanIdGLFormat(t *testing.T) { // Run test for gl-sast report type os.Remove(fmt.Sprintf("%s.%s", fileName, printer.FormatGLSast)) } + func TestRunResultsShow_ContainersFFIsOn_includeContainersResult(t *testing.T) { clearFlags() - clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "json") assertTypePresentJSON(t, params.ContainersType, 1) // Remove generated json file removeFileBySuffix(t, printer.FormatJSON) } -func TestRunResultsShow_ContainersFFIsOff_excludeContainersResult(t *testing.T) { - clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: false} - execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "json") - assertTypePresentJSON(t, params.ContainersType, 0) - // Remove generated json file - removeFileBySuffix(t, printer.FormatJSON) -} + func TestRunResultsShow_jetbrainsIsNotSupported_excludeContainersResult(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "json", "--agent", "jetbrains") assertTypePresentJSON(t, params.ContainersType, 0) // Remove generated json file @@ -810,7 +791,6 @@ func TestRunResultsShow_jetbrainsIsNotSupported_excludeContainersResult(t *testi func TestRunResultsShow_EclipseIsNotSupported_excludeContainersResult(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "json", "--agent", "Eclipse") assertTypePresentJSON(t, params.ContainersType, 0) // Remove generated json file @@ -819,7 +799,6 @@ func TestRunResultsShow_EclipseIsNotSupported_excludeContainersResult(t *testing func TestRunResultsShow_VsCodeIsNotSupported_excludeContainersResult(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "json", "--agent", "vs code") assertTypePresentJSON(t, params.ContainersType, 0) // Remove generated json file @@ -828,7 +807,6 @@ func TestRunResultsShow_VsCodeIsNotSupported_excludeContainersResult(t *testing. func TestRunResultsShow_VisualStudioIsNotSupported_excludeContainersResult(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "json", "--agent", "Visual Studio") assertTypePresentJSON(t, params.ContainersType, 0) // Remove generated json file @@ -952,11 +930,7 @@ func assertResultsPresentSummaryJSON(t *testing.T, isResultsEnabled bool, scanTy assert.Assert(t, false, "%s result summary should be present", scanType) } } -func TestRunGetResultsShow_ContainersFFOffAndResultsHasContainersResultsOnly_NilAssertion(t *testing.T) { - clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: false} - execCmdNilAssertion(t, "results", "show", "--scan-id", "CONTAINERS_ONLY", "--report-format", "summaryConsole") -} + func TestRunGetResultsByScanIdGLSastAndAScaFormat(t *testing.T) { execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "gl-sast,gl-sca") // Run test for gl-sast report type @@ -1215,7 +1189,7 @@ func TestGetResultsSummaryConsoleFormatWithCriticalDisabled(t *testing.T) { stdoutString := buffer.String() fmt.Print(stdoutString) - totalSummary := "| TOTAL N/A 5 1 1 0 Completed |" + totalSummary := "| TOTAL N/A 5 2 1 0 Completed |" assert.Equal(t, strings.Contains(stdoutString, totalSummary), true, "Expected Total summary without critical:"+totalSummary) @@ -1234,7 +1208,7 @@ func Test_enhanceWithScanSummary(t *testing.T) { name: "scan summary with no vulnerabilities", summary: createEmptyResultSummary(), results: &wrappers.ScanResultsCollection{ - Results: nil, + Results: []*wrappers.ScanResult{}, TotalCount: 0, ScanID: "MOCK", }, @@ -1252,17 +1226,21 @@ func Test_enhanceWithScanSummary(t *testing.T) { } func createEmptyResultSummary() *wrappers.ResultSummary { + var containersIssues = new(int) + *containersIssues = 0 + return &wrappers.ResultSummary{ - TotalIssues: 0, - CriticalIssues: 0, - HighIssues: 0, - MediumIssues: 0, - LowIssues: 0, - InfoIssues: 0, - SastIssues: 0, - ScaIssues: 0, - KicsIssues: 0, - SCSOverview: &wrappers.SCSOverview{}, + TotalIssues: 0, + CriticalIssues: 0, + HighIssues: 0, + MediumIssues: 0, + LowIssues: 0, + InfoIssues: 0, + SastIssues: 0, + ScaIssues: 0, + KicsIssues: 0, + ContainersIssues: containersIssues, + SCSOverview: &wrappers.SCSOverview{}, APISecurity: wrappers.APISecResult{ APICount: 0, TotalRisksCount: 0, diff --git a/internal/commands/scan.go b/internal/commands/scan.go index fdc035880..d029cb2f1 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -95,6 +95,7 @@ const ( ConfigContainersImagesFilterKey = "imagesFilter" ConfigContainersPackagesFilterKey = "packagesFilter" ConfigContainersNonFinalStagesFilterKey = "nonFinalStagesFilter" + ConfigUserCustomImagesKey = "userCustomImages" resultsMapValue = "value" resultsMapType = "type" trueString = "true" @@ -602,6 +603,7 @@ func scanCreateSubCommand( "", fmt.Sprintf("Parameters to use in SCA resolver (requires --%s).", commonParams.ScaResolverFlag), ) + createScanCmd.PersistentFlags().Bool(commonParams.ContainerResolveLocallyFlag, false, "Execute container resolver locally.") createScanCmd.PersistentFlags().String(commonParams.ContainerImagesFlag, "", "List of container images to scan, ex: manuelbcd/vulnapp:latest,debian:10.") createScanCmd.PersistentFlags().String(commonParams.ScanTypes, "", "Scan types, ex: (sast,iac-security,sca,api-security)") @@ -795,7 +797,6 @@ func setupScanTypeProjectAndConfig( return err } } - containerEngineCLIEnabled, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.ContainerEngineCLIEnabled) sastConfig := addSastScan(cmd, resubmitConfig) if sastConfig != nil { @@ -813,7 +814,7 @@ func setupScanTypeProjectAndConfig( if apiSecConfig != nil { configArr = append(configArr, apiSecConfig) } - var containersConfig = addContainersScan(cmd, resubmitConfig, containerEngineCLIEnabled.Status) + var containersConfig = addContainersScan(cmd, resubmitConfig) if containersConfig != nil { configArr = append(configArr, containersConfig) } @@ -989,8 +990,8 @@ func addScaScan(cmd *cobra.Command, resubmitConfig []wrappers.Config, hasContain return nil } -func addContainersScan(cmd *cobra.Command, resubmitConfig []wrappers.Config, containerEngineCLIEnabled bool) map[string]interface{} { - if !scanTypeEnabled(commonParams.ContainersType) || !containerEngineCLIEnabled { +func addContainersScan(cmd *cobra.Command, resubmitConfig []wrappers.Config) map[string]interface{} { + if !scanTypeEnabled(commonParams.ContainersType) { return nil } containerMapConfig := make(map[string]interface{}) @@ -1015,6 +1016,10 @@ func addContainersScan(cmd *cobra.Command, resubmitConfig []wrappers.Config, con if imageTagFilter != "" { containerConfig.ImagesFilter = imageTagFilter } + userCustomImages, _ := cmd.Flags().GetString(commonParams.ContainerImagesFlag) + if userCustomImages != "" { + containerConfig.UserCustomImages = userCustomImages + } containerMapConfig[resultsMapValue] = &containerConfig return containerMapConfig @@ -1041,6 +1046,10 @@ func initializeContainersConfigWithResubmitValues(resubmitConfig []wrappers.Conf if resubmitImagesFilter != nil && resubmitImagesFilter != "" { containerConfig.ImagesFilter = resubmitImagesFilter.(string) } + resubmitUserCustomImages := config.Value[ConfigUserCustomImagesKey] + if resubmitUserCustomImages != nil && resubmitUserCustomImages != "" { + containerConfig.UserCustomImages = resubmitUserCustomImages.(string) + } } } @@ -1167,7 +1176,6 @@ func validateScanTypes(cmd *cobra.Command, jwtWrapper wrappers.JWTWrapper, featu var scanTypes []string var SCSScanTypes []string - runContainerEngineCLI := isContainersEngineEnabled(featureFlagsWrapper) allowedEngines, err := jwtWrapper.GetAllowedEngines(featureFlagsWrapper) if err != nil { err = errors.Errorf("Error validating scan types: %v", err) @@ -1184,7 +1192,7 @@ func validateScanTypes(cmd *cobra.Command, jwtWrapper wrappers.JWTWrapper, featu scanTypes = strings.Split(userScanTypes, ",") for _, scanType := range scanTypes { - if !allowedEngines[scanType] || (scanType == commonParams.ContainersType && !(runContainerEngineCLI)) { + if !allowedEngines[scanType] { keys := reflect.ValueOf(allowedEngines).MapKeys() err = errors.Errorf(engineNotAllowed, scanType, scanType, keys) return err @@ -1200,9 +1208,6 @@ func validateScanTypes(cmd *cobra.Command, jwtWrapper wrappers.JWTWrapper, featu } else { for k := range allowedEngines { - if k == commonParams.ContainersType && !(runContainerEngineCLI) { - continue - } scanTypes = append(scanTypes, k) } } @@ -1213,16 +1218,6 @@ func validateScanTypes(cmd *cobra.Command, jwtWrapper wrappers.JWTWrapper, featu return nil } -func isContainersEngineEnabled(featureFlagsWrapper wrappers.FeatureFlagsWrapper) bool { - containerEngineCLIEnabled, err := featureFlagsWrapper.GetSpecificFlag(wrappers.ContainerEngineCLIEnabled) - if err != nil { - logger.PrintfIfVerbose("Failed to fetch CONTAINER_ENGINE_CLI_ENABLED FF, defaulting to `false`. Error: %s", err) - return false - } - - return containerEngineCLIEnabled.Status -} - func scanTypeEnabled(scanType string) bool { scanTypes := strings.Split(actualScanTypes, ",") for _, a := range scanTypes { @@ -1409,7 +1404,6 @@ func isDirFiltered(filename string, filters []string) (bool, error) { } } } - return false, nil } @@ -1504,17 +1498,16 @@ func addScaResults(zipWriter *zip.Writer) error { } func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) ( - url, zipFilePath string, - err error, -) { + url, zipFilePath string, err error) { + var preSignedURL string sourceDirFilter, _ := cmd.Flags().GetString(commonParams.SourceDirFilterFlag) userIncludeFilter, _ := cmd.Flags().GetString(commonParams.IncludeFilterFlag) projectName, _ := cmd.Flags().GetString(commonParams.ProjectName) - containerEngineCLIEnabled, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.ContainerEngineCLIEnabled) - - containerScanTriggered := strings.Contains(actualScanTypes, commonParams.ContainersType) && containerEngineCLIEnabled.Status + containerScanTriggered := strings.Contains(actualScanTypes, commonParams.ContainersType) + containerImagesFlag, _ := cmd.Flags().GetString(commonParams.ContainerImagesFlag) + containerResolveLocally, _ := cmd.Flags().GetBool(commonParams.ContainerResolveLocallyFlag) scaResolverParams, scaResolver := getScaResolverFlags(cmd) zipFilePath, directoryPath, err := definePathForZipFileOrDirectory(cmd) @@ -1535,14 +1528,29 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW if directoryPath != "" { var dirPathErr error - resolversErr := runScannerResolvers(cmd, directoryPath, projectName, containerScanTriggered, scaResolver, scaResolverParams) - if resolversErr != nil { - if unzip { - _ = cleanTempUnzipDirectory(directoryPath) + + // execute scaResolver only in sca type of scans + if strings.Contains(actualScanTypes, commonParams.ScaType) { + scaErr := runScaResolver(directoryPath, scaResolver, scaResolverParams, projectName) + if scaErr != nil { + if unzip { + _ = cleanTempUnzipDirectory(directoryPath) + } + return "", "", errors.Wrapf(scaErr, "ScaResolver error") } - return "", "", resolversErr } - if isSingleContainerScanTriggered() { + + if containerScanTriggered && containerResolveLocally { + containerResolverError := runContainerResolver(cmd, directoryPath, containerImagesFlag, containerResolveLocally) + if containerResolverError != nil { + if unzip { + _ = cleanTempUnzipDirectory(directoryPath) + } + return "", "", containerResolverError + } + } + + if isSingleContainerScanTriggered() && containerResolveLocally { logger.PrintIfVerbose("Single container scan triggered: compressing only the container resolution file") containerResolutionFilePath := filepath.Join(directoryPath, containerResolutionFileName) zipFilePath, dirPathErr = util.CompressFile(containerResolutionFilePath, containerResolutionFileName, directoryCreationPrefix) @@ -1568,13 +1576,12 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW return preSignedURL, zipFilePath, nil } -func runContainerResolver(cmd *cobra.Command, directoryPath string) error { - containerImages, _ := cmd.Flags().GetString(commonParams.ContainerImagesFlag) +func runContainerResolver(cmd *cobra.Command, directoryPath string, containerImageFlag string, containerResolveLocally bool) error { debug, _ := cmd.Flags().GetBool(commonParams.DebugFlag) var containerImagesList []string - if containerImages != "" { - containerImagesList = strings.Split(strings.TrimSpace(containerImages), ",") + if containerImageFlag != "" { + containerImagesList = strings.Split(strings.TrimSpace(containerImageFlag), ",") for _, containerImageName := range containerImagesList { if containerImagesErr := validateContainerImageFormat(containerImageName); containerImagesErr != nil { return containerImagesErr @@ -1582,26 +1589,11 @@ func runContainerResolver(cmd *cobra.Command, directoryPath string) error { } logger.PrintIfVerbose(fmt.Sprintf("User input container images identified: %v", strings.Join(containerImagesList, ", "))) } - containerResolverERR := containerResolver.Resolve(directoryPath, directoryPath, containerImagesList, debug) - if containerResolverERR != nil { - return containerResolverERR - } - return nil -} - -func runScannerResolvers(cmd *cobra.Command, directoryPath, projectName string, containerScanTriggered bool, scaResolver, scaResolverParams string) error { - // Make sure scaResolver only runs in sca type of scans - if strings.Contains(actualScanTypes, commonParams.ScaType) { - dirPathErr := runScaResolver(directoryPath, scaResolver, scaResolverParams, projectName) - if dirPathErr != nil { - return errors.Wrapf(dirPathErr, "ScaResolver error") - } - } - if containerScanTriggered { - containerResolverError := runContainerResolver(cmd, directoryPath) - if containerResolverError != nil { - return containerResolverError + if containerResolveLocally || len(containerImagesList) > 0 { + containerResolverERR := containerResolver.Resolve(directoryPath, directoryPath, containerImagesList, debug) + if containerResolverERR != nil { + return containerResolverERR } } return nil @@ -2855,9 +2847,18 @@ func validateCreateScanFlags(cmd *cobra.Command) error { } func validateContainerImageFormat(containerImage string) error { + if strings.HasSuffix(containerImage, ".tar") { + + var err = util.FileExists(containerImage) + if err != nil { + return errors.Errorf("--container-images flag error: %v", err) + } + return nil + } + imageParts := strings.Split(containerImage, ":") if len(imageParts) != 2 || imageParts[0] == "" || imageParts[1] == "" { - return errors.Errorf("Invalid value for --container-images flag. The value must be in the format :") + return errors.Errorf("Invalid value for --container-images flag. The value must be in the format : or .tar") } return nil } diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index dc7dbeb37..c34428ca3 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -148,53 +148,38 @@ func TestCreateScan(t *testing.T) { func TestCreateScanFromFolder_ContainersImagesAndDefaultScanTypes_ScanCreatedSuccessfully(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-b", "dummy_branch", "--container-images", "image1:latest,image2:tag"} execCmdNilAssertion(t, append(baseArgs, "-s", blankSpace+"."+blankSpace)...) } func TestCreateScanFromZip_ContainersImagesAndDefaultScanTypes_ScanCreatedSuccessfully(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "scan", "create", "--project-name", "MOCK", "-s", "data/sources.zip", "-b", "dummy_branch", "--container-images", "image1:latest,image2:tag") } func TestCreateScanFromZip_ContainerTypeAndFilterFlags_ScanCreatedSuccessfully(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} execCmdNilAssertion(t, "scan", "create", "--project-name", "MOCK", "--scan-types", "container-security", "-s", "data/sources.zip", "-b", "dummy_branch", "--file-filter", "!.java") } func TestCreateScanFromFolder_InvalidContainersImagesAndNoContainerScanType_ScanCreatedSuccessfully(t *testing.T) { // When no container scan type is provided, we will ignore the container images flag and its value clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-b", "dummy_branch", "--scan-types", "sast", "--container-images", "image1,image2:tag"} execCmdNilAssertion(t, append(baseArgs, "-s", blankSpace+"."+blankSpace)...) } func TestCreateScanFromFolder_ContainerImagesFlagWithoutValue_FailCreatingScan(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} err := execCmdNotNilAssertion(t, "scan", "create", "--project-name", "MOCK", "-s", dummyRepo, "-b", "dummy_branch", "--container-images") assert.Assert(t, err.Error() == "flag needs an argument: --container-images") } func TestCreateScanFromFolder_InvalidContainerImageFormat_FailCreatingScan(t *testing.T) { clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-b", "dummy_branch", "--container-images", "image1,image2:tag"} err := execCmdNotNilAssertion(t, append(baseArgs, "-s", blankSpace+"."+blankSpace)...) - assert.Assert(t, err.Error() == "Invalid value for --container-images flag. The value must be in the format :") -} - -func TestCreateContainersScan_ContainerFFIsOff_FailCreatingScan(t *testing.T) { - clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: false} - baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-b", "dummy_branch", "--scan-types", "container-security"} - err := execCmdNotNilAssertion(t, append(baseArgs, "-s", blankSpace+"."+blankSpace)...) - fmt.Println(err) - assert.ErrorContains(t, err, "you would need to purchase a license") + assert.Assert(t, err.Error() == "Invalid value for --container-images flag. The value must be in the format : or .tar") } func TestCreateScanWithThreshold_ShouldSuccess(t *testing.T) { @@ -285,6 +270,51 @@ func TestCreateScanWithScaResolverFailed(t *testing.T) { assert.Assert(t, strings.Contains(err.Error(), scaPathError), err.Error()) } +func TestCreateScanWithScaResolverParamsWrong(t *testing.T) { + tests := []struct { + name string + sourceDir string + scaResolver string + scaResolverParams string + projectName string + expectedError string + }{ + { + name: "ScaResolver wrong scaResolver path", + sourceDir: "/sourceDir", + scaResolver: "./ScaResolver", + scaResolverParams: "params", + projectName: "ProjectName", + expectedError: "/ScaResolver: no such file or directory", + }, + { + name: "Invalid scaResolverParams format", + sourceDir: "/sourceDir", + scaResolver: "./ScaResolver", + scaResolverParams: "\"unclosed quote", + projectName: "ProjectName", + expectedError: "EOF found when expecting closing quote", // Expected shlex error + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err := runScaResolver(tt.sourceDir, tt.scaResolver, tt.scaResolverParams, tt.projectName) + assert.Assert(t, strings.Contains(err.Error(), tt.expectedError), err.Error()) + }) + } +} + +func TestCreateScanWithScaResolverNoScaResolver(t *testing.T) { + var sourceDir = "/sourceDir" + var scaResolver = "" + var scaResolverParams = "params" + var projectName = "ProjectName" + err := runScaResolver(sourceDir, scaResolver, scaResolverParams, projectName) + assert.Assert(t, err == nil) +} + func TestCreateScanWithScanTypes(t *testing.T) { baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-s", dummyRepo, "-b", "dummy_branch"} execCmdNilAssertion(t, append(baseArgs, "--scan-types", "sast")...) @@ -1583,6 +1613,8 @@ func Test_validateThresholds(t *testing.T) { } func TestValidateContainerImageFormat(t *testing.T) { + var errMessage = "Invalid value for --container-images flag. The value must be in the format : or .tar" + testCases := []struct { name string containerImage string @@ -1593,25 +1625,30 @@ func TestValidateContainerImageFormat(t *testing.T) { containerImage: "nginx:latest", expectedError: nil, }, + { + name: "Valid compressed container image format", + containerImage: "nginx.tar", + expectedError: errors.Errorf("--container-images flag error: nginx.tar does not exist"), + }, { name: "Missing image name", containerImage: ":latest", - expectedError: errors.Errorf("Invalid value for --container-images flag. The value must be in the format :"), + expectedError: errors.New(errMessage), }, { name: "Missing image tag", containerImage: "nginx:", - expectedError: errors.Errorf("Invalid value for --container-images flag. The value must be in the format :"), + expectedError: errors.New(errMessage), }, { name: "Empty image name and tag", containerImage: ":", - expectedError: errors.Errorf("Invalid value for --container-images flag. The value must be in the format :"), + expectedError: errors.New(errMessage), }, { name: "Extra colon", containerImage: "nginx:latest:extra", - expectedError: errors.Errorf("Invalid value for --container-images flag. The value must be in the format :"), + expectedError: errors.New(errMessage), }, } @@ -1633,6 +1670,109 @@ func TestValidateContainerImageFormat(t *testing.T) { } } +func TestAddContainersScan_WithCustomImages_ShouldSetUserCustomImages(t *testing.T) { + // Setup + var resubmitConfig []wrappers.Config + + // Create command with container flag + cmdCommand := &cobra.Command{} + cmdCommand.Flags().String(commonParams.ContainerImagesFlag, "", "Container images") + + // Set test values for container images (comma-separated private artifactory images) + expectedImages := "artifactory.company.com/repo/image1:latest,artifactory.company.com/repo/image2:1.0.3" + _ = cmdCommand.Flags().Set(commonParams.ContainerImagesFlag, expectedImages) + + // Enable container scan type + originalScanTypes := actualScanTypes + actualScanTypes = commonParams.ContainersType // Use string instead of slice + defer func() { + actualScanTypes = originalScanTypes // Restore original value instead of nil + }() + + // Execute + result := addContainersScan(cmdCommand, resubmitConfig) + + // Verify + containerMapConfig, ok := result[resultsMapValue].(*wrappers.ContainerConfig) + assert.Assert(t, ok, "Expected result to contain a ContainerConfig") + + // Check that the UserCustomImages field was correctly set + assert.Equal(t, containerMapConfig.UserCustomImages, expectedImages, + "Expected UserCustomImages to be set to '%s', but got '%s'", + expectedImages, containerMapConfig.UserCustomImages) +} + +func TestInitializeContainersConfigWithResubmitValues_UserCustomImages(t *testing.T) { + // Define test cases + testCases := []struct { + name string + resubmitConfig []wrappers.Config + expectedCustomImages string + }{ + { + name: "When UserCustomImages is valid string, it should be set in containerConfig", + resubmitConfig: []wrappers.Config{ + { + Type: commonParams.ContainersType, + Value: map[string]interface{}{ + ConfigUserCustomImagesKey: "image1:tag1,image2:tag2", + }, + }, + }, + expectedCustomImages: "image1:tag1,image2:tag2", + }, + { + name: "When UserCustomImages is empty string, containerConfig should not be updated", + resubmitConfig: []wrappers.Config{ + { + Type: commonParams.ContainersType, + Value: map[string]interface{}{ + ConfigUserCustomImagesKey: "", + }, + }, + }, + expectedCustomImages: "", + }, + { + name: "When UserCustomImages is nil, containerConfig should not be updated", + resubmitConfig: []wrappers.Config{ + { + Type: commonParams.ContainersType, + Value: map[string]interface{}{ + ConfigUserCustomImagesKey: nil, + }, + }, + }, + expectedCustomImages: "", + }, + { + name: "When config.Value doesn't have UserCustomImages key, containerConfig should not be updated", + resubmitConfig: []wrappers.Config{ + { + Type: commonParams.ContainersType, + Value: map[string]interface{}{}, + }, + }, + expectedCustomImages: "", + }, + } + + // Run each test case + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Initialize containerConfig + containerConfig := &wrappers.ContainerConfig{} + + // Call the function under test + initializeContainersConfigWithResubmitValues(tc.resubmitConfig, containerConfig) + + // Assert the result + assert.Equal(t, tc.expectedCustomImages, containerConfig.UserCustomImages, + "Expected UserCustomImages to be %q but got %q", tc.expectedCustomImages, containerConfig.UserCustomImages) + }) + } +} + func Test_WhenScaResolverAndResultsFileExist_ThenAddScaResultsShouldRemoveThemAfterAddingToZip(t *testing.T) { // Step 1: Create a temporary file to simulate the SCA results file and check for errors. tempFile, err := os.CreateTemp("", "sca_results_test") @@ -1904,36 +2044,32 @@ func TestAddSastScan_ScanFlags(t *testing.T) { func TestValidateScanTypes(t *testing.T) { tests := []struct { - name string - userScanTypes string - userSCSScanTypes string - allowedEngines map[string]bool - containerEngineCLIEnabled bool - expectedError string + name string + userScanTypes string + userSCSScanTypes string + allowedEngines map[string]bool + expectedError string }{ { - name: "No licenses available", - userScanTypes: "scs", - userSCSScanTypes: "scs,secret-detection", - allowedEngines: map[string]bool{"scs": false, "enterprise-secrets": false}, - containerEngineCLIEnabled: true, - expectedError: "It looks like the \"scs\" scan type does", + name: "No licenses available", + userScanTypes: "scs", + userSCSScanTypes: "sast,secret-detection", + allowedEngines: map[string]bool{"scs": false, "enterprise-secrets": false}, + expectedError: "It looks like the \"scs\" scan type does", }, { - name: "SCS license available, secret-detection not available", - userScanTypes: "scs", - userSCSScanTypes: "secret-detection", - allowedEngines: map[string]bool{"scs": true, "enterprise-secrets": false}, - containerEngineCLIEnabled: true, - expectedError: "It looks like the \"secret-detection\" scan type does not exist", + name: "SCS license available, secret-detection not available", + userScanTypes: "scs", + userSCSScanTypes: "secret-detection", + allowedEngines: map[string]bool{"scs": true, "enterprise-secrets": false}, + expectedError: "It looks like the \"secret-detection\" scan type does not exist", }, { - name: "All licenses available", - userScanTypes: "scs", - userSCSScanTypes: "secret-detection", - allowedEngines: map[string]bool{"scs": true, "enterprise-secrets": true}, - containerEngineCLIEnabled: true, - expectedError: "", + name: "All licenses available", + userScanTypes: "scs", + userSCSScanTypes: "secret-detection", + allowedEngines: map[string]bool{"scs": true, "enterprise-secrets": true}, + expectedError: "", }, } @@ -1958,22 +2094,3 @@ func TestValidateScanTypes(t *testing.T) { }) } } - -func TestIsContainersEngineEnabled_FlagEnabled(t *testing.T) { - clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: true} - mock.FFErr = nil - - result := isContainersEngineEnabled(mock.FeatureFlagsMockWrapper{}) - assert.Assert(t, result, "expected result to be true") -} - -func TestIsContainersEngineEnabled_FlagRetrievalFails(t *testing.T) { - clearFlags() - mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.ContainerEngineCLIEnabled, Status: false} - mock.FFErr = errors.New("something went wrong while fetching ff") - - result := isContainersEngineEnabled(mock.FeatureFlagsMockWrapper{}) - - assert.Assert(t, !result, "expected result to be false") -} diff --git a/internal/commands/util/utils.go b/internal/commands/util/utils.go index b0d7ff5ed..fd08fa00b 100644 --- a/internal/commands/util/utils.go +++ b/internal/commands/util/utils.go @@ -231,3 +231,11 @@ func CloseZipWriter(zipWriter *zip.Writer, outputFile *os.File) { logger.PrintfIfVerbose("Failed to close zip writer: %s", outputFile.Name()) } } + +func FileExists(filename string) error { + _, err := os.Stat(filename) + if os.IsNotExist(err) { + return errors.Errorf("%s does not exist", filename) + } + return err +} diff --git a/internal/params/flags.go b/internal/params/flags.go index a6b6a6c9e..6343b4347 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -145,6 +145,7 @@ const ( LastSastScanTime = "sca-last-sast-scan-time" ProjecPrivatePackageFlag = "project-private-package" SastRedundancyFlag = "sast-redundancy" + ContainerResolveLocallyFlag = "containers-local-resolution" ContainerImagesFlag = "container-images" ContainersTypeFlag = "container-security" VSCodeAgent = "VS Code" diff --git a/internal/wrappers/feature-flags.go b/internal/wrappers/feature-flags.go index 14df8fd61..b0fb817a0 100644 --- a/internal/wrappers/feature-flags.go +++ b/internal/wrappers/feature-flags.go @@ -12,7 +12,6 @@ const PackageEnforcementEnabled = "PACKAGE_ENFORCEMENT_ENABLED" const CVSSV3Enabled = "CVSS_V3_ENABLED" const MinioEnabled = "MINIO_ENABLED" const SastCustomStateEnabled = "SAST_CUSTOM_STATES_ENABLED" -const ContainerEngineCLIEnabled = "CONTAINER_ENGINE_CLI_ENABLED" const SCSEngineCLIEnabled = "NEW_2MS_SCORECARD_RESULTS_CLI_ENABLED" const NewScanReportEnabled = "NEW_SAST_SCAN_REPORT_ENABLED" const RiskManagementEnabled = "RISK_MANAGEMENT_IDES_PROJECT_RESULTS_SCORES_API_ENABLED" diff --git a/internal/wrappers/scans.go b/internal/wrappers/scans.go index 1265e01ea..99925390a 100644 --- a/internal/wrappers/scans.go +++ b/internal/wrappers/scans.go @@ -147,6 +147,7 @@ type ContainerConfig struct { ImagesFilter string `json:"imagesFilter,omitempty"` PackagesFilter string `json:"packagesFilter,omitempty"` NonFinalStagesFilter string `json:"nonFinalStagesFilter,omitempty"` + UserCustomImages string `json:"userCustomImages,omitempty"` } type APISecConfig struct { SwaggerFilter string `json:"swaggerFilter,omitempty"` diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index b6809b5fc..6c575658c 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -421,11 +421,9 @@ func TestContainerEngineScansE2E_ContainerImagesFlagAndScanType(t *testing.T) { flag(params.BranchFlag), "dummy_branch", flag(params.ScanInfoFormatFlag), printer.FormatJSON, } - if isFFEnabled(t, wrappers.ContainerEngineCLIEnabled) { - scanID, projectID := executeCreateScan(t, testArgs) - assert.Assert(t, scanID != "", "Scan ID should not be empty") - assert.Assert(t, projectID != "", "Project ID should not be empty") - } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty") + assert.Assert(t, projectID != "", "Project ID should not be empty") } func TestContainerEngineScansE2E_ContainerImagesFlagOnly(t *testing.T) { @@ -439,11 +437,9 @@ func TestContainerEngineScansE2E_ContainerImagesFlagOnly(t *testing.T) { flag(params.ScanTypes), params.ContainersTypeFlag, flag(params.ScanInfoFormatFlag), printer.FormatJSON, } - if isFFEnabled(t, wrappers.ContainerEngineCLIEnabled) { - scanID, projectID := executeCreateScan(t, testArgs) - assert.Assert(t, scanID != "", "Scan ID should not be empty") - assert.Assert(t, projectID != "", "Project ID should not be empty") - } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty") + assert.Assert(t, projectID != "", "Project ID should not be empty") } func TestContainerEngineScansE2E_ContainerImagesAndDebugFlags(t *testing.T) { @@ -458,11 +454,9 @@ func TestContainerEngineScansE2E_ContainerImagesAndDebugFlags(t *testing.T) { flag(params.ScanTypes), params.ContainersTypeFlag, flag(params.ScanInfoFormatFlag), printer.FormatJSON, } - if isFFEnabled(t, wrappers.ContainerEngineCLIEnabled) { - scanID, projectID := executeCreateScan(t, testArgs) - assert.Assert(t, scanID != "", "Scan ID should not be empty") - assert.Assert(t, projectID != "", "Project ID should not be empty") - } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty") + assert.Assert(t, projectID != "", "Project ID should not be empty") } func TestContainerEngineScansE2E_ContainerImagesFlagAndEmptyFolderProject(t *testing.T) { @@ -476,11 +470,9 @@ func TestContainerEngineScansE2E_ContainerImagesFlagAndEmptyFolderProject(t *tes flag(params.ScanInfoFormatFlag), printer.FormatJSON, flag(params.ScanTypes), params.ContainersTypeFlag, } - if isFFEnabled(t, wrappers.ContainerEngineCLIEnabled) { - scanID, projectID := executeCreateScan(t, testArgs) - assert.Assert(t, scanID != "", "Scan ID should not be empty") - assert.Assert(t, projectID != "", "Project ID should not be empty") - } + scanID, projectID := executeCreateScan(t, testArgs) + assert.Assert(t, scanID != "", "Scan ID should not be empty") + assert.Assert(t, projectID != "", "Project ID should not be empty") } func TestContainerEngineScansE2E_InvalidContainerImagesFlag(t *testing.T) { @@ -493,10 +485,8 @@ func TestContainerEngineScansE2E_InvalidContainerImagesFlag(t *testing.T) { flag(params.BranchFlag), "dummy_branch", flag(params.ScanInfoFormatFlag), printer.FormatJSON, } - if isFFEnabled(t, wrappers.ContainerEngineCLIEnabled) { - err, _ := executeCommand(t, testArgs...) - assertError(t, err, "Invalid value for --container-images flag. The value must be in the format :") - } + err, _ := executeCommand(t, testArgs...) + assertError(t, err, "Invalid value for --container-images flag. The value must be in the format : or .tar") } // Create scans from current dir, zip and url and perform assertions in executeScanAssertions @@ -918,11 +908,7 @@ func executeScanAssertions(t *testing.T, projectID, scanID string, tags map[stri } func createScan(t *testing.T, source string, tags map[string]string) (string, string) { - if isFFEnabled(t, wrappers.ContainerEngineCLIEnabled) { - return executeCreateScan(t, getCreateArgs(source, tags, "sast , sca , iac-security , api-security, container-security, scs")) - } else { - return executeCreateScan(t, getCreateArgs(source, tags, "sast , sca , iac-security , api-security, scs")) - } + return executeCreateScan(t, getCreateArgs(source, tags, "sast , sca , iac-security , api-security, container-security, scs")) } func createScanNoWait(t *testing.T, source string, tags map[string]string, projectName string) (string, string) {