Skip to content

Commit

Permalink
Analyzer partial implementations (#3114)
Browse files Browse the repository at this point in the history
* Add POC analyze sub-command

* Address lint errors

* added http logging to most analyzers

* Use custom RoundTripper with default http.Client

* [chore] Embed scopes at compile time

* [chore] Move subcommand check up to prevent printing metrics

* Create framework of interfaces, structs, and protos

* Implement Analyzer for airbrake

* Add FullAccess permission constant

* Implement Analyzer for asana

* Implement Analyzer for bitbucket

* Implement Analyzer for github

* Implement Analyzer for gitlab

* Implemente Analyzer for huggingface

* Implement Analyzer for mailchimp

* implement analyzer for mailgun

* update cli cmd

* Implement analyzer for openai

* fix timing issue on scopes

* print permissions only if restricted key

* Implement Analyzer for mysql

* enable loggin check

* fixed the formatting issue to wrap sub-errors

* implemented analyzer for opsgenie

* implemented analyzer for postgres

* use format string

* implemented analyzer for sendgrid

* simplify returning the error

* implemented analyzer for postman

* added handling of workspace error

* Update protos to match OSS

* Generate protos

* Update data structures to match OSS

* Update airbrake implementation

* Remove asana implementation

* Remove mailchimp implementation

* Update openai implementation to match OSS

* Remove gitlab implementation

* Remove huggingface implementation

* Remove bitbucket implementation

* Fix permission in airbrake

* Remove github implementation

* Remove mailgun implementation

* Cleanup compiler errors

* Implement Analyzer interface for github

* Add parents to github resources

* Add fine_grained to github metadata

* Update with changes from main

* Remove unused function stubs

---------

Co-authored-by: Joe Leon <[email protected]>
Co-authored-by: Hon <[email protected]>
Co-authored-by: Abdul Basit <[email protected]>
Co-authored-by: Abdul Basit <[email protected]>
  • Loading branch information
5 people authored Jul 30, 2024
1 parent f664472 commit 20de56d
Show file tree
Hide file tree
Showing 17 changed files with 761 additions and 406 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ require (
google.golang.org/api v0.189.0
google.golang.org/protobuf v1.34.2
gopkg.in/h2non/gock.v1 v1.1.2
gopkg.in/yaml.v3 v3.0.1
gopkg.in/yaml.v2 v2.4.0
pault.ag/go/debian v0.16.0
pgregory.net/rapid v1.1.0
sigs.k8s.io/yaml v1.4.0
Expand Down Expand Up @@ -307,5 +307,6 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect
google.golang.org/grpc v1.64.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
pault.ag/go/topsort v0.1.1 // indirect
)
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go v1.55.2 h1:/2OFM8uFfK9e+cqHTw9YPrvTzIXT2XkFGXRM7WbJb7E=
github.com/aws/aws-sdk-go v1.55.2/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.3 h1:0B5hOX+mIx7I5XPOrjrHlKSDQV/+ypFZpIHOx5LOk3E=
github.com/aws/aws-sdk-go v1.55.3/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw=
Expand Down Expand Up @@ -275,8 +273,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
Expand Down
170 changes: 125 additions & 45 deletions pkg/analyzer/analyzers/airbrake/airbrake.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,80 @@ import (
"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/analyzer/pb/analyzerpb"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)

type ProjectsJSON struct {
Projects []struct {
Name string `json:"name"`
ID int `json:"id"`
} `json:"projects"`
var _ analyzers.Analyzer = (*Analyzer)(nil)

type Analyzer struct {
Cfg *config.Config
}

func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Airbrake }

func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
info, err := AnalyzePermissions(a.Cfg, credInfo["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{
Metadata: map[string]any{
"key_type": info.KeyType,
"reference": info.Reference,
},
}
// Copy the rest of the metadata over.
for k, v := range info.Misc {
result.Metadata[k] = v
}

// Build a list of Bindings by referencing the same permissions list
// for each resource.
permissions := allPermissions()
for _, proj := range info.Projects {
resource := analyzers.Resource{
Name: proj.Name,
FullyQualifiedName: strconv.Itoa(proj.ID),
Type: "project",
}
for _, perm := range permissions {
binding := analyzers.Binding{
Resource: resource,
Permission: perm,
}
result.Bindings = append(result.Bindings, binding)
}
}

return &result
}

type SecretInfo struct {
KeyType string
Projects []Project
Reference string
Scopes []analyzers.Permission
Misc map[string]string
}

type Project struct {
Name string `json:"name"`
ID int `json:"id"`
}

// validateKey checks if the key is valid and returns the projects associated with the key
func validateKey(cfg *config.Config, key string) (bool, ProjectsJSON, error) {
func validateKey(cfg *config.Config, key string) (bool, []Project, error) {
type ProjectsJSON struct {
Projects []Project `json:"projects"`
}
// create struct to hold response
var projects ProjectsJSON

Expand All @@ -31,7 +94,7 @@ func validateKey(cfg *config.Config, key string) (bool, ProjectsJSON, error) {
// create request
req, err := http.NewRequest("GET", "https://api.airbrake.io/api/v4/projects", nil)
if err != nil {
return false, projects, err
return false, projects.Projects, err
}

// add key as url param
Expand All @@ -42,7 +105,7 @@ func validateKey(cfg *config.Config, key string) (bool, ProjectsJSON, error) {
// send request
resp, err := client.Do(req)
if err != nil {
return false, projects, err
return false, projects.Projects, err
}

// read response
Expand All @@ -51,73 +114,90 @@ func validateKey(cfg *config.Config, key string) (bool, ProjectsJSON, error) {
// if status code is 200, decode response
if resp.StatusCode == 200 {
err := json.NewDecoder(resp.Body).Decode(&projects)
return true, projects, err
return true, projects.Projects, err
}

// if status code is not 200, return false
return false, projects, nil
return false, projects.Projects, nil
}

func AnalyzePermissions(cfg *config.Config, key string) {
// validate key
valid, projects, err := validateKey(cfg, key)
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
info, err := AnalyzePermissions(cfg, key)
if err != nil {
color.Red("[x]" + err.Error())
color.Red("[x] %s", err.Error())
return
}

if !valid {
color.Red("[x] Invalid Airbrake User API Key")
return
color.Green("[!] Valid Airbrake User API Key\n\n")
color.Green("[i] Key Type: " + info.KeyType)
if v, ok := info.Misc["expiration"]; ok {
color.Green("[i] Expiration: %s", v)
}
if v, ok := info.Misc["duration"]; ok {
color.Green("[i] Duration: %s", v)
}

color.Green("[!] Valid Airbrake User API Key\n\n")
color.Green("\n[i] Projects:")
printProjects(info.Projects...)

color.Green("\n[i] Permissions:")
printPermissions(info.Scopes)
}

func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
valid, projects, err := validateKey(cfg, key)
if err != nil {
return nil, err
}
if !valid {
return nil, fmt.Errorf("Invalid Airbrake User API Key")
}

info := &SecretInfo{
Projects: projects,
Reference: "https://docs.airbrake.io/docs/devops-tools/api/",
// If the token exists, it has all permissions.
Scopes: allPermissions(),
Misc: make(map[string]string),
}
if len(key) == 40 {
color.Green("[i] Key Type: User Key")
color.Green("[i] Expiration: Never")
info.KeyType = "User Key"
info.Misc["expiration"] = "Never"
} else {
color.Yellow("[i] Key Type: User Token")
color.Yellow("[i] Duration: Short-Lived")
// ToDo: determine how long these are valid for
info.KeyType = "User Token"
info.Misc["duration"] = "Short Lived"
}
return info, nil
}

// if key is valid, print projects
if valid {
color.Green("\n[i] Projects:")
printProjects(projects)
func allPermissions() []analyzers.Permission {
permissions := make([]analyzers.Permission, len(scope_order))
for i, perm := range scope_order {
permissions[i] = analyzers.Permission{Value: perm}
}

color.Green("\n[i] Permissions:")
printPermissions()
return permissions
}

func printProjects(projects ProjectsJSON) {
func printProjects(projects ...Project) {
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Project ID", "Project Name"})
for _, project := range projects.Projects {
t.AppendRow([]interface{}{color.GreenString(strconv.Itoa(project.ID)), color.GreenString(project.Name)})
for _, project := range projects {
t.AppendRow([]any{color.GreenString("%d", project.ID), color.GreenString("%s", project.Name)})
}
t.Render()
}

func printPermissions() {
func printPermissions(scopes []analyzers.Permission) {
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Scope", "Permissions"})
for s := range scope_order {
scope := scope_order[s][0]
permissions := scope_mapping[scope]
if scope == "Authentication" {
t.AppendRow([]interface{}{scope, permissions[0]})
continue
}
for i, permission := range permissions {
for _, scope := range scopes {
scope := scope.Value
for i, permission := range scope_mapping[scope] {
if i == 0 {
t.AppendRow([]interface{}{color.GreenString(scope), color.GreenString(permission)})
} else {
t.AppendRow([]interface{}{"", color.GreenString(permission)})
t.AppendRow([]any{color.GreenString("%s", scope), color.GreenString("%s", permission)})
continue
}
}
}
Expand Down
22 changes: 11 additions & 11 deletions pkg/analyzer/analyzers/airbrake/scopes.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package airbrake

var scope_order = [][]string{
{"Authentication"},
{"Performance Monitoring"},
{"Error Notification"},
{"Projects"},
{"Deploys"},
{"Groups"},
{"Notices"},
{"Project Activities"},
{"Source Maps"},
{"iOS Crash Reports"},
var scope_order = []string{
"Authentication",
"Performance Monitoring",
"Error Notification",
"Projects",
"Deploys",
"Groups",
"Notices",
"Project Activities",
"Source Maps",
"iOS Crash Reports",
}

var scope_mapping = map[string][]string{
Expand Down
55 changes: 37 additions & 18 deletions pkg/analyzer/analyzers/asana/asana.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,34 @@ package asana

import (
"encoding/json"
"fmt"
"net/http"
"os"

"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/analyzer/pb/analyzerpb"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)

var _ analyzers.Analyzer = (*Analyzer)(nil)

type Analyzer struct {
Cfg *config.Config
}

func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Asana }

func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
_, err := AnalyzePermissions(a.Cfg, credInfo["key"])
if err != nil {
return nil, err
}
return nil, fmt.Errorf("not implemented")
}

type MeJSON struct {
Data struct {
Email string `json:"email"`
Expand All @@ -24,48 +43,48 @@ type MeJSON struct {
} `json:"data"`
}

func getMetadata(cfg *config.Config, key string) (MeJSON, error) {
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
me, err := AnalyzePermissions(cfg, key)
if err != nil {
color.Red("[x] %s", err.Error())
return
}
printMetadata(me)
}

func AnalyzePermissions(cfg *config.Config, key string) (*MeJSON, error) {
var me MeJSON

client := analyzers.NewAnalyzeClient(cfg)
req, err := http.NewRequest("GET", "https://app.asana.com/api/1.0/users/me", nil)
if err != nil {
return me, err
return nil, err
}

req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return me, err
return nil, err
}

if resp.StatusCode != 200 {
return me, nil
return nil, fmt.Errorf("Invalid Asana API Key")
}

defer resp.Body.Close()

err = json.NewDecoder(resp.Body).Decode(&me)
if err != nil {
return me, err
return nil, err
}
return me, nil
}

func AnalyzePermissions(cfg *config.Config, key string) {
me, err := getMetadata(cfg, key)
if err != nil {
color.Red("[x] ", err.Error())
return
if me.Data.Email == "" {
return nil, fmt.Errorf("Invalid Asana API Key")
}
printMetadata(me)
return &me, nil
}

func printMetadata(me MeJSON) {
if me.Data.Email == "" {
color.Red("[x] Invalid Asana API Key\n")
return
}
func printMetadata(me *MeJSON) {
color.Green("[!] Valid Asana API Key\n\n")
color.Yellow("[i] User Information")
color.Yellow(" Name: %s", me.Data.Name)
Expand Down
Loading

0 comments on commit 20de56d

Please sign in to comment.