Skip to content

Commit e967efa

Browse files
authored
Support for Customize Endpoint in Gitlab Analyzer (#3832)
* pass on host to gitlab analyzer from detector * remove version from metadata as that causes the test to fail * reverted the cli to old one. code refactoring
1 parent 6d1c59f commit e967efa

File tree

4 files changed

+53
-40
lines changed

4 files changed

+53
-40
lines changed
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"AnalyzerType":5,"Bindings":[{"Resource":{"Name":"gitlab.com/user/22466472","FullyQualifiedName":"gitlab.com/user/22466472","Type":"user","Metadata":{"token_created_at":"2024-08-15T06:33:00.337Z","token_expires_at":"2025-08-15","token_id":10470457,"token_name":"test-project-token","token_revoked":false},"Parent":null},"Permission":{"Value":"read_api","Parent":null}},{"Resource":{"Name":"gitlab.com/user/22466472","FullyQualifiedName":"gitlab.com/user/22466472","Type":"user","Metadata":{"token_created_at":"2024-08-15T06:33:00.337Z","token_expires_at":"2025-08-15","token_id":10470457,"token_name":"test-project-token","token_revoked":false},"Parent":null},"Permission":{"Value":"read_repository","Parent":null}},{"Resource":{"Name":"truffletester / trufflehog","FullyQualifiedName":"gitlab.com/project/60871295","Type":"project","Metadata":null,"Parent":null},"Permission":{"Value":"Developer","Parent":null}}],"UnboundedResources":null,"Metadata":{"enterprise":true,"version":"17.6.0-pre"}}
1+
{"AnalyzerType":5,"Bindings":[{"Resource":{"Name":"gitlab.com/user/22466472","FullyQualifiedName":"gitlab.com/user/22466472","Type":"user","Metadata":{"token_created_at":"2024-08-15T06:33:00.337Z","token_expires_at":"2025-08-15","token_id":10470457,"token_name":"test-project-token","token_revoked":false},"Parent":null},"Permission":{"Value":"read_api","Parent":null}},{"Resource":{"Name":"gitlab.com/user/22466472","FullyQualifiedName":"gitlab.com/user/22466472","Type":"user","Metadata":{"token_created_at":"2024-08-15T06:33:00.337Z","token_expires_at":"2025-08-15","token_id":10470457,"token_name":"test-project-token","token_revoked":false},"Parent":null},"Permission":{"Value":"read_repository","Parent":null}},{"Resource":{"Name":"truffletester / trufflehog","FullyQualifiedName":"gitlab.com/project/60871295","Type":"project","Metadata":null,"Parent":null},"Permission":{"Value":"Developer","Parent":null}}],"UnboundedResources":null,"Metadata":{"enterprise":true}}

pkg/analyzer/analyzers/gitlab/gitlab.go

