main.go 5.5 KB

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