leaks.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  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. )
  15. type LeakElem struct {
  16. Line string `json:"line"`
  17. Commit string `json:"commit"`
  18. }
  19. func start(opts *Options, repoUrl string) {
  20. c := make(chan os.Signal, 2)
  21. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  22. err := exec.Command("git", "clone", repoUrl).Run()
  23. if err != nil {
  24. log.Fatalf("failed to clone repo %v", err)
  25. }
  26. repoName := getLocalRepoName(repoUrl)
  27. if err := os.Chdir(repoName); err != nil {
  28. log.Fatal(err)
  29. }
  30. go func() {
  31. <-c
  32. cleanup(repoName)
  33. os.Exit(1)
  34. }()
  35. report := getLeaks(repoName)
  36. cleanup(repoName)
  37. reportJson, _ := json.MarshalIndent(report, "", "\t")
  38. err = ioutil.WriteFile(fmt.Sprintf("%s_leaks.json", repoName), reportJson, 0644)
  39. }
  40. // getLocalRepoName generates the name of the local clone folder based on the given URL
  41. func getLocalRepoName(url string) string {
  42. splitSlashes := strings.Split(url, "/")
  43. name := splitSlashes[len(splitSlashes)-1]
  44. name = strings.TrimSuffix(name, ".git")
  45. splitColons := strings.Split(name, ":")
  46. name = splitColons[len(splitColons)-1]
  47. return name
  48. }
  49. func cleanup(repoName string) {
  50. if err := os.Chdir(appRoot); err != nil {
  51. log.Fatalf("failed cleaning up repo. Does the repo exist? %v", err)
  52. }
  53. err := exec.Command("rm", "-rf", repoName).Run()
  54. if err != nil {
  55. log.Fatal(err)
  56. }
  57. }
  58. func getLeaks(repoName string) []LeakElem {
  59. var (
  60. out []byte
  61. err error
  62. commitWG sync.WaitGroup
  63. gitLeakReceiverWG sync.WaitGroup
  64. concurrent = 100
  65. semaphoreChan = make(chan struct{}, concurrent)
  66. gitLeaks = make(chan LeakElem)
  67. report []LeakElem
  68. )
  69. go func(commitWG *sync.WaitGroup, gitLeakReceiverWG *sync.WaitGroup) {
  70. for gitLeak := range gitLeaks {
  71. fmt.Println(gitLeak)
  72. report = append(report, gitLeak)
  73. gitLeakReceiverWG.Done()
  74. }
  75. }(&commitWG, &gitLeakReceiverWG)
  76. out, err = exec.Command("git", "rev-list", "--all", "--remotes", "--topo-order").Output()
  77. if err != nil {
  78. log.Fatalf("error retrieving commits%v\n", err)
  79. }
  80. commits := bytes.Split(out, []byte("\n"))
  81. commitWG.Add(len(commits))
  82. for _, currCommitB := range commits {
  83. currCommit := string(currCommitB)
  84. go func(currCommit string, repoName string, commitWG *sync.WaitGroup, gitLeakReceiverWG *sync.WaitGroup) {
  85. defer commitWG.Done()
  86. var leakPrs bool
  87. if err := os.Chdir(fmt.Sprintf("%s/%s", appRoot, repoName)); err != nil {
  88. log.Fatal(err)
  89. }
  90. commitCmp := fmt.Sprintf("%s^!", currCommit)
  91. semaphoreChan <- struct{}{}
  92. out, err := exec.Command("git", "diff", commitCmp).Output()
  93. <-semaphoreChan
  94. if err != nil {
  95. return
  96. }
  97. lines := checkRegex(string(out))
  98. if len(lines) == 0 {
  99. return
  100. }
  101. for _, line := range lines {
  102. leakPrs = checkEntropy(line)
  103. if leakPrs {
  104. gitLeakReceiverWG.Add(1)
  105. gitLeaks <- LeakElem{line, currCommit}
  106. }
  107. }
  108. }(currCommit, repoName, &commitWG, &gitLeakReceiverWG)
  109. }
  110. commitWG.Wait()
  111. gitLeakReceiverWG.Wait()
  112. return report
  113. }