github.go 7.1 KB

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