Files
trufflehog/pkg/analyzer/analyzers/mailgun/mailgun.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

171 lines
4.2 KiB
Go

//go:generate generate_permissions permissions.yaml permissions.go mailgun
package mailgun
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strconv"
"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.AnalyzerTypeMailgun }
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 *DomainsJSON) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzers.AnalyzerTypeMailgun,
Bindings: make([]analyzers.Binding, len(info.Items)),
}
for idx, domain := range info.Items {
result.Bindings[idx] = analyzers.Binding{
Resource: analyzers.Resource{
Name: domain.URL,
FullyQualifiedName: "mailgun/" + domain.ID + "/" + domain.URL,
Type: "domain",
Metadata: map[string]any{
"created_at": domain.CreatedAt,
"type": domain.Type,
"state": domain.State,
"is_disabled": domain.IsDisabled,
},
},
Permission: analyzers.Permission{
Value: PermissionStrings[FullAccess],
},
}
}
return &result
}
type Domain struct {
ID string `json:"id"`
URL string `json:"name"`
IsDisabled bool `json:"is_disabled"`
Type string `json:"type"`
State string `json:"state"`
CreatedAt string `json:"created_at"`
}
type DomainsJSON struct {
Items []Domain `json:"items"`
TotalCount int `json:"total_count"`
}
func getDomains(cfg *config.Config, apiKey string) (DomainsJSON, int, error) {
var domainsJSON DomainsJSON
client := analyzers.NewAnalyzeClient(cfg)
req, err := http.NewRequest("GET", "https://api.mailgun.net/v4/domains", nil)
if err != nil {
return domainsJSON, -1, err
}
req.SetBasicAuth("api", apiKey)
resp, err := client.Do(req)
if err != nil {
return domainsJSON, -1, err
}
if resp.StatusCode != 200 {
return domainsJSON, resp.StatusCode, nil
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&domainsJSON)
if err != nil {
return domainsJSON, resp.StatusCode, err
}
return domainsJSON, resp.StatusCode, nil
}
func AnalyzeAndPrintPermissions(cfg *config.Config, apiKey string) {
data, err := AnalyzePermissions(cfg, apiKey)
if err != nil {
color.Red("[x] %s", err.Error())
return
}
printMetadata(data)
}
func AnalyzePermissions(cfg *config.Config, apiKey string) (*DomainsJSON, error) {
// Get the domains associated with the API key
domains, statusCode, err := getDomains(cfg, apiKey)
if err != nil {
return nil, fmt.Errorf("Error getting domains: %s", err)
}
if statusCode != 200 {
return nil, fmt.Errorf("Invalid Mailgun API key.")
}
color.Green("[i] Valid Mailgun API key\n\n")
color.Green("[i] Permissions: Full Access\n\n")
return &domains, nil
}
func printMetadata(domains *DomainsJSON) {
if domains.TotalCount == 0 {
color.Red("[i] No domains found")
return
}
color.Yellow("[i] Found %d domain(s)", domains.TotalCount)
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Domain", "Type", "State", "Created At", "Disabled"})
for _, domain := range domains.Items {
var colorFunc func(format string, a ...interface{}) string
switch {
case domain.IsDisabled:
colorFunc = color.RedString
case domain.Type == "sandbox" || domain.State == "unverified":
colorFunc = color.YellowString
default:
colorFunc = color.GreenString
}
t.AppendRow([]interface{}{
colorFunc(domain.URL),
colorFunc(domain.Type),
colorFunc(domain.State),
colorFunc(domain.CreatedAt),
colorFunc(strconv.FormatBool(domain.IsDisabled)),
})
}
t.Render()
}