options.go 7.2 KB

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