github.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "net/url"
  8. "os"
  9. "strconv"
  10. "strings"
  11. "github.com/google/go-github/github"
  12. log "github.com/sirupsen/logrus"
  13. "golang.org/x/oauth2"
  14. git "gopkg.in/src-d/go-git.v4"
  15. "gopkg.in/src-d/go-git.v4/storage/memory"
  16. )
  17. var githubPages = 100
  18. // auditPR audits a single github PR
  19. func auditGithubPR() ([]Leak, error) {
  20. var leaks []Leak
  21. ctx := context.Background()
  22. githubClient := github.NewClient(githubToken())
  23. splits := strings.Split(opts.GithubPR, "/")
  24. owner := splits[len(splits)-4]
  25. repo := splits[len(splits)-3]
  26. prNum, err := strconv.Atoi(splits[len(splits)-1])
  27. if err != nil {
  28. return nil, err
  29. }
  30. page := 1
  31. for {
  32. commits, resp, err := githubClient.PullRequests.ListCommits(ctx, owner, repo, prNum, &github.ListOptions{
  33. PerPage: githubPages,
  34. Page: page,
  35. })
  36. if err != nil {
  37. return leaks, err
  38. }
  39. for _, commit := range commits {
  40. totalCommits = totalCommits + 1
  41. commit, _, err := githubClient.Repositories.GetCommit(ctx, owner, repo, *commit.SHA)
  42. if err != nil {
  43. continue
  44. }
  45. files := commit.Files
  46. for _, f := range files {
  47. diff := gitDiff{
  48. sha: commit.GetSHA(),
  49. content: *f.Patch,
  50. filePath: *f.Filename,
  51. repoName: repo,
  52. githubCommit: commit,
  53. author: commit.GetCommitter().GetLogin(),
  54. message: *commit.Commit.Message,
  55. }
  56. leaks = append(leaks, inspect(diff)...)
  57. }
  58. }
  59. page = resp.NextPage
  60. if resp.LastPage == 0 {
  61. break
  62. }
  63. }
  64. return leaks, nil
  65. }
  66. // auditGithubRepos kicks off audits if --github-user or --github-org options are set.
  67. // First, we gather all the github repositories from the github api (this doesnt actually clone the repo).
  68. // After all the repos have been pulled from github's api we proceed to audit the repos by calling auditGithubRepo.
  69. // If an error occurs during an audit of a repo, that error is logged but won't break the execution cycle.
  70. func auditGithubRepos() ([]Leak, error) {
  71. var (
  72. err error
  73. githubRepos []*github.Repository
  74. pagedGithubRepos []*github.Repository
  75. resp *github.Response
  76. githubOrgOptions *github.RepositoryListByOrgOptions
  77. githubOptions *github.RepositoryListOptions
  78. done bool
  79. leaks []Leak
  80. ownerDir string
  81. )
  82. ctx := context.Background()
  83. githubClient := github.NewClient(githubToken())
  84. if opts.GithubOrg != "" {
  85. if opts.GithubURL != "" && opts.GithubURL != defaultGithubURL {
  86. ghURL, _ := url.Parse(opts.GithubURL)
  87. githubClient.BaseURL = ghURL
  88. }
  89. githubOrgOptions = &github.RepositoryListByOrgOptions{
  90. ListOptions: github.ListOptions{PerPage: 100},
  91. }
  92. } else if opts.GithubUser != "" {
  93. if opts.GithubURL != "" && opts.GithubURL != defaultGithubURL {
  94. ghURL, _ := url.Parse(opts.GithubURL)
  95. githubClient.BaseURL = ghURL
  96. }
  97. githubOptions = &github.RepositoryListOptions{
  98. Affiliation: "owner",
  99. ListOptions: github.ListOptions{
  100. PerPage: 100,
  101. },
  102. }
  103. }
  104. for {
  105. if done {
  106. break
  107. }
  108. if opts.GithubUser != "" {
  109. pagedGithubRepos, resp, err = githubClient.Repositories.List(ctx, opts.GithubUser, githubOptions)
  110. if err != nil {
  111. done = true
  112. }
  113. githubOptions.Page = resp.NextPage
  114. githubRepos = append(githubRepos, pagedGithubRepos...)
  115. if resp.NextPage == 0 {
  116. done = true
  117. }
  118. } else if opts.GithubOrg != "" {
  119. pagedGithubRepos, resp, err = githubClient.Repositories.ListByOrg(ctx, opts.GithubOrg, githubOrgOptions)
  120. if err != nil {
  121. done = true
  122. }
  123. githubOrgOptions.Page = resp.NextPage
  124. githubRepos = append(githubRepos, pagedGithubRepos...)
  125. if resp.NextPage == 0 {
  126. done = true
  127. }
  128. }
  129. if opts.Log == "Debug" || opts.Log == "debug" {
  130. for _, githubRepo := range pagedGithubRepos {
  131. log.Debugf("staging repos %s", *githubRepo.Name)
  132. }
  133. }
  134. }
  135. if opts.Disk {
  136. ownerDir, _ = ioutil.TempDir(dir, opts.GithubUser)
  137. }
  138. for _, githubRepo := range githubRepos {
  139. repo, err := cloneGithubRepo(githubRepo)
  140. if err != nil {
  141. log.Warn(err)
  142. continue
  143. }
  144. leaksFromRepo, err := auditGitRepo(repo)
  145. if opts.Disk {
  146. os.RemoveAll(fmt.Sprintf("%s/%s", ownerDir, *githubRepo.Name))
  147. }
  148. if len(leaksFromRepo) == 0 {
  149. log.Infof("no leaks found for repo %s", *githubRepo.Name)
  150. } else {
  151. log.Warnf("leaks found for repo %s", *githubRepo.Name)
  152. }
  153. if err != nil {
  154. log.Warn(err)
  155. }
  156. leaks = append(leaks, leaksFromRepo...)
  157. }
  158. return leaks, nil
  159. }
  160. // cloneGithubRepo clones a repo from the url parsed from a github repo. The repo
  161. // will be cloned to disk if --disk is set. If the repo is private, you must include the
  162. // --private/-p option. After the repo is clone, an audit will begin.
  163. func cloneGithubRepo(githubRepo *github.Repository) (*RepoDescriptor, error) {
  164. var (
  165. repo *git.Repository
  166. err error
  167. )
  168. if opts.ExcludeForks && githubRepo.GetFork() {
  169. return nil, fmt.Errorf("skipping %s, excluding forks", *githubRepo.Name)
  170. }
  171. for _, re := range whiteListRepos {
  172. if re.FindString(*githubRepo.Name) != "" {
  173. return nil, fmt.Errorf("skipping %s, whitelisted", *githubRepo.Name)
  174. }
  175. }
  176. log.Infof("cloning: %s", *githubRepo.Name)
  177. if opts.Disk {
  178. ownerDir, err := ioutil.TempDir(dir, opts.GithubUser)
  179. if err != nil {
  180. return nil, fmt.Errorf("unable to generater owner temp dir: %v", err)
  181. }
  182. if sshAuth != nil {
  183. repo, err = git.PlainClone(fmt.Sprintf("%s/%s", ownerDir, *githubRepo.Name), false, &git.CloneOptions{
  184. URL: *githubRepo.SSHURL,
  185. Auth: sshAuth,
  186. })
  187. } else {
  188. repo, err = git.PlainClone(fmt.Sprintf("%s/%s", ownerDir, *githubRepo.Name), false, &git.CloneOptions{
  189. URL: *githubRepo.CloneURL,
  190. })
  191. }
  192. } else {
  193. if sshAuth != nil {
  194. repo, err = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
  195. URL: *githubRepo.SSHURL,
  196. Auth: sshAuth,
  197. })
  198. } else {
  199. repo, err = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
  200. URL: *githubRepo.CloneURL,
  201. })
  202. }
  203. }
  204. if err != nil {
  205. return nil, err
  206. }
  207. return &RepoDescriptor{
  208. repository: repo,
  209. name: *githubRepo.Name,
  210. }, nil
  211. }
  212. // githubToken returns an oauth2 client for the github api to consume. This token is necessary
  213. // if you are running audits with --github-user or --github-org
  214. func githubToken() *http.Client {
  215. githubToken := os.Getenv("GITHUB_TOKEN")
  216. if githubToken == "" {
  217. return nil
  218. }
  219. ts := oauth2.StaticTokenSource(
  220. &oauth2.Token{AccessToken: githubToken},
  221. )
  222. return oauth2.NewClient(context.Background(), ts)
  223. }