options.go 7.3 KB

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