Skip to content

Commit 347ec60

Browse files
updated anthropic analyzer to analyze admin keys
1 parent 544cf23 commit 347ec60

File tree

2 files changed

+262
-14
lines changed

2 files changed

+262
-14
lines changed

pkg/analyzer/analyzers/anthropic/anthropic.go

+38-10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package anthropic
44
import (
55
"errors"
66
"os"
7+
"strings"
78

89
"github.com/fatih/color"
910
"github.com/jedib0t/go-pretty/v6/table"
@@ -17,7 +18,8 @@ var _ analyzers.Analyzer = (*Analyzer)(nil)
1718

1819
const (
1920
// Key Types
20-
APIKey = "API-Key"
21+
APIKey = "API-Key"
22+
AdminKey = "Admin-Key"
2123
)
2224

2325
type Analyzer struct {
@@ -28,7 +30,6 @@ type Analyzer struct {
2830
type SecretInfo struct {
2931
Valid bool
3032
Type string // key type - TODO: Handle Anthropic Admin Keys
31-
Reference string
3233
AnthropicResources []AnthropicResource
3334
Permissions string // always full_access
3435
Misc map[string]string
@@ -39,6 +40,7 @@ type AnthropicResource struct {
3940
ID string
4041
Name string
4142
Type string
43+
Parent *AnthropicResource
4244
Metadata map[string]string
4345
}
4446

@@ -73,7 +75,7 @@ func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
7375
}
7476

7577
if info.Valid {
76-
color.Green("[!] Valid Anthropic API key\n\n")
78+
color.Green("[!] Valid Anthropic %s\n\n", info.Type)
7779
// no user information
7880
// print full access permission
7981
printPermission(info.Permissions)
@@ -88,16 +90,23 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
8890
// create a HTTP client
8991
client := analyzers.NewAnalyzeClient(cfg)
9092

91-
var secretInfo = &SecretInfo{
92-
Type: APIKey, // TODO: implement Admin-Key type as well
93-
}
93+
keyType := getKeyType(key)
9494

95-
if err := listModels(client, key, secretInfo); err != nil {
96-
return nil, err
95+
var secretInfo = &SecretInfo{
96+
Type: keyType,
9797
}
9898

99-
if err := listMessageBatches(client, key, secretInfo); err != nil {
100-
return nil, err
99+
switch keyType {
100+
case APIKey:
101+
if err := captureAPIKeyResources(client, key, secretInfo); err != nil {
102+
return nil, err
103+
}
104+
case AdminKey:
105+
if err := captureAdminKeyResources(client, key, secretInfo); err != nil {
106+
return nil, err
107+
}
108+
default:
109+
return nil, errors.New("unsupported key type")
101110
}
102111

103112
// anthropic key has full access only
@@ -133,6 +142,14 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
133142
},
134143
}
135144

145+
if Anthropicresource.Parent != nil {
146+
binding.Resource.Parent = &analyzers.Resource{
147+
Name: Anthropicresource.Parent.Name,
148+
FullyQualifiedName: Anthropicresource.Parent.ID,
149+
Type: Anthropicresource.Parent.Type,
150+
}
151+
}
152+
136153
for key, value := range Anthropicresource.Metadata {
137154
binding.Resource.Metadata[key] = value
138155
}
@@ -162,3 +179,14 @@ func printAnthropicResources(resources []AnthropicResource) {
162179
}
163180
t.Render()
164181
}
182+
183+
// getKeyType return the type of key
184+
func getKeyType(key string) string {
185+
if strings.Contains(key, "sk-ant-admin01") {
186+
return AdminKey
187+
} else if strings.Contains(key, "sk-ant-api03") {
188+
return APIKey
189+
}
190+
191+
return ""
192+
}

pkg/analyzer/analyzers/anthropic/requests.go

+224-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ import (
77
"net/http"
88
)
99

10+
var endpoints = map[string]string{
11+
// api key endpoints
12+
"models": "https://api.anthropic.com/v1/models",
13+
"messageBatches": "https://api.anthropic.com/v1/messages/batches",
14+
15+
// admin key endpoints
16+
"orgUsers": "https://api.anthropic.com/v1/organizations/users",
17+
"workspaces": "https://api.anthropic.com/v1/organizations/workspaces",
18+
"workspaceMembers": "https://api.anthropic.com/v1/organizations/workspaces/%s/members", // require workspace id
19+
"apiKeys": "https://api.anthropic.com/v1/organizations/api_keys",
20+
}
21+
1022
type ModelsResponse struct {
1123
Data []struct {
1224
ID string `json:"id"`
@@ -25,6 +37,47 @@ type MessageResponse struct {
2537
} `json:"data"`
2638
}
2739

40+
type OrgUsersResponse struct {
41+
Data []struct {
42+
ID string `json:"id"`
43+
Type string `json:"type"`
44+
Email string `json:"email"`
45+
Name string `json:"name"`
46+
Role string `json:"role"`
47+
} `json:"data"`
48+
}
49+
50+
type WorkspacesResponse struct {
51+
Data []struct {
52+
ID string `json:"id"`
53+
Type string `json:"type"`
54+
Name string `json:"name"`
55+
} `json:"data"`
56+
}
57+
58+
type WorkspaceMembersResponse struct {
59+
Data []struct {
60+
WorkspaceID string `json:"workspace_id"`
61+
UserID string `json:"user_id"`
62+
Type string `json:"type"`
63+
WorkspaceRole string `json:"workspace_role"`
64+
} `json:"data"`
65+
}
66+
67+
type APIKeysResponse struct {
68+
Data []struct {
69+
ID string `json:"id"`
70+
Type string `json:"type"`
71+
Name string `json:"name"`
72+
WorkspaceID string `json:"workspace_id"`
73+
CreatedBy struct {
74+
ID string `json:"id"`
75+
} `json:"created_by"`
76+
PartialKeyHint string `json:"partial_key_hint"`
77+
Status string `json:"status"`
78+
} `json:"data"`
79+
}
80+
2881
// makeAnthropicRequest send the API request to passed url with passed key as API Key and return response body and status code
2982
func makeAnthropicRequest(client *http.Client, url, key string) ([]byte, int, error) {
3083
// create request
@@ -56,8 +109,38 @@ func makeAnthropicRequest(client *http.Client, url, key string) ([]byte, int, er
56109
return responseBodyByte, resp.StatusCode, nil
57110
}
58111

59-
func listModels(client *http.Client, key string, secretInfo *SecretInfo) error {
60-
response, statusCode, err := makeAnthropicRequest(client, "https://api.anthropic.com/v1/models", key)
112+
// captureAPIKeyResources capture resources associated with api key
113+
func captureAPIKeyResources(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
114+
if err := captureModels(client, apiKey, secretInfo); err != nil {
115+
return err
116+
}
117+
118+
if err := captureMessageBatches(client, apiKey, secretInfo); err != nil {
119+
return err
120+
}
121+
122+
return nil
123+
}
124+
125+
// captureAdminKeyResources capture resources associated with admin key
126+
func captureAdminKeyResources(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
127+
if err := captureOrgUsers(client, adminKey, secretInfo); err != nil {
128+
return err
129+
}
130+
131+
if err := captureWorkspaces(client, adminKey, secretInfo); err != nil {
132+
return err
133+
}
134+
135+
if err := captureAPIKeys(client, adminKey, secretInfo); err != nil {
136+
return err
137+
}
138+
139+
return nil
140+
}
141+
142+
func captureModels(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
143+
response, statusCode, err := makeAnthropicRequest(client, endpoints["models"], apiKey)
61144
if err != nil {
62145
return err
63146
}
@@ -86,8 +169,8 @@ func listModels(client *http.Client, key string, secretInfo *SecretInfo) error {
86169
}
87170
}
88171

89-
func listMessageBatches(client *http.Client, key string, secretInfo *SecretInfo) error {
90-
response, statusCode, err := makeAnthropicRequest(client, "https://api.anthropic.com/v1/messages/batches", key)
172+
func captureMessageBatches(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
173+
response, statusCode, err := makeAnthropicRequest(client, endpoints["messageBatches"], apiKey)
91174
if err != nil {
92175
return err
93176
}
@@ -119,3 +202,140 @@ func listMessageBatches(client *http.Client, key string, secretInfo *SecretInfo)
119202
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
120203
}
121204
}
205+
206+
func captureOrgUsers(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
207+
response, statusCode, err := makeAnthropicRequest(client, endpoints["orgUsers"], adminKey)
208+
if err != nil {
209+
return err
210+
}
211+
212+
switch statusCode {
213+
case http.StatusOK:
214+
var users OrgUsersResponse
215+
216+
if err := json.Unmarshal(response, &users); err != nil {
217+
return err
218+
}
219+
220+
for _, user := range users.Data {
221+
secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
222+
ID: user.ID,
223+
Name: user.Name,
224+
Type: user.Type,
225+
Metadata: map[string]string{
226+
"Role": user.Role,
227+
"Email": user.Email,
228+
},
229+
})
230+
}
231+
232+
return nil
233+
case http.StatusNotFound, http.StatusUnauthorized:
234+
return fmt.Errorf("invalid/revoked api-key")
235+
default:
236+
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
237+
}
238+
}
239+
240+
func captureWorkspaces(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
241+
response, statusCode, err := makeAnthropicRequest(client, endpoints["workspaces"], adminKey)
242+
if err != nil {
243+
return err
244+
}
245+
246+
switch statusCode {
247+
case http.StatusOK:
248+
var workspaces WorkspacesResponse
249+
250+
if err := json.Unmarshal(response, &workspaces); err != nil {
251+
return err
252+
}
253+
254+
for _, workspace := range workspaces.Data {
255+
resource := AnthropicResource{
256+
ID: workspace.ID,
257+
Name: workspace.Name,
258+
Type: workspace.Type,
259+
}
260+
261+
secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, resource)
262+
// capture each workspace members
263+
if err := captureWorkspaceMembers(client, adminKey, resource, secretInfo); err != nil {
264+
return err
265+
}
266+
}
267+
268+
return nil
269+
case http.StatusNotFound, http.StatusUnauthorized:
270+
return fmt.Errorf("invalid/revoked api-key")
271+
default:
272+
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
273+
}
274+
}
275+
276+
func captureWorkspaceMembers(client *http.Client, key string, parentWorkspace AnthropicResource, secretInfo *SecretInfo) error {
277+
response, statusCode, err := makeAnthropicRequest(client, fmt.Sprintf(endpoints["workspaceMembers"], parentWorkspace.ID), key)
278+
if err != nil {
279+
return err
280+
}
281+
282+
switch statusCode {
283+
case http.StatusOK:
284+
var members WorkspaceMembersResponse
285+
286+
if err := json.Unmarshal(response, &members); err != nil {
287+
return err
288+
}
289+
290+
for _, member := range members.Data {
291+
secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
292+
ID: fmt.Sprintf("anthropic/workspace/%s/member/%s", member.WorkspaceID, member.UserID),
293+
Name: member.UserID,
294+
Type: member.Type,
295+
Parent: &parentWorkspace,
296+
})
297+
}
298+
299+
return nil
300+
case http.StatusNotFound, http.StatusUnauthorized:
301+
return fmt.Errorf("invalid/revoked api-key")
302+
default:
303+
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
304+
}
305+
}
306+
307+
func captureAPIKeys(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
308+
response, statusCode, err := makeAnthropicRequest(client, endpoints["apiKeys"], adminKey)
309+
if err != nil {
310+
return err
311+
}
312+
313+
switch statusCode {
314+
case http.StatusOK:
315+
var apiKeys APIKeysResponse
316+
317+
if err := json.Unmarshal(response, &apiKeys); err != nil {
318+
return err
319+
}
320+
321+
for _, apiKey := range apiKeys.Data {
322+
secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
323+
ID: apiKey.ID,
324+
Name: apiKey.Name,
325+
Type: apiKey.Type,
326+
Metadata: map[string]string{
327+
"WorkspaceID": apiKey.WorkspaceID,
328+
"CreatedBy": apiKey.CreatedBy.ID,
329+
"PartialKeyHint": apiKey.PartialKeyHint,
330+
"Status": apiKey.Status,
331+
},
332+
})
333+
}
334+
335+
return nil
336+
case http.StatusNotFound, http.StatusUnauthorized:
337+
return fmt.Errorf("invalid/revoked api-key")
338+
default:
339+
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
340+
}
341+
}

0 commit comments

Comments
 (0)