Jelajahi Sumber

pretty apparent 'protect' and 'detect' should be merged into one command (#1294)

Zachary Rice 2 tahun lalu
induk
melakukan
221d5c40e7
3 mengubah file dengan 187 tambahan dan 259 penghapusan
  1. 5 160
      cmd/detect.go
  2. 6 99
      cmd/protect.go
  3. 176 0
      cmd/root.go

+ 5 - 160
cmd/detect.go

@@ -2,16 +2,11 @@ package cmd
 
 import (
 	"os"
-	"path/filepath"
-	"strings"
 	"time"
 
-	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/log"
 	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
 
-	"github.com/zricethezav/gitleaks/v8/config"
 	"github.com/zricethezav/gitleaks/v8/detect"
 	"github.com/zricethezav/gitleaks/v8/report"
 )
@@ -35,117 +30,22 @@ var detectCmd = &cobra.Command{
 func runDetect(cmd *cobra.Command, args []string) {
 	initConfig()
 	var (
-		vc       config.ViperConfig
 		findings []report.Finding
 		err      error
 	)
 
-	// Load config
-	if err = viper.Unmarshal(&vc); err != nil {
-		log.Fatal().Err(err).Msg("Failed to load config")
-	}
-	cfg, err := vc.Translate()
-	if err != nil {
-		log.Fatal().Err(err).Msg("Failed to load config")
-	}
-	cfg.Path, _ = cmd.Flags().GetString("config")
+	// setup config (aka, the thing that defines rules)
+	cfg := Config(cmd)
 
 	// start timer
 	start := time.Now()
 
-	// Setup detector
-	detector := detect.NewDetector(cfg)
-	// set color flag at first
-	if detector.NoColor, err = cmd.Flags().GetBool("no-color"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-	// also init logger again without color
-	if detector.NoColor {
-		log.Logger = log.Output(zerolog.ConsoleWriter{
-			Out:     os.Stderr,
-			NoColor: detector.NoColor,
-		})
-	}
-	detector.Config.Path, err = cmd.Flags().GetString("config")
-	if err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
+	// grab source
 	source, err := cmd.Flags().GetString("source")
 	if err != nil {
 		log.Fatal().Err(err).Msg("")
 	}
-	// if config path is not set, then use the {source}/.gitleaks.toml path.
-	// note that there may not be a `{source}/.gitleaks.toml` file, this is ok.
-	if detector.Config.Path == "" {
-		detector.Config.Path = filepath.Join(source, ".gitleaks.toml")
-	}
-	// set verbose flag
-	if detector.Verbose, err = cmd.Flags().GetBool("verbose"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-	// set redact flag
-	if detector.Redact, err = cmd.Flags().GetUint("redact"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-	if detector.MaxTargetMegaBytes, err = cmd.Flags().GetInt("max-target-megabytes"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-	// set ignore gitleaks:allow flag
-	if detector.IgnoreGitleaksAllow, err = cmd.Flags().GetBool("ignore-gitleaks-allow"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-
-	gitleaksIgnorePath, err := cmd.Flags().GetString("gitleaks-ignore-path")
-	if err != nil {
-		log.Fatal().Err(err).Msg("could not get .gitleaksignore path")
-	}
-
-	if fileExists(gitleaksIgnorePath) {
-		if err = detector.AddGitleaksIgnore(gitleaksIgnorePath); err != nil {
-			log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
-		}
-	}
-
-	if fileExists(filepath.Join(gitleaksIgnorePath, ".gitleaksignore")) {
-		if err = detector.AddGitleaksIgnore(filepath.Join(gitleaksIgnorePath, ".gitleaksignore")); err != nil {
-			log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
-		}
-	}
-
-	if fileExists(filepath.Join(source, ".gitleaksignore")) {
-		if err = detector.AddGitleaksIgnore(filepath.Join(source, ".gitleaksignore")); err != nil {
-			log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
-		}
-	}
-
-	// ignore findings from the baseline (an existing report in json format generated earlier)
-	baselinePath, _ := cmd.Flags().GetString("baseline-path")
-	if baselinePath != "" {
-		err = detector.AddBaseline(baselinePath, source)
-		if err != nil {
-			log.Error().Msgf("Could not load baseline. The path must point of a gitleaks report generated using the default format: %s", err)
-		}
-	}
-
-	// If set, only apply rules that are defined in the flag
-	rules, _ := cmd.Flags().GetStringSlice("enable-rule")
-	if len(rules) > 0 {
-		log.Info().Msg("Overriding enabled rules: " + strings.Join(rules, ", "))
-		ruleOverride := make(map[string]config.Rule)
-		for _, ruleName := range rules {
-			if rule, ok := cfg.Rules[ruleName]; ok {
-				ruleOverride[ruleName] = rule
-			} else {
-				log.Fatal().Msgf("Requested rule %s not found in rules", ruleName)
-			}
-		}
-		detector.Config.Rules = ruleOverride
-	}
-
-	// set follow symlinks flag
-	if detector.FollowSymlinks, err = cmd.Flags().GetBool("follow-symlinks"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
+	detector := Detector(cmd, cfg, source)
 
 	// set exit code
 	exitCode, err := cmd.Flags().GetInt("exit-code")
@@ -192,61 +92,6 @@ func runDetect(cmd *cobra.Command, args []string) {
 		}
 	}
 
-	// log info about the scan
-	if err == nil {
-		log.Info().Msgf("scan completed in %s", FormatDuration(time.Since(start)))
-		if len(findings) != 0 {
-			log.Warn().Msgf("leaks found: %d", len(findings))
-		} else {
-			log.Info().Msg("no leaks found")
-		}
-	} else {
-		log.Warn().Msgf("partial scan completed in %s", FormatDuration(time.Since(start)))
-		if len(findings) != 0 {
-			log.Warn().Msgf("%d leaks found in partial scan", len(findings))
-		} else {
-			log.Warn().Msg("no leaks found in partial scan")
-		}
-	}
-
-	// write report if desired
-	reportPath, _ := cmd.Flags().GetString("report-path")
-	ext, _ := cmd.Flags().GetString("report-format")
-	if reportPath != "" {
-		if err := report.Write(findings, cfg, ext, reportPath); err != nil {
-			log.Fatal().Err(err).Msg("could not write")
-		}
-	}
-
-	if err != nil {
-		os.Exit(1)
-	}
-
-	if len(findings) != 0 {
-		os.Exit(exitCode)
-	}
+	findingSummaryAndExit(findings, cmd, cfg, exitCode, start, err)
 }
 
-func fileExists(fileName string) bool {
-	// check for a .gitleaksignore file
-	info, err := os.Stat(fileName)
-	if err != nil && !os.IsNotExist(err) {
-		return false
-	}
-
-	if info != nil && err == nil {
-		if !info.IsDir() {
-			return true
-		}
-	}
-	return false
-}
-
-func FormatDuration(d time.Duration) string {
-	scale := 100 * time.Second
-	// look for the max scale that is smaller than d
-	for scale > d {
-		scale = scale / 10
-	}
-	return d.Round(scale / 100).String()
-}

+ 6 - 99
cmd/protect.go

@@ -1,24 +1,17 @@
 package cmd
 
 import (
-	"os"
-	"path/filepath"
 	"time"
 
-	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/log"
 	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
 
-	"github.com/zricethezav/gitleaks/v8/config"
 	"github.com/zricethezav/gitleaks/v8/detect"
 	"github.com/zricethezav/gitleaks/v8/report"
 )
 
 func init() {
 	protectCmd.Flags().Bool("staged", false, "detect secrets in a --staged state")
-	protectCmd.Flags().String("log-opts", "", "git log options")
-	protectCmd.Flags().StringP("gitleaks-ignore-path", "i", ".", "path to .gitleaksignore file or folder containing one")
 	rootCmd.AddCommand(protectCmd)
 }
 
@@ -30,85 +23,20 @@ var protectCmd = &cobra.Command{
 
 func runProtect(cmd *cobra.Command, args []string) {
 	initConfig()
-	var vc config.ViperConfig
+	var err error
 
-	if err := viper.Unmarshal(&vc); err != nil {
-		log.Fatal().Err(err).Msg("Failed to load config")
-	}
-	cfg, err := vc.Translate()
-	if err != nil {
-		log.Fatal().Err(err).Msg("Failed to load config")
-	}
+	// setup config (aka, the thing that defines rules)
+	cfg := Config(cmd)
 
-	cfg.Path, _ = cmd.Flags().GetString("config")
 	exitCode, _ := cmd.Flags().GetInt("exit-code")
 	staged, _ := cmd.Flags().GetBool("staged")
-	start := time.Now()
-
-	// Setup detector
-	detector := detect.NewDetector(cfg)
-	// set color flag at first
-	if detector.NoColor, err = cmd.Flags().GetBool("no-color"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-	// also init logger again without color
-	if detector.NoColor {
-		log.Logger = log.Output(zerolog.ConsoleWriter{
-			Out:     os.Stderr,
-			NoColor: detector.NoColor,
-		})
-	}
-	detector.Config.Path, err = cmd.Flags().GetString("config")
-	if err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
 	source, err := cmd.Flags().GetString("source")
 	if err != nil {
 		log.Fatal().Err(err).Msg("")
 	}
-	// if config path is not set, then use the {source}/.gitleaks.toml path.
-	// note that there may not be a `{source}/.gitleaks.toml` file, this is ok.
-	if detector.Config.Path == "" {
-		detector.Config.Path = filepath.Join(source, ".gitleaks.toml")
-	}
-	// set verbose flag
-	if detector.Verbose, err = cmd.Flags().GetBool("verbose"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-	// set redact flag
-	if detector.Redact, err = cmd.Flags().GetUint("redact"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-	if detector.MaxTargetMegaBytes, err = cmd.Flags().GetInt("max-target-megabytes"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-	// set ignore gitleaks:allow flag
-	if detector.IgnoreGitleaksAllow, err = cmd.Flags().GetBool("ignore-gitleaks-allow"); err != nil {
-		log.Fatal().Err(err).Msg("")
-	}
-
-	gitleaksIgnorePath, err := cmd.Flags().GetString("gitleaks-ignore-path")
-	if err != nil {
-		log.Fatal().Err(err).Msg("could not get .gitleaksignore path")
-	}
-
-	if fileExists(gitleaksIgnorePath) {
-		if err = detector.AddGitleaksIgnore(gitleaksIgnorePath); err != nil {
-			log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
-		}
-	}
-
-	if fileExists(filepath.Join(gitleaksIgnorePath, ".gitleaksignore")) {
-		if err = detector.AddGitleaksIgnore(filepath.Join(gitleaksIgnorePath, ".gitleaksignore")); err != nil {
-			log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
-		}
-	}
+	start := time.Now()
 
-	if fileExists(filepath.Join(source, ".gitleaksignore")) {
-		if err = detector.AddGitleaksIgnore(filepath.Join(source, ".gitleaksignore")); err != nil {
-			log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
-		}
-	}
+	detector := Detector(cmd, cfg, source)
 
 	// get log options for git scan
 	logOpts, err := cmd.Flags().GetString("log-opts")
@@ -123,27 +51,6 @@ func runProtect(cmd *cobra.Command, args []string) {
 	} else {
 		findings, err = detector.DetectGit(source, logOpts, detect.ProtectType)
 	}
-	if err != nil {
-		// don't exit on error, just log it
-		log.Error().Err(err).Msg("")
-	}
 
-	// log info about the scan
-	log.Info().Msgf("scan completed in %s", FormatDuration(time.Since(start)))
-	if len(findings) != 0 {
-		log.Warn().Msgf("leaks found: %d", len(findings))
-	} else {
-		log.Info().Msg("no leaks found")
-	}
-
-	reportPath, _ := cmd.Flags().GetString("report-path")
-	ext, _ := cmd.Flags().GetString("report-format")
-	if reportPath != "" {
-		if err = report.Write(findings, cfg, ext, reportPath); err != nil {
-			log.Fatal().Err(err).Msg("")
-		}
-	}
-	if len(findings) != 0 {
-		os.Exit(exitCode)
-	}
+	findingSummaryAndExit(findings, cmd, cfg, exitCode, start, err)
 }

+ 176 - 0
cmd/root.go

@@ -5,6 +5,7 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
+	"time"
 
 	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/log"
@@ -12,6 +13,8 @@ import (
 	"github.com/spf13/viper"
 
 	"github.com/zricethezav/gitleaks/v8/config"
+	"github.com/zricethezav/gitleaks/v8/detect"
+	"github.com/zricethezav/gitleaks/v8/report"
 )
 
 const banner = `
@@ -51,6 +54,9 @@ func init() {
 	rootCmd.PersistentFlags().Uint("redact", 0, "redact secrets from logs and stdout. To redact only parts of the secret just apply a percent value from 0..100. For example --redact=20 (default 100%)")
 	rootCmd.Flag("redact").NoOptDefVal = "100"
 	rootCmd.PersistentFlags().Bool("no-banner", false, "suppress banner")
+	rootCmd.PersistentFlags().String("log-opts", "", "git log options")
+	rootCmd.PersistentFlags().StringSlice("enable-rule", []string{}, "only enable specific rules by id, ex: `gitleaks detect --enable-rule=atlassian-api-token --enable-rule=slack-access-token`")
+	rootCmd.PersistentFlags().StringP("gitleaks-ignore-path", "i", ".", "path to .gitleaksignore file or folder containing one")
 	err := viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
 	if err != nil {
 		log.Fatal().Msgf("err binding config %s", err.Error())
@@ -149,3 +155,173 @@ func Execute() {
 		log.Fatal().Msg(err.Error())
 	}
 }
+
+func Config(cmd *cobra.Command) config.Config {
+	var vc config.ViperConfig
+	if err := viper.Unmarshal(&vc); err != nil {
+		log.Fatal().Err(err).Msg("Failed to load config")
+	}
+	cfg, err := vc.Translate()
+	if err != nil {
+		log.Fatal().Err(err).Msg("Failed to load config")
+	}
+	cfg.Path, _ = cmd.Flags().GetString("config")
+
+	return cfg
+}
+
+func Detector(cmd *cobra.Command, cfg config.Config, source string) *detect.Detector {
+	var err error
+
+	// Setup common detector
+	detector := detect.NewDetector(cfg)
+	// set color flag at first
+	if detector.NoColor, err = cmd.Flags().GetBool("no-color"); err != nil {
+		log.Fatal().Err(err).Msg("")
+	}
+	// also init logger again without color
+	if detector.NoColor {
+		log.Logger = log.Output(zerolog.ConsoleWriter{
+			Out:     os.Stderr,
+			NoColor: detector.NoColor,
+		})
+	}
+	detector.Config.Path, err = cmd.Flags().GetString("config")
+	if err != nil {
+		log.Fatal().Err(err).Msg("")
+	}
+
+	// if config path is not set, then use the {source}/.gitleaks.toml path.
+	// note that there may not be a `{source}/.gitleaks.toml` file, this is ok.
+	if detector.Config.Path == "" {
+		detector.Config.Path = filepath.Join(source, ".gitleaks.toml")
+	}
+	// set verbose flag
+	if detector.Verbose, err = cmd.Flags().GetBool("verbose"); err != nil {
+		log.Fatal().Err(err).Msg("")
+	}
+	// set redact flag
+	if detector.Redact, err = cmd.Flags().GetUint("redact"); err != nil {
+		log.Fatal().Err(err).Msg("")
+	}
+	if detector.MaxTargetMegaBytes, err = cmd.Flags().GetInt("max-target-megabytes"); err != nil {
+		log.Fatal().Err(err).Msg("")
+	}
+	// set ignore gitleaks:allow flag
+	if detector.IgnoreGitleaksAllow, err = cmd.Flags().GetBool("ignore-gitleaks-allow"); err != nil {
+		log.Fatal().Err(err).Msg("")
+	}
+
+	gitleaksIgnorePath, err := cmd.Flags().GetString("gitleaks-ignore-path")
+	if err != nil {
+		log.Fatal().Err(err).Msg("could not get .gitleaksignore path")
+	}
+
+	if fileExists(gitleaksIgnorePath) {
+		if err = detector.AddGitleaksIgnore(gitleaksIgnorePath); err != nil {
+			log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
+		}
+	}
+
+	if fileExists(filepath.Join(gitleaksIgnorePath, ".gitleaksignore")) {
+		if err = detector.AddGitleaksIgnore(filepath.Join(gitleaksIgnorePath, ".gitleaksignore")); err != nil {
+			log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
+		}
+	}
+
+	if fileExists(filepath.Join(source, ".gitleaksignore")) {
+		if err = detector.AddGitleaksIgnore(filepath.Join(source, ".gitleaksignore")); err != nil {
+			log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
+		}
+	}
+
+	// ignore findings from the baseline (an existing report in json format generated earlier)
+	baselinePath, _ := cmd.Flags().GetString("baseline-path")
+	if baselinePath != "" {
+		err = detector.AddBaseline(baselinePath, source)
+		if err != nil {
+			log.Error().Msgf("Could not load baseline. The path must point of a gitleaks report generated using the default format: %s", err)
+		}
+	}
+
+	// If set, only apply rules that are defined in the flag
+	rules, _ := cmd.Flags().GetStringSlice("enable-rule")
+	if len(rules) > 0 {
+		log.Info().Msg("Overriding enabled rules: " + strings.Join(rules, ", "))
+		ruleOverride := make(map[string]config.Rule)
+		for _, ruleName := range rules {
+			if rule, ok := cfg.Rules[ruleName]; ok {
+				ruleOverride[ruleName] = rule
+			} else {
+				log.Fatal().Msgf("Requested rule %s not found in rules", ruleName)
+			}
+		}
+		detector.Config.Rules = ruleOverride
+	}
+
+	// set follow symlinks flag
+	if detector.FollowSymlinks, err = cmd.Flags().GetBool("follow-symlinks"); err != nil {
+		log.Fatal().Err(err).Msg("")
+	}
+	return detector
+}
+
+func findingSummaryAndExit(findings []report.Finding, cmd *cobra.Command, cfg config.Config, exitCode int, start time.Time, err error) {
+	if err == nil {
+		log.Info().Msgf("scan completed in %s", FormatDuration(time.Since(start)))
+		if len(findings) != 0 {
+			log.Warn().Msgf("leaks found: %d", len(findings))
+		} else {
+			log.Info().Msg("no leaks found")
+		}
+	} else {
+		log.Warn().Msgf("partial scan completed in %s", FormatDuration(time.Since(start)))
+		if len(findings) != 0 {
+			log.Warn().Msgf("%d leaks found in partial scan", len(findings))
+		} else {
+			log.Warn().Msg("no leaks found in partial scan")
+		}
+	}
+
+	// write report if desired
+	reportPath, _ := cmd.Flags().GetString("report-path")
+	ext, _ := cmd.Flags().GetString("report-format")
+	if reportPath != "" {
+		if err := report.Write(findings, cfg, ext, reportPath); err != nil {
+			log.Fatal().Err(err).Msg("could not write")
+		}
+	}
+
+	if err != nil {
+		os.Exit(1)
+	}
+
+	if len(findings) != 0 {
+		os.Exit(exitCode)
+	}
+
+}
+
+func fileExists(fileName string) bool {
+	// check for a .gitleaksignore file
+	info, err := os.Stat(fileName)
+	if err != nil && !os.IsNotExist(err) {
+		return false
+	}
+
+	if info != nil && err == nil {
+		if !info.IsDir() {
+			return true
+		}
+	}
+	return false
+}
+
+func FormatDuration(d time.Duration) string {
+	scale := 100 * time.Second
+	// look for the max scale that is smaller than d
+	for scale > d {
+		scale = scale / 10
+	}
+	return d.Round(scale / 100).String()
+}