4
4
"context"
5
5
"encoding/json"
6
6
"fmt"
7
+ "io"
7
8
"net/http"
8
9
"strings"
9
10
@@ -44,6 +45,14 @@ type callerIdentity struct {
44
45
ServiceToken bool `json:"serviceToken"`
45
46
}
46
47
48
+ func (s Scanner ) getClient () * http.Client {
49
+ if s .client == nil {
50
+ return defaultClient
51
+ }
52
+
53
+ return s .client
54
+ }
55
+
47
56
// We are not including "mob-" because client keys are not sensitive.
48
57
// They are expected to be public.
49
58
func (s Scanner ) Keywords () []string {
@@ -66,54 +75,16 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
66
75
}
67
76
68
77
if verify {
69
- req , err := http .NewRequestWithContext (ctx , "GET" , "https://app.launchdarkly.com/api/v2/caller-identity" , nil )
70
- if err != nil {
71
- continue
72
- }
73
- client := s .client
74
- if client == nil {
75
- client = defaultClient
76
- }
77
- req .Header .Add ("Authorization" , resMatch )
78
- res , err := client .Do (req )
79
- if err == nil {
80
- defer res .Body .Close ()
81
- if res .StatusCode >= 200 && res .StatusCode < 300 {
82
- s1 .Verified = true
83
- var callerIdentity callerIdentity
84
- if err := json .NewDecoder (res .Body ).Decode (& callerIdentity ); err == nil { // no error in parsing
85
- s1 .ExtraData ["type" ] = callerIdentity .TokenKind
86
- s1 .ExtraData ["account_id" ] = callerIdentity .AccountId
87
- s1 .ExtraData ["environment_id" ] = callerIdentity .EnvironmentId
88
- s1 .ExtraData ["project_id" ] = callerIdentity .ProjectId
89
- s1 .ExtraData ["environment_name" ] = callerIdentity .EnvironmentName
90
- s1 .ExtraData ["project_name" ] = callerIdentity .ProjectName
91
- s1 .ExtraData ["auth_kind" ] = callerIdentity .AuthKind
92
- s1 .ExtraData ["token_kind" ] = callerIdentity .TokenKind
93
- s1 .ExtraData ["client_id" ] = callerIdentity .ClientID
94
- s1 .ExtraData ["token_name" ] = callerIdentity .TokenName
95
- s1 .ExtraData ["member_id" ] = callerIdentity .MemberId
96
- if callerIdentity .TokenKind == "auth" {
97
- if callerIdentity .ServiceToken {
98
- s1 .ExtraData ["token_type" ] = "service"
99
- } else {
100
- s1 .ExtraData ["token_type" ] = "personal"
101
- }
102
- }
103
- }
104
-
105
- s1 .AnalysisInfo = map [string ]string {
106
- "key" : resMatch ,
107
- }
108
-
109
- } else if res .StatusCode == 401 {
110
- // 401 is expected for an invalid token, so there is nothing to do here.
111
- } else {
112
- err = fmt .Errorf ("unexpected HTTP response status %d" , res .StatusCode )
113
- s1 .SetVerificationError (err , resMatch )
78
+ extraData , isVerified , verificationErr := verifyLaunchDarklyKey (ctx , s .getClient (), resMatch )
79
+ s1 .Verified = isVerified
80
+ s1 .SetVerificationError (verificationErr )
81
+ s1 .ExtraData = extraData
82
+
83
+ // only api keys can be analyzed
84
+ if strings .HasPrefix (resMatch , "api-" ) {
85
+ s1 .AnalysisInfo = map [string ]string {
86
+ "key" : resMatch ,
114
87
}
115
- } else {
116
- s1 .SetVerificationError (err , resMatch )
117
88
}
118
89
}
119
90
@@ -130,3 +101,57 @@ func (s Scanner) Type() detectorspb.DetectorType {
130
101
func (s Scanner ) Description () string {
131
102
return "LaunchDarkly is a feature management platform that allows teams to control the visibility of features to users. LaunchDarkly API keys can be used to access and modify feature flags and other resources within a LaunchDarkly account."
132
103
}
104
+
105
+ func verifyLaunchDarklyKey (ctx context.Context , client * http.Client , key string ) (map [string ]string , bool , error ) {
106
+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , "https://app.launchdarkly.com/api/v2/caller-identity" , http .NoBody )
107
+ if err != nil {
108
+ return nil , false , err
109
+ }
110
+
111
+ req .Header .Add ("Authorization" , key )
112
+
113
+ resp , err := client .Do (req )
114
+ if err != nil {
115
+ return nil , false , err
116
+ }
117
+
118
+ defer func () {
119
+ _ , _ = io .Copy (io .Discard , resp .Body )
120
+ _ = resp .Body .Close ()
121
+ }()
122
+
123
+ switch resp .StatusCode {
124
+ case http .StatusOK :
125
+ var callerIdentity callerIdentity
126
+ var extraData = make (map [string ]string )
127
+
128
+ if err := json .NewDecoder (resp .Body ).Decode (& callerIdentity ); err != nil {
129
+ return nil , false , err
130
+ }
131
+
132
+ extraData ["type" ] = callerIdentity .AccountId
133
+ extraData ["account" ] = callerIdentity .AccountId
134
+ extraData ["environment_id" ] = callerIdentity .EnvironmentId
135
+ extraData ["project_id" ] = callerIdentity .ProjectId
136
+ extraData ["environment_name" ] = callerIdentity .EnvironmentName
137
+ extraData ["project_name" ] = callerIdentity .ProjectName
138
+ extraData ["auth_kind" ] = callerIdentity .AuthKind
139
+ extraData ["token_kind" ] = callerIdentity .TokenKind
140
+ extraData ["client_id" ] = callerIdentity .ClientID
141
+ extraData ["token_name" ] = callerIdentity .TokenName
142
+ extraData ["member_id" ] = callerIdentity .MemberId
143
+ if callerIdentity .TokenKind == "auth" {
144
+ if callerIdentity .ServiceToken {
145
+ extraData ["token_type" ] = "service"
146
+ } else {
147
+ extraData ["token_type" ] = "personal"
148
+ }
149
+ }
150
+
151
+ return extraData , true , nil
152
+ case http .StatusUnauthorized :
153
+ return nil , false , nil
154
+ default :
155
+ return nil , false , fmt .Errorf ("unexpected status code: %d" , resp .StatusCode )
156
+ }
157
+ }
0 commit comments