4
0

options.go 7.3 KB

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