diff --git a/README.md b/README.md index c7b2c1952..3e89a956c 100644 --- a/README.md +++ b/README.md @@ -263,9 +263,9 @@ jobs: extra_args: --debug --only-verified ``` -# Precommit Hook +# Pre-commit Hook -Trufflehog can be used in a precommit hook to prevent credentials from leaking before they ever leave your computer. +Trufflehog can be used in a pre-commit hook to prevent credentials from leaking before they ever leave your computer. An example `.pre-commit-config.yaml` is provided (see [pre-commit.com](https://pre-commit.com/) for installation). ```yaml diff --git a/go.mod b/go.mod index 3b1c07afe..9adc98b7c 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/felixge/fgprof v0.9.3 github.com/getsentry/sentry-go v0.22.0 github.com/go-errors/errors v1.4.2 - github.com/go-git/go-git/v5 v5.8.0 + github.com/go-git/go-git/v5 v5.8.1 github.com/go-ldap/ldap/v3 v3.4.5 github.com/go-logr/logr v1.2.4 github.com/go-logr/zapr v1.2.4 @@ -34,7 +34,6 @@ require ( github.com/go-sql-driver/mysql v1.7.1 github.com/gobwas/glob v0.2.3 github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/golang/mock v1.4.3 github.com/google/go-cmp v0.5.9 github.com/google/go-containerregistry v0.15.2 github.com/google/go-github/v42 v42.0.0 @@ -77,6 +76,7 @@ require ( cloud.google.com/go/compute v1.20.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.0 // indirect + dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.24 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect @@ -129,8 +129,6 @@ require ( github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.4 // indirect - github.com/imdario/mergo v0.3.15 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jpillora/s3 v1.1.4 // indirect diff --git a/go.sum b/go.sum index c2c276ae8..e9b86dae5 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI= cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= @@ -181,8 +183,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmS github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git/v5 v5.8.0 h1:Rc543s6Tyq+YcyPwZRvU4jzZGM8rB/wWu94TnTIYALQ= -github.com/go-git/go-git/v5 v5.8.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= +github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A= +github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= @@ -303,13 +305,9 @@ github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru/v2 v2.0.4 h1:7GHuZcgid37q8o5i3QI9KMT4nCWQQ3Kx3Ov6bb9MfK0= -github.com/hashicorp/golang-lru/v2 v2.0.4/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg= diff --git a/main.go b/main.go index 163509453..614101f13 100644 --- a/main.go +++ b/main.go @@ -66,6 +66,7 @@ var ( gitScanSinceCommit = gitScan.Flag("since-commit", "Commit to start scan from.").String() gitScanBranch = gitScan.Flag("branch", "Branch to scan.").String() gitScanMaxDepth = gitScan.Flag("max-depth", "Maximum depth of commits to scan.").Int() + gitScanBare = gitScan.Flag("bare", "Scan bare repository (e.g. useful while using in pre-receive hooks)").Bool() _ = gitScan.Flag("allow", "No-op flag for backwards compat.").Bool() _ = gitScan.Flag("entropy", "No-op flag for backwards compat.").Bool() _ = gitScan.Flag("regex", "No-op flag for backwards compat.").Bool() @@ -377,6 +378,7 @@ func run(state overseer.State) { HeadRef: *gitScanBranch, BaseRef: *gitScanSinceCommit, MaxDepth: *gitScanMaxDepth, + Bare: *gitScanBare, Filter: filter, ExcludeGlobs: excludedGlobs, } diff --git a/pkg/engine/git.go b/pkg/engine/git.go index d6786e190..6e0983298 100644 --- a/pkg/engine/git.go +++ b/pkg/engine/git.go @@ -36,6 +36,9 @@ func (e *Engine) ScanGit(ctx context.Context, c sources.GitConfig) error { if c.ExcludeGlobs != nil { opts = append(opts, git.ScanOptionExcludeGlobs(c.ExcludeGlobs)) } + if c.Bare { + opts = append(opts, git.ScanOptionBare(c.Bare)) + } scanOptions := git.NewScanOptions(opts...) connection := &sourcespb.Git{ @@ -46,8 +49,7 @@ func (e *Engine) ScanGit(ctx context.Context, c sources.GitConfig) error { Directories: []string{c.RepoPath}, } var conn anypb.Any - err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{}) - if err != nil { + if err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{}); err != nil { ctx.Logger().Error(err, "failed to marshal git connection") return err } diff --git a/pkg/gitparse/gitparse.go b/pkg/gitparse/gitparse.go index 2f5262cf3..2813178a2 100644 --- a/pkg/gitparse/gitparse.go +++ b/pkg/gitparse/gitparse.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "io" + "os" "os/exec" "path/filepath" "strconv" @@ -160,7 +161,7 @@ func (c1 *Commit) Equal(c2 *Commit) bool { } // RepoPath parses the output of the `git log` command for the `source` path. -func (c *Parser) RepoPath(ctx context.Context, source string, head string, abbreviatedLog bool, excludedGlobs []string) (chan Commit, error) { +func (c *Parser) RepoPath(ctx context.Context, source string, head string, abbreviatedLog bool, excludedGlobs []string, isBare bool) (chan Commit, error) { args := []string{"-C", source, "log", "-p", "--full-history", "--date=format:%a %b %d %H:%M:%S %Y %z"} if abbreviatedLog { args = append(args, "--diff-filter=AM") @@ -177,7 +178,21 @@ func (c *Parser) RepoPath(ctx context.Context, source string, head string, abbre cmd := exec.Command("git", args...) absPath, err := filepath.Abs(source) if err == nil { - cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_DIR=%s", filepath.Join(absPath, ".git"))) + if !isBare { + cmd.Env = append(cmd.Env, "GIT_DIR="+filepath.Join(absPath, ".git")) + } else { + cmd.Env = append(cmd.Env, + "GIT_DIR="+absPath, + ) + // We need those variables to handle incoming commits + // while using trufflehog in pre-receive hooks + if dir := os.Getenv("GIT_OBJECT_DIRECTORY"); dir != "" { + cmd.Env = append(cmd.Env, "GIT_OBJECT_DIRECTORY="+dir) + } + if dir := os.Getenv("GIT_ALTERNATE_OBJECT_DIRECTORIES"); dir != "" { + cmd.Env = append(cmd.Env, "GIT_ALTERNATE_OBJECT_DIRECTORIES="+dir) + } + } } return c.executeCommand(ctx, cmd, false) diff --git a/pkg/sources/git/git.go b/pkg/sources/git/git.go index d0e5f5b27..5148c7bb6 100644 --- a/pkg/sources/git/git.go +++ b/pkg/sources/git/git.go @@ -251,12 +251,12 @@ func (s *Source) scanDirs(ctx context.Context, chunksChan chan *sources.Chunk) e if len(gitDir) == 0 { continue } - if strings.HasSuffix(gitDir, "git") { + if !s.scanOptions.Bare && strings.HasSuffix(gitDir, "git") { // TODO: Figure out why we skip directories ending in "git". continue } // try paths instead of url - repo, err := RepoFromPath(gitDir) + repo, err := RepoFromPath(gitDir, s.scanOptions.Bare) if err != nil { ctx.Logger().Info("error scanning repository", "repo", gitDir, "error", err) continue @@ -278,10 +278,11 @@ func (s *Source) scanDirs(ctx context.Context, chunksChan chan *sources.Chunk) e return nil } -func RepoFromPath(path string) (*git.Repository, error) { - options := &git.PlainOpenOptions{ - DetectDotGit: true, - EnableDotGitCommonDir: true, +func RepoFromPath(path string, isBare bool) (*git.Repository, error) { + options := &git.PlainOpenOptions{} + if !isBare { + options.DetectDotGit = true + options.EnableDotGitCommonDir = true } return git.PlainOpenWithOptions(path, options) } @@ -393,7 +394,7 @@ func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string return err } - commitChan, err := gitparse.NewParser().RepoPath(ctx, path, scanOptions.HeadHash, scanOptions.BaseHash == "", scanOptions.ExcludeGlobs) + commitChan, err := gitparse.NewParser().RepoPath(ctx, path, scanOptions.HeadHash, scanOptions.BaseHash == "", scanOptions.ExcludeGlobs, scanOptions.Bare) if err != nil { return err } @@ -621,8 +622,10 @@ func (s *Git) ScanRepo(ctx context.Context, repo *git.Repository, repoPath strin if err := s.ScanCommits(ctx, repo, repoPath, scanOptions, chunksChan); err != nil { return err } - if err := s.ScanStaged(ctx, repo, repoPath, scanOptions, chunksChan); err != nil { - ctx.Logger().V(1).Info("error scanning unstaged changes", "error", err) + if !scanOptions.Bare { + if err := s.ScanStaged(ctx, repo, repoPath, scanOptions, chunksChan); err != nil { + ctx.Logger().V(1).Info("error scanning unstaged changes", "error", err) + } } // We're logging time, but the repoPath is usually a dynamically generated folder in /tmp diff --git a/pkg/sources/git/scan_options.go b/pkg/sources/git/scan_options.go index 09572a6fb..d55914191 100644 --- a/pkg/sources/git/scan_options.go +++ b/pkg/sources/git/scan_options.go @@ -10,6 +10,7 @@ type ScanOptions struct { BaseHash string // When scanning a git.Log, this is the oldest/first commit. HeadHash string MaxDepth int64 + Bare bool ExcludeGlobs []string LogOptions *git.LogOptions } @@ -52,6 +53,12 @@ func ScanOptionLogOptions(logOptions *git.LogOptions) ScanOption { } } +func ScanOptionBare(bare bool) ScanOption { + return func(scanOptions *ScanOptions) { + scanOptions.Bare = bare + } +} + func NewScanOptions(options ...ScanOption) *ScanOptions { scanOptions := &ScanOptions{ Filter: common.FilterEmpty(), diff --git a/pkg/sources/sources.go b/pkg/sources/sources.go index a09975f41..4ecc46e80 100644 --- a/pkg/sources/sources.go +++ b/pkg/sources/sources.go @@ -141,6 +141,8 @@ type GitConfig struct { BaseRef string // MaxDepth is the maximum depth to scan the source. MaxDepth int + // Bare is an indicator to handle bare repositories properly. + Bare bool // Filter is the filter to use to scan the source. Filter *common.Filter // ExcludeGlobs is a list of globs to exclude from the scan.