github.go 4.8 KB

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