options.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package gitleaks
  2. import (
  3. "fmt"
  4. "net"
  5. "net/url"
  6. "os"
  7. "path/filepath"
  8. "regexp"
  9. "runtime"
  10. "strings"
  11. "time"
  12. "github.com/jessevdk/go-flags"
  13. log "github.com/sirupsen/logrus"
  14. )
  15. // Options for gitleaks
  16. type Options struct {
  17. // remote target options
  18. Repo string `short:"r" long:"repo" description:"Repo url to audit"`
  19. GithubUser string `long:"github-user" description:"Github user to audit"`
  20. GithubOrg string `long:"github-org" description:"Github organization to audit"`
  21. GithubURL string `long:"github-url" default:"https://api.github.com/" description:"GitHub API Base URL, use for GitHub Enterprise. Example: https://github.example.com/api/v3/"`
  22. GithubPR string `long:"github-pr" description:"Github PR url to audit. This does not clone the repo. GITHUB_TOKEN must be set"`
  23. GitLabUser string `long:"gitlab-user" description:"GitLab user ID to audit"`
  24. GitLabOrg string `long:"gitlab-org" description:"GitLab group ID to audit"`
  25. CommitStop string `long:"commit-stop" description:"sha of commit to stop at"`
  26. Commit string `long:"commit" description:"sha of commit to audit"`
  27. Depth int64 `long:"depth" description:"maximum commit depth"`
  28. // local target option
  29. RepoPath string `long:"repo-path" description:"Path to repo"`
  30. OwnerPath string `long:"owner-path" description:"Path to owner directory (repos discovered)"`
  31. // Process options
  32. Threads int `long:"threads" description:"Maximum number of threads gitleaks spawns"`
  33. Disk bool `long:"disk" description:"Clones repo(s) to disk"`
  34. SingleSearch string `long:"single-search" description:"single regular expression to search for"`
  35. ConfigPath string `long:"config" description:"path to gitleaks config"`
  36. SSHKey string `long:"ssh-key" description:"path to ssh key"`
  37. ExcludeForks bool `long:"exclude-forks" description:"exclude forks for organization/user audits"`
  38. Entropy float64 `long:"entropy" short:"e" description:"Include entropy checks during audit. Entropy scale: 0.0(no entropy) - 8.0(max entropy)"`
  39. NoiseReduction bool `long:"noise-reduction" description:"Reduce the number of finds when entropy checks are enabled"`
  40. RepoConfig bool `long:"repo-config" description:"Load config from target repo. Config file must be \".gitleaks.toml\""`
  41. Branch string `long:"branch" description:"Branch to audit"`
  42. // TODO: IncludeMessages string `long:"messages" description:"include commit messages in audit"`
  43. // Output options
  44. Log string `short:"l" long:"log" description:"log level"`
  45. Verbose bool `short:"v" long:"verbose" description:"Show verbose output from gitleaks audit"`
  46. Report string `long:"report" description:"path to write report file. Needs to be csv or json"`
  47. Redact bool `long:"redact" description:"redact secrets from log messages and report"`
  48. Version bool `long:"version" description:"version number"`
  49. SampleConfig bool `long:"sample-config" description:"prints a sample config file"`
  50. }
  51. // ParseOpts parses the options
  52. func ParseOpts() *Options {
  53. var opts Options
  54. parser := flags.NewParser(&opts, flags.Default)
  55. _, err := parser.Parse()
  56. if err != nil {
  57. if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {
  58. os.Exit(0)
  59. }
  60. }
  61. if len(os.Args) == 1 {
  62. // TODO: this will be a feature, check locally
  63. parser.WriteHelp(os.Stdout)
  64. os.Exit(0)
  65. }
  66. if opts.Version {
  67. fmt.Println(version)
  68. os.Exit(0)
  69. }
  70. if opts.SampleConfig {
  71. fmt.Println(defaultConfig)
  72. os.Exit(0)
  73. }
  74. opts.setLogs()
  75. err = opts.guard()
  76. if err != nil {
  77. log.Fatal(err)
  78. }
  79. return &opts
  80. }
  81. // optsGuard prevents invalid options
  82. func (opts *Options) guard() error {
  83. var err error
  84. if opts.GithubOrg != "" && opts.GithubUser != "" {
  85. return fmt.Errorf("github user and organization set")
  86. } else if opts.GithubOrg != "" && opts.OwnerPath != "" {
  87. return fmt.Errorf("github organization set and local owner path")
  88. } else if opts.GithubUser != "" && opts.OwnerPath != "" {
  89. return fmt.Errorf("github user set and local owner path")
  90. }
  91. if opts.Threads > runtime.GOMAXPROCS(0) {
  92. return fmt.Errorf("%d available threads", runtime.GOMAXPROCS(0))
  93. }
  94. // do the URL Parse and error checking here, so we can skip it later
  95. // empty string is OK, it will default to the public github URL.
  96. if opts.GithubURL != "" && opts.GithubURL != defaultGithubURL {
  97. if !strings.HasSuffix(opts.GithubURL, "/") {
  98. opts.GithubURL += "/"
  99. }
  100. ghURL, err := url.Parse(opts.GithubURL)
  101. if err != nil {
  102. return err
  103. }
  104. tcpPort := "443"
  105. if ghURL.Scheme == "http" {
  106. tcpPort = "80"
  107. }
  108. timeout := time.Duration(1 * time.Second)
  109. _, err = net.DialTimeout("tcp", ghURL.Host+":"+tcpPort, timeout)
  110. if err != nil {
  111. return fmt.Errorf("%s unreachable, error: %s", ghURL.Host, err)
  112. }
  113. }
  114. if opts.SingleSearch != "" {
  115. singleSearchRegex, err = regexp.Compile(opts.SingleSearch)
  116. if err != nil {
  117. return fmt.Errorf("unable to compile regex: %s, %v", opts.SingleSearch, err)
  118. }
  119. }
  120. if opts.Entropy > 8 {
  121. return fmt.Errorf("The maximum level of entropy is 8")
  122. }
  123. if opts.Report != "" {
  124. if !strings.HasSuffix(opts.Report, ".json") && !strings.HasSuffix(opts.Report, ".csv") {
  125. return fmt.Errorf("Report should be a .json or .csv file")
  126. }
  127. dirPath := filepath.Dir(opts.Report)
  128. if _, err := os.Stat(dirPath); os.IsNotExist(err) {
  129. return fmt.Errorf("%s does not exist", dirPath)
  130. }
  131. }
  132. return nil
  133. }
  134. // setLogLevel sets log level for gitleaks. Default is Warning
  135. func (opts *Options) setLogs() {
  136. switch opts.Log {
  137. case "info":
  138. log.SetLevel(log.InfoLevel)
  139. case "debug":
  140. log.SetLevel(log.DebugLevel)
  141. case "warn":
  142. log.SetLevel(log.WarnLevel)
  143. default:
  144. log.SetLevel(log.InfoLevel)
  145. }
  146. log.SetFormatter(&log.TextFormatter{
  147. FullTimestamp: true,
  148. })
  149. }