@@ -3,10 +3,12 @@ package kontent
3
3
import (
4
4
"context"
5
5
"fmt"
6
- regexp "github.com/wasilibs/go-re2 "
6
+ "io "
7
7
"net/http"
8
8
"strings"
9
9
10
+ regexp "github.com/wasilibs/go-re2"
11
+
10
12
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
11
13
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
12
14
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -19,9 +21,12 @@ var _ detectors.Detector = (*Scanner)(nil)
19
21
20
22
var (
21
23
client = common .SaneHttpClient ()
22
-
23
24
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
24
- keyPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"kontent" }) + `\b([a-z0-9-]{36})\b` )
25
+ apiKeyPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"kontent" }) + common .BuildRegexJWT ("30,34" , "200,400" , "40,43" ))
26
+ envIDPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"kontent" , "env" }) + common .UUIDPattern )
27
+
28
+ // API return this error when the environment does not exist or the api key does not have the persmission to access that environment
29
+ envErr = "The specified API key does not provide the permissions required to access the environment"
25
30
)
26
31
27
32
// Keywords are used for efficiently pre-filtering chunks.
@@ -34,31 +39,40 @@ func (s Scanner) Keywords() []string {
34
39
func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
35
40
dataStr := string (data )
36
41
37
- matches := keyPat . FindAllStringSubmatch ( dataStr , - 1 )
42
+ var uniqueAPIKeys , uniqueEnvIDs = make ( map [ string ] struct {}), make ( map [ string ] struct {} )
38
43
39
- for _ , match := range matches {
40
- resMatch := strings .TrimSpace (match [1 ])
44
+ for _ , apiKey := range apiKeyPat .FindAllStringSubmatch (dataStr , - 1 ) {
45
+ uniqueAPIKeys [apiKey [1 ]] = struct {}{}
46
+ }
41
47
42
- s1 := detectors.Result {
43
- DetectorType : detectorspb .DetectorType_Kontent ,
44
- Raw : []byte (resMatch ),
48
+ for _ , envID := range envIDPat .FindAllStringSubmatch (dataStr , - 1 ) {
49
+ uniqueEnvIDs [envID [1 ]] = struct {}{}
50
+ }
51
+
52
+ for envID := range uniqueEnvIDs {
53
+ if _ , ok := detectors .UuidFalsePositives [detectors .FalsePositive (envID )]; ok {
54
+ continue
55
+ }
56
+
57
+ if detectors .StringShannonEntropy (envID ) < 3 {
58
+ continue
45
59
}
46
60
47
- if verify {
48
- req , err := http .NewRequestWithContext (ctx , "GET" , fmt .Sprintf ("https://deliver.kontent.ai/%s/items" , resMatch ), nil )
49
- if err != nil {
50
- continue
61
+ for apiKey := range uniqueAPIKeys {
62
+ s1 := detectors.Result {
63
+ DetectorType : detectorspb .DetectorType_Kontent ,
64
+ Raw : []byte (envID ),
65
+ RawV2 : []byte (envID + apiKey ),
51
66
}
52
- res , err := client .Do (req )
53
- if err == nil {
54
- defer res .Body .Close ()
55
- if res .StatusCode >= 200 && res .StatusCode < 300 {
56
- s1 .Verified = true
57
- }
67
+
68
+ if verify {
69
+ isVerified , verificationErr := verifyKontentAPIKey (client , envID , apiKey )
70
+ s1 .Verified = isVerified
71
+ s1 .SetVerificationError (verificationErr )
58
72
}
59
- }
60
73
61
- results = append (results , s1 )
74
+ results = append (results , s1 )
75
+ }
62
76
}
63
77
64
78
return results , nil
@@ -71,3 +85,43 @@ func (s Scanner) Type() detectorspb.DetectorType {
71
85
func (s Scanner ) Description () string {
72
86
return "Kontent is a headless CMS (Content Management System) that allows users to manage and deliver content to any device or application. Kontent API keys can be used to access and manage this content."
73
87
}
88
+
89
+ // api docs: https://kontent.ai/learn/docs/apis/openapi/management-api-v2/#operation/retrieve-environment-information
90
+ func verifyKontentAPIKey (client * http.Client , envID , apiKey string ) (bool , error ) {
91
+ req , err := http .NewRequest (http .MethodGet , fmt .Sprintf ("https://manage.kontent.ai/v2/projects/%s" , envID ), nil )
92
+ if err != nil {
93
+ return false , nil
94
+ }
95
+
96
+ req .Header .Add ("Authorization" , "Bearer " + apiKey )
97
+
98
+ resp , err := client .Do (req )
99
+ if err != nil {
100
+ return false , err
101
+ }
102
+
103
+ defer func () {
104
+ _ , _ = io .Copy (io .Discard , resp .Body )
105
+ _ = resp .Body .Close ()
106
+ }()
107
+
108
+ switch resp .StatusCode {
109
+ case http .StatusOK :
110
+ return true , nil
111
+ case http .StatusForbidden :
112
+ bodyBytes , err := io .ReadAll (resp .Body )
113
+ if err != nil {
114
+ return false , err
115
+ }
116
+
117
+ if strings .Contains (string (bodyBytes ), envErr ) {
118
+ return true , nil
119
+ }
120
+
121
+ return false , nil
122
+ case http .StatusUnauthorized :
123
+ return false , nil
124
+ default :
125
+ return false , fmt .Errorf ("unexpected status code: %d" , resp .StatusCode )
126
+ }
127
+ }
0 commit comments