@@ -3,6 +3,7 @@ package grafana
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "io"
6
7
"net/http"
7
8
"strings"
8
9
@@ -23,13 +24,22 @@ var _ detectors.Detector = (*Scanner)(nil)
23
24
var (
24
25
defaultClient = common .SaneHttpClient ()
25
26
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
26
- keyPat = regexp .MustCompile (`\b(glc_ [A-Za-z0-9+\/]{50,150}\={0,2 })` )
27
+ keyPat = regexp .MustCompile (`\b(glc_eyJ [A-Za-z0-9+\/=]{60,160 })` )
27
28
)
28
29
30
+ func (s Scanner ) getClient () * http.Client {
31
+ client := s .client
32
+ if client == nil {
33
+ client = defaultClient
34
+ }
35
+
36
+ return client
37
+ }
38
+
29
39
// Keywords are used for efficiently pre-filtering chunks.
30
40
// Use identifiers in the secret preferably, or the provider name.
31
41
func (s Scanner ) Keywords () []string {
32
- return []string {"glc_ " }
42
+ return []string {"glc_eyJ " }
33
43
}
34
44
35
45
// FromData will find and optionally verify Grafana secrets in a given set of bytes.
@@ -47,29 +57,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
47
57
}
48
58
49
59
if verify {
50
- client := s .client
51
- if client == nil {
52
- client = defaultClient
53
- }
54
- req , err := http .NewRequestWithContext (ctx , "GET" , "https://grafana.com/api/v1/tokens?region=us" , nil )
55
- if err != nil {
56
- continue
57
- }
58
- req .Header .Add ("Authorization" , fmt .Sprintf ("Bearer %s" , resMatch ))
59
- res , err := client .Do (req )
60
- if err == nil {
61
- defer res .Body .Close ()
62
- if res .StatusCode >= 200 && res .StatusCode < 300 || res .StatusCode == 403 {
63
- s1 .Verified = true
64
- } else if res .StatusCode == 401 {
65
- // The secret is determinately not verified (nothing to do)
66
- } else {
67
- err = fmt .Errorf ("unexpected HTTP response status %d" , res .StatusCode )
68
- s1 .SetVerificationError (err , resMatch )
69
- }
70
- } else {
71
- s1 .SetVerificationError (err , resMatch )
72
- }
60
+ isVerified , verificationErr := verifyGrafanaKey (ctx , s .getClient (), resMatch )
61
+ s1 .Verified = isVerified
62
+ s1 .SetVerificationError (verificationErr , resMatch )
73
63
}
74
64
75
65
results = append (results , s1 )
@@ -85,3 +75,37 @@ func (s Scanner) Type() detectorspb.DetectorType {
85
75
func (s Scanner ) Description () string {
86
76
return "Grafana is an open-source platform for monitoring and observability. Grafana API keys can be used to access and manage Grafana resources."
87
77
}
78
+
79
+ func verifyGrafanaKey (ctx context.Context , client * http.Client , token string ) (bool , error ) {
80
+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , "https://grafana.com/api/v1/tokens?region=us" , http .NoBody )
81
+ if err != nil {
82
+ return false , err
83
+ }
84
+
85
+ req .Header .Add ("Authorization" , fmt .Sprintf ("Bearer %s" , token ))
86
+
87
+ resp , err := client .Do (req )
88
+ if err != nil {
89
+ return false , err
90
+ }
91
+
92
+ defer func () {
93
+ _ , _ = io .Copy (io .Discard , resp .Body )
94
+ _ = resp .Body .Close ()
95
+ }()
96
+
97
+ switch resp .StatusCode {
98
+ case http .StatusOK :
99
+ return true , nil
100
+ case http .StatusUnauthorized :
101
+ bodyBytes , err := io .ReadAll (resp .Body )
102
+ if err != nil {
103
+ return false , err
104
+ }
105
+
106
+ // token is valid but has restricted permissions
107
+ return strings .Contains (string (bodyBytes ), "Unauthorized" ), nil
108
+ default :
109
+ return false , fmt .Errorf ("unexpected status code: %d" , resp .StatusCode )
110
+ }
111
+ }
0 commit comments