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

Merge pull request #1 from zricethezav/master

update upstream
Adam Kobi 7 лет назад
Родитель
Сommit
1cdf315bfa
6 измененных файлов с 189 добавлено и 62 удалено
  1. 10 0
      CHANGELOG.md
  2. 28 25
      README.md
  3. 25 2
      github.go
  4. 3 0
      gitleaks.toml
  5. 45 0
      gitleaks_test.go
  6. 78 35
      main.go

+ 10 - 0
CHANGELOG.md

@@ -1,6 +1,16 @@
 CHANGELOG
 =========
 
+1.22.0
+----
+- context inclusion for redactions 
+- noise reduction for entropy signals
+
+1.21.0
+----
+- added support for cloning repositories using github api
+- auditing PRs now allows for whitelisting files
+
 1.20.0
 ----
 - adding gitlab user and group support

+ 28 - 25
README.md

@@ -33,7 +33,7 @@ It has been sucessfully used in a number of different scenarios, including;
 </p>
 
 #### Installation
-Written in Go, gitleaks is available in binary form for many popular platforms and OS types from the [releases page](https://github.com/zricethezav/gitleaks). Alternatively, executed via Docker or it can be installed using Go directly, as per the below;
+Written in Go, gitleaks is available in binary form for many popular platforms and OS types from the [releases page](https://github.com/zricethezav/gitleaks/releases). Alternatively, executed via Docker or it can be installed using Go directly, as per the below;
 
 ##### Docker
 
@@ -63,30 +63,31 @@ Usage:
   gitleaks [OPTIONS]
 
 Application Options:
-  -r, --repo=          Repo url to audit
-      --github-user=   Github user to audit
-      --github-org=    Github organization to audit
-      --github-url=    GitHub API Base URL, use for GitHub Enterprise. Example: https://github.example.com/api/v3/ (default: https://api.github.com/)
-      --github-pr=     Github PR url to audit. This does not clone the repo. GITHUB_TOKEN must be set
-      --gitlab-user=   GitLab user ID to audit
-      --gitlab-org=    GitLab group ID to audit
-  -c, --commit=        sha of commit to stop at
-      --depth=         maximum commit depth
-      --repo-path=     Path to repo
-      --owner-path=    Path to owner directory (repos discovered)
-      --threads=       Maximum number of threads gitleaks spawns
-      --disk           Clones repo(s) to disk
-      --single-search= single regular expression to search for
-      --config=        path to gitleaks config
-      --ssh-key=       path to ssh key
-      --exclude-forks  exclude forks for organization/user audits
-  -e, --entropy=       Include entropy checks during audit. Entropy scale: 0.0(no entropy) - 8.0(max entropy)
-  -l, --log=           log level
-  -v, --verbose        Show verbose output from gitleaks audit
-      --report=        path to write report file
-      --redact         redact secrets from log messages and report
-      --version        version number
-      --sample-config  prints a sample config file
+  -r, --repo=           Repo url to audit
+      --github-user=    Github user to audit
+      --github-org=     Github organization to audit
+      --github-url=     GitHub API Base URL, use for GitHub Enterprise. Example: https://github.example.com/api/v3/ (default: https://api.github.com/)
+      --github-pr=      Github PR url to audit. This does not clone the repo. GITHUB_TOKEN must be set
+      --gitlab-user=    GitLab user ID to audit
+      --gitlab-org=     GitLab group ID to audit
+  -c, --commit=         sha of commit to stop at
+      --depth=          maximum commit depth
+      --repo-path=      Path to repo
+      --owner-path=     Path to owner directory (repos discovered)
+      --threads=        Maximum number of threads gitleaks spawns
+      --disk            Clones repo(s) to disk
+      --single-search=  single regular expression to search for
+      --config=         path to gitleaks config
+      --ssh-key=        path to ssh key
+      --exclude-forks   exclude forks for organization/user audits
+  -e, --entropy=        Include entropy checks during audit. Entropy scale: 0.0(no entropy) - 8.0(max entropy)
+      --noise-reduction Reduce the number of finds when entropy checks are enabled
+  -l, --log=            log level
+  -v, --verbose         Show verbose output from gitleaks audit
+      --report=         path to write report file
+      --redact          redact secrets from log messages and report
+      --version         version number
+      --sample-config   prints a sample config file
 
 Help Options:
   -h, --help           Show this help message
@@ -127,6 +128,8 @@ From C-U at Home:
 
 <b>Donate</b>: https://www.cuathome.us/give/
 
+#### Speaking of Community
+Please read this https://corruptcu.com/
 
 
 

+ 25 - 2
github.go

@@ -14,6 +14,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/oauth2"
 	git "gopkg.in/src-d/go-git.v4"
+	gitHttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
 	"gopkg.in/src-d/go-git.v4/storage/memory"
 )
 
@@ -50,6 +51,10 @@ func auditGithubPR() ([]Leak, error) {
 			}
 			files := commit.Files
 			for _, f := range files {
+				if f.Patch == nil || f.Filename == nil {
+					continue
+				}
+
 				diff := gitDiff{
 					sha:          commit.GetSHA(),
 					content:      *f.Patch,
@@ -58,6 +63,7 @@ func auditGithubPR() ([]Leak, error) {
 					githubCommit: commit,
 					author:       commit.GetCommitter().GetLogin(),
 					message:      *commit.Commit.Message,
+					date:         *commit.Commit.Committer.Date,
 				}
 				leaks = append(leaks, inspect(diff)...)
 			}
@@ -177,6 +183,7 @@ func cloneGithubRepo(githubRepo *github.Repository) (*RepoDescriptor, error) {
 		repo *git.Repository
 		err  error
 	)
+	githubToken := os.Getenv("GITHUB_TOKEN")
 	if opts.ExcludeForks && githubRepo.GetFork() {
 		return nil, fmt.Errorf("skipping %s, excluding forks", *githubRepo.Name)
 	}
@@ -191,22 +198,38 @@ func cloneGithubRepo(githubRepo *github.Repository) (*RepoDescriptor, error) {
 		if err != nil {
 			return nil, fmt.Errorf("unable to generater owner temp dir: %v", err)
 		}
-		if sshAuth != nil {
+		if sshAuth != nil && githubToken == "" {
 			repo, err = git.PlainClone(fmt.Sprintf("%s/%s", ownerDir, *githubRepo.Name), false, &git.CloneOptions{
 				URL:  *githubRepo.SSHURL,
 				Auth: sshAuth,
 			})
+		} else if githubToken != "" {
+			repo, err = git.PlainClone(fmt.Sprintf("%s/%s", ownerDir, *githubRepo.Name), false, &git.CloneOptions{
+				URL: *githubRepo.CloneURL,
+				Auth: &gitHttp.BasicAuth{
+					Username: "fakeUsername", // yes, this can be anything except an empty string
+					Password: githubToken,
+				},
+			})
 		} else {
 			repo, err = git.PlainClone(fmt.Sprintf("%s/%s", ownerDir, *githubRepo.Name), false, &git.CloneOptions{
 				URL: *githubRepo.CloneURL,
 			})
 		}
 	} else {
-		if sshAuth != nil {
+		if sshAuth != nil && githubToken == "" {
 			repo, err = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
 				URL:  *githubRepo.SSHURL,
 				Auth: sshAuth,
 			})
+		} else if githubToken != "" {
+			repo, err = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
+				URL: *githubRepo.CloneURL,
+				Auth: &gitHttp.BasicAuth{
+					Username: "fakeUsername", // yes, this can be anything except an empty string
+					Password: githubToken,
+				},
+			})
 		} else {
 			repo, err = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
 				URL: *githubRepo.CloneURL,

+ 3 - 0
gitleaks.toml

@@ -29,6 +29,9 @@ regex = '''-----BEGIN PGP PRIVATE KEY BLOCK-----'''
 [[regexes]]
 description = "Slack token"
 regex = '''xox[baprs]-.*'''
+[[regexes]]
+description = "Strip API Key"
+regex = '''(?i)(sk|pk)_(test|live)_[0-9a-zA-Z]{10,32}'''
 
 [whitelist]
 regexes = [

+ 45 - 0
gitleaks_test.go

@@ -176,6 +176,7 @@ func TestRun(t *testing.T) {
 		description    string
 		expectedErrMsg string
 		whiteListRepos []string
+		whiteListFiles []*regexp.Regexp
 		numLeaks       int
 		configPath     string
 		commitPerPage  int
@@ -298,6 +299,27 @@ func TestRun(t *testing.T) {
 			expectedErrMsg: "",
 			commitPerPage:  1,
 		},
+		{
+			testOpts: Options{
+				GithubPR: "https://github.com/gitleakstest/gronit/pull/1",
+			},
+			description:    "test github pr with whitelisted files",
+			numLeaks:       0,
+			expectedErrMsg: "",
+			commitPerPage:  1,
+			whiteListFiles: []*regexp.Regexp{
+				regexp.MustCompile("main.go"),
+			},
+		},
+		{
+			testOpts: Options{
+				GithubPR: "https://github.com/gitleakstest/gronit/pull/2",
+			},
+			description:    "test github pr with commits without patch info",
+			numLeaks:       0,
+			expectedErrMsg: "",
+			commitPerPage:  1,
+		},
 	}
 	g := goblin.Goblin(t)
 	for _, test := range tests {
@@ -309,6 +331,11 @@ func TestRun(t *testing.T) {
 				if test.commitPerPage != 0 {
 					githubPages = test.commitPerPage
 				}
+				if test.whiteListFiles != nil {
+					whiteListFiles = test.whiteListFiles
+				} else {
+					whiteListFiles = nil
+				}
 				opts = test.testOpts
 				leaks, err := run()
 				if err != nil {
@@ -565,6 +592,15 @@ func TestAuditRepo(t *testing.T) {
 			},
 			numLeaks: 6,
 		},
+		{
+			repo:        leaksRepo,
+			description: "leaks present with entropy",
+			testOpts: Options{
+				Entropy:        4.7,
+				NoiseReduction: true,
+			},
+			numLeaks: 2,
+		},
 		{
 			repo:        leaksRepo,
 			description: "Audit until specific commit",
@@ -587,6 +623,15 @@ func TestAuditRepo(t *testing.T) {
 			numLeaks:    298,
 			configPath:  path.Join(configsDir, "entropy"),
 		},
+		{
+			repo: leaksRepo,
+			testOpts: Options{
+				NoiseReduction: true,
+			},
+			description: "toml entropy range",
+			numLeaks:    58,
+			configPath:  path.Join(configsDir, "entropy"),
+		},
 		{
 			repo:           leaksRepo,
 			description:    "toml bad entropy range",

+ 78 - 35
main.go

@@ -77,13 +77,14 @@ type Options struct {
 	OwnerPath string `long:"owner-path" description:"Path to owner directory (repos discovered)"`
 
 	// Process options
-	Threads      int     `long:"threads" description:"Maximum number of threads gitleaks spawns"`
-	Disk         bool    `long:"disk" description:"Clones repo(s) to disk"`
-	SingleSearch string  `long:"single-search" description:"single regular expression to search for"`
-	ConfigPath   string  `long:"config" description:"path to gitleaks config"`
-	SSHKey       string  `long:"ssh-key" description:"path to ssh key"`
-	ExcludeForks bool    `long:"exclude-forks" description:"exclude forks for organization/user audits"`
-	Entropy      float64 `long:"entropy" short:"e" description:"Include entropy checks during audit. Entropy scale: 0.0(no entropy) - 8.0(max entropy)"`
+	Threads        int     `long:"threads" description:"Maximum number of threads gitleaks spawns"`
+	Disk           bool    `long:"disk" description:"Clones repo(s) to disk"`
+	SingleSearch   string  `long:"single-search" description:"single regular expression to search for"`
+	ConfigPath     string  `long:"config" description:"path to gitleaks config"`
+	SSHKey         string  `long:"ssh-key" description:"path to ssh key"`
+	ExcludeForks   bool    `long:"exclude-forks" description:"exclude forks for organization/user audits"`
+	Entropy        float64 `long:"entropy" short:"e" description:"Include entropy checks during audit. Entropy scale: 0.0(no entropy) - 8.0(max entropy)"`
+	NoiseReduction bool    `long:"noise-reduction" description:"Reduce the number of finds when entropy checks are enabled"`
 	// TODO: IncludeMessages  string `long:"messages" description:"include commit messages in audit"`
 
 	// Output options
@@ -101,6 +102,9 @@ type Config struct {
 		Description string
 		Regex       string
 	}
+	Entropy struct {
+		LineRegexes []string
+	}
 	Whitelist struct {
 		Files   []string
 		Regexes []string
@@ -130,7 +134,7 @@ type entropyRange struct {
 }
 
 const defaultGithubURL = "https://api.github.com/"
-const version = "1.20.0"
+const version = "1.22.0"
 const errExit = 2
 const leakExit = 1
 const defaultConfig = `
@@ -169,9 +173,24 @@ regex = '''(?i)github(.{0,4})?['\"][0-9a-zA-Z]{35,40}['\"]'''
 description = "Slack"
 regex = '''xox[baprs]-([0-9a-zA-Z]{10,48})?'''
 
+[entropy]
+lineregexes = [
+	"api",
+	"key",
+	"signature",
+	"secret",
+	"password",
+	"pass",
+	"pwd",
+	"token",
+	"curl",
+	"wget",
+	"https?",
+]
+
 [whitelist]
 files = [
-  "(.*?)(jpg|gif|doc|pdf|bin)$" 
+  "(.*?)(jpg|gif|doc|pdf|bin)$"
 ]
 #commits = [
 #  "BADHA5H1",
@@ -196,6 +215,7 @@ var (
 	whiteListCommits  map[string]bool
 	whiteListRepos    []*regexp.Regexp
 	entropyRanges     []entropyRange
+	entropyRegexes    []*regexp.Regexp
 	fileDiffRegex     *regexp.Regexp
 	sshAuth           *ssh.PublicKeys
 	dir               string
@@ -325,6 +345,10 @@ func run() ([]Leak, error) {
 // writeReport writes a report to a file specified in the --report= option.
 // Default format for report is JSON. You can use the --csv option to write the report as a csv
 func writeReport(leaks []Leak) error {
+	if len(leaks) == 0 {
+		return nil
+	}
+
 	var err error
 	log.Infof("writing report to %s", opts.Report)
 	if strings.HasSuffix(opts.Report, ".csv") {
@@ -488,11 +512,6 @@ func auditGitReference(repo *RepoDescriptor, ref *plumbing.Reference) []Leak {
 				if bin || err != nil {
 					return nil
 				}
-				for _, re := range whiteListFiles {
-					if re.FindString(f.Name) != "" {
-						return nil
-					}
-				}
 				content, err := f.Contents()
 				if err != nil {
 					return nil
@@ -560,12 +579,6 @@ func auditGitReference(repo *RepoDescriptor, ref *plumbing.Reference) []Leak {
 					} else if to != nil {
 						filePath = to.Path()
 					}
-					for _, re := range whiteListFiles {
-						if re.FindString(filePath) != "" {
-							skipFile = true
-							break
-						}
-					}
 					if skipFile {
 						continue
 					}
@@ -604,12 +617,19 @@ func auditGitReference(repo *RepoDescriptor, ref *plumbing.Reference) []Leak {
 // will skip lines that include a whitelisted regex. A list of leaks is returned.
 // If verbose mode (-v/--verbose) is set, then checkDiff will log leaks as they are discovered.
 func inspect(diff gitDiff) []Leak {
-	lines := strings.Split(diff.content, "\n")
 	var (
 		leaks    []Leak
 		skipLine bool
 	)
 
+	for _, re := range whiteListFiles {
+		if re.FindString(diff.filePath) != "" {
+			return leaks
+		}
+	}
+
+	lines := strings.Split(diff.content, "\n")
+
 	for _, line := range lines {
 		skipLine = false
 		for leakType, re := range regexes {
@@ -634,23 +654,12 @@ func inspect(diff gitDiff) []Leak {
 		}
 
 		if opts.Entropy > 0 || len(entropyRanges) != 0 {
-			entropyLeak := false
 			words := strings.Fields(line)
 			for _, word := range words {
 				entropy := getShannonEntropy(word)
-				if entropy >= opts.Entropy && len(entropyRanges) == 0 {
-					entropyLeak = true
-				}
-				if len(entropyRanges) != 0 {
-					for _, eR := range entropyRanges {
-						if entropy > eR.v1 && entropy < eR.v2 {
-							entropyLeak = true
-						}
-					}
-				}
-				if entropyLeak {
+
+				if entropyIsHighEnough(entropy) && highEntropyLineIsALeak(line) {
 					leaks = addLeak(leaks, line, word, fmt.Sprintf("Entropy: %.2f", entropy), diff)
-					entropyLeak = false
 				}
 			}
 		}
@@ -673,7 +682,7 @@ func addLeak(leaks []Leak, line string, offender string, leakType string, diff g
 	}
 	if opts.Redact {
 		leak.Offender = "REDACTED"
-		leak.Line = "REDACTED"
+		leak.Line = strings.Replace(line, offender, "REDACTED", -1)
 	}
 
 	if opts.Verbose {
@@ -704,6 +713,36 @@ func getShannonEntropy(data string) (entropy float64) {
 	return entropy
 }
 
+func entropyIsHighEnough(entropy float64) bool {
+	if entropy >= opts.Entropy && len(entropyRanges) == 0 {
+		return true
+	}
+
+	if len(entropyRanges) != 0 {
+		for _, eR := range entropyRanges {
+			if entropy > eR.v1 && entropy < eR.v2 {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func highEntropyLineIsALeak(line string) bool {
+	if !opts.NoiseReduction {
+		return true
+	}
+
+	for _, re := range entropyRegexes {
+		if re.FindString(line) != "" {
+			return true
+		}
+	}
+
+	return false
+}
+
 // discoverRepos walks all the children of `path`. If a child directory
 // contain a .git file then that repo will be added to the list of repos returned
 func discoverRepos(ownerPath string) ([]*RepoDescriptor, error) {
@@ -846,6 +885,10 @@ func loadToml() error {
 		}
 	}
 
+	for _, regex := range config.Entropy.LineRegexes {
+		entropyRegexes = append(entropyRegexes, regexp.MustCompile(regex))
+	}
+
 	if singleSearchRegex != nil {
 		regexes["singleSearch"] = singleSearchRegex
 	} else {