2022-01-13 20:02:24 +00:00
package main
import (
"fmt"
2022-02-07 18:29:06 +00:00
"net/http"
_ "net/http/pprof"
2022-01-13 20:02:24 +00:00
"os"
"runtime"
"strconv"
2022-03-15 00:27:14 +00:00
"strings"
2022-04-03 18:51:56 +00:00
"syscall"
2022-02-07 18:29:06 +00:00
"time"
2022-04-04 04:13:39 +00:00
"github.com/felixge/fgprof"
"github.com/gorilla/mux"
2022-04-03 18:51:56 +00:00
"github.com/jpillora/overseer"
2022-04-04 04:13:39 +00:00
"github.com/sirupsen/logrus"
2022-08-10 17:11:13 +00:00
"gopkg.in/alecthomas/kingpin.v2"
2022-02-10 18:54:33 +00:00
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
2022-12-20 16:14:49 +00:00
"github.com/trufflesecurity/trufflehog/v3/pkg/config"
2022-08-29 18:45:37 +00:00
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
2022-02-10 18:54:33 +00:00
"github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine"
2022-11-30 19:10:05 +00:00
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
2022-02-10 18:54:33 +00:00
"github.com/trufflesecurity/trufflehog/v3/pkg/output"
2022-08-29 18:45:37 +00:00
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
2022-02-10 18:54:33 +00:00
"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
2022-08-29 18:45:37 +00:00
"github.com/trufflesecurity/trufflehog/v3/pkg/updater"
"github.com/trufflesecurity/trufflehog/v3/pkg/version"
2022-01-13 20:02:24 +00:00
)
2022-04-03 18:51:56 +00:00
var (
2022-10-31 16:36:10 +00:00
cli = kingpin . New ( "TruffleHog" , "TruffleHog is a tool for finding credentials." )
cmd string
debug = cli . Flag ( "debug" , "Run in debug mode." ) . Bool ( )
trace = cli . Flag ( "trace" , "Run in trace mode." ) . Bool ( )
jsonOut = cli . Flag ( "json" , "Output in JSON format." ) . Short ( 'j' ) . Bool ( )
jsonLegacy = cli . Flag ( "json-legacy" , "Use the pre-v3.0 JSON format. Only works with git, gitlab, and github sources." ) . Bool ( )
concurrency = cli . Flag ( "concurrency" , "Number of concurrent workers." ) . Default ( strconv . Itoa ( runtime . NumCPU ( ) ) ) . Int ( )
noVerification = cli . Flag ( "no-verification" , "Don't verify the results." ) . Bool ( )
onlyVerified = cli . Flag ( "only-verified" , "Only output verified results." ) . Bool ( )
filterUnverified = cli . Flag ( "filter-unverified" , "Only output first unverified result per chunk per detector if there are more than one results." ) . Bool ( )
2022-12-20 16:14:49 +00:00
configFilename = cli . Flag ( "config" , "Path to configuration file." ) . ExistingFile ( )
2022-04-03 18:51:56 +00:00
// rules = cli.Flag("rules", "Path to file with custom rules.").String()
printAvgDetectorTime = cli . Flag ( "print-avg-detector-time" , "Print the average time spent on each detector." ) . Bool ( )
noUpdate = cli . Flag ( "no-update" , "Don't check for updates." ) . Bool ( )
2022-04-21 17:08:51 +00:00
fail = cli . Flag ( "fail" , "Exit with code 183 if results are found." ) . Bool ( )
2022-04-03 18:51:56 +00:00
gitScan = cli . Command ( "git" , "Find credentials in git repositories." )
2022-08-24 06:26:09 +00:00
gitScanURI = gitScan . Arg ( "uri" , "Git repository URL. https://, file://, or ssh:// schema expected." ) . Required ( ) . String ( )
2022-04-03 18:51:56 +00:00
gitScanIncludePaths = gitScan . Flag ( "include-paths" , "Path to file with newline separated regexes for files to include in scan." ) . Short ( 'i' ) . String ( )
gitScanExcludePaths = gitScan . Flag ( "exclude-paths" , "Path to file with newline separated regexes for files to exclude in scan." ) . Short ( 'x' ) . String ( )
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 ( )
_ = 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 ( )
githubScan = cli . Command ( "github" , "Find credentials in GitHub repositories." )
githubScanEndpoint = githubScan . Flag ( "endpoint" , "GitHub endpoint." ) . Default ( "https://api.github.com" ) . String ( )
githubScanRepos = githubScan . Flag ( "repo" , ` GitHub repository to scan. You can repeat this flag. Example: "https://github.com/dustin-decker/secretsandstuff" ` ) . Strings ( )
githubScanOrgs = githubScan . Flag ( "org" , ` GitHub organization to scan. You can repeat this flag. Example: "trufflesecurity" ` ) . Strings ( )
2022-08-26 16:37:16 +00:00
githubScanToken = githubScan . Flag ( "token" , "GitHub token. Can be provided with environment variable GITHUB_TOKEN." ) . Envar ( "GITHUB_TOKEN" ) . String ( )
2022-04-03 18:51:56 +00:00
githubIncludeForks = githubScan . Flag ( "include-forks" , "Include forks in scan." ) . Bool ( )
githubIncludeMembers = githubScan . Flag ( "include-members" , "Include organization member repositories in scan." ) . Bool ( )
2022-12-16 21:28:16 +00:00
githubIncludeRepos = githubScan . Flag ( "include-repos" , ` Repositories to include in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Github repo full name. Example: "trufflesecurity/trufflehog", "trufflesecurity/t*" ` ) . Strings ( )
githubExcludeRepos = githubScan . Flag ( "exclude-repos" , ` Repositories to exclude in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Github repo full name. Example: "trufflesecurity/driftwood", "trufflesecurity/d*" ` ) . Strings ( )
2022-04-03 18:51:56 +00:00
gitlabScan = cli . Command ( "gitlab" , "Find credentials in GitLab repositories." )
// TODO: Add more GitLab options
2022-11-15 21:02:37 +00:00
gitlabScanEndpoint = gitlabScan . Flag ( "endpoint" , "GitLab endpoint." ) . Default ( "https://gitlab.com" ) . String ( )
gitlabScanRepos = gitlabScan . Flag ( "repo" , "GitLab repo url. You can repeat this flag. Leave empty to scan all repos accessible with provided credential. Example: https://gitlab.com/org/repo.git" ) . Strings ( )
gitlabScanToken = gitlabScan . Flag ( "token" , "GitLab token. Can be provided with environment variable GITLAB_TOKEN." ) . Envar ( "GITLAB_TOKEN" ) . Required ( ) . String ( )
gitlabScanIncludePaths = gitlabScan . Flag ( "include-paths" , "Path to file with newline separated regexes for files to include in scan." ) . Short ( 'i' ) . String ( )
gitlabScanExcludePaths = gitlabScan . Flag ( "exclude-paths" , "Path to file with newline separated regexes for files to exclude in scan." ) . Short ( 'x' ) . String ( )
2022-04-03 18:51:56 +00:00
filesystemScan = cli . Command ( "filesystem" , "Find credentials in a filesystem." )
filesystemDirectories = filesystemScan . Flag ( "directory" , "Path to directory to scan. You can repeat this flag." ) . Required ( ) . Strings ( )
// TODO: Add more filesystem scan options. Currently only supports scanning a list of directories.
// filesystemScanRecursive = filesystemScan.Flag("recursive", "Scan recursively.").Short('r').Bool()
// filesystemScanIncludePaths = filesystemScan.Flag("include-paths", "Path to file with newline separated regexes for files to include in scan.").Short('i').String()
// filesystemScanExcludePaths = filesystemScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
s3Scan = cli . Command ( "s3" , "Find credentials in S3 buckets." )
2022-08-26 16:37:16 +00:00
s3ScanKey = s3Scan . Flag ( "key" , "S3 key used to authenticate. Can be provided with environment variable AWS_ACCESS_KEY_ID." ) . Envar ( "AWS_ACCESS_KEY_ID" ) . String ( )
s3ScanSecret = s3Scan . Flag ( "secret" , "S3 secret used to authenticate. Can be provided with environment variable AWS_SECRET_ACCESS_KEY." ) . Envar ( "AWS_SECRET_ACCESS_KEY" ) . String ( )
2022-04-03 18:51:56 +00:00
s3ScanCloudEnv = s3Scan . Flag ( "cloud-environment" , "Use IAM credentials in cloud environment." ) . Bool ( )
s3ScanBuckets = s3Scan . Flag ( "bucket" , "Name of S3 bucket to scan. You can repeat this flag." ) . Strings ( )
2022-05-04 22:08:11 +00:00
syslogScan = cli . Command ( "syslog" , "Scan syslog" )
syslogAddress = syslogScan . Flag ( "address" , "Address and port to listen on for syslog. Example: 127.0.0.1:514" ) . String ( )
syslogProtocol = syslogScan . Flag ( "protocol" , "Protocol to listen on. udp or tcp" ) . String ( )
syslogTLSCert = syslogScan . Flag ( "cert" , "Path to TLS cert." ) . String ( )
syslogTLSKey = syslogScan . Flag ( "key" , "Path to TLS key." ) . String ( )
syslogFormat = syslogScan . Flag ( "format" , "Log format. Can be rfc3164 or rfc5424" ) . String ( )
2023-01-06 05:44:37 +00:00
circleCiScan = cli . Command ( "circleci" , "Scan CircleCI" )
circleCiScanToken = circleCiScan . Flag ( "token" , "CircleCI token. Can also be provided with environment variable" ) . Envar ( "CIRCLECI_TOKEN" ) . Required ( ) . String ( )
2022-04-03 18:51:56 +00:00
)
func init ( ) {
for i , arg := range os . Args {
if strings . HasPrefix ( arg , "--" ) {
2022-04-04 07:04:24 +00:00
split := strings . SplitN ( arg , "=" , 2 )
split [ 0 ] = strings . ReplaceAll ( split [ 0 ] , "_" , "-" )
os . Args [ i ] = strings . Join ( split , "=" )
2022-04-03 18:51:56 +00:00
}
}
2022-04-11 16:47:03 +00:00
cli . Version ( "trufflehog " + version . BuildVersion )
2022-04-03 18:51:56 +00:00
cmd = kingpin . MustParse ( cli . Parse ( os . Args [ 1 : ] ) )
2022-04-04 04:13:39 +00:00
if * jsonOut {
logrus . SetFormatter ( & logrus . JSONFormatter { } )
}
2022-04-11 23:38:08 +00:00
switch {
case * trace :
2022-12-14 00:46:09 +00:00
log . SetLevel ( 5 )
2022-04-11 23:38:08 +00:00
logrus . SetLevel ( logrus . TraceLevel )
logrus . Debugf ( "running version %s" , version . BuildVersion )
case * debug :
2022-12-14 00:46:09 +00:00
log . SetLevel ( 2 )
2022-04-04 04:13:39 +00:00
logrus . SetLevel ( logrus . DebugLevel )
logrus . Debugf ( "running version %s" , version . BuildVersion )
2022-04-11 23:38:08 +00:00
default :
2022-04-04 04:13:39 +00:00
logrus . SetLevel ( logrus . InfoLevel )
}
2022-04-03 18:51:56 +00:00
}
2022-01-13 20:02:24 +00:00
func main ( ) {
2022-04-03 18:51:56 +00:00
updateCfg := overseer . Config {
Program : run ,
Debug : * debug ,
RestartSignal : syscall . SIGTERM ,
// TODO: Eventually add a PreUpgrade func for signature check w/ x509 PKCS1v15
// PreUpgrade: checkUpdateSignature(binaryPath string),
}
if ! * noUpdate {
updateCfg . Fetcher = updater . Fetcher ( version . BuildVersion )
}
2022-04-08 06:55:01 +00:00
if version . BuildVersion == "dev" {
updateCfg . Fetcher = nil
}
2022-04-03 18:51:56 +00:00
err := overseer . RunErr ( updateCfg )
if err != nil {
logrus . WithError ( err ) . Fatal ( "error occured with trufflehog updater 🐷" )
}
}
func run ( state overseer . State ) {
2022-04-11 16:47:03 +00:00
if * debug {
2023-01-03 16:36:27 +00:00
logrus . Debugf ( "trufflehog %s" , version . BuildVersion )
2022-04-03 18:51:56 +00:00
}
2022-06-01 01:45:28 +00:00
if * githubScanToken != "" {
// NOTE: this kludge is here to do an authenticated shallow commit
// TODO: refactor to better pass credentials
os . Setenv ( "GITHUB_TOKEN" , * githubScanToken )
}
2022-02-23 15:40:18 +00:00
// When setting a base commit, chunks must be scanned in order.
if * gitScanSinceCommit != "" {
* concurrency = 1
}
2022-02-04 03:07:39 +00:00
if * debug {
go func ( ) {
2022-02-07 18:29:06 +00:00
router := mux . NewRouter ( )
router . PathPrefix ( "/debug/pprof" ) . Handler ( http . DefaultServeMux )
router . PathPrefix ( "/debug/fgprof" ) . Handler ( fgprof . Handler ( ) )
2022-02-10 18:54:33 +00:00
logrus . Info ( "starting pprof and fgprof server on :18066 /debug/pprof and /debug/fgprof" )
2022-02-07 18:29:06 +00:00
if err := http . ListenAndServe ( ":18066" , router ) ; err != nil {
2022-02-10 18:54:33 +00:00
logrus . Error ( err )
2022-02-07 18:29:06 +00:00
}
2022-02-04 03:07:39 +00:00
} ( )
}
2022-12-21 01:03:53 +00:00
logger , sync := log . New ( "trufflehog" , log . WithConsoleSink ( os . Stderr ) )
context . SetDefaultLogger ( logger )
2022-11-30 19:10:05 +00:00
defer func ( ) { _ = sync ( ) } ( )
2022-02-04 03:07:39 +00:00
2022-12-20 16:14:49 +00:00
conf := & config . Config { }
if * configFilename != "" {
var err error
conf , err = config . Read ( * configFilename )
if err != nil {
logger . Error ( err , "error parsing the provided configuration file" )
os . Exit ( 1 )
}
}
2022-12-21 01:03:53 +00:00
ctx := context . TODO ( )
2022-01-13 20:02:24 +00:00
e := engine . Start ( ctx ,
engine . WithConcurrency ( * concurrency ) ,
engine . WithDecoders ( decoders . DefaultDecoders ( ) ... ) ,
2022-01-19 06:24:56 +00:00
engine . WithDetectors ( ! * noVerification , engine . DefaultDetectors ( ) ... ) ,
2022-12-20 16:14:49 +00:00
engine . WithDetectors ( ! * noVerification , conf . Detectors ... ) ,
2022-10-31 16:36:10 +00:00
engine . WithFilterUnverified ( * filterUnverified ) ,
2022-01-13 20:02:24 +00:00
)
2022-01-14 20:40:50 +00:00
filter , err := common . FilterFromFiles ( * gitScanIncludePaths , * gitScanExcludePaths )
if err != nil {
2022-02-16 05:49:54 +00:00
logrus . WithError ( err ) . Fatal ( "could not create filter" )
2022-01-14 20:40:50 +00:00
}
2022-01-20 00:48:37 +00:00
var repoPath string
2022-04-30 00:28:04 +00:00
var remote bool
2022-01-13 20:02:24 +00:00
switch cmd {
case gitScan . FullCommand ( ) :
2022-11-03 23:36:52 +00:00
repoPath , remote , err = git . PrepareRepoSinceCommit ( ctx , * gitScanURI , * gitScanSinceCommit )
2022-01-15 00:07:45 +00:00
if err != nil || repoPath == "" {
logrus . WithError ( err ) . Fatal ( "error preparing git repo for scanning" )
}
if remote {
defer os . RemoveAll ( repoPath )
}
2022-08-10 17:11:13 +00:00
2022-08-16 16:15:25 +00:00
g := func ( c * sources . Config ) {
2022-08-10 17:11:13 +00:00
c . RepoPath = repoPath
c . HeadRef = * gitScanBranch
c . BaseRef = * gitScanSinceCommit
c . MaxDepth = * gitScanMaxDepth
c . Filter = filter
}
if err = e . ScanGit ( ctx , sources . NewConfig ( g ) ) ; err != nil {
logrus . WithError ( err ) . Fatal ( "Failed to scan Git." )
2022-01-13 20:02:24 +00:00
}
case githubScan . FullCommand ( ) :
2022-01-20 00:13:59 +00:00
if len ( * githubScanOrgs ) == 0 && len ( * githubScanRepos ) == 0 {
2022-11-30 19:10:05 +00:00
logrus . Fatal ( "You must specify at least one organization or repository." )
2022-01-20 00:13:59 +00:00
}
2022-08-10 17:11:13 +00:00
2022-08-16 16:15:25 +00:00
github := func ( c * sources . Config ) {
2022-08-10 17:11:13 +00:00
c . Endpoint = * githubScanEndpoint
c . Repos = * githubScanRepos
c . Orgs = * githubScanOrgs
c . Token = * githubScanToken
c . IncludeForks = * githubIncludeForks
c . IncludeMembers = * githubIncludeMembers
c . Concurrency = * concurrency
2022-12-16 21:28:16 +00:00
c . ExcludeRepos = * githubExcludeRepos
c . IncludeRepos = * githubIncludeRepos
2022-08-10 17:11:13 +00:00
}
if err = e . ScanGitHub ( ctx , sources . NewConfig ( github ) ) ; err != nil {
logrus . WithError ( err ) . Fatal ( "Failed to scan Github." )
2022-01-20 00:13:59 +00:00
}
2022-01-13 20:02:24 +00:00
case gitlabScan . FullCommand ( ) :
2022-11-15 21:02:37 +00:00
filter , err := common . FilterFromFiles ( * gitlabScanIncludePaths , * gitlabScanExcludePaths )
if err != nil {
logrus . WithError ( err ) . Fatal ( "could not create filter" )
}
2022-08-16 16:15:25 +00:00
gitlab := func ( c * sources . Config ) {
2022-08-10 17:11:13 +00:00
c . Endpoint = * gitlabScanEndpoint
c . Token = * gitlabScanToken
c . Repos = * gitlabScanRepos
2022-11-15 21:02:37 +00:00
c . Filter = filter
2022-08-10 17:11:13 +00:00
}
if err = e . ScanGitLab ( ctx , sources . NewConfig ( gitlab ) ) ; err != nil {
2022-03-15 00:05:15 +00:00
logrus . WithError ( err ) . Fatal ( "Failed to scan GitLab." )
}
2022-01-20 00:13:59 +00:00
case filesystemScan . FullCommand ( ) :
2022-08-16 16:15:25 +00:00
fs := func ( c * sources . Config ) {
2022-08-10 17:11:13 +00:00
c . Directories = * filesystemDirectories
}
if err = e . ScanFileSystem ( ctx , sources . NewConfig ( fs ) ) ; err != nil {
2022-03-15 00:04:19 +00:00
logrus . WithError ( err ) . Fatal ( "Failed to scan filesystem" )
}
2022-01-20 00:13:59 +00:00
case s3Scan . FullCommand ( ) :
2022-08-16 16:15:25 +00:00
s3 := func ( c * sources . Config ) {
2022-08-10 17:11:13 +00:00
c . Key = * s3ScanKey
c . Secret = * s3ScanSecret
c . Buckets = * s3ScanBuckets
}
if err = e . ScanS3 ( ctx , sources . NewConfig ( s3 ) ) ; err != nil {
2022-03-15 00:07:07 +00:00
logrus . WithError ( err ) . Fatal ( "Failed to scan S3." )
}
2022-05-04 22:08:11 +00:00
case syslogScan . FullCommand ( ) :
2022-08-16 16:15:25 +00:00
syslog := func ( c * sources . Config ) {
2022-08-10 17:11:13 +00:00
c . Address = * syslogAddress
c . Protocol = * syslogProtocol
c . CertPath = * syslogTLSCert
c . KeyPath = * syslogTLSKey
c . Format = * syslogFormat
c . Concurrency = * concurrency
}
if err = e . ScanSyslog ( ctx , sources . NewConfig ( syslog ) ) ; err != nil {
2022-05-04 22:08:11 +00:00
logrus . WithError ( err ) . Fatal ( "Failed to scan syslog." )
}
2023-01-06 05:44:37 +00:00
case circleCiScan . FullCommand ( ) :
if err = e . ScanCircleCI ( ctx , * circleCiScanToken ) ; err != nil {
logrus . WithError ( err ) . Fatal ( "Failed to scan CircleCI." )
}
2022-01-13 20:02:24 +00:00
}
2022-05-25 16:35:44 +00:00
// asynchronously wait for scanning to finish and cleanup
2022-08-29 18:45:37 +00:00
go e . Finish ( ctx )
2022-01-13 20:02:24 +00:00
2022-01-20 00:48:37 +00:00
if ! * jsonLegacy && ! * jsonOut {
2022-02-08 17:12:41 +00:00
fmt . Fprintf ( os . Stderr , "🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷\n\n" )
2022-01-20 00:48:37 +00:00
}
2022-01-20 17:43:37 +00:00
2022-05-25 16:35:44 +00:00
// NOTE: this loop will terminate when the results channel is closed in
// e.Finish()
2022-03-01 04:38:13 +00:00
foundResults := false
2022-01-13 20:02:24 +00:00
for r := range e . ResultsChan ( ) {
2022-01-27 04:38:31 +00:00
if * onlyVerified && ! r . Verified {
continue
}
2022-03-01 04:38:13 +00:00
foundResults = true
2022-01-20 17:43:37 +00:00
2022-01-20 00:48:37 +00:00
switch {
case * jsonLegacy :
2022-11-03 23:36:52 +00:00
output . PrintLegacyJSON ( ctx , & r )
2022-01-20 00:48:37 +00:00
case * jsonOut :
2022-05-25 16:35:44 +00:00
output . PrintJSON ( & r )
2022-01-20 00:48:37 +00:00
default :
2022-01-21 02:14:16 +00:00
output . PrintPlainOutput ( & r )
2022-01-13 20:02:24 +00:00
}
}
2022-01-21 02:14:16 +00:00
logrus . Debugf ( "scanned %d chunks" , e . ChunksScanned ( ) )
2022-10-27 19:54:22 +00:00
logrus . Debugf ( "scanned %d bytes" , e . BytesScanned ( ) )
2022-02-07 18:29:06 +00:00
if * printAvgDetectorTime {
printAverageDetectorTime ( e )
}
2022-03-01 04:38:13 +00:00
2022-04-21 17:08:51 +00:00
if foundResults && * fail {
logrus . Debug ( "exiting with code 183 because results were found" )
os . Exit ( 183 )
2022-03-01 04:38:13 +00:00
}
2022-02-07 18:29:06 +00:00
}
func printAverageDetectorTime ( e * engine . Engine ) {
fmt . Fprintln ( os . Stderr , "Average detector time is the measurement of average time spent on each detector when results are returned." )
for detectorName , durations := range e . DetectorAvgTime ( ) {
var total time . Duration
for _ , d := range durations {
total += d
}
avgDuration := total / time . Duration ( len ( durations ) )
fmt . Fprintf ( os . Stderr , "%s: %s\n" , detectorName , avgDuration )
}
2022-01-20 00:48:37 +00:00
}