Skip to content
94 changes: 94 additions & 0 deletions pkg/detectors/gitlab/v1/gitlab_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,100 @@ func TestGitlab_FromChunk_WithV2Secrets(t *testing.T) {
}
}

// This test ensures gitlab v1 detector does not work on gitlab v3 secrets
func TestGitlab_FromChunk_WithV3Secrets(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("GITLABV3")
secretInactive := testSecrets.MustGetField("GITLABV3_INACTIVE")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "verified v3 secret, not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a gitlab super secret %s within", secret)),
verify: true,
},
want: nil,
wantErr: false,
},
{
name: "verified v3 secret, not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("gitlab %s", secret)),
verify: true,
},
want: nil,
wantErr: false,
},
{
name: "unverified v3 secret, not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a gitlab secret %s within", secretInactive)),
verify: true,
},
want: nil,
wantErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.s.SetCloudEndpoint("https://gitlab.com")
tt.s.UseCloudEndpoint(true)
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Gitlab.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatal("no raw secret present")
}
if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
t.Fatalf(" wantVerificationError = %v, verification error = %v,", tt.wantVerificationErr, got[i].VerificationError())
}
got[i].AnalysisInfo = nil
}
opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
if diff := cmp.Diff(got, tt.want, opts); diff != "" {
t.Errorf("Gitlab.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}

func BenchmarkV2FromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
Expand Down
94 changes: 94 additions & 0 deletions pkg/detectors/gitlab/v2/gitlab_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,100 @@ func TestGitlabV2_FromChunk_WithV1Secrets(t *testing.T) {
}
}

// This test ensures gitlab v2 detector does not work on gitlab v3 secrets
func TestGitlabV2_FromChunk_WithV3Secrets(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("GITLABV3")
secretInactive := testSecrets.MustGetField("GITLABV3_INACTIVE")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "verified v3 secret, not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a gitlab super secret %s within", secret)),
verify: true,
},
want: nil,
wantErr: false,
},
{
name: "verified v3 secret, not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("gitlab %s", secret)),
verify: true,
},
want: nil,
wantErr: false,
},
{
name: "unverified v3 secret, not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a gitlab secret %s within", secretInactive)),
verify: true,
},
want: nil,
wantErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.s.SetCloudEndpoint("https://gitlab.com")
tt.s.UseCloudEndpoint(true)
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Gitlab.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatal("no raw secret present")
}
if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
t.Fatalf(" wantVerificationError = %v, verification error = %v,", tt.wantVerificationErr, got[i].VerificationError())
}
got[i].AnalysisInfo = nil
}
opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
if diff := cmp.Diff(got, tt.want, opts); diff != "" {
t.Errorf("Gitlab.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}

func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
Expand Down
102 changes: 102 additions & 0 deletions pkg/detectors/gitlab/v3/gitlab_v3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package gitlab

import (
"context"
"fmt"
"maps"
"net/http"
"strings"

regexp "github.com/wasilibs/go-re2"

"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v1"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)

type Scanner struct {
client *http.Client
detectors.EndpointSetter
}

// Ensure the Scanner satisfies the interfaces at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var _ detectors.EndpointCustomizer = (*Scanner)(nil)
var _ detectors.Versioner = (*Scanner)(nil)

func (Scanner) Version() int { return 3 }
func (Scanner) CloudEndpoint() string { return "https://gitlab.com" }

var (
defaultClient = common.SaneHttpClient()
// pattern taken from gitlab's PR for the format change: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169322
keyPat = regexp.MustCompile(`\b(glpat-[a-zA-Z0-9\-=_]{27,300}.[0-9a-z]{2}.[a-z0-9]{9})\b`)
)

func (s Scanner) getClient() *http.Client {
if s.client != nil {
return s.client
}

return defaultClient
}

// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string { return []string{"glpat-"} }

func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Gitlab
}

func (s Scanner) Description() string {
return "GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager providing wiki, issue-tracking, and CI/CD pipeline features. GitLab Personal Access Tokens (PATs) can be used to authenticate and access GitLab resources."
}

// FromData will find and optionally verify Gitlab secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)

matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
resMatch := strings.TrimSpace(match[1])

for _, endpoint := range s.Endpoints() {
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Gitlab,
Raw: []byte(resMatch),
RawV2: []byte(resMatch + endpoint),
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/gitlab/",
"version": fmt.Sprintf("%d", s.Version()),
},
}

if verify {

isVerified, extraData, verificationErr := v1.VerifyGitlab(ctx, s.getClient(), endpoint, resMatch)
s1.Verified = isVerified
maps.Copy(s1.ExtraData, extraData)

s1.SetVerificationError(verificationErr)

// for verified keys set the analysis info
if s1.Verified {
s1.AnalysisInfo = map[string]string{
"key": resMatch,
"host": endpoint,
}

// if secret is verified with one endpoint, break the loop to continue to next secret
results = append(results, s1)
break
}
}

results = append(results, s1)
}
}

return results, nil
}
Loading
Loading