scan.go 3.7 KB

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