|
|
@@ -1,39 +1,47 @@
|
|
|
package main
|
|
|
|
|
|
import (
|
|
|
- "encoding/json"
|
|
|
- "fmt"
|
|
|
+ "context"
|
|
|
+ "github.com/google/go-github/github"
|
|
|
+ "github.com/mitchellh/go-homedir"
|
|
|
+ "golang.org/x/oauth2"
|
|
|
+ "io/ioutil"
|
|
|
"log"
|
|
|
"net/http"
|
|
|
"os"
|
|
|
+ "path"
|
|
|
+ "path/filepath"
|
|
|
"regexp"
|
|
|
"strings"
|
|
|
)
|
|
|
|
|
|
var (
|
|
|
- appRoot string
|
|
|
- regexes map[string]*regexp.Regexp
|
|
|
- stopWords []string
|
|
|
- base64Chars string
|
|
|
- hexChars string
|
|
|
- opts *Options
|
|
|
- assignRegex *regexp.Regexp
|
|
|
+ regexes map[string]*regexp.Regexp
|
|
|
+ stopWords []string
|
|
|
+ base64Chars string
|
|
|
+ hexChars string
|
|
|
+ assignRegex *regexp.Regexp
|
|
|
+ gitLeaksPath string
|
|
|
+ gitLeaksClonePath string
|
|
|
+ gitLeaksReportPath string
|
|
|
)
|
|
|
|
|
|
-// RepoElem used for parsing json from github api
|
|
|
-type RepoElem struct {
|
|
|
- RepoURL string `json:"html_url"`
|
|
|
+type RepoDesc struct {
|
|
|
+ name string
|
|
|
+ url string
|
|
|
+ path string
|
|
|
+ owner *Owner
|
|
|
}
|
|
|
|
|
|
-func init() {
|
|
|
- var (
|
|
|
- err error
|
|
|
- )
|
|
|
+type Owner struct {
|
|
|
+ name string
|
|
|
+ url string
|
|
|
+ accountType string
|
|
|
+ path string
|
|
|
+ reportPath string
|
|
|
+}
|
|
|
|
|
|
- appRoot, err = os.Getwd()
|
|
|
- if err != nil {
|
|
|
- log.Fatalf("Can't get working dir: %s", err)
|
|
|
- }
|
|
|
+func init() {
|
|
|
base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
|
|
|
hexChars = "1234567890abcdefABCDEF"
|
|
|
|
|
|
@@ -52,46 +60,159 @@ func init() {
|
|
|
// "Custom": regexp.MustCompile(".*")
|
|
|
}
|
|
|
assignRegex = regexp.MustCompile(`(=|:|:=|<-)`)
|
|
|
+ homeDir, err := homedir.Dir()
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal("Cant find home dir")
|
|
|
+ }
|
|
|
+
|
|
|
+ gitLeaksPath = filepath.Join(homeDir, ".gitleaks")
|
|
|
+ if _, err := os.Stat(gitLeaksPath); os.IsNotExist(err) {
|
|
|
+ os.Mkdir(gitLeaksPath, os.ModePerm)
|
|
|
+ }
|
|
|
+ gitLeaksClonePath = filepath.Join(gitLeaksPath, "clones")
|
|
|
+ if _, err := os.Stat(gitLeaksClonePath); os.IsNotExist(err) {
|
|
|
+ os.Mkdir(gitLeaksClonePath, os.ModePerm)
|
|
|
+ }
|
|
|
+ gitLeaksReportPath = filepath.Join(gitLeaksPath, "report")
|
|
|
+ if _, err := os.Stat(gitLeaksReportPath); os.IsNotExist(err) {
|
|
|
+ os.Mkdir(gitLeaksReportPath, os.ModePerm)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-func main() {
|
|
|
- args := os.Args[1:]
|
|
|
- opts = parseOptions(args)
|
|
|
+func getOwner(opts *Options) *Owner {
|
|
|
+ var owner Owner
|
|
|
if opts.RepoURL != "" {
|
|
|
- start(opts)
|
|
|
- } else if opts.UserURL != "" || opts.OrgURL != "" {
|
|
|
- repoList := repoScan(opts)
|
|
|
- for _, repo := range repoList {
|
|
|
- opts.RepoURL = repo.RepoURL
|
|
|
- start(opts)
|
|
|
+ splitSlashes := strings.Split(opts.RepoURL, "/")
|
|
|
+ owner = Owner{
|
|
|
+ name: splitSlashes[len(splitSlashes)-2],
|
|
|
+ url: opts.RepoURL,
|
|
|
+ accountType: "users",
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if opts.UserURL != "" {
|
|
|
+ _, ownerName := path.Split(opts.UserURL)
|
|
|
+ owner = Owner{
|
|
|
+ name: ownerName,
|
|
|
+ url: opts.UserURL,
|
|
|
+ accountType: "user",
|
|
|
+ }
|
|
|
+ } else if opts.OrgURL != "" {
|
|
|
+ _, ownerName := path.Split(opts.OrgURL)
|
|
|
+ owner = Owner{
|
|
|
+ name: ownerName,
|
|
|
+ url: opts.OrgURL,
|
|
|
+ accountType: "org",
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ if opts.Tmp {
|
|
|
+ dir, err := ioutil.TempDir("", owner.name)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal("Cant make temp dir")
|
|
|
+ }
|
|
|
+ owner.path = dir
|
|
|
+ } else {
|
|
|
+ owner.path = filepath.Join(gitLeaksClonePath, owner.name)
|
|
|
+ if _, err := os.Stat(owner.path); os.IsNotExist(err) {
|
|
|
+ os.Mkdir(owner.path, os.ModePerm)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ owner.reportPath = filepath.Join(gitLeaksPath, "report", owner.name)
|
|
|
+ return &owner
|
|
|
}
|
|
|
|
|
|
-// repoScan attempts to parse all repo urls from an organization or user
|
|
|
-func repoScan(opts *Options) []RepoElem {
|
|
|
+// getRepos
|
|
|
+func getRepos(opts *Options, owner *Owner) []RepoDesc {
|
|
|
var (
|
|
|
- targetURL string
|
|
|
- target string
|
|
|
- targetType string
|
|
|
- repoList []RepoElem
|
|
|
+ allRepos []*github.Repository
|
|
|
+ repos []*github.Repository
|
|
|
+ repoDescs []RepoDesc
|
|
|
+ resp *github.Response
|
|
|
+ ctx = context.Background()
|
|
|
+ err error
|
|
|
)
|
|
|
+ if opts.RepoURL != "" {
|
|
|
+ _, repoName := path.Split(opts.RepoURL)
|
|
|
+ if strings.HasSuffix(repoName, ".git") {
|
|
|
+ repoName = repoName[:len(repoName)-4]
|
|
|
+ }
|
|
|
+ ownerPath := filepath.Join(owner.path, repoName)
|
|
|
+ repo := RepoDesc{
|
|
|
+ name: repoName,
|
|
|
+ url: opts.RepoURL,
|
|
|
+ owner: owner,
|
|
|
+ path: ownerPath}
|
|
|
+ repoDescs = append(repoDescs, repo)
|
|
|
+ return repoDescs
|
|
|
+ }
|
|
|
|
|
|
- if opts.UserURL != "" {
|
|
|
- targetURL = opts.UserURL
|
|
|
- targetType = "users"
|
|
|
- } else {
|
|
|
- targetURL = opts.OrgURL
|
|
|
- targetType = "orgs"
|
|
|
+ tokenClient := getAccessToken(opts)
|
|
|
+ gitClient := github.NewClient(tokenClient)
|
|
|
+
|
|
|
+ // TODO include fork check
|
|
|
+ orgOpt := &github.RepositoryListByOrgOptions{
|
|
|
+ ListOptions: github.ListOptions{PerPage: 10},
|
|
|
+ }
|
|
|
+ userOpt := &github.RepositoryListOptions{
|
|
|
+ ListOptions: github.ListOptions{PerPage: 10},
|
|
|
}
|
|
|
- splitTargetURL := strings.Split(targetURL, "/")
|
|
|
- target = splitTargetURL[len(splitTargetURL)-1]
|
|
|
|
|
|
- resp, err := http.Get(fmt.Sprintf("https://api.github.com/%s/%s/repos", targetType, target))
|
|
|
- if err != nil {
|
|
|
- log.Fatal(err)
|
|
|
+ for {
|
|
|
+ if opts.UserURL != "" {
|
|
|
+ repos, resp, err = gitClient.Repositories.List(
|
|
|
+ ctx, owner.name, userOpt)
|
|
|
+ } else if opts.OrgURL != "" {
|
|
|
+ repos, resp, err = gitClient.Repositories.ListByOrg(
|
|
|
+ ctx, owner.name, orgOpt)
|
|
|
+ }
|
|
|
+ allRepos = append(allRepos, repos...)
|
|
|
+ if resp.NextPage == 0 || err != nil {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, repo := range repos {
|
|
|
+ repoPath := filepath.Join(owner.path, *repo.Name)
|
|
|
+ repoDescs = append(repoDescs,
|
|
|
+ RepoDesc{
|
|
|
+ name: *repo.Name,
|
|
|
+ url: *repo.CloneURL,
|
|
|
+ owner: owner,
|
|
|
+ path: repoPath})
|
|
|
+ }
|
|
|
+
|
|
|
+ orgOpt.Page = resp.NextPage
|
|
|
+ userOpt.Page = resp.NextPage
|
|
|
}
|
|
|
- defer resp.Body.Close()
|
|
|
- json.NewDecoder(resp.Body).Decode(&repoList)
|
|
|
- return repoList
|
|
|
+
|
|
|
+ return repoDescs
|
|
|
+}
|
|
|
+
|
|
|
+// getAccessToken checks
|
|
|
+// 1. option
|
|
|
+// 2. env var
|
|
|
+// TODO. $HOME/.gitleaks/.creds
|
|
|
+func getAccessToken(opts *Options) *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
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ args := os.Args[1:]
|
|
|
+ opts := parseOptions(args)
|
|
|
+ owner := getOwner(opts)
|
|
|
+ repos := getRepos(opts, owner)
|
|
|
+ start(repos, owner, opts)
|
|
|
}
|