github.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package hosts
  2. import (
  3. "context"
  4. "strconv"
  5. "strings"
  6. "sync"
  7. "github.com/zricethezav/gitleaks/v4/audit"
  8. "github.com/zricethezav/gitleaks/v4/manager"
  9. "github.com/zricethezav/gitleaks/v4/options"
  10. "github.com/google/go-github/github"
  11. log "github.com/sirupsen/logrus"
  12. "golang.org/x/oauth2"
  13. "gopkg.in/src-d/go-git.v4"
  14. "gopkg.in/src-d/go-git.v4/plumbing"
  15. "gopkg.in/src-d/go-git.v4/plumbing/object"
  16. )
  17. // Github wraps a github client and manager. This struct implements what the Host interface defines.
  18. type Github struct {
  19. client *github.Client
  20. manager *manager.Manager
  21. wg sync.WaitGroup
  22. }
  23. // NewGithubClient accepts a manager struct and returns a Github host pointer which will be used to
  24. // perform a github audit on an organization, user, or PR.
  25. func NewGithubClient(m *manager.Manager) (*Github, error) {
  26. var err error
  27. ctx := context.Background()
  28. token := oauth2.StaticTokenSource(
  29. &oauth2.Token{AccessToken: options.GetAccessToken(m.Opts)},
  30. )
  31. var githubClient *github.Client
  32. httpClient := oauth2.NewClient(ctx, token)
  33. if m.Opts.BaseURL == "" {
  34. githubClient = github.NewClient(httpClient)
  35. } else {
  36. githubClient, err = github.NewEnterpriseClient(m.Opts.BaseURL, m.Opts.BaseURL, httpClient)
  37. }
  38. return &Github{
  39. manager: m,
  40. client: githubClient,
  41. }, err
  42. }
  43. // Audit will audit a github user or organization's repos.
  44. func (g *Github) Audit() {
  45. ctx := context.Background()
  46. listOptions := github.ListOptions{
  47. PerPage: 100,
  48. Page: 1,
  49. }
  50. var githubRepos []*github.Repository
  51. for {
  52. var (
  53. _githubRepos []*github.Repository
  54. resp *github.Response
  55. err error
  56. )
  57. if g.manager.Opts.User != "" {
  58. _githubRepos, resp, err = g.client.Repositories.List(ctx, g.manager.Opts.User,
  59. &github.RepositoryListOptions{ListOptions: listOptions})
  60. } else if g.manager.Opts.Organization != "" {
  61. _githubRepos, resp, err = g.client.Repositories.ListByOrg(ctx, g.manager.Opts.Organization,
  62. &github.RepositoryListByOrgOptions{ListOptions: listOptions})
  63. }
  64. for _, r := range _githubRepos {
  65. if g.manager.Opts.ExcludeForks && r.GetFork() {
  66. log.Debugf("excluding forked repo: %s", *r.Name)
  67. continue
  68. }
  69. githubRepos = append(githubRepos, r)
  70. }
  71. if resp == nil {
  72. break
  73. }
  74. if resp.LastPage != 0 {
  75. log.Infof("gathering github repos... progress: page %d of %d", listOptions.Page, resp.LastPage)
  76. } else {
  77. log.Infof("gathering github repos... progress: page %d of %d", listOptions.Page, listOptions.Page)
  78. }
  79. listOptions.Page = resp.NextPage
  80. if err != nil || listOptions.Page == 0 {
  81. break
  82. }
  83. }
  84. for _, repo := range githubRepos {
  85. r := audit.NewRepo(g.manager)
  86. err := r.Clone(&git.CloneOptions{
  87. URL: *repo.CloneURL,
  88. })
  89. r.Name = *repo.Name
  90. if err != nil {
  91. log.Warn("unable to clone via https and access token, attempting with ssh now")
  92. auth, err := options.SSHAuth(g.manager.Opts)
  93. if err != nil {
  94. log.Warnf("unable to get ssh auth, skipping clone and audit for repo %s: %+v\n", *repo.CloneURL, err)
  95. }
  96. err = r.Clone(&git.CloneOptions{
  97. URL: *repo.SSHURL,
  98. Auth: auth,
  99. })
  100. if err != nil {
  101. log.Warnf("err cloning %s, skipping clone and audit: %+v\n", *repo.SSHURL, err)
  102. }
  103. }
  104. if err = r.Audit(); err != nil {
  105. log.Warn(err)
  106. }
  107. }
  108. }
  109. // AuditPR audits a single github PR
  110. func (g *Github) AuditPR() {
  111. ctx := context.Background()
  112. splits := strings.Split(g.manager.Opts.PullRequest, "/")
  113. owner := splits[len(splits)-4]
  114. repoName := splits[len(splits)-3]
  115. prNum, err := strconv.Atoi(splits[len(splits)-1])
  116. repo := audit.NewRepo(g.manager)
  117. repo.Name = repoName
  118. log.Infof("auditing pr %s\n", g.manager.Opts.PullRequest)
  119. if err != nil {
  120. return
  121. }
  122. page := 1
  123. for {
  124. commits, resp, err := g.client.PullRequests.ListCommits(ctx, owner, repoName, prNum, &github.ListOptions{
  125. PerPage: 100, Page: page})
  126. if err != nil {
  127. return
  128. }
  129. for _, c := range commits {
  130. c, _, err := g.client.Repositories.GetCommit(ctx, owner, repo.Name, *c.SHA)
  131. if err != nil {
  132. continue
  133. }
  134. commitObj := object.Commit{
  135. Hash: plumbing.NewHash(*c.SHA),
  136. Author: object.Signature{
  137. Name: *c.Commit.Author.Name,
  138. Email: *c.Commit.Author.Email,
  139. When: *c.Commit.Author.Date,
  140. },
  141. }
  142. for _, f := range c.Files {
  143. if f.Patch == nil {
  144. continue
  145. }
  146. audit.InspectFile(*f.Patch, *f.Filename, &commitObj, repo)
  147. }
  148. }
  149. page = resp.NextPage
  150. if resp.LastPage == 0 {
  151. break
  152. }
  153. }
  154. }