2022-01-13 20:02:24 +00:00
package git
import (
"bytes"
2022-05-02 19:47:38 +00:00
"fmt"
2022-01-13 20:02:24 +00:00
"strings"
"testing"
"github.com/kylelemons/godebug/pretty"
log "github.com/sirupsen/logrus"
2022-08-29 18:45:37 +00:00
"google.golang.org/protobuf/types/known/anypb"
2022-05-02 19:47:38 +00:00
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
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/pb/credentialspb"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
2022-01-13 20:02:24 +00:00
)
func TestSource_Scan ( t * testing . T ) {
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2022-05-02 19:47:38 +00:00
secret , err := common . GetTestSecret ( ctx )
if err != nil {
t . Fatal ( fmt . Errorf ( "failed to access secret: %v" , err ) )
}
basicUser := secret . MustGetField ( "GITLAB_USER" )
basicPass := secret . MustGetField ( "GITLAB_PASS" )
2022-01-13 20:02:24 +00:00
type init struct {
2022-05-02 19:47:38 +00:00
name string
verify bool
connection * sourcespb . Git
concurrency int
2022-01-13 20:02:24 +00:00
}
tests := [ ] struct {
name string
init init
wantChunk * sources . Chunk
wantErr bool
} {
{
name : "local repo" ,
init : init {
name : "this repo" ,
connection : & sourcespb . Git {
2022-01-19 00:59:18 +00:00
Directories : [ ] string { "../../../" } ,
2022-01-13 20:02:24 +00:00
Credential : & sourcespb . Git_Unauthenticated {
Unauthenticated : & credentialspb . Unauthenticated { } ,
} ,
} ,
2022-05-02 19:47:38 +00:00
concurrency : 4 ,
2022-01-13 20:02:24 +00:00
} ,
wantChunk : & sources . Chunk {
SourceType : sourcespb . SourceType_SOURCE_TYPE_GIT ,
SourceName : "this repo" ,
Verify : false ,
} ,
wantErr : false ,
} ,
{
name : "remote repo, unauthenticated" ,
init : init {
name : "test source" ,
connection : & sourcespb . Git {
Repositories : [ ] string { "https://github.com/dustin-decker/secretsandstuff.git" } ,
Credential : & sourcespb . Git_Unauthenticated {
Unauthenticated : & credentialspb . Unauthenticated { } ,
} ,
} ,
2022-05-02 19:47:38 +00:00
concurrency : 4 ,
} ,
wantChunk : & sources . Chunk {
SourceType : sourcespb . SourceType_SOURCE_TYPE_GIT ,
SourceName : "test source" ,
Verify : false ,
} ,
wantErr : false ,
} ,
{
name : "remote repo, unauthenticated, concurrency 0" ,
init : init {
name : "test source" ,
connection : & sourcespb . Git {
Repositories : [ ] string { "https://github.com/dustin-decker/secretsandstuff.git" } ,
Credential : & sourcespb . Git_Unauthenticated {
Unauthenticated : & credentialspb . Unauthenticated { } ,
} ,
} ,
concurrency : 0 ,
} ,
wantChunk : & sources . Chunk {
SourceType : sourcespb . SourceType_SOURCE_TYPE_GIT ,
SourceName : "test source" ,
Verify : false ,
} ,
wantErr : false ,
} ,
{
name : "remote repo, basic auth" ,
init : init {
name : "test source" ,
connection : & sourcespb . Git {
Repositories : [ ] string { "https://github.com/dustin-decker/secretsandstuff.git" } ,
Credential : & sourcespb . Git_BasicAuth {
BasicAuth : & credentialspb . BasicAuth {
Username : basicUser ,
Password : basicPass ,
} ,
} ,
} ,
concurrency : 4 ,
2022-01-13 20:02:24 +00:00
} ,
wantChunk : & sources . Chunk {
SourceType : sourcespb . SourceType_SOURCE_TYPE_GIT ,
SourceName : "test source" ,
Verify : false ,
} ,
wantErr : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
s := Source { }
log . SetLevel ( log . DebugLevel )
conn , err := anypb . New ( tt . init . connection )
if err != nil {
t . Fatal ( err )
}
2022-05-13 21:35:06 +00:00
err = s . Init ( ctx , tt . init . name , 0 , 0 , tt . init . verify , conn , tt . init . concurrency )
2022-01-13 20:02:24 +00:00
if ( err != nil ) != tt . wantErr {
t . Errorf ( "Source.Init() error = %v, wantErr %v" , err , tt . wantErr )
return
}
chunksCh := make ( chan * sources . Chunk , 1 )
go func ( ) {
s . Chunks ( ctx , chunksCh )
} ( )
gotChunk := <- chunksCh
gotChunk . Data = nil
// Commits don't come in a deterministic order, so remove metadata comparison
gotChunk . SourceMetadata = nil
if diff := pretty . Compare ( gotChunk , tt . wantChunk ) ; diff != "" {
t . Errorf ( "Source.Chunks() %s diff: (-got +want)\n%s" , tt . name , diff )
t . Errorf ( "Data: %s" , string ( gotChunk . Data ) )
}
} )
}
}
func Test_generateLink ( t * testing . T ) {
type args struct {
repo string
commit string
file string
}
tests := [ ] struct {
name string
args args
want string
} {
{
name : "test link gen" ,
args : args {
repo : "https://github.com/trufflesec-julian/confluence-go-api.git" ,
commit : "047b4a2ba42fc5b6c0bd535c5307434a666db5ec" ,
file : ".gitignore" ,
} ,
want : "https://github.com/trufflesec-julian/confluence-go-api/blob/047b4a2ba42fc5b6c0bd535c5307434a666db5ec/.gitignore" ,
} ,
{
name : "test link gen - no file" ,
args : args {
repo : "https://github.com/trufflesec-julian/confluence-go-api.git" ,
commit : "047b4a2ba42fc5b6c0bd535c5307434a666db5ec" ,
} ,
want : "https://github.com/trufflesec-julian/confluence-go-api/commit/047b4a2ba42fc5b6c0bd535c5307434a666db5ec" ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if got := GenerateLink ( tt . args . repo , tt . args . commit , tt . args . file ) ; got != tt . want {
t . Errorf ( "generateLink() = %v, want %v" , got , tt . want )
}
} )
}
}
// We ran into an issue where upgrading a dependency caused the git patch chunking to break
// So this test exists to make sure that when something changes, we know about it.
func TestSource_Chunks_Integration ( t * testing . T ) {
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
type init struct {
name string
verify bool
connection * sourcespb . Git
}
type byteCompare struct {
B [ ] byte
Found bool
2022-03-22 16:27:15 +00:00
Multi bool
2022-01-13 20:02:24 +00:00
}
tests := [ ] struct {
name string
init init
//verified
expectedChunkData map [ string ] * byteCompare
} {
{
name : "remote repo, unauthenticated" ,
init : init {
name : "test source" ,
connection : & sourcespb . Git {
Repositories : [ ] string { "https://github.com/dustin-decker/secretsandstuff.git" } ,
Credential : & sourcespb . Git_Unauthenticated {
Unauthenticated : & credentialspb . Unauthenticated { } ,
} ,
} ,
} ,
expectedChunkData : map [ string ] * byteCompare {
2022-07-25 15:44:15 +00:00
"70001020fab32b1fcf2f1f0e5c66424eae649826-aws" : { B : [ ] byte ( "[default]\naws_access_key_id = AKIAXYZDQCEN4B6JSJQI\naws_secret_access_key = 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\noutput = json\nregion = us-east-2\n" ) } ,
"a6f8aa55736d4a85be31a0048a4607396898647a-bump" : { B : [ ] byte ( "f\n" ) } ,
"73ab4713057944753f1bdeb80e757380e64c6b5b-bump" : { B : [ ] byte ( " s \n" ) } ,
"2f251b8c1e72135a375b659951097ec7749d4af9-bump" : { B : [ ] byte ( " \n" ) } ,
"e6c8bbabd8796ea3cd85bfc2e55b27e0a491747f-bump" : { B : [ ] byte ( "oops \n" ) } ,
"735b52b0eb40610002bb1088e902bd61824eb305-bump" : { B : [ ] byte ( "oops\n" ) } ,
2022-08-10 23:10:45 +00:00
"ce62d79908803153ef6e145e042d3e80488ef747-bump" : { B : [ ] byte ( "\n" ) } ,
2022-07-25 15:44:15 +00:00
// Normally we might expect to see this commit, and we may in the future.
// But at the moment we're ignoring any commit unless it contains at least one non-space character.
2022-08-23 20:29:20 +00:00
"27fbead3bf883cdb7de9d7825ed401f28f9398f1-slack" : { B : [ ] byte ( "yup, just did that\n\ngithub_lol: \"ffc7e0f9400fb6300167009e42d2f842cd7956e2\"\n\noh, goodness. there's another one!\n" ) } ,
"8afb0ecd4998b1179e428db5ebbcdc8221214432-slack" : { B : [ ] byte ( "oops might drop a slack token here\n\ngithub_secret=\"369963c1434c377428ca8531fbc46c0c43d037a0\"\n\nyup, just did that\n" ) , Multi : true } ,
"8fe6f04ef1839e3fc54b5147e3d0e0b7ab971bd5-aws" : { B : [ ] byte ( "blah blaj\n\nthis is the secret: AKIA2E0A8F3B244C9986\n\nokay thank you bye\n" ) , Multi : true } ,
2022-07-25 15:44:15 +00:00
"84e9c75e388ae3e866e121087ea2dd45a71068f2-aws" : { B : [ ] byte ( "this is the secret: [Default]\nAccess key Id: AKIAILE3JG6KMS3HZGCA\nSecret Access Key: 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\n" ) , Multi : true } ,
2022-01-13 20:02:24 +00:00
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
s := Source { }
log . SetLevel ( log . DebugLevel )
conn , err := anypb . New ( tt . init . connection )
if err != nil {
t . Fatal ( err )
}
2022-05-13 21:35:06 +00:00
err = s . Init ( ctx , tt . init . name , 0 , 0 , tt . init . verify , conn , 4 )
2022-01-13 20:02:24 +00:00
if err != nil {
t . Fatal ( err )
}
chunksCh := make ( chan * sources . Chunk , 1 )
go func ( ) {
defer close ( chunksCh )
err := s . Chunks ( ctx , chunksCh )
if err != nil {
panic ( err )
}
} ( )
for chunk := range chunksCh {
key := ""
switch meta := chunk . SourceMetadata . GetData ( ) . ( type ) {
case * source_metadatapb . MetaData_Git :
2022-08-23 20:29:20 +00:00
key = strings . TrimRight ( meta . Git . Commit + "-" + meta . Git . File , "\n" )
2022-01-13 20:02:24 +00:00
}
if expectedData , exists := tt . expectedChunkData [ key ] ; ! exists {
t . Errorf ( "A chunk exists that was not expected with key %q" , key )
} else {
2022-03-22 16:27:15 +00:00
if bytes . Equal ( chunk . Data , expectedData . B ) {
( * tt . expectedChunkData [ key ] ) . Found = true
} else if ! expectedData . Multi {
2022-01-13 20:02:24 +00:00
t . Errorf ( "Got %q: %q, which was not expected" , key , string ( chunk . Data ) )
}
}
}
for key , expected := range tt . expectedChunkData {
if ! expected . Found {
t . Errorf ( "Expected data with key %q not found" , key )
}
2022-05-02 19:47:38 +00:00
2022-01-13 20:02:24 +00:00
}
} )
}
}
func TestSource_Chunks_Edge_Cases ( t * testing . T ) {
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2022-05-02 19:47:38 +00:00
secret , err := common . GetTestSecret ( ctx )
if err != nil {
t . Fatal ( fmt . Errorf ( "failed to access secret: %v" , err ) )
}
basicUser := secret . MustGetField ( "GITLAB_USER" )
basicPass := secret . MustGetField ( "GITLAB_PASS" )
2022-01-13 20:02:24 +00:00
type init struct {
name string
verify bool
connection * sourcespb . Git
}
tests := [ ] struct {
name string
init init
wantErr string
} {
{
name : "empty repo" ,
init : init {
name : "test source" ,
connection : & sourcespb . Git {
Repositories : [ ] string { "https://github.com/git-fixtures/empty.git" } ,
Credential : & sourcespb . Git_Unauthenticated {
Unauthenticated : & credentialspb . Unauthenticated { } ,
} ,
} ,
} ,
wantErr : "remote" ,
} ,
2022-05-02 19:47:38 +00:00
{
name : "no repo" ,
init : init {
name : "test source" ,
connection : & sourcespb . Git {
Repositories : [ ] string { "" } ,
Credential : & sourcespb . Git_Unauthenticated {
Unauthenticated : & credentialspb . Unauthenticated { } ,
} ,
} ,
} ,
wantErr : "remote" ,
} ,
{
name : "no repo, basic auth" ,
init : init {
name : "test source" ,
connection : & sourcespb . Git {
Repositories : [ ] string { "" } ,
Credential : & sourcespb . Git_BasicAuth {
BasicAuth : & credentialspb . BasicAuth {
Username : basicUser ,
Password : basicPass ,
} ,
} ,
} ,
} ,
wantErr : "remote" ,
} ,
2022-01-13 20:02:24 +00:00
{
name : "symlinks repo" ,
init : init {
name : "test source" ,
connection : & sourcespb . Git {
Repositories : [ ] string { "https://github.com/git-fixtures/symlinks.git" } ,
Credential : & sourcespb . Git_Unauthenticated {
Unauthenticated : & credentialspb . Unauthenticated { } ,
} ,
} ,
} ,
} ,
{
name : "submodule repo" ,
init : init {
name : "test source" ,
connection : & sourcespb . Git {
Repositories : [ ] string { "https://github.com/git-fixtures/submodule.git" } ,
Credential : & sourcespb . Git_Unauthenticated {
Unauthenticated : & credentialspb . Unauthenticated { } ,
} ,
} ,
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
s := Source { }
log . SetLevel ( log . DebugLevel )
conn , err := anypb . New ( tt . init . connection )
if err != nil {
t . Fatal ( err )
}
2022-05-13 21:35:06 +00:00
err = s . Init ( ctx , tt . init . name , 0 , 0 , tt . init . verify , conn , 4 )
2022-01-13 20:02:24 +00:00
if err != nil {
t . Errorf ( "Source.Init() error = %v" , err )
return
}
chunksCh := make ( chan * sources . Chunk , 1 )
go func ( ) {
for chunk := range chunksCh {
chunk . Data = nil
}
} ( )
if err := s . Chunks ( ctx , chunksCh ) ; err != nil && ! strings . Contains ( err . Error ( ) , tt . wantErr ) {
t . Errorf ( "Source.Chunks() error = %v, wantErr %v" , err , tt . wantErr )
}
} )
}
}
2022-05-02 19:47:38 +00:00
func TestPrepareRepo ( t * testing . T ) {
tests := [ ] struct {
uri string
path bool
remote bool
err error
} {
{
uri : "https://github.com/dustin-decker/secretsandstuff.git" ,
path : true ,
remote : true ,
err : nil ,
} ,
2022-05-02 22:04:05 +00:00
{
uri : "http://github.com/dustin-decker/secretsandstuff.git" ,
path : true ,
remote : true ,
err : nil ,
} ,
2022-05-02 19:47:38 +00:00
{
uri : "file:///path/to/file.json" ,
path : true ,
remote : false ,
err : nil ,
} ,
{
uri : "no bueno" ,
path : false ,
remote : false ,
err : fmt . Errorf ( "unsupported Git URI: no bueno" ) ,
} ,
}
for _ , tt := range tests {
repo , b , err := PrepareRepo ( tt . uri )
var repoLen bool
if len ( repo ) > 0 {
repoLen = true
} else {
repoLen = false
}
if repoLen != tt . path || b != tt . remote {
t . Errorf ( "PrepareRepo(%v) got: %v, %v, %v want: %v, %v, %v" , tt . uri , repo , b , err , tt . path , tt . remote , tt . err )
}
}
}
func BenchmarkPrepareRepo ( b * testing . B ) {
uri := "https://github.com/dustin-decker/secretsandstuff.git"
for i := 0 ; i < b . N ; i ++ {
_ , _ , _ = PrepareRepo ( uri )
}
}