updated anthropic analyzer to analyze admin keys (#3973)
This commit is contained in:
@@ -4,6 +4,7 @@ package anthropic
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
@@ -17,7 +18,8 @@ var _ analyzers.Analyzer = (*Analyzer)(nil)
|
||||
|
||||
const (
|
||||
// Key Types
|
||||
APIKey = "API-Key"
|
||||
APIKey = "API-Key"
|
||||
AdminKey = "Admin-Key"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
@@ -28,7 +30,6 @@ type Analyzer struct {
|
||||
type SecretInfo struct {
|
||||
Valid bool
|
||||
Type string // key type - TODO: Handle Anthropic Admin Keys
|
||||
Reference string
|
||||
AnthropicResources []AnthropicResource
|
||||
Permissions string // always full_access
|
||||
Misc map[string]string
|
||||
@@ -39,6 +40,7 @@ type AnthropicResource struct {
|
||||
ID string
|
||||
Name string
|
||||
Type string
|
||||
Parent *AnthropicResource
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
@@ -73,7 +75,7 @@ func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
|
||||
}
|
||||
|
||||
if info.Valid {
|
||||
color.Green("[!] Valid Anthropic API key\n\n")
|
||||
color.Green("[!] Valid Anthropic %s\n\n", info.Type)
|
||||
// no user information
|
||||
// print full access permission
|
||||
printPermission(info.Permissions)
|
||||
@@ -88,16 +90,23 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
|
||||
// create a HTTP client
|
||||
client := analyzers.NewAnalyzeClient(cfg)
|
||||
|
||||
keyType := getKeyType(key)
|
||||
|
||||
var secretInfo = &SecretInfo{
|
||||
Type: APIKey, // TODO: implement Admin-Key type as well
|
||||
Type: keyType,
|
||||
}
|
||||
|
||||
if err := listModels(client, key, secretInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := listMessageBatches(client, key, secretInfo); err != nil {
|
||||
return nil, err
|
||||
switch keyType {
|
||||
case APIKey:
|
||||
if err := captureAPIKeyResources(client, key, secretInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case AdminKey:
|
||||
if err := captureAdminKeyResources(client, key, secretInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unsupported key type")
|
||||
}
|
||||
|
||||
// anthropic key has full access only
|
||||
@@ -133,6 +142,14 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
|
||||
},
|
||||
}
|
||||
|
||||
if Anthropicresource.Parent != nil {
|
||||
binding.Resource.Parent = &analyzers.Resource{
|
||||
Name: Anthropicresource.Parent.Name,
|
||||
FullyQualifiedName: Anthropicresource.Parent.ID,
|
||||
Type: Anthropicresource.Parent.Type,
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range Anthropicresource.Metadata {
|
||||
binding.Resource.Metadata[key] = value
|
||||
}
|
||||
@@ -162,3 +179,14 @@ func printAnthropicResources(resources []AnthropicResource) {
|
||||
}
|
||||
t.Render()
|
||||
}
|
||||
|
||||
// getKeyType return the type of key
|
||||
func getKeyType(key string) string {
|
||||
if strings.Contains(key, "sk-ant-admin01") {
|
||||
return AdminKey
|
||||
} else if strings.Contains(key, "sk-ant-api03") {
|
||||
return APIKey
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -7,6 +7,18 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var endpoints = map[string]string{
|
||||
// api key endpoints
|
||||
"models": "https://api.anthropic.com/v1/models",
|
||||
"messageBatches": "https://api.anthropic.com/v1/messages/batches",
|
||||
|
||||
// admin key endpoints
|
||||
"orgUsers": "https://api.anthropic.com/v1/organizations/users",
|
||||
"workspaces": "https://api.anthropic.com/v1/organizations/workspaces",
|
||||
"workspaceMembers": "https://api.anthropic.com/v1/organizations/workspaces/%s/members", // require workspace id
|
||||
"apiKeys": "https://api.anthropic.com/v1/organizations/api_keys",
|
||||
}
|
||||
|
||||
type ModelsResponse struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
@@ -25,6 +37,47 @@ type MessageResponse struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type OrgUsersResponse struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type WorkspacesResponse struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type WorkspaceMembersResponse struct {
|
||||
Data []struct {
|
||||
WorkspaceID string `json:"workspace_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Type string `json:"type"`
|
||||
WorkspaceRole string `json:"workspace_role"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type APIKeysResponse struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
WorkspaceID string `json:"workspace_id"`
|
||||
CreatedBy struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"created_by"`
|
||||
PartialKeyHint string `json:"partial_key_hint"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// makeAnthropicRequest send the API request to passed url with passed key as API Key and return response body and status code
|
||||
func makeAnthropicRequest(client *http.Client, url, key string) ([]byte, int, error) {
|
||||
// create request
|
||||
@@ -56,8 +109,38 @@ func makeAnthropicRequest(client *http.Client, url, key string) ([]byte, int, er
|
||||
return responseBodyByte, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
func listModels(client *http.Client, key string, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeAnthropicRequest(client, "https://api.anthropic.com/v1/models", key)
|
||||
// captureAPIKeyResources capture resources associated with api key
|
||||
func captureAPIKeyResources(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
|
||||
if err := captureModels(client, apiKey, secretInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := captureMessageBatches(client, apiKey, secretInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// captureAdminKeyResources capture resources associated with admin key
|
||||
func captureAdminKeyResources(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
|
||||
if err := captureOrgUsers(client, adminKey, secretInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := captureWorkspaces(client, adminKey, secretInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := captureAPIKeys(client, adminKey, secretInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func captureModels(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeAnthropicRequest(client, endpoints["models"], apiKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -86,8 +169,8 @@ func listModels(client *http.Client, key string, secretInfo *SecretInfo) error {
|
||||
}
|
||||
}
|
||||
|
||||
func listMessageBatches(client *http.Client, key string, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeAnthropicRequest(client, "https://api.anthropic.com/v1/messages/batches", key)
|
||||
func captureMessageBatches(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeAnthropicRequest(client, endpoints["messageBatches"], apiKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -119,3 +202,140 @@ func listMessageBatches(client *http.Client, key string, secretInfo *SecretInfo)
|
||||
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func captureOrgUsers(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeAnthropicRequest(client, endpoints["orgUsers"], adminKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
var users OrgUsersResponse
|
||||
|
||||
if err := json.Unmarshal(response, &users); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range users.Data {
|
||||
secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
|
||||
ID: user.ID,
|
||||
Name: user.Name,
|
||||
Type: user.Type,
|
||||
Metadata: map[string]string{
|
||||
"Role": user.Role,
|
||||
"Email": user.Email,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
case http.StatusNotFound, http.StatusUnauthorized:
|
||||
return fmt.Errorf("invalid/revoked api-key")
|
||||
default:
|
||||
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func captureWorkspaces(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeAnthropicRequest(client, endpoints["workspaces"], adminKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
var workspaces WorkspacesResponse
|
||||
|
||||
if err := json.Unmarshal(response, &workspaces); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, workspace := range workspaces.Data {
|
||||
resource := AnthropicResource{
|
||||
ID: workspace.ID,
|
||||
Name: workspace.Name,
|
||||
Type: workspace.Type,
|
||||
}
|
||||
|
||||
secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, resource)
|
||||
// capture each workspace members
|
||||
if err := captureWorkspaceMembers(client, adminKey, resource, secretInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
case http.StatusNotFound, http.StatusUnauthorized:
|
||||
return fmt.Errorf("invalid/revoked api-key")
|
||||
default:
|
||||
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func captureWorkspaceMembers(client *http.Client, key string, parentWorkspace AnthropicResource, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeAnthropicRequest(client, fmt.Sprintf(endpoints["workspaceMembers"], parentWorkspace.ID), key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
var members WorkspaceMembersResponse
|
||||
|
||||
if err := json.Unmarshal(response, &members); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, member := range members.Data {
|
||||
secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
|
||||
ID: fmt.Sprintf("anthropic/workspace/%s/member/%s", member.WorkspaceID, member.UserID),
|
||||
Name: member.UserID,
|
||||
Type: member.Type,
|
||||
Parent: &parentWorkspace,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
case http.StatusNotFound, http.StatusUnauthorized:
|
||||
return fmt.Errorf("invalid/revoked api-key")
|
||||
default:
|
||||
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func captureAPIKeys(client *http.Client, adminKey string, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeAnthropicRequest(client, endpoints["apiKeys"], adminKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
var apiKeys APIKeysResponse
|
||||
|
||||
if err := json.Unmarshal(response, &apiKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, apiKey := range apiKeys.Data {
|
||||
secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
|
||||
ID: apiKey.ID,
|
||||
Name: apiKey.Name,
|
||||
Type: apiKey.Type,
|
||||
Metadata: map[string]string{
|
||||
"WorkspaceID": apiKey.WorkspaceID,
|
||||
"CreatedBy": apiKey.CreatedBy.ID,
|
||||
"PartialKeyHint": apiKey.PartialKeyHint,
|
||||
"Status": apiKey.Status,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
case http.StatusNotFound, http.StatusUnauthorized:
|
||||
return fmt.Errorf("invalid/revoked api-key")
|
||||
default:
|
||||
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user