github.go 7.6 KB

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