@@ -3,6 +3,7 @@ package grafana
33import (
44 "context"
55 "fmt"
6+ "io"
67 "net/http"
78 "strings"
89
@@ -23,13 +24,22 @@ var _ detectors.Detector = (*Scanner)(nil)
2324var (
2425 defaultClient = common .SaneHttpClient ()
2526 // 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 })` )
2728)
2829
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+
2939// Keywords are used for efficiently pre-filtering chunks.
3040// Use identifiers in the secret preferably, or the provider name.
3141func (s Scanner ) Keywords () []string {
32- return []string {"glc_ " }
42+ return []string {"glc_eyJ " }
3343}
3444
3545// 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
4757 }
4858
4959 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 )
7363 }
7464
7565 results = append (results , s1 )
@@ -85,3 +75,37 @@ func (s Scanner) Type() detectorspb.DetectorType {
8575func (s Scanner ) Description () string {
8676 return "Grafana is an open-source platform for monitoring and observability. Grafana API keys can be used to access and manage Grafana resources."
8777}
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