leaks.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package main
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io/ioutil"
  7. "log"
  8. "os"
  9. "os/exec"
  10. "os/signal"
  11. "strings"
  12. "sync"
  13. "syscall"
  14. "gopkg.in/src-d/go-git.v4"
  15. )
  16. // LeakElem contains the line and commit of a leak
  17. type LeakElem struct {
  18. Content string `json:"content"`
  19. Commit string `json:"commit"`
  20. }
  21. // start clones and determines if there are any leaks
  22. func start(opts *Options) {
  23. c := make(chan os.Signal, 2)
  24. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  25. //get current working directory
  26. dir,err := os.Getwd()
  27. if err != nil {
  28. fmt.Println(err)
  29. return
  30. }
  31. repoName := getLocalRepoName(opts.RepoURL)
  32. fmt.Printf("Cloning \x1b[37;1m%s\x1b[0m...\n", opts.RepoURL)
  33. r,err := git.PlainClone(dir + "/" + repoName, false, &git.CloneOptions{
  34. URL: opts.RepoURL,
  35. RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
  36. })
  37. log.Printf("Cloned the repo::",r)
  38. if err != nil {
  39. log.Printf("failed to clone repo %v", err)
  40. return
  41. }
  42. fmt.Printf("Evaluating \x1b[37;1m%s\x1b[0m...\n", opts.RepoURL)
  43. if err = os.Chdir(repoName); err != nil {
  44. log.Fatal(err)
  45. }
  46. go func() {
  47. <-c
  48. cleanup(repoName)
  49. os.Exit(1)
  50. }()
  51. report := getLeaks(repoName, opts)
  52. if len(report) == 0 {
  53. fmt.Printf("No Leaks detected for \x1b[35;2m%s\x1b[0m...\n\n", opts.RepoURL)
  54. }
  55. cleanup(repoName)
  56. reportJSON, _ := json.MarshalIndent(report, "", "\t")
  57. err = ioutil.WriteFile(fmt.Sprintf("%s_leaks.json", repoName), reportJSON, 0644)
  58. if err != nil {
  59. log.Fatalf("Can't write to file: %s", err)
  60. }
  61. }
  62. // getLocalRepoName generates the name of the local clone folder based on the given URL
  63. func getLocalRepoName(url string) string {
  64. splitSlashes := strings.Split(url, "/")
  65. name := splitSlashes[len(splitSlashes)-1]
  66. name = strings.TrimSuffix(name, ".git")
  67. splitColons := strings.Split(name, ":")
  68. name = splitColons[len(splitColons)-1]
  69. return name
  70. }
  71. // cleanup deletes the repo
  72. func cleanup(repoName string) {
  73. if err := os.Chdir(appRoot); err != nil {
  74. log.Fatalf("failed cleaning up repo. Does the repo exist? %v", err)
  75. }
  76. err := exec.Command("rm", "-rf", repoName).Run()
  77. if err != nil {
  78. log.Fatal(err)
  79. }
  80. }
  81. // getLeaks will attempt to find gitleaks
  82. func getLeaks(repoName string, opts *Options) []LeakElem {
  83. var (
  84. out []byte
  85. err error
  86. commitWG sync.WaitGroup
  87. gitLeakReceiverWG sync.WaitGroup
  88. gitLeaks = make(chan LeakElem)
  89. report []LeakElem
  90. )
  91. semaphoreChan := make(chan struct{}, opts.Concurrency)
  92. go func(commitWG *sync.WaitGroup, gitLeakReceiverWG *sync.WaitGroup) {
  93. for gitLeak := range gitLeaks {
  94. fmt.Printf("commit: %s\ncontent: %s\n\n", gitLeak.Commit, gitLeak.Content)
  95. report = append(report, gitLeak)
  96. gitLeakReceiverWG.Done()
  97. }
  98. }(&commitWG, &gitLeakReceiverWG)
  99. out, err = exec.Command("git", "rev-list", "--all", "--remotes", "--topo-order").Output()
  100. if err != nil {
  101. log.Fatalf("error retrieving commits%v\n", err)
  102. }
  103. commits := bytes.Split(out, []byte("\n"))
  104. commitWG.Add(len(commits))
  105. for _, currCommitB := range commits {
  106. currCommit := string(currCommitB)
  107. go func(currCommit string, repoName string, commitWG *sync.WaitGroup,
  108. gitLeakReceiverWG *sync.WaitGroup, opts *Options) {
  109. defer commitWG.Done()
  110. var leakPrs bool
  111. if currCommit == "" {
  112. return
  113. }
  114. if err := os.Chdir(fmt.Sprintf("%s/%s", appRoot, repoName)); err != nil {
  115. log.Fatal(err)
  116. }
  117. commitCmp := fmt.Sprintf("%s^!", currCommit)
  118. semaphoreChan <- struct{}{}
  119. out, err := exec.Command("git", "diff", commitCmp).Output()
  120. <-semaphoreChan
  121. if err != nil {
  122. fmt.Printf("error retrieving diff for commit %s try turning concurrency factor down %v\n", currCommit, err)
  123. cleanup(repoName)
  124. return
  125. }
  126. lines := checkRegex(string(out))
  127. if len(lines) == 0 {
  128. return
  129. }
  130. for _, line := range lines {
  131. leakPrs = checkShannonEntropy(line, opts.B64EntropyCutoff, opts.HexEntropyCutoff)
  132. if leakPrs {
  133. if opts.Strict && containsStopWords(line) {
  134. continue
  135. }
  136. gitLeakReceiverWG.Add(1)
  137. gitLeaks <- LeakElem{line, currCommit}
  138. }
  139. }
  140. }(currCommit, repoName, &commitWG, &gitLeakReceiverWG, opts)
  141. }
  142. commitWG.Wait()
  143. gitLeakReceiverWG.Wait()
  144. return report
  145. }