| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- package main
- import (
- "context"
- "fmt"
- _ "fmt"
- "github.com/google/go-github/github"
- "golang.org/x/oauth2"
- "io/ioutil"
- "net/http"
- "os"
- "os/signal"
- "path"
- "strings"
- "github.com/mitchellh/go-homedir"
- "path/filepath"
- "log"
- )
- type Owner struct {
- name string
- url string
- accountType string
- path string
- reportPath string
- repos []Repo
- }
- func ownerPath(ownerName string) (string, error){
- if opts.Tmp {
- fmt.Println("creating tmp")
- dir, err := ioutil.TempDir("", ownerName)
- return dir, err
- }
- gitleaksHome := os.Getenv("GITLEAKS_HOME")
- if gitleaksHome == "" {
- homeDir, err := homedir.Dir()
- if err != nil {
- return "", fmt.Errorf("could not find system home dir")
- }
- gitleaksHome = filepath.Join(homeDir, ".gitleaks")
- }
- // make sure gitleaks home exists
- if _, err := os.Stat(gitleaksHome); os.IsNotExist(err) {
- os.Mkdir(gitleaksHome, os.ModePerm)
- }
- return gitleaksHome + "/" + ownerName, nil
- }
- // newOwner instantiates an owner and creates any necessary resources for said owner.
- // newOwner returns a Owner struct pointer
- func newOwner() *Owner {
- name := ownerName()
- ownerPath, err := ownerPath(name)
- if err != nil{
- failF("%v", err)
- }
- owner := &Owner{
- name: name,
- url: opts.URL,
- accountType: ownerType(),
- path: ownerPath,
- }
- // listen for ctrl-c
- // NOTE: need some help on how to actually shut down gracefully.
- // On interrupt a repo may still be trying to clone... This has no
- // actual effect other than extraneous logging.
- sigC := make(chan os.Signal, 1)
- signal.Notify(sigC, os.Interrupt, os.Interrupt)
- go func() {
- <-sigC
- owner.rmTmp()
- }()
- // if running on local repo, just go right to it.
- if opts.LocalMode {
- repo := newLocalRepo(opts.RepoPath)
- owner.repos = append(owner.repos, *repo)
- return owner
- }
- /*
- err := owner.setupDir()
- if err != nil {
- owner.failF("%v", err)
- }*/
- err = owner.fetchRepos()
- if err != nil {
- owner.failF("%v", err)
- }
- return owner
- }
- // fetchRepos is used by newOwner and is responsible for fetching one or more
- // of the owner's repos. If opts.RepoURL is not the empty string then fetchRepos will
- // only grab the repo specified in opts.RepoURL. Otherwise, fetchRepos will reach out to
- // github's api and grab all repos associated with owner.
- func (owner *Owner) fetchRepos() error {
- var err error
- ctx := context.Background()
- if owner.accountType == "" {
- // single repo, ambiguous account type
- _, repoName := path.Split(opts.URL)
- repo := newRepo(repoName, opts.URL, owner.path + "/" + repoName)
- owner.repos = append(owner.repos, *repo)
- } else {
- // org or user account type, would fail if not valid before
- tokenClient := githubTokenClient()
- gitClient := github.NewClient(tokenClient)
- if owner.accountType == "org" {
- // org account type
- orgOpt := &github.RepositoryListByOrgOptions{
- ListOptions: github.ListOptions{PerPage: 10},
- }
- err = owner.fetchOrgRepos(orgOpt, gitClient, ctx)
- } else {
- // user account type
- userOpt := &github.RepositoryListOptions{
- ListOptions: github.ListOptions{PerPage: 10},
- }
- err = owner.fetchUserRepos(userOpt, gitClient, ctx)
- }
- }
- return err
- }
- // fetchOrgRepos used by fetchRepos is responsible for parsing github's org repo response. If no
- // github token is available then fetchOrgRepos might run into a rate limit in which case owner will
- // log an error and gitleaks will exit. The rate limit for no token is 50 req/hour... not much.
- func (owner *Owner) fetchOrgRepos(orgOpts *github.RepositoryListByOrgOptions, gitClient *github.Client,
- ctx context.Context) error {
- var (
- githubRepos []*github.Repository
- resp *github.Response
- err error
- )
- for {
- githubRepos, resp, err = gitClient.Repositories.ListByOrg(
- ctx, owner.name, orgOpts)
- owner.addRepos(githubRepos)
- if _, ok := err.(*github.RateLimitError); ok {
- fmt.Println("Hit rate limit")
- } else if err != nil {
- return fmt.Errorf("failed fetching org repos, bad request")
- } else if resp.NextPage == 0 {
- break
- }
- orgOpts.Page = resp.NextPage
- }
- return nil
- }
- // fetchUserRepos used by fetchRepos is responsible for parsing github's user repo response. If no
- // github token is available then fetchUserRepos might run into a rate limit in which case owner will
- // log an error and gitleaks will exit. The rate limit for no token is 50 req/hour... not much.
- // sorry for the redundancy
- func (owner *Owner) fetchUserRepos(userOpts *github.RepositoryListOptions, gitClient *github.Client,
- ctx context.Context) error {
- var (
- githubRepos []*github.Repository
- resp *github.Response
- err error
- )
- for {
- githubRepos, resp, err = gitClient.Repositories.List(
- ctx, owner.name, userOpts)
- owner.addRepos(githubRepos)
- if _, ok := err.(*github.RateLimitError); ok {
- fmt.Println("Hit rate limit")
- break
- } else if err != nil {
- return fmt.Errorf("failed fetching user repos, bad request")
- } else if resp.NextPage == 0 {
- break
- }
- userOpts.Page = resp.NextPage
- }
- return nil
- }
- // addRepos used by fetchUserRepos and fetchOrgRepos appends new repos from
- // github's org/user response.
- func (owner *Owner) addRepos(githubRepos []*github.Repository) {
- for _, repo := range githubRepos {
- owner.repos = append(owner.repos, *newRepo(*repo.Name, *repo.CloneURL, owner.path + "/" + *repo.Name))
- }
- }
- // auditRepos
- func (owner *Owner) auditRepos() int {
- exitCode := EXIT_CLEAN
- for _, repo := range owner.repos {
- leaksPst, err := repo.audit(owner)
- if err != nil {
- failF("%v\n", err)
- }
- if leaksPst {
- log.Printf("\x1b[31;2mLEAKS DETECTED for %s\x1b[0m!\n", repo.name)
- exitCode = EXIT_LEAKS
- } else {
- log.Printf("No Leaks detected for \x1b[32;2m%s\x1b[0m\n", repo.name)
- }
- }
- return exitCode
- }
- // failF prints a failure message out to stderr
- // and exits with a exit code 2
- func (owner *Owner) failF(format string, args ...interface{}) {
- fmt.Fprintf(os.Stderr, format, args...)
- os.Exit(EXIT_FAILURE)
- }
- // setupDir sets up the owner's directory for clones and reports.
- // If the temporary option is set then a temporary directory will be
- // used for the owner repo clones.
- func (owner *Owner) setupDir() error {
- if opts.Tmp {
- fmt.Println("creating tmp")
- dir, err := ioutil.TempDir("", owner.name)
- if err != nil {
- fmt.Errorf("unable to create temp directories for cloning")
- }
- owner.path = dir
- } else {
- if _, err := os.Stat(owner.path); os.IsNotExist(err) {
- os.Mkdir(owner.path, os.ModePerm)
- }
- }
- return nil
- }
- // rmTmp removes the temporary repo
- func (owner *Owner) rmTmp() {
- log.Printf("removing tmp gitleaks repo for %s\n", owner.name)
- os.RemoveAll(owner.path)
- os.Exit(EXIT_FAILURE)
- }
- // ownerType returns the owner type extracted from opts.
- // If no owner type is provided, gitleaks assumes the owner is ambiguous
- // and the user is running gitleaks on a single repo
- func ownerType() string {
- if opts.OrgMode {
- return "org"
- } else if opts.UserMode {
- return "user"
- }
- return ""
- }
- // ownerName returns the owner name extracted from the urls provided in opts.
- // If no RepoURL, OrgURL, or UserURL is provided, then owner will log an error
- // and gitleaks will exit.
- func ownerName() string {
- if opts.RepoMode {
- splitSlashes := strings.Split(opts.URL, "/")
- return splitSlashes[len(splitSlashes)-2]
- } else if opts.UserMode || opts.OrgMode {
- _, ownerName := path.Split(opts.URL)
- return ownerName
- }
- // local repo
- return ""
- }
- // githubTokenClient creates an oauth client from your github access token.
- // Gitleaks will attempt to retrieve your github access token from a cli argument
- // or an env var - "GITHUB_TOKEN".
- // Might be good to eventually parse the token from a Config or creds file in
- // $GITLEAKS_HOME
- func githubTokenClient() *http.Client {
- var token string
- if opts.Token != "" {
- token = opts.Token
- } else {
- token = os.Getenv("GITHUB_TOKEN")
- }
- if token == "" {
- return nil
- }
- tokenService := oauth2.StaticTokenSource(
- &oauth2.Token{AccessToken: token},
- )
- tokenClient := oauth2.NewClient(context.Background(), tokenService)
- return tokenClient
- }
|