options.go 8.2 KB

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