From b2b13b4a0278fda3457b7084087a0797cbe16943 Mon Sep 17 00:00:00 2001 From: Jeff Armstrong Date: Thu, 9 Sep 2021 04:38:42 -0700 Subject: [PATCH] Feature/sca resolver (#268) * - Added support for SCA resolver. * - Updated scan process. * - Changed name of included sca results file to .cxsca-results.json. * - ClI parameter or environment variable can now be used to activate resolver. If both values are found the CLI parameter wins. --- internal/commands/root.go | 1 + internal/commands/scan.go | 61 ++++++++++++++++++++++++++++++++++- internal/params/binds.go | 1 + internal/params/envs.go | 1 + internal/params/keys.go | 1 + test/integration/scan_test.go | 16 +++++++++ 6 files changed, 80 insertions(+), 1 deletion(-) diff --git a/internal/commands/root.go b/internal/commands/root.go index 97539a914..4c1f1ac88 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -49,6 +49,7 @@ const ( GroupList = "groups" IncrementalSast = "sast-incremental" PresetName = "sast-preset-name" + ScaResolverFlag = "sca-resolver" AccessKeyIDFlag = "client-id" AccessKeySecretFlag = "client-secret" AccessKeyIDFlagUsage = "The OAuth2 client ID" diff --git a/internal/commands/scan.go b/internal/commands/scan.go index 228952ec9..279c0c7ff 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "log" "os" + "os/exec" "path" "path/filepath" "strings" @@ -38,6 +39,7 @@ const ( ) var ( + scaResolverResultsFile = "" actualScanTypes = "sast,kics,sca" filterScanListFlagUsage = fmt.Sprintf("Filter the list of scans. Use ';' as the delimeter for arrays. Available filters are: %s", strings.Join([]string{ @@ -279,6 +281,7 @@ func scanCreateSubCommand( } createScanCmd.PersistentFlags().String(IncrementalSast, "false", "Incremental SAST scan should be performed.") createScanCmd.PersistentFlags().String(PresetName, "", "The name of the Checkmarx preset to use.") + createScanCmd.PersistentFlags().String(ScaResolverFlag, "", "Resolve SCA project dependencies (default true)") createScanCmd.PersistentFlags().String(ScanTypes, "", "Scan types, ex: (sast,kics,sca)") createScanCmd.PersistentFlags().String(TagList, "", "List of tags, ex: (tagA,tagB:val,etc)") createScanCmd.PersistentFlags().StringP(BranchFlag, BranchFlagSh, commonParams.Branch, BranchFlagUsage) @@ -286,11 +289,15 @@ func scanCreateSubCommand( createScanCmd.PersistentFlags().String(TargetFlag, "cx_result", "Output file") createScanCmd.PersistentFlags().String(TargetPathFlag, ".", "Output Path") createScanCmd.PersistentFlags().StringSlice(FilterFlag, []string{}, filterResultsListFlagUsage) - // Link the environment variable to the CLI argument(s). + // Link the environment variables to the CLI argument(s). err = viper.BindPFlag(commonParams.BranchKey, createScanCmd.PersistentFlags().Lookup(BranchFlag)) if err != nil { log.Fatal(err) } + err = viper.BindPFlag(commonParams.ScaToolKey, createScanCmd.PersistentFlags().Lookup(ScaResolverFlag)) + if err != nil { + log.Fatal(err) + } return createScanCmd } @@ -479,6 +486,7 @@ func addScaScan() map[string]interface{} { func compressFolder(sourceDir, filter, userIncludeFilter string) (string, error) { var err error + scaToolPath := viper.GetString(commonParams.ScaToolKey) outputFile, err := ioutil.TempFile(os.TempDir(), "cx-*.zip") if err != nil { log.Fatal("Cannot source code temp file.", err) @@ -488,6 +496,12 @@ func compressFolder(sourceDir, filter, userIncludeFilter string) (string, error) if err != nil { log.Fatal(err) } + if len(scaToolPath) > 0 && len(scaResolverResultsFile) > 0 { + err = addScaResults(zipWriter) + if err != nil { + log.Fatal(err) + } + } // Close the file if err = zipWriter.Close(); err != nil { log.Fatal(err) @@ -618,6 +632,50 @@ func filterMatched(filters []string, fileName string) bool { return matched } +func runScaResolver(sourceDir string) { + scaToolPath := viper.GetString(commonParams.ScaToolKey) + if len(scaToolPath) > 0 { + fmt.Println("Using SCA resolver: " + scaToolPath) + scaFile, err := ioutil.TempFile("", "sca") + scaResolverResultsFile = scaFile.Name() + ".json" + if err != nil { + log.Fatal(err) + } + if scaToolPath != "nop" { + out, err := exec.Command(scaToolPath, "offline", "-s", sourceDir, "-n", ProjectName, "-r", scaResolverResultsFile).Output() + fmt.Println(string(out)) + if err != nil { + log.Fatal(err) + } + } else { + fmt.Println("Creating 'No Op' resolver file.") + d1 := []byte("{}") + err := os.WriteFile(scaResolverResultsFile, d1, 0644) + if err != nil { + log.Fatal(err) + } + } + } +} + +func addScaResults(zipWriter *zip.Writer) error { + fmt.Println("Included SCA Results: ", ".cxsca-results.json") + dat, err := ioutil.ReadFile(scaResolverResultsFile) + os.Remove(scaResolverResultsFile) + if err != nil { + return err + } + f, err := zipWriter.Create(".cxsca-results.json") + if err != nil { + return err + } + _, err = f.Write(dat) + if err != nil { + return err + } + return nil +} + func determineSourceFile(uploadsWrapper wrappers.UploadsWrapper, sourcesFile, sourceDir, @@ -626,6 +684,7 @@ func determineSourceFile(uploadsWrapper wrappers.UploadsWrapper, var err error var preSignedURL string if sourceDir != "" { + runScaResolver(sourceDir) sourcesFile, _ = compressFolder(sourceDir, sourceDirFilter, userIncludeFilter) } if sourcesFile != "" { diff --git a/internal/params/binds.go b/internal/params/binds.go index a5480cb34..097e6244c 100644 --- a/internal/params/binds.go +++ b/internal/params/binds.go @@ -11,6 +11,7 @@ var EnvVarsBinds = []struct { {ProxyDomainKey, ProxyDomainEnv, ""}, {BaseAuthURIKey, BaseAuthURIEnv, ""}, {AstAPIKey, AstAPIKeyEnv, ""}, + {ScaToolKey, ScaToolEnv, ""}, {AgentNameKey, AgentNameEnv, "ASTCLI"}, {ScansPathKey, ScansPathEnv, "api/scans"}, {ProjectsPathKey, ProjectsPathEnv, "api/projects"}, diff --git a/internal/params/envs.go b/internal/params/envs.go index b95c3ad07..0e019a0fb 100644 --- a/internal/params/envs.go +++ b/internal/params/envs.go @@ -8,6 +8,7 @@ const ( ProxyTypeEnv = "CX_PROXY_AUTH_TYPE" ProxyDomainEnv = "CX_PROXY_NTLM_DOMAIN" BaseAuthURIEnv = "CX_BASE_AUTH_URI" + ScaToolEnv = "SCA_RESOLVER" AstAPIKeyEnv = "CX_APIKEY" AccessKeyIDEnv = "CX_CLIENT_ID" AccessKeySecretEnv = "CX_CLIENT_SECRET" diff --git a/internal/params/keys.go b/internal/params/keys.go index be4ffaaf0..c2308023f 100644 --- a/internal/params/keys.go +++ b/internal/params/keys.go @@ -11,6 +11,7 @@ var ( ProxyDomainKey = strings.ToLower(ProxyDomainEnv) BaseAuthURIKey = strings.ToLower(BaseAuthURIEnv) AstAPIKey = strings.ToLower(AstAPIKeyEnv) + ScaToolKey = strings.ToLower(ScaToolEnv) ScansPathKey = strings.ToLower(ScansPathEnv) AgentNameKey = strings.ToLower(AgentNameEnv) ProjectsPathKey = strings.ToLower(ProjectsPathEnv) diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index b71c4b89c..e5506b580 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -48,6 +48,18 @@ func TestNoWaitScan(t *testing.T) { executeScanTest(t, projectID, scanID, map[string]string{}) } +// Test ScaResolver environment variable, this is a nop test +func TestScaResolverEnv(t *testing.T) { + scanID, projectID := createScanNoWaitWithResolver(t, Dir, map[string]string{}) + defer deleteProject(t, projectID) + assert.Assert( + t, + pollScanUntilStatus(t, scanID, scansApi.ScanCompleted, FullScanWait, ScanPollSleep), + "Polling should complete when resolver used.", + ) + executeScanTest(t, projectID, scanID, map[string]string{}) +} + // Perform an initial scan with complete sources and an incremental scan with a smaller wait time func TestIncrementalScan(t *testing.T) { projectName := fmt.Sprintf("integration_test_incremental_%s", uuid.New().String()) @@ -174,6 +186,10 @@ func createScanNoWait(t *testing.T, source string, tags map[string]string) (stri return executeCreateScan(t, append(getCreateArgs(source, tags), "--nowait")) } +func createScanNoWaitWithResolver(t *testing.T, source string, tags map[string]string) (string, string) { + return executeCreateScan(t, append(getCreateArgs(source, tags), "--nowait", "--sca-resolver", "nop")) +} + func createScanIncremental(t *testing.T, source string, name string, tags map[string]string) (string, string) { return executeCreateScan(t, append(getCreateArgsWithName(source, tags, name), "--sast-incremental")) }