options.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "regexp"
  7. "strconv"
  8. "strings"
  9. )
  10. const usage = `
  11. usage: gitleaks [options] <URL>/<path_to_repo>
  12. Options:
  13. -u --user Git user mode
  14. -r --repo Git repo mode
  15. -o --org Git organization mode
  16. -l --local Local mode, gitleaks will look for local repo in <path>
  17. -v --verbose Verbose mode, will output leaks as gitleaks finds them
  18. --report-path=<STR> Report output, default $GITLEAKS_HOME/report
  19. --clone-path=<STR> Gitleaks will clone repos here, default $GITLEAKS_HOME/clones
  20. -t --temp Clone to temporary directory
  21. --concurrency=<INT> Upper bound on concurrent diffs
  22. --since=<STR> Commit to stop at
  23. --b64Entropy=<INT> Base64 entropy cutoff (default is 70)
  24. --hexEntropy=<INT> Hex entropy cutoff (default is 40)
  25. -e --entropy Enable entropy
  26. -h --help Display this message
  27. --token=<STR> Github API token
  28. --stopwords Enables stopwords
  29. `
  30. // Options for gitleaks. need to support remote repo/owner
  31. // and local repo/owner mode
  32. type Options struct {
  33. URL string
  34. RepoPath string
  35. ReportPath string
  36. ClonePath string
  37. Concurrency int
  38. B64EntropyCutoff int
  39. HexEntropyCutoff int
  40. UserMode bool
  41. OrgMode bool
  42. RepoMode bool
  43. LocalMode bool
  44. Strict bool
  45. Entropy bool
  46. SinceCommit string
  47. Tmp bool
  48. Token string
  49. Verbose bool
  50. }
  51. // help prints the usage string and exits
  52. func help() {
  53. os.Stderr.WriteString(usage)
  54. }
  55. // optionsNextInt is a parseOptions helper that returns the value (int) of an option if valid
  56. func (opts *Options) nextInt(args []string, i *int) int {
  57. if len(args) > *i+1 {
  58. *i++
  59. } else {
  60. help()
  61. }
  62. argInt, err := strconv.Atoi(args[*i])
  63. if err != nil {
  64. opts.failF("Invalid %s option: %s\n", args[*i-1], args[*i])
  65. }
  66. return argInt
  67. }
  68. // optionsNextString is a parseOptions helper that returns the value (string) of an option if valid
  69. func (opts *Options) nextString(args []string, i *int) string {
  70. if len(args) > *i+1 {
  71. *i++
  72. } else {
  73. opts.failF("Invalid %s option: %s\n", args[*i-1], args[*i])
  74. }
  75. return args[*i]
  76. }
  77. // optInt grabs the string ...
  78. func (opts *Options) optString(arg string, prefixes ...string) (bool, string) {
  79. for _, prefix := range prefixes {
  80. if strings.HasPrefix(arg, prefix) {
  81. return true, arg[len(prefix):]
  82. }
  83. }
  84. return false, ""
  85. }
  86. // optInt grabs the int ...
  87. func (opts *Options) optInt(arg string, prefixes ...string) (bool, int) {
  88. for _, prefix := range prefixes {
  89. if strings.HasPrefix(arg, prefix) {
  90. i, err := strconv.Atoi(arg[len(prefix):])
  91. if err != nil {
  92. opts.failF("Invalid %s int option\n", prefix)
  93. }
  94. return true, i
  95. }
  96. }
  97. return false, 0
  98. }
  99. // newOpts generates opts and parses arguments
  100. func newOpts(args []string) *Options {
  101. opts, err := defaultOptions()
  102. if err != nil {
  103. opts.failF("%v", err)
  104. }
  105. err = opts.parseOptions(args)
  106. if err != nil {
  107. opts.failF("%v", err)
  108. }
  109. return opts
  110. }
  111. // deafultOptions provides the default options
  112. func defaultOptions() (*Options, error) {
  113. return &Options{
  114. Concurrency: 10,
  115. B64EntropyCutoff: 70,
  116. HexEntropyCutoff: 40,
  117. }, nil
  118. }
  119. // parseOptions
  120. func (opts *Options) parseOptions(args []string) error {
  121. if len(args) == 0 {
  122. opts.LocalMode = true
  123. opts.RepoPath, _ = os.Getwd()
  124. }
  125. for i := 0; i < len(args); i++ {
  126. arg := args[i]
  127. switch arg {
  128. case "--stopwords":
  129. opts.Strict = true
  130. case "-e", "--entropy":
  131. opts.Entropy = true
  132. case "-o", "--org":
  133. opts.OrgMode = true
  134. case "-u", "--user":
  135. opts.UserMode = true
  136. case "-r", "--repo":
  137. opts.RepoMode = true
  138. case "-l", "--local":
  139. opts.LocalMode = true
  140. case "-v", "--verbose":
  141. opts.Verbose = true
  142. case "-t", "--temp":
  143. opts.Tmp = true
  144. case "-h", "--help":
  145. help()
  146. os.Exit(ExitClean)
  147. default:
  148. if match, value := opts.optString(arg, "--token="); match {
  149. opts.Token = value
  150. } else if match, value := opts.optString(arg, "--since="); match {
  151. opts.SinceCommit = value
  152. } else if match, value := opts.optString(arg, "--report-path="); match {
  153. opts.ReportPath = value
  154. } else if match, value := opts.optString(arg, "--clone-path="); match {
  155. opts.ClonePath = value
  156. } else if match, value := opts.optInt(arg, "--b64Entropy="); match {
  157. opts.B64EntropyCutoff = value
  158. } else if match, value := opts.optInt(arg, "--hexEntropy="); match {
  159. opts.HexEntropyCutoff = value
  160. } else if match, value := opts.optInt(arg, "--concurrency="); match {
  161. opts.Concurrency = value
  162. } else if i == len(args)-1 {
  163. fmt.Println(args[i])
  164. if opts.LocalMode {
  165. opts.RepoPath = filepath.Clean(args[i])
  166. } else {
  167. if isGithubTarget(args[i]) {
  168. opts.URL = args[i]
  169. } else {
  170. help()
  171. return fmt.Errorf("Unknown option %s\n", arg)
  172. }
  173. }
  174. } else {
  175. help()
  176. return fmt.Errorf("Unknown option %s\n", arg)
  177. }
  178. }
  179. }
  180. // TODO cleanup this logic
  181. if !opts.RepoMode && !opts.UserMode && !opts.OrgMode && !opts.LocalMode {
  182. if opts.URL != "" {
  183. opts.RepoMode = true
  184. err := opts.guards()
  185. if err != nil{
  186. return err
  187. }
  188. return nil
  189. }
  190. pwd, _ = os.Getwd()
  191. // check if pwd contains a .git, if it does, run local mode
  192. dotGitPath := filepath.Join(pwd, ".git")
  193. if _, err := os.Stat(dotGitPath); os.IsNotExist(err) {
  194. return fmt.Errorf("gitleaks has no target: %v", err)
  195. } else {
  196. opts.LocalMode = true
  197. opts.RepoPath = pwd
  198. opts.RepoMode = false
  199. }
  200. }
  201. err := opts.guards()
  202. if err != nil{
  203. return err
  204. }
  205. return err
  206. }
  207. // failF prints a failure message out to stderr, displays help
  208. // and exits with a exit code 2
  209. func (opts *Options) failF(format string, args ...interface{}) {
  210. fmt.Fprintf(os.Stderr, format, args...)
  211. help()
  212. os.Exit(ExitFailure)
  213. }
  214. // guards will prevent gitleaks from continuing if any invalid options
  215. // are found.
  216. func (opts *Options) guards() error {
  217. if (opts.RepoMode || opts.OrgMode || opts.UserMode) && opts.LocalMode {
  218. return fmt.Errorf("Cannot run Gitleaks on repo/user/org mode and local mode\n")
  219. } else if (opts.RepoMode || opts.OrgMode || opts.UserMode) && !isGithubTarget(opts.URL) {
  220. return fmt.Errorf("Not valid github target %s\n", opts.URL)
  221. } else if (opts.RepoMode || opts.UserMode) && opts.OrgMode {
  222. return fmt.Errorf("Cannot run Gitleaks on more than one mode\n")
  223. } else if (opts.OrgMode || opts.UserMode) && opts.RepoMode {
  224. return fmt.Errorf("Cannot run Gitleaks on more than one mode\n")
  225. } else if (opts.OrgMode || opts.RepoMode) && opts.UserMode {
  226. return fmt.Errorf("Cannot run Gitleaks on more than one mode\n")
  227. } else if opts.LocalMode && opts.Tmp {
  228. return fmt.Errorf("Cannot run Gitleaks with temp settings and local mode\n")
  229. } else if opts.SinceCommit != "" && (opts.OrgMode || opts.UserMode) {
  230. return fmt.Errorf("Cannot run Gitleaks with since commit flag and a owner mode\n")
  231. } else if opts.ClonePath != "" && opts.Tmp {
  232. return fmt.Errorf("Cannot run Gitleaks with --clone-path set and temporary repo\n")
  233. }
  234. return nil
  235. }
  236. // isGithubTarget checks if url is a valid github target
  237. func isGithubTarget(url string) bool {
  238. re := regexp.MustCompile("github.com")
  239. return re.MatchString(url)
  240. }