options.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. package options
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "os/user"
  7. "strings"
  8. "github.com/zricethezav/gitleaks/v5/version"
  9. "github.com/go-git/go-git/v5"
  10. "github.com/go-git/go-git/v5/plumbing/transport/http"
  11. "github.com/go-git/go-git/v5/plumbing/transport/ssh"
  12. "github.com/jessevdk/go-flags"
  13. log "github.com/sirupsen/logrus"
  14. )
  15. // No leaks or early exit due to invalid options
  16. // This block defines the exit codes. Success
  17. const (
  18. // No leaks or early exit due to invalid options
  19. Success = 0
  20. LeaksPresent = 1
  21. ErrorEncountered = 2
  22. donateMessage = "👋 maintaining gitleaks takes a lot of work so consider sponsoring me or donating a little something\n❤️ https://github.com/sponsors/zricethezav\n💸 https://www.paypal.me/zricethezav\n₿ btc:3GndEzRZa6rJ8ZpkLureUcc5TDHMYfpDxn"
  23. )
  24. // Options stores values of command line options
  25. type Options struct {
  26. Verbose bool `short:"v" long:"verbose" description:"Show verbose output from scan"`
  27. Repo string `short:"r" long:"repo" description:"Target repository"`
  28. Config string `long:"config" description:"config path"`
  29. Disk bool `long:"disk" description:"Clones repo(s) to disk"`
  30. Version bool `long:"version" description:"version number"`
  31. Username string `long:"username" description:"Username for git repo"`
  32. Password string `long:"password" description:"Password for git repo"`
  33. AccessToken string `long:"access-token" description:"Access token for git repo"`
  34. Commit string `long:"commit" description:"sha of commit to scan or \"latest\" to scan the last commit of the repository"`
  35. FilesAtCommit string `long:"files-at-commit" description:"sha of commit to scan all files at commit"`
  36. Threads int `long:"threads" description:"Maximum number of threads gitleaks spawns"`
  37. SSH string `long:"ssh-key" description:"path to ssh key used for auth"`
  38. Uncommited bool `long:"uncommitted" description:"run gitleaks on uncommitted code"`
  39. RepoPath string `long:"repo-path" description:"Path to repo"`
  40. OwnerPath string `long:"owner-path" description:"Path to owner directory (repos discovered)"`
  41. Branch string `long:"branch" description:"Branch to scan"`
  42. Report string `long:"report" description:"path to write json leaks file"`
  43. ReportFormat string `long:"report-format" default:"json" description:"json or csv"`
  44. Redact bool `long:"redact" description:"redact secrets from log messages and leaks"`
  45. Debug bool `long:"debug" description:"log debug messages"`
  46. RepoConfig bool `long:"repo-config" description:"Load config from target repo. Config file must be \".gitleaks.toml\" or \"gitleaks.toml\""`
  47. PrettyPrint bool `long:"pretty" description:"Pretty print json if leaks are present"`
  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. Timeout string `long:"timeout" description:"Time allowed per scan. Ex: 10us, 30s, 1m, 1h10m1s"`
  53. Depth int `long:"depth" description:"Number of commits to scan"`
  54. Deletion bool `long:"include-deletion" description:"Scan for patch deletions in addition to patch additions"`
  55. // Hosts
  56. Host string `long:"host" description:"git hosting service like gitlab or github. Supported hosts include: Github, Gitlab"`
  57. BaseURL string `long:"baseurl" description:"Base URL for API requests. Defaults to the public GitLab or GitHub API, but can be set to a domain endpoint to use with a self hosted server."`
  58. Organization string `long:"org" description:"organization to scan"`
  59. User string `long:"user" description:"user to scan"`
  60. PullRequest string `long:"pr" description:"pull/merge request url"`
  61. ExcludeForks bool `long:"exclude-forks" description:"scan excludes forks"`
  62. }
  63. // ParseOptions is responsible for parsing options passed in by cli. An Options struct
  64. // is returned if successful. This struct is passed around the program
  65. // and will determine how the program executes. If err, an err message or help message
  66. // will be displayed and the program will exit with code 0.
  67. func ParseOptions() (Options, error) {
  68. var opts Options
  69. parser := flags.NewParser(&opts, flags.Default)
  70. _, err := parser.Parse()
  71. if err != nil {
  72. if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type != flags.ErrHelp {
  73. parser.WriteHelp(os.Stdout)
  74. }
  75. fmt.Println(donateMessage)
  76. os.Exit(0)
  77. }
  78. if opts.Version {
  79. if version.Version == "" {
  80. fmt.Println("Gitleaks uses LDFLAGS to pull most recent version. Build with 'make build' for version")
  81. } else {
  82. fmt.Printf("%s\n", version.Version)
  83. }
  84. os.Exit(Success)
  85. }
  86. if opts.Debug {
  87. log.SetLevel(log.DebugLevel)
  88. }
  89. return opts, nil
  90. }
  91. // Guard checks to makes sure there are no invalid options set.
  92. // If invalid sets of options are present, a descriptive error will return
  93. // else nil is returned
  94. func (opts Options) Guard() error {
  95. if !oneOrNoneSet(opts.Repo, opts.OwnerPath, opts.RepoPath, opts.Host) {
  96. return fmt.Errorf("only one target option must can be set. target options: repo, owner-path, repo-path, host")
  97. }
  98. if !oneOrNoneSet(opts.Organization, opts.User, opts.PullRequest) {
  99. return fmt.Errorf("only one target option must can be set. target options: repo, owner-path, repo-path, host")
  100. }
  101. if !oneOrNoneSet(opts.AccessToken, opts.Password) {
  102. log.Warn("both access-token and password are set. Only password will be attempted")
  103. }
  104. return nil
  105. }
  106. func oneOrNoneSet(optStr ...string) bool {
  107. c := 0
  108. for _, s := range optStr {
  109. if s != "" {
  110. c++
  111. }
  112. }
  113. if c <= 1 {
  114. return true
  115. }
  116. return false
  117. }
  118. // CloneOptions returns a git.cloneOptions pointer. The authentication method
  119. // is determined by what is passed in via command-Line options. If No
  120. // Username/PW or AccessToken is available and the repo target is not using the
  121. // git protocol then the repo must be a available via no auth.
  122. func (opts Options) CloneOptions() (*git.CloneOptions, error) {
  123. progress := ioutil.Discard
  124. if opts.Verbose {
  125. progress = os.Stdout
  126. }
  127. if strings.HasPrefix(opts.Repo, "git") {
  128. // using git protocol so needs ssh auth
  129. auth, err := SSHAuth(opts)
  130. if err != nil {
  131. return nil, err
  132. }
  133. return &git.CloneOptions{
  134. URL: opts.Repo,
  135. Auth: auth,
  136. Progress: progress,
  137. }, nil
  138. }
  139. if opts.Password != "" && opts.Username != "" {
  140. // auth using username and password
  141. return &git.CloneOptions{
  142. URL: opts.Repo,
  143. Auth: &http.BasicAuth{
  144. Username: opts.Username,
  145. Password: opts.Password,
  146. },
  147. Progress: progress,
  148. }, nil
  149. }
  150. if opts.AccessToken != "" {
  151. return &git.CloneOptions{
  152. URL: opts.Repo,
  153. Auth: &http.BasicAuth{
  154. Username: "gitleaks_user",
  155. Password: opts.AccessToken,
  156. },
  157. Progress: progress,
  158. }, nil
  159. }
  160. if os.Getenv("GITLEAKS_ACCESS_TOKEN") != "" {
  161. return &git.CloneOptions{
  162. URL: opts.Repo,
  163. Auth: &http.BasicAuth{
  164. Username: "gitleaks_user",
  165. Password: os.Getenv("GITLEAKS_ACCESS_TOKEN"),
  166. },
  167. Progress: progress,
  168. }, nil
  169. }
  170. // No Auth, publicly available
  171. return &git.CloneOptions{
  172. URL: opts.Repo,
  173. Progress: progress,
  174. }, nil
  175. }
  176. // SSHAuth tried to generate ssh public keys based on what was passed via cli. If no
  177. // path was passed via cli then this will attempt to retrieve keys from the default
  178. // location for ssh keys, $HOME/.ssh/id_rsa. This function is only called if the
  179. // repo url using the git:// protocol.
  180. func SSHAuth(opts Options) (*ssh.PublicKeys, error) {
  181. if opts.SSH != "" {
  182. return ssh.NewPublicKeysFromFile("git", opts.SSH, "")
  183. }
  184. c, err := user.Current()
  185. if err != nil {
  186. return nil, err
  187. }
  188. defaultPath := fmt.Sprintf("%s/.ssh/id_rsa", c.HomeDir)
  189. return ssh.NewPublicKeysFromFile("git", defaultPath, "")
  190. }
  191. // OpenLocal checks what options are set, if no remote targets are set
  192. // then return true
  193. func (opts Options) OpenLocal() bool {
  194. if opts.Uncommited || opts.RepoPath != "" || opts.Repo == "" {
  195. return true
  196. }
  197. return false
  198. }
  199. // CheckUncommitted returns a boolean that indicates whether or not gitleaks should check unstaged pre-commit changes
  200. // or if gitleaks should check the entire git history
  201. func (opts Options) CheckUncommitted() bool {
  202. // check to make sure no remote shit is set
  203. if opts.Uncommited {
  204. return true
  205. }
  206. if opts == (Options{}) {
  207. return true
  208. }
  209. if opts.Repo != "" {
  210. return false
  211. }
  212. if opts.RepoPath != "" {
  213. return false
  214. }
  215. if opts.OwnerPath != "" {
  216. return false
  217. }
  218. if opts.Host != "" {
  219. return false
  220. }
  221. return true
  222. }
  223. // GetAccessToken accepts options and returns a string which is the access token to a git host.
  224. // Setting this option or environment var is necessary if performing an scan with any of the git hosting providers
  225. // in the host pkg. The access token set by cli options takes precedence over env vars.
  226. func GetAccessToken(opts Options) string {
  227. if opts.AccessToken != "" {
  228. return opts.AccessToken
  229. }
  230. return os.Getenv("GITLEAKS_ACCESS_TOKEN")
  231. }