|
|
@@ -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()
|
|
|
+}
|