Просмотр исходного кода

Merge pull request #11 from zricethezav/org_user_scan

Org user scan
Zachary Rice 8 лет назад
Родитель
Сommit
a67910ec00
3 измененных файлов с 109 добавлено и 14 удалено
  1. 20 8
      leaks.go
  2. 48 3
      main.go
  3. 41 3
      options.go

+ 20 - 8
leaks.go

@@ -14,20 +14,24 @@ import (
 	"syscall"
 )
 
+// LeakElem contains the line and commit of a leak
 type LeakElem struct {
 	Line   string `json:"line"`
 	Commit string `json:"commit"`
 }
 
-func start(_ *Options, repoURL string) {
+// start clones and determines if there are any leaks
+func start(opts *Options) {
+	fmt.Printf("\nEvaluating \x1b[37;1m%s\x1b[0m...\n", opts.RepoURL)
 	c := make(chan os.Signal, 2)
 	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
 
-	err := exec.Command("git", "clone", repoURL).Run()
+	err := exec.Command("git", "clone", opts.RepoURL).Run()
 	if err != nil {
-		log.Fatalf("failed to clone repo %v", err)
+		log.Printf("failed to clone repo %v", err)
+		return
 	}
-	repoName := getLocalRepoName(repoURL)
+	repoName := getLocalRepoName(opts.RepoURL)
 	if err = os.Chdir(repoName); err != nil {
 		log.Fatal(err)
 	}
@@ -37,7 +41,10 @@ func start(_ *Options, repoURL string) {
 		os.Exit(1)
 	}()
 
-	report := getLeaks(repoName)
+	report := getLeaks(repoName, opts.Concurrency)
+	if len(report) == 0 {
+		fmt.Printf("No Leaks detected for \x1b[35;2m%s\x1b[0m...\n\n", opts.RepoURL)
+	}
 	cleanup(repoName)
 	reportJSON, _ := json.MarshalIndent(report, "", "\t")
 	err = ioutil.WriteFile(fmt.Sprintf("%s_leaks.json", repoName), reportJSON, 0644)
@@ -57,6 +64,7 @@ func getLocalRepoName(url string) string {
 	return name
 }
 
+// cleanup deletes the repo
 func cleanup(repoName string) {
 	if err := os.Chdir(appRoot); err != nil {
 		log.Fatalf("failed cleaning up repo. Does the repo exist? %v", err)
@@ -67,18 +75,22 @@ func cleanup(repoName string) {
 	}
 }
 
-func getLeaks(repoName string) []LeakElem {
+// getLeaks will attempt to find gitleaks
+func getLeaks(repoName string, concurrency int) []LeakElem {
 	var (
 		out               []byte
 		err               error
 		commitWG          sync.WaitGroup
 		gitLeakReceiverWG sync.WaitGroup
-		concurrent        = 100
-		semaphoreChan     = make(chan struct{}, concurrent)
 		gitLeaks          = make(chan LeakElem)
 		report            []LeakElem
 	)
 
+	if concurrency == 0 {
+		concurrency = 100
+	}
+	semaphoreChan := make(chan struct{}, concurrency)
+
 	go func(commitWG *sync.WaitGroup, gitLeakReceiverWG *sync.WaitGroup) {
 		for gitLeak := range gitLeaks {
 			fmt.Println(gitLeak)

+ 48 - 3
main.go

@@ -1,9 +1,14 @@
 package main
 
 import (
+	"encoding/json"
+	"fmt"
+	_ "io/ioutil"
 	"log"
+	"net/http"
 	"os"
 	"regexp"
+	"strings"
 )
 
 var (
@@ -35,8 +40,48 @@ func init() {
 }
 
 func main() {
-	args := os.Args[2:]
-	repoURL := os.Args[1]
+	args := os.Args[1:]
 	opts := parseOptions(args)
-	start(opts, repoURL)
+	if opts.RepoURL != "" {
+		start(opts)
+	} else if opts.UserURL != "" || opts.OrgURL != "" {
+		repoList := repoScan(opts)
+		for _, repo := range repoList {
+			opts.RepoURL = repo.RepoURL
+			start(opts)
+		}
+	}
+}
+
+// RepoElem used for parsing json from github api
+type RepoElem struct {
+	RepoURL string `json:"html_url"`
+}
+
+// repoScan attempts to parse all repo urls from an organization or user
+func repoScan(opts *Options) []RepoElem {
+	var (
+		targetURL  string
+		target     string
+		targetType string
+		repoList   []RepoElem
+	)
+
+	if opts.UserURL != "" {
+		targetURL = opts.UserURL
+		targetType = "users"
+	} else {
+		targetURL = opts.OrgURL
+		targetType = "org"
+	}
+	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)
+	}
+	defer resp.Body.Close()
+	json.NewDecoder(resp.Body).Decode(&repoList)
+	return repoList
 }

+ 41 - 3
options.go

@@ -16,15 +16,22 @@ Options:
 	-h --help 		Display this message
 `
 
+// Options for gitleaks
 type Options struct {
 	Concurrency int
+	UserURL     string
+	OrgURL      string
+	RepoURL     string
 }
 
+// help prints the usage string and exits
 func help() {
 	os.Stderr.WriteString(usage)
 	os.Exit(1)
 }
 
+// optionsNextInt is a parseOptions helper that returns the value (int) of an option
+// if valid.
 func optionsNextInt(args []string, i *int) int {
 	if len(args) > *i+1 {
 		*i++
@@ -39,20 +46,51 @@ func optionsNextInt(args []string, i *int) int {
 	return argInt
 }
 
+// optionsNextString is a parseOptions helper that returns the value (string) of an option
+// if valid.
+func optionsNextString(args []string, i *int) string {
+	if len(args) > *i+1 {
+		*i++
+	} else {
+		fmt.Printf("Invalid %s option: %s\n", args[*i-1], args[*i])
+		help()
+	}
+	return args[*i]
+}
+
+// parseOptions
 func parseOptions(args []string) *Options {
 	opts := &Options{}
+
+	// default is repo if no additional options
+	if len(args) == 1 {
+		opts.RepoURL = args[0]
+		return opts
+	}
+
 	for i := 0; i < len(args); i++ {
 		arg := args[i]
 		switch arg {
 		case "-c":
 			opts.Concurrency = optionsNextInt(args, &i)
+		case "-o":
+			opts.OrgURL = optionsNextString(args, &i)
+		case "-u":
+			opts.UserURL = optionsNextString(args, &i)
+		case "-r":
+			opts.RepoURL = optionsNextString(args, &i)
 		case "-h", "--help":
 			help()
 			return nil
 		default:
-			fmt.Printf("Uknown option %s\n\n", arg)
-			help()
-			return nil
+			if i == len(args)-1 && opts.OrgURL == "" && opts.RepoURL == "" &&
+				opts.UserURL == "" {
+				opts.RepoURL = arg
+			} else {
+				fmt.Printf("Uknown option %s\n\n", arg)
+				help()
+				return nil
+			}
 		}
 	}