scan.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package scan
  2. import (
  3. "os"
  4. "path/filepath"
  5. "github.com/go-git/go-git/v5"
  6. "github.com/zricethezav/gitleaks/v7/config"
  7. "github.com/zricethezav/gitleaks/v7/options"
  8. )
  9. // Scanner abstracts unique scanner internals while exposing the Scan function which
  10. // returns a report.
  11. type Scanner interface {
  12. Scan() (Report, error)
  13. }
  14. // ScannerType is the scanner type which is determined based on program arguments
  15. type ScannerType int
  16. const (
  17. typeRepoScanner ScannerType = iota + 1
  18. typeDirScanner
  19. typeCommitScanner
  20. typeCommitsScanner
  21. typeUnstagedScanner
  22. typeFilesAtCommitScanner
  23. typeNoGitScanner
  24. typeEmpty
  25. )
  26. // NewScanner accepts options and a config which will be used to determine and create a
  27. // new scanner which is then returned.
  28. func NewScanner(opts options.Options, cfg config.Config) (Scanner, error) {
  29. var (
  30. repo *git.Repository
  31. err error
  32. )
  33. // We want to return a dir scanner immediately since if the scan type is a directory scan
  34. // we don't want to clone/open a repo until inside ParentScanner.Scan
  35. st, err := scanType(opts)
  36. if err != nil {
  37. return nil, err
  38. }
  39. if st == typeDirScanner {
  40. if opts.AdditionalConfig != "" {
  41. additionalCfg, err := config.LoadAdditionalConfig(opts.AdditionalConfig)
  42. if err != nil {
  43. return nil, err
  44. }
  45. cfg = cfg.AppendConfig(additionalCfg)
  46. }
  47. return NewParentScanner(opts, cfg), nil
  48. }
  49. // Clone or open a repo if we need it
  50. if needsRepo(st) {
  51. repo, err = getRepo(opts)
  52. if err != nil {
  53. return nil, err
  54. }
  55. }
  56. // load up alternative config if possible, if not use default/specified config. Will append if AppendRepoConfig is true
  57. if opts.RepoConfigPath != "" && !opts.NoGit {
  58. repoCfg, err := config.LoadRepoConfig(repo, opts.RepoConfigPath)
  59. if err != nil {
  60. return nil, err
  61. }
  62. if opts.AppendRepoConfig {
  63. cfg = cfg.AppendConfig(repoCfg)
  64. } else {
  65. cfg = repoCfg
  66. }
  67. }
  68. // append additional config with the rest of the config
  69. if opts.AdditionalConfig != "" {
  70. additionalCfg, err := config.LoadAdditionalConfig(opts.AdditionalConfig)
  71. if err != nil {
  72. return nil, err
  73. }
  74. cfg = cfg.AppendConfig(additionalCfg)
  75. }
  76. switch st {
  77. case typeCommitScanner:
  78. c, err := obtainCommit(repo, opts.Commit)
  79. if err != nil {
  80. return nil, err
  81. }
  82. return NewCommitScanner(opts, cfg, repo, c), nil
  83. case typeCommitsScanner:
  84. commits, err := optsToCommits(opts)
  85. if err != nil {
  86. return nil, err
  87. }
  88. return NewCommitsScanner(opts, cfg, repo, commits), nil
  89. case typeFilesAtCommitScanner:
  90. c, err := obtainCommit(repo, opts.FilesAtCommit)
  91. if err != nil {
  92. return nil, err
  93. }
  94. return NewFilesAtCommitScanner(opts, cfg, repo, c), nil
  95. case typeUnstagedScanner:
  96. return NewUnstagedScanner(opts, cfg, repo), nil
  97. case typeDirScanner:
  98. return NewParentScanner(opts, cfg), nil
  99. case typeNoGitScanner:
  100. return NewNoGitScanner(opts, cfg), nil
  101. default:
  102. return NewRepoScanner(opts, cfg, repo), nil
  103. }
  104. }
  105. func scanType(opts options.Options) (ScannerType, error) {
  106. if opts.Commit != "" {
  107. return typeCommitScanner, nil
  108. }
  109. if opts.Commits != "" || opts.CommitsFile != "" {
  110. return typeCommitsScanner, nil
  111. }
  112. if opts.FilesAtCommit != "" {
  113. return typeFilesAtCommitScanner, nil
  114. }
  115. if opts.Path != "" && !opts.NoGit {
  116. if opts.CheckUncommitted() {
  117. return typeUnstagedScanner, nil
  118. }
  119. _, err := os.Stat(filepath.Join(opts.Path))
  120. if err != nil {
  121. return typeEmpty, err
  122. }
  123. // check if path/.git exists, if it does, this is a repo scan
  124. // if not this is a multi-repo scan
  125. _, err = os.Stat(filepath.Join(opts.Path, ".git"))
  126. if os.IsNotExist(err) {
  127. return typeDirScanner, nil
  128. }
  129. return typeRepoScanner, nil
  130. }
  131. if opts.Path != "" && opts.NoGit {
  132. _, err := os.Stat(filepath.Join(opts.Path))
  133. if err != nil {
  134. return typeEmpty, err
  135. }
  136. return typeNoGitScanner, nil
  137. }
  138. if opts.CheckUncommitted() {
  139. return typeUnstagedScanner, nil
  140. }
  141. // default to the most commonly used scanner, RepoScanner
  142. return typeRepoScanner, nil
  143. }
  144. func needsRepo(st ScannerType) bool {
  145. if !(st == typeDirScanner || st == typeNoGitScanner) {
  146. return true
  147. }
  148. return false
  149. }