diff --git a/internal/commands/scan.go b/internal/commands/scan.go index bb09bdd0f..b2e32a084 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -14,6 +14,7 @@ import ( "path" "path/filepath" "reflect" + "regexp" "slices" "strconv" "strings" @@ -106,6 +107,7 @@ const ( "--scs-repo-url your_repo_url --scs-repo-token your_repo_token" ScsRepoWarningMsg = "SCS scan warning: Unable to start Scorecard scan due to missing required flags, please include in the ast-cli arguments: " + "--scs-repo-url your_repo_url --scs-repo-token your_repo_token" + ScsScorecardUnsupportedHostWarningMsg = "SCS scan warning: Unable to run Scorecard scanner due to unsupported repo host. Currently, Scorecard can only run on GitHub Cloud repos." ) var ( @@ -956,69 +958,91 @@ func createResubmitConfig(resubmitConfig []wrappers.Config, scsRepoToken, scsRep } return scsConfig } -func addSCSScan(cmd *cobra.Command, resubmitConfig []wrappers.Config, hasEnterpriseSecretsLicense bool) (map[string]interface{}, error) { - if scanTypeEnabled(commonParams.ScsType) || scanTypeEnabled(commonParams.MicroEnginesType) { - scsConfig := wrappers.SCSConfig{} - SCSMapConfig := make(map[string]interface{}) - SCSMapConfig[resultsMapType] = commonParams.MicroEnginesType // scs is still microengines in the scans API - userScanTypes, _ := cmd.Flags().GetString(commonParams.ScanTypes) - scsRepoToken := viper.GetString(commonParams.ScsRepoTokenKey) - if token, _ := cmd.Flags().GetString(commonParams.SCSRepoTokenFlag); token != "" { - scsRepoToken = token - } - viper.Set(commonParams.SCSRepoTokenFlag, scsRepoToken) // sanitizeLogs uses viper to get the value - scsRepoURL, _ := cmd.Flags().GetString(commonParams.SCSRepoURLFlag) - viper.Set(commonParams.SCSRepoURLFlag, scsRepoURL) // sanitizeLogs uses viper to get the value - SCSEngines, _ := cmd.Flags().GetString(commonParams.SCSEnginesFlag) - if resubmitConfig != nil { - scsConfig = createResubmitConfig(resubmitConfig, scsRepoToken, scsRepoURL, hasEnterpriseSecretsLicense) - SCSMapConfig[resultsMapValue] = &scsConfig - return SCSMapConfig, nil - } - - scsSecretDetectionSelected := false - scsScoreCardSelected := false - - if SCSEngines != "" { - SCSEnginesTypes := strings.Split(SCSEngines, ",") - for _, engineType := range SCSEnginesTypes { - engineType = strings.TrimSpace(engineType) - switch engineType { - case ScsSecretDetectionType: - scsSecretDetectionSelected = true - case ScsScoreCardType: - scsScoreCardSelected = true - } - } - } else { - scsSecretDetectionSelected = true - scsScoreCardSelected = true - } - if scsSecretDetectionSelected && hasEnterpriseSecretsLicense { - scsConfig.Twoms = trueString - } - if scsScoreCardSelected { - if scsRepoToken != "" && scsRepoURL != "" { - scsConfig.Scorecard = trueString - scsConfig.RepoToken = scsRepoToken - scsConfig.RepoURL = strings.ToLower(scsRepoURL) - } else { - if userScanTypes == "" { - fmt.Println(ScsRepoWarningMsg) - } else { - return nil, errors.Errorf(ScsRepoRequiredMsg) - } - } +func getSCSEnginesSelected(scsEngines string) (isScorecardSelected, isSecretDetectionSelected bool) { + if scsEngines == "" { + return true, true + } + scsEnginesTypes := strings.Split(scsEngines, ",") + for _, engineType := range scsEnginesTypes { + engineType = strings.TrimSpace(engineType) + switch engineType { + case ScsSecretDetectionType: + isSecretDetectionSelected = true + case ScsScoreCardType: + isScorecardSelected = true } - if scsConfig.Scorecard != trueString && scsConfig.Twoms != trueString { - return nil, nil + } + return isScorecardSelected, isSecretDetectionSelected +} + +func isURLSupportedByScorecard(scsRepoURL string) bool { + // only for https; currently our scorecard solution doesn't support GitHub Enterprise Server hosts + githubURLPattern := regexp.MustCompile(`^(?:https?://)?github\.com/.+`) + isGithubURL := githubURLPattern.MatchString(scsRepoURL) + if scsRepoURL != "" && !isGithubURL { + fmt.Println(ScsScorecardUnsupportedHostWarningMsg) + } + return isGithubURL +} + +func isScorecardRunnable(scsRepoToken, scsRepoURL, userScanTypes string) (bool, error) { + if scsRepoToken == "" || scsRepoURL == "" { + if userScanTypes != "" { + return false, errors.Errorf(ScsRepoRequiredMsg) } + fmt.Println(ScsRepoWarningMsg) + return false, nil + } + return isURLSupportedByScorecard(scsRepoURL), nil +} + +func addSCSScan(cmd *cobra.Command, resubmitConfig []wrappers.Config, hasEnterpriseSecretsLicense bool) (map[string]interface{}, error) { + if !scanTypeEnabled(commonParams.ScsType) && !scanTypeEnabled(commonParams.MicroEnginesType) { + return nil, nil + } + scsConfig := wrappers.SCSConfig{} + SCSMapConfig := make(map[string]interface{}) + SCSMapConfig[resultsMapType] = commonParams.MicroEnginesType // scs is still microengines in the scans API + userScanTypes, _ := cmd.Flags().GetString(commonParams.ScanTypes) + scsRepoToken := viper.GetString(commonParams.ScsRepoTokenKey) + if token, _ := cmd.Flags().GetString(commonParams.SCSRepoTokenFlag); token != "" { + scsRepoToken = token + } + viper.Set(commonParams.SCSRepoTokenFlag, scsRepoToken) // sanitizeLogs uses viper to get the value + scsRepoURL, _ := cmd.Flags().GetString(commonParams.SCSRepoURLFlag) + viper.Set(commonParams.SCSRepoURLFlag, scsRepoURL) // sanitizeLogs uses viper to get the value + SCSEngines, _ := cmd.Flags().GetString(commonParams.SCSEnginesFlag) + if resubmitConfig != nil { + scsConfig = createResubmitConfig(resubmitConfig, scsRepoToken, scsRepoURL, hasEnterpriseSecretsLicense) SCSMapConfig[resultsMapValue] = &scsConfig return SCSMapConfig, nil } - return nil, nil + scsScoreCardSelected, scsSecretDetectionSelected := getSCSEnginesSelected(SCSEngines) + + if scsSecretDetectionSelected && hasEnterpriseSecretsLicense { + scsConfig.Twoms = trueString + } + + if scsScoreCardSelected { + canRunScorecard, err := isScorecardRunnable(scsRepoToken, scsRepoURL, userScanTypes) + if err != nil { + return nil, err + } + if canRunScorecard { + scsConfig.Scorecard = trueString + scsConfig.RepoToken = scsRepoToken + scsConfig.RepoURL = strings.ToLower(scsRepoURL) + } + } + + if scsConfig.Scorecard != trueString && scsConfig.Twoms != trueString { + return nil, nil + } + + SCSMapConfig[resultsMapValue] = &scsConfig + return SCSMapConfig, nil } func validateScanTypes(cmd *cobra.Command, jwtWrapper wrappers.JWTWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) error { diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index 6d299d035..5c6525814 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -6,6 +6,7 @@ import ( "archive/zip" "bytes" "fmt" + "io" "log" "os" "reflect" @@ -31,7 +32,9 @@ const ( unknownFlag = "unknown flag: --chibutero" blankSpace = " " errorMissingBranch = "Failed creating a scan: Please provide a branch" + dummyGitlabRepo = "https://gitlab.com/dummy-org/gitlab-dummy" dummyRepo = "https://github.com/dummyuser/dummy_project.git" + dummyShortenedGithubRepo = "github.com/dummyuser/dummy_project.git" dummyToken = "dummyToken" dummySSHRepo = "git@github.com:dummyRepo/dummyProject.git" errorSourceBadFormat = "Failed creating a scan: Input in bad format: Sources input has bad format: " @@ -1034,6 +1037,165 @@ func TestCreateScan_WithSCSSecretDetection_scsMapHasSecretDetection(t *testing.T } } +func TestCreateScan_WithSCSSecretDetectionAndScorecardShortenedGithubRepo_scsMapHasBoth(t *testing.T) { + // Create a pipe for capturing stdout + r, w, _ := os.Pipe() + oldStdout := os.Stdout + defer func() { os.Stdout = oldStdout }() + os.Stdout = w // Redirecting stdout to the pipe + + var resubmitConfig []wrappers.Config + cmdCommand := &cobra.Command{ + Use: "scan", + Short: "Scan a project", + Long: `Scan a project`, + } + cmdCommand.PersistentFlags().String(commonParams.SCSEnginesFlag, "", "SCS Engine flag") + cmdCommand.PersistentFlags().String(commonParams.SCSRepoTokenFlag, "", "GitHub token to be used with SCS engines") + cmdCommand.PersistentFlags().String(commonParams.SCSRepoURLFlag, "", "GitHub url to be used with SCS engines") + _ = cmdCommand.Execute() + _ = cmdCommand.Flags().Set(commonParams.SCSEnginesFlag, "secret-detection,scorecard") + _ = cmdCommand.Flags().Set(commonParams.SCSRepoTokenFlag, dummyToken) + _ = cmdCommand.Flags().Set(commonParams.SCSRepoURLFlag, dummyShortenedGithubRepo) + + result, _ := addSCSScan(cmdCommand, resubmitConfig, true) + + // Close the writer to signal that we are done capturing the output + w.Close() + + // Read from the pipe (stdout) + var buf bytes.Buffer + _, err := io.Copy(&buf, r) // Copy the captured output to a buffer + if err != nil { + t.Fatalf("Failed to capture output: %v", err) + } + + output := buf.String() + if strings.Contains(output, ScsScorecardUnsupportedHostWarningMsg) { + t.Errorf("Expected output to not contain %q, but got %q", ScsScorecardUnsupportedHostWarningMsg, output) + } + + scsConfig := wrappers.SCSConfig{ + Twoms: "true", + Scorecard: "true", + RepoURL: dummyShortenedGithubRepo, + RepoToken: dummyToken, + } + scsMapConfig := make(map[string]interface{}) + scsMapConfig[resultsMapType] = commonParams.MicroEnginesType + scsMapConfig[resultsMapValue] = &scsConfig + + if !reflect.DeepEqual(result, scsMapConfig) { + t.Errorf("Expected %+v, but got %+v", scsMapConfig, result) + } +} + +func TestCreateScan_WithSCSSecretDetectionAndScorecardGitLabRepo_scsMapHasSecretDetection(t *testing.T) { + // Create a pipe for capturing stdout + r, w, _ := os.Pipe() + oldStdout := os.Stdout + defer func() { os.Stdout = oldStdout }() + os.Stdout = w // Redirecting stdout to the pipe + + var resubmitConfig []wrappers.Config + cmdCommand := &cobra.Command{ + Use: "scan", + Short: "Scan a project", + Long: `Scan a project`, + } + cmdCommand.PersistentFlags().String(commonParams.SCSEnginesFlag, "", "SCS Engine flag") + cmdCommand.PersistentFlags().String(commonParams.SCSRepoTokenFlag, "", "GitHub token to be used with SCS engines") + cmdCommand.PersistentFlags().String(commonParams.SCSRepoURLFlag, "", "GitHub url to be used with SCS engines") + _ = cmdCommand.Execute() + _ = cmdCommand.Flags().Set(commonParams.SCSEnginesFlag, "secret-detection,scorecard") + _ = cmdCommand.Flags().Set(commonParams.SCSRepoTokenFlag, dummyToken) + _ = cmdCommand.Flags().Set(commonParams.SCSRepoURLFlag, dummyGitlabRepo) + + result, _ := addSCSScan(cmdCommand, resubmitConfig, true) + + // Close the writer to signal that we are done capturing the output + w.Close() + + // Read from the pipe (stdout) + var buf bytes.Buffer + _, err := io.Copy(&buf, r) // Copy the captured output to a buffer + if err != nil { + t.Fatalf("Failed to capture output: %v", err) + } + + output := buf.String() + if !strings.Contains(output, ScsScorecardUnsupportedHostWarningMsg) { + t.Errorf("Expected output to contain %q, but got %q", ScsScorecardUnsupportedHostWarningMsg, output) + } + + scsConfig := wrappers.SCSConfig{ + Twoms: "true", + Scorecard: "", + RepoURL: "", + RepoToken: "", + } + scsMapConfig := make(map[string]interface{}) + scsMapConfig[resultsMapType] = commonParams.MicroEnginesType + scsMapConfig[resultsMapValue] = &scsConfig + + if !reflect.DeepEqual(result, scsMapConfig) { + t.Errorf("Expected %+v, but got %+v", scsMapConfig, result) + } +} + +func TestCreateScan_WithSCSSecretDetectionAndScorecardGitSSHRepo_scsMapHasSecretDetection(t *testing.T) { + // Create a pipe for capturing stdout + r, w, _ := os.Pipe() + oldStdout := os.Stdout + defer func() { os.Stdout = oldStdout }() + os.Stdout = w // Redirecting stdout to the pipe + + var resubmitConfig []wrappers.Config + cmdCommand := &cobra.Command{ + Use: "scan", + Short: "Scan a project", + Long: `Scan a project`, + } + cmdCommand.PersistentFlags().String(commonParams.SCSEnginesFlag, "", "SCS Engine flag") + cmdCommand.PersistentFlags().String(commonParams.SCSRepoTokenFlag, "", "GitHub token to be used with SCS engines") + cmdCommand.PersistentFlags().String(commonParams.SCSRepoURLFlag, "", "GitHub url to be used with SCS engines") + _ = cmdCommand.Execute() + _ = cmdCommand.Flags().Set(commonParams.SCSEnginesFlag, "secret-detection,scorecard") + _ = cmdCommand.Flags().Set(commonParams.SCSRepoTokenFlag, dummyToken) + _ = cmdCommand.Flags().Set(commonParams.SCSRepoURLFlag, dummySSHRepo) + + result, _ := addSCSScan(cmdCommand, resubmitConfig, true) + + // Close the writer to signal that we are done capturing the output + w.Close() + + // Read from the pipe (stdout) + var buf bytes.Buffer + _, err := io.Copy(&buf, r) // Copy the captured output to a buffer + if err != nil { + t.Fatalf("Failed to capture output: %v", err) + } + + output := buf.String() + if !strings.Contains(output, ScsScorecardUnsupportedHostWarningMsg) { + t.Errorf("Expected output to contain %q, but got %q", ScsScorecardUnsupportedHostWarningMsg, output) + } + + scsConfig := wrappers.SCSConfig{ + Twoms: "true", + Scorecard: "", + RepoURL: "", + RepoToken: "", + } + scsMapConfig := make(map[string]interface{}) + scsMapConfig[resultsMapType] = commonParams.MicroEnginesType + scsMapConfig[resultsMapValue] = &scsConfig + + if !reflect.DeepEqual(result, scsMapConfig) { + t.Errorf("Expected %+v, but got %+v", scsMapConfig, result) + } +} + func Test_isDirFiltered(t *testing.T) { type args struct { filename string