trufflehog/pkg/analyzer/analyzers/analyzers.go
Hon ab8c843fec
Analyzer capitalization (#3188)
* capitalization

* Lowercase analyze labels for the subcommand

* Canonicalize input and lowercase when matching command

* add warning

---------

Co-authored-by: Miccah Castorina <m.castorina93@gmail.com>
2024-08-06 17:00:40 -07:00

257 lines
5.4 KiB
Go

package analyzers
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/fatih/color"
"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 (
Analyzer interface {
Type() analyzerpb.AnalyzerType
Analyze(ctx context.Context, credentialInfo map[string]string) (*AnalyzerResult, error)
}
// AnalyzerResult is the output of analysis.
AnalyzerResult struct {
AnalyzerType analyzerpb.AnalyzerType
Bindings []Binding
UnboundedResources []Resource
Metadata map[string]any
}
Resource struct {
Name string
FullyQualifiedName string
Type string
Metadata map[string]any
Parent *Resource
}
Permission struct {
Value string
Parent *Permission
}
Binding struct {
Resource Resource
Permission Permission
}
)
type PermissionType string
const (
READ PermissionType = "Read"
WRITE PermissionType = "Write"
READ_WRITE PermissionType = "Read & Write"
NONE PermissionType = "None"
ERROR PermissionType = "Error"
FullAccess string = "full_access"
)
// Sorted list of all available analyzers. Used for valid sub-commands and TUI
// selection. TODO: Change slice type to Analyzer interface when all available
// analyzers implement it.
var AvailableAnalyzers = []string{
"Airbrake",
"Asana",
"Bitbucket",
"GitHub",
"GitLab",
"HuggingFace",
"Mailchimp",
"Mailgun",
"MySQL",
"OpenAI",
"Opsgenie",
"Postgres",
"Postman",
"Sendgrid",
"Shopify",
"Slack",
"Sourcegraph",
"Square",
"Stripe",
"Twilio",
}
type PermissionStatus struct {
Value bool
IsError bool
}
type HttpStatusTest struct {
URL string
Method string
Payload map[string]interface{}
Params map[string]string
Valid []int
Invalid []int
Type PermissionType
Status PermissionStatus
Risk string
}
func (h *HttpStatusTest) RunTest(headers map[string]string) error {
// If body data, marshal to JSON
var data io.Reader
if h.Payload != nil {
jsonData, err := json.Marshal(h.Payload)
if err != nil {
return err
}
data = bytes.NewBuffer(jsonData)
}
// Create new HTTP request
client := &http.Client{}
req, err := http.NewRequest(h.Method, h.URL, data)
if err != nil {
return err
}
// Add custom headers if provided
for key, value := range headers {
req.Header.Set(key, value)
}
// Execute HTTP Request
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Check response status code
switch {
case StatusContains(resp.StatusCode, h.Valid):
h.Status.Value = true
case StatusContains(resp.StatusCode, h.Invalid):
h.Status.Value = false
default:
h.Status.IsError = true
}
return nil
}
type Scope struct {
Name string
Tests []interface{}
}
func StatusContains(status int, vals []int) bool {
for _, v := range vals {
if status == v {
return true
}
}
return false
}
func GetWriterFromStatus(status PermissionType) func(a ...interface{}) string {
switch status {
case READ:
return color.New(color.FgYellow).SprintFunc()
case WRITE:
return color.New(color.FgGreen).SprintFunc()
case READ_WRITE:
return color.New(color.FgGreen).SprintFunc()
case NONE:
return color.New().SprintFunc()
case ERROR:
return color.New(color.FgRed).SprintFunc()
default:
return color.New().SprintFunc()
}
}
var GreenWriter = color.New(color.FgGreen).SprintFunc()
var YellowWriter = color.New(color.FgYellow).SprintFunc()
var RedWriter = color.New(color.FgRed).SprintFunc()
var DefaultWriter = color.New().SprintFunc()
type AnalyzeClient struct {
http.Client
LoggingEnabled bool
LogFile string
}
func CreateLogFileName(baseName string) string {
// Get the current time
currentTime := time.Now()
// Format the time as "2024_06_30_07_15_30"
timeString := currentTime.Format("2006_01_02_15_04_05")
// Create the log file name
logFileName := fmt.Sprintf("%s_%s.log", timeString, baseName)
return logFileName
}
func NewAnalyzeClient(cfg *config.Config) *http.Client {
if cfg == nil || !cfg.LoggingEnabled {
return &http.Client{}
}
return &http.Client{
Transport: LoggingRoundTripper{
parent: http.DefaultTransport,
logFile: cfg.LogFile,
},
}
}
type LoggingRoundTripper struct {
parent http.RoundTripper
// TODO: io.Writer
logFile string
}
func (r LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
startTime := time.Now()
resp, err := r.parent.RoundTrip(req)
if err != nil {
return resp, err
}
// TODO: JSON
logEntry := fmt.Sprintf("Date: %s, Method: %s, Path: %s, Status: %d\n", startTime.Format(time.RFC3339), req.Method, req.URL.Path, resp.StatusCode)
// Open log file in append mode.
file, err := os.OpenFile(r.logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return resp, fmt.Errorf("failed to open log file: %w", err)
}
defer file.Close()
// Write log entry to file.
if _, err := file.WriteString(logEntry); err != nil {
return resp, fmt.Errorf("failed to write log entry to file: %w", err)
}
return resp, nil
}
// BindAllPermissions creates a Binding for each permission to the given
// resource.
func BindAllPermissions(r Resource, perms ...Permission) []Binding {
bindings := make([]Binding, len(perms))
for i, perm := range perms {
bindings[i] = Binding{
Resource: r,
Permission: perm,
}
}
return bindings
}