Files
trufflehog/pkg/analyzer/analyzers/slack/slack.go
Bill Rich abecab0d8f
Lint / golangci-lint (push) Waiting to run
Lint / semgrep (push) Waiting to run
Release / Release (push) Waiting to run
Scan for secrets / test (push) Waiting to run
Snifftest / Run Snifftest (push) Waiting to run
Test / test (push) Waiting to run
Test / test-community (push) Waiting to run
Remove proto (#3489)
Only used for tracking analyzer types
2024-10-21 17:16:26 -07:00

243 lines
5.7 KiB
Go

//go:generate generate_permissions permissions.yaml permissions.go slack
package slack
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/table"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
var _ analyzers.Analyzer = (*Analyzer)(nil)
type Analyzer struct {
Cfg *config.Config
}
func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeSlack }
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
key, ok := credInfo["key"]
if !ok {
return nil, errors.New("key not found in credentialInfo")
}
info, err := AnalyzePermissions(a.Cfg, key)
if err != nil {
return nil, err
}
return secretInfoToAnalyzerResult(info), nil
}
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzers.AnalyzerTypeSlack,
Metadata: nil,
}
resourceType := "user"
fullyQualifiedName := info.User.TeamId + "/" + info.User.UserId
if info.User.BotId != "" {
resourceType = "bot"
fullyQualifiedName = info.User.BotId
}
resource := analyzers.Resource{
Name: info.User.User,
FullyQualifiedName: fullyQualifiedName,
Type: resourceType,
Metadata: map[string]any{
"url": info.User.Url,
"team": info.User.Team,
"team_id": info.User.TeamId,
"scopes": strings.Split(info.Scopes, ","),
},
}
// extract all permissions
permissions := extractPermissions(info)
result.Bindings = analyzers.BindAllPermissions(resource, permissions...)
return &result
}
func extractPermissions(info *SecretInfo) []analyzers.Permission {
var permissions []analyzers.Permission
for _, scope := range strings.Split(info.Scopes, ",") {
perms, ok := scope_mapping[scope]
if !ok {
continue
}
for _, perm := range perms {
if _, ok := StringToPermission[perm]; !ok {
// not in out generated permissions,
continue
}
permissions = append(permissions, analyzers.Permission{
Value: perm,
Parent: nil,
})
}
}
return permissions
}
// Add in showAll to printScopes + deal with testing enterprise + add scope details
type SlackUserData struct {
Ok bool `json:"ok"`
Url string `json:"url"`
Team string `json:"team"`
User string `json:"user"`
TeamId string `json:"team_id"`
UserId string `json:"user_id"`
BotId string `json:"bot_id"`
IsEnterprise bool `json:"is_enterprise"`
}
type SecretInfo struct {
Scopes string
User SlackUserData
}
func getSlackOAuthScopes(cfg *config.Config, key string) (scopes string, userData SlackUserData, err error) {
userData = SlackUserData{}
scopes = ""
// URL to which the request will be sent
url := "https://slack.com/api/auth.test"
// Create a client to send the request
client := analyzers.NewAnalyzeClient(cfg)
// Create the request
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return scopes, userData, err
}
// Add the Authorization header to the request
req.Header.Add("Authorization", "Bearer "+key)
// Send the request
resp, err := client.Do(req)
if err != nil {
return scopes, userData, err
}
defer resp.Body.Close() // Close the response body when the function returns
// print body
body, err := io.ReadAll(resp.Body)
if err != nil {
return scopes, userData, err
}
// Unmarshal the response body into the SlackUserData struct
if err := json.Unmarshal(body, &userData); err != nil {
return scopes, userData, err
}
// Print all headers received from the server
scopes = resp.Header.Get("X-Oauth-Scopes")
return scopes, userData, err
}
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
info, err := AnalyzePermissions(cfg, key)
if err != nil {
color.Red("[x] Error: %v", err)
return
}
color.Green("[!] Valid Slack API Key\n\n")
printIdentityInfo(info.User)
printScopes(strings.Split(info.Scopes, ","))
}
func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
scopes, userData, err := getSlackOAuthScopes(cfg, key)
if err != nil {
return nil, fmt.Errorf("error getting Slack OAuth scopes: %w", err)
}
if !userData.Ok {
return nil, fmt.Errorf("invalid Slack token")
}
return &SecretInfo{
Scopes: scopes,
User: userData,
}, nil
}
func printIdentityInfo(userData SlackUserData) {
if userData.Url != "" {
color.Green("URL: %v", userData.Url)
}
if userData.Team != "" {
color.Green("Team: %v", userData.Team)
}
if userData.User != "" {
color.Green("User: %v", userData.User)
}
if userData.TeamId != "" {
color.Green("Team ID: %v", userData.TeamId)
}
if userData.UserId != "" {
color.Green("User ID: %v", userData.UserId)
}
if userData.BotId != "" {
color.Green("Bot ID: %v", userData.BotId)
}
fmt.Println("")
if userData.IsEnterprise {
color.Green("[!] Slack is Enterprise")
} else {
color.Yellow("[-] Slack is not Enterprise")
}
fmt.Println("")
}
func printScopes(scopes []string) {
t := table.NewWriter()
// if !showAll {
// t.SetOutputMirror(os.Stdout)
// t.AppendHeader(table.Row{"Scopes"})
// for _, scope := range scopes {
// t.AppendRow([]interface{}{color.GreenString(scope)})
// }
// } else {
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Scope", "Permissions"})
for _, scope := range scopes {
perms := scope_mapping[scope]
if perms == nil {
t.AppendRow([]interface{}{color.GreenString(scope), color.GreenString("")})
} else {
t.AppendRow([]interface{}{color.GreenString(scope), color.GreenString(strings.Join(perms, ", "))})
}
}
//}
t.Render()
}