+20-13
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import (
2121

2222
var _ analyzers.Analyzer = (*Analyzer)(nil)
2323

24+
const (
25+
DefaultGitLabHost = "https://gitlab.com"
26+
)
27+
2428
type Analyzer struct {
2529
Cfg *config.Config
2630
}
@@ -32,8 +36,12 @@ func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analy
3236
if !ok {
3337
return nil, errors.New("key not found in credentialInfo")
3438
}
39+
host, ok := credInfo["host"]
40+
if !ok {
41+
host = DefaultGitLabHost
42+
}
3543

36-
info, err := AnalyzePermissions(a.Cfg, key)
44+
info, err := AnalyzePermissions(a.Cfg, key, host)
3745
if err != nil {
3846
return nil, err
3947
}
@@ -44,7 +52,6 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
4452
result := analyzers.AnalyzerResult{
4553
AnalyzerType: analyzers.AnalyzerTypeGitLab,
4654
Metadata: map[string]any{
47-
"version": info.Metadata.Version,
4855
"enterprise": info.Metadata.Enterprise,
4956
},
5057
Bindings: []analyzers.Binding{},
@@ -133,11 +140,11 @@ type MetadataJSON struct {
133140
Enterprise bool `json:"enterprise"`
134141
}
135142

136-
func getPersonalAccessToken(cfg *config.Config, key string) (AccessTokenJSON, int, error) {
143+
func getPersonalAccessToken(cfg *config.Config, key, host string) (AccessTokenJSON, int, error) {
137144
var tokens AccessTokenJSON
138145

139146
client := analyzers.NewAnalyzeClient(cfg)
140-
req, err := http.NewRequest("GET", "https://gitlab.com/api/v4/personal_access_tokens/self", nil)
147+
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v4/personal_access_tokens/self", host), nil)
141148
if err != nil {
142149
return tokens, -1, err
143150
}
@@ -155,11 +162,11 @@ func getPersonalAccessToken(cfg *config.Config, key string) (AccessTokenJSON, in
155162
return tokens, resp.StatusCode, nil
156163
}
157164

158-
func getAccessibleProjects(cfg *config.Config, key string) ([]ProjectsJSON, error) {
165+
func getAccessibleProjects(cfg *config.Config, key, host string) ([]ProjectsJSON, error) {
159166
var projects []ProjectsJSON
160167

161168
client := analyzers.NewAnalyzeClient(cfg)
162-
req, err := http.NewRequest("GET", "https://gitlab.com/api/v4/projects", nil)
169+
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v4/projects", host), nil)
163170
if err != nil {
164171
return projects, err
165172
}
@@ -197,11 +204,11 @@ func getAccessibleProjects(cfg *config.Config, key string) ([]ProjectsJSON, erro
197204
return projects, nil
198205
}
199206

200-
func getMetadata(cfg *config.Config, key string) (MetadataJSON, error) {
207+
func getMetadata(cfg *config.Config, key, host string) (MetadataJSON, error) {
201208
var metadata MetadataJSON
202209

203210
client := analyzers.NewAnalyzeClient(cfg)
204-
req, err := http.NewRequest("GET", "https://gitlab.com/api/v4/metadata", nil)
211+
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v4/metadata", host), nil)
205212
if err != nil {
206213
return metadata, err
207214
}
@@ -244,22 +251,22 @@ type SecretInfo struct {
244251
Projects []ProjectsJSON
245252
}
246253

247-
func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
254+
func AnalyzePermissions(cfg *config.Config, key string, host string) (*SecretInfo, error) {
248255
// get personal_access_tokens accessible
249-
token, statusCode, err := getPersonalAccessToken(cfg, key)
256+
token, statusCode, err := getPersonalAccessToken(cfg, key, host)
250257
if err != nil {
251258
return nil, err
252259
}
253260
if statusCode != http.StatusOK {
254261
return nil, fmt.Errorf("Invalid GitLab Access Token")
255262
}
256263

257-
meta, err := getMetadata(cfg, key)
264+
meta, err := getMetadata(cfg, key, host)
258265
if err != nil {
259266
return nil, err
260267
}
261268

262-
projects, err := getAccessibleProjects(cfg, key)
269+
projects, err := getAccessibleProjects(cfg, key, host)
263270
if err != nil {
264271
return nil, err
265272
}
@@ -272,7 +279,7 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
272279
}
273280

274281
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
275-
info, err := AnalyzePermissions(cfg, key)
282+
info, err := AnalyzePermissions(cfg, key, DefaultGitLabHost)
276283
if err != nil {
277284
color.Red("[x] Error: %s", err)
278285
return

pkg/detectors/gitlab/v1/gitlab.go

+16-13
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,14 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
7070
}
7171

7272
if verify {
73-
isVerified, extraData, verificationErr := s.verifyGitlab(ctx, resMatch)
73+
isVerified, extraData, analysisInfo, verificationErr := s.verifyGitlab(ctx, resMatch)
7474
s1.Verified = isVerified
7575
for key, value := range extraData {
7676
s1.ExtraData[key] = value
7777
}
7878

7979
s1.SetVerificationError(verificationErr, resMatch)
80-
s1.AnalysisInfo = map[string]string{
81-
"key": resMatch,
82-
}
80+
s1.AnalysisInfo = analysisInfo
8381
}
8482

8583
results = append(results, s1)
@@ -88,7 +86,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
8886
return results, nil
8987
}
9088

91-
func (s Scanner) verifyGitlab(ctx context.Context, resMatch string) (bool, map[string]string, error) {
89+
func (s Scanner) verifyGitlab(ctx context.Context, resMatch string) (bool, map[string]string, map[string]string, error) {
9290
// there are 4 read 'scopes' for a gitlab token: api, read_user, read_repo, and read_registry
9391
// they all grant access to different parts of the API. I couldn't find an endpoint that every
9492
// one of these scopes has access to, so we just check an example endpoint for each scope. If any
@@ -108,43 +106,48 @@ func (s Scanner) verifyGitlab(ctx context.Context, resMatch string) (bool, map[s
108106
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
109107
res, err := client.Do(req)
110108
if err != nil {
111-
return false, nil, err
109+
return false, nil, nil, err
112110
}
113111

114112
defer res.Body.Close()
115113

116114
bodyBytes, err := io.ReadAll(res.Body)
117115
if err != nil {
118-
return false, nil, err
116+
return false, nil, nil, err
117+
}
118+
119+
analysisInfo := map[string]string{
120+
"key": resMatch,
121+
"host": baseURL,
119122
}
120123

121124
// 200 means good key and has `read_user` scope
122125
// 403 means good key but not the right scope
123126
// 401 is bad key
124127
switch res.StatusCode {
125128
case http.StatusOK:
126-
return json.Valid(bodyBytes), nil, nil
129+
return json.Valid(bodyBytes), nil, analysisInfo, nil
127130
case http.StatusForbidden:
128131
// check if the user account is blocked or not
129132
stringBody := string(bodyBytes)
130133
if strings.Contains(stringBody, BlockedUserMessage) {
131134
return true, map[string]string{
132135
"blocked": "True",
133-
}, nil
136+
}, analysisInfo, nil
134137
}
135138

136139
// Good key but not the right scope
137-
return true, nil, nil
140+
return true, nil, analysisInfo, nil
138141
case http.StatusUnauthorized:
139142
// Nothing to do; zero values are the ones we want
140-
return false, nil, nil
143+
return false, nil, nil, nil
141144
default:
142-
return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
145+
return false, nil, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
143146
}
144147

145148
}
146149

147-
return false, nil, nil
150+
return false, nil, nil, nil
148151
}
149152

150153
func (s Scanner) Type() detectorspb.DetectorType {

pkg/detectors/gitlab/v2/gitlab_v2.go

+16-13
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,14 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
5959
}
6060

6161
if verify {
62-
isVerified, extraData, verificationErr := s.verifyGitlab(ctx, resMatch)
62+
isVerified, extraData, analysisInfo, verificationErr := s.verifyGitlab(ctx, resMatch)
6363
s1.Verified = isVerified
6464
for key, value := range extraData {
6565
s1.ExtraData[key] = value
6666
}
6767

6868
s1.SetVerificationError(verificationErr, resMatch)
69-
s1.AnalysisInfo = map[string]string{
70-
"key": resMatch,
71-
}
69+
s1.AnalysisInfo = analysisInfo
7270
}
7371

7472
results = append(results, s1)
@@ -77,7 +75,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
7775
return results, nil
7876
}
7977

80-
func (s Scanner) verifyGitlab(ctx context.Context, resMatch string) (bool, map[string]string, error) {
78+
func (s Scanner) verifyGitlab(ctx context.Context, resMatch string) (bool, map[string]string, map[string]string, error) {
8179
// there are 4 read 'scopes' for a gitlab token: api, read_user, read_repo, and read_registry
8280
// they all grant access to different parts of the API. I couldn't find an endpoint that every
8381
// one of these scopes has access to, so we just check an example endpoint for each scope. If any
@@ -96,41 +94,46 @@ func (s Scanner) verifyGitlab(ctx context.Context, resMatch string) (bool, map[s
9694
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
9795
res, err := client.Do(req)
9896
if err != nil {
99-
return false, nil, err
97+
return false, nil, nil, err
10098
}
10199
defer res.Body.Close()
102100

103101
bodyBytes, err := io.ReadAll(res.Body)
104102
if err != nil {
105-
return false, nil, err
103+
return false, nil, nil, err
104+
}
105+
106+
analysisInfo := map[string]string{
107+
"key": resMatch,
108+
"host": baseURL,
106109
}
107110

108111
// 200 means good key and has `read_user` scope
109112
// 403 means good key but not the right scope
110113
// 401 is bad key
111114
switch res.StatusCode {
112115
case http.StatusOK:
113-
return true, nil, nil
116+
return true, nil, analysisInfo, nil
114117
case http.StatusForbidden:
115118
// check if the user account is blocked or not
116119
stringBody := string(bodyBytes)
117120
if strings.Contains(stringBody, v1.BlockedUserMessage) {
118121
return true, map[string]string{
119122
"blocked": "True",
120-
}, nil
123+
}, analysisInfo, nil
121124
}
122125

123126
// Good key but not the right scope
124-
return true, nil, nil
127+
return true, nil, analysisInfo, nil
125128
case http.StatusUnauthorized:
126129
// Nothing to do; zero values are the ones we want
127-
return false, nil, nil
130+
return false, nil, nil, nil
128131
default:
129-
return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
132+
return false, nil, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
130133
}
131134

132135
}
133-
return false, nil, nil
136+
return false, nil, nil, nil
134137
}
135138

136139
func (s Scanner) Type() detectorspb.DetectorType {

0 commit comments

Comments
 (0)