4
4
"context"
5
5
"fmt"
6
6
"net/http"
7
- "strings"
8
7
9
8
regexp "github.com/wasilibs/go-re2"
10
9
@@ -13,15 +12,16 @@ import (
13
12
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
14
13
)
15
14
16
- type Scanner struct {}
15
+ type Scanner struct {
16
+ client * http.Client
17
+ }
17
18
18
19
// Ensure the Scanner satisfies the interface at compile time.
19
20
var _ detectors.Detector = (* Scanner )(nil )
20
21
21
22
var (
22
- client = common .SaneHttpClient ()
23
-
24
- keyPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"ocean" , "do" }) + `\b([A-Za-z0-9_-]{64})\b` )
23
+ defaultClient = common .SaneHttpClient ()
24
+ keyPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"ocean" , "do" }) + `\b([A-Za-z0-9_-]{64})\b` )
25
25
)
26
26
27
27
// Keywords are used for efficiently pre-filtering chunks.
@@ -33,31 +33,29 @@ func (s Scanner) Keywords() []string {
33
33
// FromData will find and optionally verify DigitalOceanToken secrets in a given set of bytes.
34
34
func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
35
35
dataStr := string (data )
36
+ var uniqueTokens = make (map [string ]struct {})
36
37
37
- matches := keyPat .FindAllStringSubmatch (dataStr , - 1 )
38
-
39
- for _ , match := range matches {
40
- resMatch := strings .TrimSpace (match [1 ])
38
+ for _ , matches := range keyPat .FindAllStringSubmatch (dataStr , - 1 ) {
39
+ uniqueTokens [matches [1 ]] = struct {}{}
40
+ }
41
41
42
+ for token := range uniqueTokens {
42
43
s1 := detectors.Result {
43
44
DetectorType : detectorspb .DetectorType_DigitalOceanToken ,
44
- Raw : []byte (resMatch ),
45
+ Raw : []byte (token ),
45
46
}
46
47
47
48
if verify {
48
- req , err := http . NewRequestWithContext ( ctx , "GET" , "https://api.digitalocean.com/v2/account" , nil )
49
- if err ! = nil {
50
- continue
49
+ client := s . client
50
+ if client = = nil {
51
+ client = defaultClient
51
52
}
52
- req .Header .Add ("Authorization" , fmt .Sprintf ("Bearer %s" , resMatch ))
53
- res , err := client .Do (req )
54
- if err == nil {
55
- defer res .Body .Close ()
56
- if res .StatusCode >= 200 && res .StatusCode < 300 {
57
- s1 .Verified = true
58
- s1 .AnalysisInfo = map [string ]string {
59
- "key" : resMatch ,
60
- }
53
+ isVerified , verificationErr := verifyDigitalOceanToken (ctx , client , token )
54
+ s1 .Verified = isVerified
55
+ s1 .SetVerificationError (verificationErr )
56
+ if s1 .Verified {
57
+ s1 .AnalysisInfo = map [string ]string {
58
+ "key" : token ,
61
59
}
62
60
}
63
61
}
@@ -68,6 +66,31 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
68
66
return results , nil
69
67
}
70
68
69
+ func verifyDigitalOceanToken (ctx context.Context , client * http.Client , token string ) (bool , error ) {
70
+ // Ref: https://docs.digitalocean.com/reference/api/digitalocean/#tag/Account
71
+
72
+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , "https://api.digitalocean.com/v2/account" , nil )
73
+ if err != nil {
74
+ return false , fmt .Errorf ("failed to create request: %w" , err )
75
+ }
76
+
77
+ req .Header .Set ("Authorization" , fmt .Sprintf ("Bearer %s" , token ))
78
+ resp , err := client .Do (req )
79
+ if err != nil {
80
+ return false , fmt .Errorf ("failed to make request: %w" , err )
81
+ }
82
+ defer resp .Body .Close ()
83
+
84
+ switch resp .StatusCode {
85
+ case http .StatusOK :
86
+ return true , nil
87
+ case http .StatusUnauthorized :
88
+ return false , nil
89
+ default :
90
+ return false , fmt .Errorf ("unexpected status code: %d" , resp .StatusCode )
91
+ }
92
+ }
93
+
71
94
func (s Scanner ) Type () detectorspb.DetectorType {
72
95
return detectorspb .DetectorType_DigitalOceanToken
73
96
}
0 commit comments