scan.go 4.0 KB

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