main.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. package main
  2. import (
  3. "context"
  4. "github.com/google/go-github/github"
  5. "github.com/mitchellh/go-homedir"
  6. "golang.org/x/oauth2"
  7. "io/ioutil"
  8. "log"
  9. "net/http"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "regexp"
  14. "strings"
  15. )
  16. var (
  17. regexes map[string]*regexp.Regexp
  18. stopWords []string
  19. base64Chars string
  20. hexChars string
  21. assignRegex *regexp.Regexp
  22. fileDiffRegex *regexp.Regexp
  23. gitLeaksPath string
  24. gitLeaksClonePath string
  25. gitLeaksReportPath string
  26. )
  27. type RepoDesc struct {
  28. name string
  29. url string
  30. path string
  31. owner *Owner
  32. }
  33. type Owner struct {
  34. name string
  35. url string
  36. accountType string
  37. path string
  38. reportPath string
  39. }
  40. func init() {
  41. base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
  42. hexChars = "1234567890abcdefABCDEF"
  43. stopWords = []string{"setting", "info", "env", "environment"}
  44. regexes = map[string]*regexp.Regexp{
  45. "PKCS8": regexp.MustCompile("-----BEGIN PRIVATE KEY-----"),
  46. "RSA": regexp.MustCompile("-----BEGIN RSA PRIVATE KEY-----"),
  47. "SSH": regexp.MustCompile("-----BEGIN OPENSSH PRIVATE KEY-----"),
  48. "Facebook": regexp.MustCompile("(?i)facebook.*['|\"][0-9a-f]{32}['|\"]"),
  49. "Twitter": regexp.MustCompile("(?i)twitter.*['|\"][0-9a-zA-Z]{35,44}['|\"]"),
  50. "Github": regexp.MustCompile("(?i)github.*[['|\"]0-9a-zA-Z]{35,40}['|\"]"),
  51. "AWS": regexp.MustCompile("AKIA[0-9A-Z]{16}"),
  52. "Reddit": regexp.MustCompile("(?i)reddit.*['|\"][0-9a-zA-Z]{14}['|\"]"),
  53. "Heroku": regexp.MustCompile("(?i)heroku.*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}"),
  54. // "Custom": regexp.MustCompile(".*")
  55. }
  56. assignRegex = regexp.MustCompile(`(=|:|:=|<-)`)
  57. fileDiffRegex = regexp.MustCompile("diff --git a.+b/")
  58. homeDir, err := homedir.Dir()
  59. if err != nil {
  60. log.Fatal("Cant find home dir")
  61. }
  62. gitLeaksPath = filepath.Join(homeDir, ".gitleaks")
  63. if _, err := os.Stat(gitLeaksPath); os.IsNotExist(err) {
  64. os.Mkdir(gitLeaksPath, os.ModePerm)
  65. }
  66. gitLeaksClonePath = filepath.Join(gitLeaksPath, "clones")
  67. if _, err := os.Stat(gitLeaksClonePath); os.IsNotExist(err) {
  68. os.Mkdir(gitLeaksClonePath, os.ModePerm)
  69. }
  70. gitLeaksReportPath = filepath.Join(gitLeaksPath, "report")
  71. if _, err := os.Stat(gitLeaksReportPath); os.IsNotExist(err) {
  72. os.Mkdir(gitLeaksReportPath, os.ModePerm)
  73. }
  74. }
  75. // getOwner
  76. func getOwner(opts *Options) *Owner {
  77. var owner Owner
  78. if opts.RepoURL != "" {
  79. splitSlashes := strings.Split(opts.RepoURL, "/")
  80. owner = Owner{
  81. name: splitSlashes[len(splitSlashes)-2],
  82. url: opts.RepoURL,
  83. accountType: "users",
  84. }
  85. } else if opts.UserURL != "" {
  86. _, ownerName := path.Split(opts.UserURL)
  87. owner = Owner{
  88. name: ownerName,
  89. url: opts.UserURL,
  90. accountType: "user",
  91. }
  92. } else if opts.OrgURL != "" {
  93. _, ownerName := path.Split(opts.OrgURL)
  94. owner = Owner{
  95. name: ownerName,
  96. url: opts.OrgURL,
  97. accountType: "org",
  98. }
  99. }
  100. if opts.Tmp {
  101. dir, err := ioutil.TempDir("", owner.name)
  102. if err != nil {
  103. log.Fatal("Cant make temp dir")
  104. }
  105. owner.path = dir
  106. } else {
  107. owner.path = filepath.Join(gitLeaksClonePath, owner.name)
  108. if _, err := os.Stat(owner.path); os.IsNotExist(err) {
  109. os.Mkdir(owner.path, os.ModePerm)
  110. }
  111. }
  112. owner.reportPath = filepath.Join(gitLeaksPath, "report", owner.name)
  113. return &owner
  114. }
  115. // getRepos
  116. func getRepos(opts *Options, owner *Owner) []RepoDesc {
  117. var (
  118. allRepos []*github.Repository
  119. repos []*github.Repository
  120. repoDescs []RepoDesc
  121. resp *github.Response
  122. ctx = context.Background()
  123. err error
  124. )
  125. if opts.RepoURL != "" {
  126. _, repoName := path.Split(opts.RepoURL)
  127. if strings.HasSuffix(repoName, ".git") {
  128. repoName = repoName[:len(repoName)-4]
  129. }
  130. ownerPath := filepath.Join(owner.path, repoName)
  131. repo := RepoDesc{
  132. name: repoName,
  133. url: opts.RepoURL,
  134. owner: owner,
  135. path: ownerPath}
  136. repoDescs = append(repoDescs, repo)
  137. return repoDescs
  138. }
  139. tokenClient := getAccessToken(opts)
  140. gitClient := github.NewClient(tokenClient)
  141. // TODO include fork check
  142. orgOpt := &github.RepositoryListByOrgOptions{
  143. ListOptions: github.ListOptions{PerPage: 10},
  144. }
  145. userOpt := &github.RepositoryListOptions{
  146. ListOptions: github.ListOptions{PerPage: 10},
  147. }
  148. for {
  149. if opts.UserURL != "" {
  150. repos, resp, err = gitClient.Repositories.List(
  151. ctx, owner.name, userOpt)
  152. } else if opts.OrgURL != "" {
  153. repos, resp, err = gitClient.Repositories.ListByOrg(
  154. ctx, owner.name, orgOpt)
  155. }
  156. allRepos = append(allRepos, repos...)
  157. if resp.NextPage == 0 || err != nil {
  158. break
  159. }
  160. for _, repo := range repos {
  161. repoPath := filepath.Join(owner.path, *repo.Name)
  162. repoDescs = append(repoDescs,
  163. RepoDesc{
  164. name: *repo.Name,
  165. url: *repo.CloneURL,
  166. owner: owner,
  167. path: repoPath})
  168. }
  169. orgOpt.Page = resp.NextPage
  170. userOpt.Page = resp.NextPage
  171. }
  172. return repoDescs
  173. }
  174. // getAccessToken checks
  175. // 1. option
  176. // 2. env var
  177. // TODO. $HOME/.gitleaks/.creds
  178. func getAccessToken(opts *Options) *http.Client {
  179. var token string
  180. if opts.Token != "" {
  181. token = opts.Token
  182. } else {
  183. token = os.Getenv("GITHUB_TOKEN")
  184. }
  185. if token == "" {
  186. return nil
  187. }
  188. tokenService := oauth2.StaticTokenSource(
  189. &oauth2.Token{AccessToken: token},
  190. )
  191. tokenClient := oauth2.NewClient(context.Background(), tokenService)
  192. return tokenClient
  193. }
  194. func main() {
  195. args := os.Args[1:]
  196. opts := parseOptions(args)
  197. owner := getOwner(opts)
  198. repos := getRepos(opts, owner)
  199. start(repos, owner, opts)
  200. }