options.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. package options
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "os/user"
  7. "strings"
  8. "github.com/zricethezav/gitleaks/v7/version"
  9. "github.com/go-git/go-git/v5"
  10. "github.com/go-git/go-git/v5/plumbing"
  11. "github.com/go-git/go-git/v5/plumbing/transport"
  12. "github.com/go-git/go-git/v5/plumbing/transport/http"
  13. "github.com/go-git/go-git/v5/plumbing/transport/ssh"
  14. "github.com/jessevdk/go-flags"
  15. log "github.com/sirupsen/logrus"
  16. )
  17. // Options stores values of command line options
  18. type Options struct {
  19. Verbose bool `short:"v" long:"verbose" description:"Show verbose output from scan"`
  20. Quiet bool `short:"q" long:"quiet" description:"Sets log level to error and only output leaks, one json object per line"`
  21. RepoURL string `short:"r" long:"repo-url" description:"Repository URL"`
  22. Path string `short:"p" long:"path" description:"Path to directory (repo if contains .git) or file"`
  23. ConfigPath string `short:"c" long:"config-path" description:"Path to config"`
  24. RepoConfigPath string `long:"repo-config-path" description:"Path to gitleaks config relative to repo root"`
  25. ClonePath string `long:"clone-path" description:"Path to clone repo to disk"`
  26. Version bool `long:"version" description:"Version number"`
  27. Username string `long:"username" description:"Username for git repo"`
  28. Password string `long:"password" description:"Password for git repo"`
  29. AccessToken string `long:"access-token" description:"Access token for git repo"`
  30. Threads int `long:"threads" description:"Maximum number of threads gitleaks spawns"`
  31. SSH string `long:"ssh-key" description:"Path to ssh key used for auth"`
  32. Unstaged bool `long:"unstaged" description:"Run gitleaks on unstaged code"`
  33. Branch string `long:"branch" description:"Branch to scan"`
  34. Redact bool `long:"redact" description:"Redact secrets from log messages and leaks"`
  35. Debug bool `long:"debug" description:"Log debug messages"`
  36. NoGit bool `long:"no-git" description:"Treat git repos as plain directories and scan those files"`
  37. CodeOnLeak int `long:"leaks-exit-code" default:"1" description:"Exit code when leaks have been encountered"`
  38. AppendRepoConfig bool `long:"append-repo-config" description:"Append the provided or default config with the repo config."`
  39. AdditionalConfig string `long:"additional-config" description:"Path to an additional gitleaks config to append with an existing config. Can be used with --append-repo-config to append up to three configurations"`
  40. // Report Options
  41. Report string `short:"o" long:"report" description:"Report output path"`
  42. ReportFormat string `short:"f" long:"format" default:"json" description:"JSON, CSV, SARIF"`
  43. // Commit Options
  44. FilesAtCommit string `long:"files-at-commit" description:"Sha of commit to scan all files at commit"`
  45. Commit string `long:"commit" description:"Sha of commit to scan or \"latest\" to scan the last commit of the repository"`
  46. Commits string `long:"commits" description:"Comma separated list of a commits to scan"`
  47. CommitsFile string `long:"commits-file" description:"Path to file of line separated list of commits to scan"`
  48. CommitFrom string `long:"commit-from" description:"Commit to start scan from"`
  49. CommitTo string `long:"commit-to" description:"Commit to stop scan"`
  50. CommitSince string `long:"commit-since" description:"Scan commits more recent than a specific date. Ex: '2006-01-02' or '2006-01-02T15:04:05-0700' format."`
  51. CommitUntil string `long:"commit-until" description:"Scan commits older than a specific date. Ex: '2006-01-02' or '2006-01-02T15:04:05-0700' format."`
  52. Depth int `long:"depth" description:"Number of commits to scan"`
  53. }
  54. // ParseOptions is responsible for parsing options passed in by cli. An Options struct
  55. // is returned if successful. This struct is passed around the program
  56. // and will determine how the program executes. If err, an err message or help message
  57. // will be displayed and the program will exit with code 0.
  58. func ParseOptions() (Options, error) {
  59. var opts Options
  60. parser := flags.NewParser(&opts, flags.Default)
  61. _, err := parser.Parse()
  62. if err != nil {
  63. if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type != flags.ErrHelp {
  64. parser.WriteHelp(os.Stdout)
  65. }
  66. os.Exit(1)
  67. }
  68. if opts.Version {
  69. if version.Version == "" {
  70. fmt.Println("Gitleaks uses LDFLAGS to pull most recent version. Build with 'make build' for version")
  71. } else {
  72. fmt.Printf("%s\n", version.Version)
  73. }
  74. os.Exit(0)
  75. }
  76. if opts.Debug {
  77. log.SetLevel(log.DebugLevel)
  78. }
  79. if opts.Quiet {
  80. log.SetLevel(log.ErrorLevel)
  81. }
  82. return opts, nil
  83. }
  84. // Guard checks to makes sure there are no invalid options set.
  85. // If invalid sets of options are present, a descriptive error will return
  86. // else nil is returned
  87. func (opts Options) Guard() error {
  88. if !oneOrNoneSet(opts.RepoURL, opts.Path) {
  89. return fmt.Errorf("only one target option must can be set. target options: repo, owner-path, repo-path, host")
  90. }
  91. if !oneOrNoneSet(opts.AccessToken, opts.Password) {
  92. log.Warn("both access-token and password are set. Only password will be attempted")
  93. }
  94. return nil
  95. }
  96. func oneOrNoneSet(optStr ...string) bool {
  97. c := 0
  98. for _, s := range optStr {
  99. if s != "" {
  100. c++
  101. }
  102. }
  103. if c <= 1 {
  104. return true
  105. }
  106. return false
  107. }
  108. // CloneOptions returns a git.cloneOptions pointer. The authentication method
  109. // is determined by what is passed in via command-Line options. If No
  110. // Username/PW or AccessToken is available and the repo target is not using the
  111. // git protocol then the repo must be a available via no auth.
  112. func (opts Options) CloneOptions() (*git.CloneOptions, error) {
  113. var err error
  114. progress := ioutil.Discard
  115. if opts.Verbose {
  116. progress = os.Stdout
  117. }
  118. cloneOpts := &git.CloneOptions{
  119. URL: opts.RepoURL,
  120. Progress: progress,
  121. }
  122. if opts.Depth != 0 {
  123. cloneOpts.Depth = opts.Depth
  124. }
  125. if opts.Branch != "" {
  126. cloneOpts.ReferenceName = plumbing.NewBranchReferenceName(opts.Branch)
  127. }
  128. var auth transport.AuthMethod
  129. if strings.HasPrefix(opts.RepoURL, "ssh://") || (!strings.Contains(opts.RepoURL, "://") && strings.Contains(opts.RepoURL, ":")) {
  130. // using ssh:// url or scp-like syntax
  131. auth, err = SSHAuth(opts)
  132. if err != nil {
  133. return nil, err
  134. }
  135. } else if opts.Password != "" && opts.Username != "" {
  136. // auth using username and password
  137. auth = &http.BasicAuth{
  138. Username: opts.Username,
  139. Password: opts.Password,
  140. }
  141. } else if opts.AccessToken != "" {
  142. auth = &http.BasicAuth{
  143. Username: "gitleaks_user",
  144. Password: opts.AccessToken,
  145. }
  146. } else if os.Getenv("GITLEAKS_ACCESS_TOKEN") != "" {
  147. auth = &http.BasicAuth{
  148. Username: "gitleaks_user",
  149. Password: os.Getenv("GITLEAKS_ACCESS_TOKEN"),
  150. }
  151. }
  152. if auth != nil {
  153. cloneOpts.Auth = auth
  154. }
  155. return cloneOpts, nil
  156. }
  157. // SSHAuth tried to generate ssh public keys based on what was passed via cli. If no
  158. // path was passed via cli then this will attempt to retrieve keys from the default
  159. // location for ssh keys, $HOME/.ssh/id_rsa. This function is only called if the
  160. // repo url using the ssh:// protocol or scp-like syntax.
  161. func SSHAuth(opts Options) (*ssh.PublicKeys, error) {
  162. params := strings.Split(opts.RepoURL, "@")
  163. if len(params) != 2 {
  164. return nil, fmt.Errorf("user must be specified in the URL")
  165. }
  166. // the part of the RepoURL before the "@" (params[0]) can be something like:
  167. // - "ssh://user" if RepoURL is an ssh:// URL
  168. // - "user" if RepoURL uses scp-like syntax
  169. // we must strip the protocol if it is present so that we only have "user"
  170. username := strings.Replace(params[0], "ssh://", "", 1)
  171. if opts.SSH != "" {
  172. return ssh.NewPublicKeysFromFile(username, opts.SSH, "")
  173. }
  174. c, err := user.Current()
  175. if err != nil {
  176. return nil, err
  177. }
  178. defaultPath := fmt.Sprintf("%s/.ssh/id_rsa", c.HomeDir)
  179. return ssh.NewPublicKeysFromFile(username, defaultPath, "")
  180. }
  181. // OpenLocal checks what options are set, if no remote targets are set
  182. // then return true
  183. func (opts Options) OpenLocal() bool {
  184. if opts.Unstaged || opts.Path != "" || opts.RepoURL == "" {
  185. return true
  186. }
  187. return false
  188. }
  189. // CheckUncommitted returns a boolean that indicates whether or not gitleaks should check unstaged pre-commit changes
  190. // or if gitleaks should check the entire git history
  191. func (opts Options) CheckUncommitted() bool {
  192. // check to make sure no remote shit is set
  193. if opts.Unstaged {
  194. return true
  195. }
  196. if opts == (Options{}) {
  197. return true
  198. }
  199. if opts.RepoURL != "" {
  200. return false
  201. }
  202. if opts.Path != "" {
  203. return false
  204. }
  205. return true
  206. }