* Add AnalysisError type and AnalysisErrorInfo interface Introduce a shared error type that provides structured metadata (analyzer type, operation, service, resource) for analysis failures. This allows the scanner to extract context from errors without depending on concrete types. * Wrap errors in simple API analyzers with AnalysisError Batch A: Airbrake, Anthropic, Asana, DigitalOcean, DockerHub, ElevenLabs, Fastly, Groq, HuggingFace, Mailchimp, Mailgun, Mux, Netlify, Ngrok, Notion, OpenAI, Opsgenie, Posthog, Postman, Sendgrid, Sourcegraph. Wraps credential validation errors with operation "validate_credentials" and AnalyzePermissions errors with operation "analyze_permissions". * Wrap errors in remaining analyzers with AnalysisError (Batches B-E) Batch B (OAuth/multi-credential): airtableoauth, airtablepat, datadog, dropbox, figma, launchdarkly, plaid Batch C (Complex): bitbucket, databricks, github, gitlab, jira, monday, planetscale, shopify, slack, square, stripe, twilio Batch D (Database): mysql, postgres (service: Database) Batch E (PrivateKey): privatekey (service: crypto) * Use Type().String() and constants for NewAnalysisError calls Address PR feedback: replace hardcoded analyzer type strings with a.Type().String() and replace raw operation/service strings with package-level constants (OperationValidateCredentials, OperationAnalyzePermissions, ServiceAPI, ServiceConfig, etc.). * Omit empty resource parenthetical from AnalysisError messages Conditionally include "(resource: ...)" only when non-empty, avoiding cluttered messages like "... (resource: ): ..." that appear for the majority of analyzers that don't set a resource. * Wrap no-data error path in GitHub analyzer with AnalysisError
323 lines
8.9 KiB
Go
323 lines
8.9 KiB
Go
//go:generate generate_permissions permissions.yaml permissions.go mux
|
|
package mux
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/fatih/color"
|
|
"github.com/jedib0t/go-pretty/v6/table"
|
|
|
|
_ "embed"
|
|
|
|
"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
|
|
}
|
|
|
|
//go:embed tests.json
|
|
var testsConfig []byte
|
|
|
|
func readTestsConfig() (*permissionTestConfig, error) {
|
|
var config permissionTestConfig
|
|
if err := json.Unmarshal(testsConfig, &config); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal tests config: %w", err)
|
|
}
|
|
return &config, nil
|
|
}
|
|
|
|
func (a Analyzer) Type() analyzers.AnalyzerType {
|
|
return analyzers.AnalyzerTypeMux
|
|
}
|
|
|
|
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
|
key, exist := credInfo["key"]
|
|
if !exist {
|
|
return nil, analyzers.NewAnalysisError(a.Type().String(), analyzers.OperationValidateCredentials, analyzers.ServiceConfig, "", errors.New("key not found in credentials info"),
|
|
)
|
|
}
|
|
secret, exist := credInfo["secret"]
|
|
if !exist {
|
|
return nil, analyzers.NewAnalysisError(a.Type().String(), analyzers.OperationValidateCredentials, analyzers.ServiceConfig, "", errors.New("secret not found in credentials info"),
|
|
)
|
|
}
|
|
|
|
info, err := AnalyzePermissions(a.Cfg, key, secret)
|
|
if err != nil {
|
|
return nil, analyzers.NewAnalysisError(a.Type().String(), analyzers.OperationAnalyzePermissions, analyzers.ServiceAPI, "", err,
|
|
)
|
|
}
|
|
|
|
return secretInfoToAnalyzerResult(info), nil
|
|
}
|
|
|
|
func AnalyzeAndPrintPermissions(cfg *config.Config, key string, secret string) {
|
|
info, err := AnalyzePermissions(cfg, key, secret)
|
|
if err != nil {
|
|
color.Red("[x] Invalid Mux Key or Secret\n")
|
|
color.Red("[x] Error : %s", err.Error())
|
|
return
|
|
}
|
|
|
|
if info == nil {
|
|
color.Red("[x] Error : %s", "No information found")
|
|
return
|
|
}
|
|
|
|
color.Green("[i] Valid Mux API Key and Secret\n")
|
|
printResourcesAndPermissions(info)
|
|
}
|
|
|
|
func AnalyzePermissions(cfg *config.Config, key string, secret string) (*secretInfo, error) {
|
|
client := analyzers.NewAnalyzeClientUnrestricted(cfg)
|
|
secretInfo := &secretInfo{}
|
|
if err := testAllPermissions(client, secretInfo, key, secret); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := populateAllResources(client, secretInfo, key, secret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return secretInfo, nil
|
|
}
|
|
|
|
func secretInfoToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {
|
|
if info == nil {
|
|
return nil
|
|
}
|
|
|
|
bindings := []analyzers.Binding{}
|
|
readAccessPermission := analyzers.Permission{
|
|
Value: PermissionStrings[Read],
|
|
}
|
|
fullAccessPermission := analyzers.Permission{
|
|
Value: PermissionStrings[FullAccess],
|
|
}
|
|
|
|
videoResourcePermission := readAccessPermission
|
|
if info.hasPermission(ResourceTypeVideo, FullAccess) {
|
|
videoResourcePermission = fullAccessPermission
|
|
}
|
|
|
|
dataResourcePermission := readAccessPermission
|
|
if info.hasPermission(ResourceTypeData, FullAccess) {
|
|
dataResourcePermission = fullAccessPermission
|
|
}
|
|
|
|
systemResourcePermission := readAccessPermission
|
|
if info.hasPermission(ResourceTypeSystem, FullAccess) {
|
|
systemResourcePermission = fullAccessPermission
|
|
}
|
|
|
|
// Binding all Mux Video Assets
|
|
for _, asset := range info.Assets {
|
|
assetResource := createAssetResource(asset)
|
|
trackResources := createAssetTrackResources(asset, &assetResource)
|
|
playbackIDResources := createAssetPlaybackIDResources(asset, &assetResource)
|
|
|
|
for _, resource := range trackResources {
|
|
bindings = append(bindings, createBinding(&resource, videoResourcePermission))
|
|
}
|
|
for _, resource := range playbackIDResources {
|
|
bindings = append(bindings, createBinding(&resource, videoResourcePermission))
|
|
}
|
|
|
|
bindings = append(bindings, createBinding(&assetResource, videoResourcePermission))
|
|
}
|
|
|
|
// Binding all Mux Data Annotations
|
|
for _, annotation := range info.Annotations {
|
|
annotationResource := createAnnotationResource(annotation)
|
|
bindings = append(bindings, createBinding(&annotationResource, dataResourcePermission))
|
|
}
|
|
|
|
// Binding all Mux System Signing Keys
|
|
for _, signingKey := range info.SigningKeys {
|
|
signingKeyResource := createSigningKeyResource(signingKey)
|
|
bindings = append(bindings, createBinding(&signingKeyResource, systemResourcePermission))
|
|
}
|
|
|
|
result := analyzers.AnalyzerResult{
|
|
AnalyzerType: analyzers.AnalyzerTypeMux,
|
|
Metadata: nil,
|
|
Bindings: bindings,
|
|
}
|
|
return &result
|
|
}
|
|
|
|
func createBinding(resource *analyzers.Resource, permission analyzers.Permission) analyzers.Binding {
|
|
return analyzers.Binding{
|
|
Resource: *resource,
|
|
Permission: permission,
|
|
}
|
|
}
|
|
|
|
func printResourcesAndPermissions(info *secretInfo) {
|
|
color.Yellow("\n[i] Permissions:")
|
|
t1 := table.NewWriter()
|
|
t1.AppendHeader(table.Row{"Resource Category", "Access Level", "Resource List"})
|
|
|
|
for idx, resource := range muxResourcesMap[ResourceTypeVideo] {
|
|
category, access := "", ""
|
|
if idx == 0 {
|
|
category = "Mux Video"
|
|
access = getAccessLevelStringFromPermission(info.Permissions[ResourceTypeVideo])
|
|
}
|
|
t1.AppendRow(table.Row{
|
|
color.GreenString(category),
|
|
color.GreenString(access),
|
|
color.GreenString(resource),
|
|
})
|
|
}
|
|
t1.AppendSeparator()
|
|
|
|
for idx, resource := range muxResourcesMap[ResourceTypeData] {
|
|
category, access := "", ""
|
|
if idx == 0 {
|
|
category = "Mux Data"
|
|
access = getAccessLevelStringFromPermission(info.Permissions[ResourceTypeData])
|
|
}
|
|
t1.AppendRow(table.Row{
|
|
color.GreenString(category),
|
|
color.GreenString(access),
|
|
color.GreenString(resource),
|
|
})
|
|
}
|
|
t1.AppendSeparator()
|
|
|
|
for idx, resource := range muxResourcesMap[ResourceTypeSystem] {
|
|
category, access := "", ""
|
|
if idx == 0 {
|
|
category = "Mux System"
|
|
access = getAccessLevelStringFromPermission(info.Permissions[ResourceTypeSystem])
|
|
}
|
|
t1.AppendRow(table.Row{
|
|
color.GreenString(category),
|
|
color.GreenString(access),
|
|
color.GreenString(resource),
|
|
})
|
|
}
|
|
t1.SetOutputMirror(os.Stdout)
|
|
t1.Render()
|
|
|
|
color.Yellow("\n[i] Resources:")
|
|
|
|
if info.hasPermission(ResourceTypeVideo, Read) {
|
|
printMuxVideoResources(info)
|
|
}
|
|
|
|
if info.hasPermission(ResourceTypeData, Read) {
|
|
printMuxDataResources(info)
|
|
}
|
|
|
|
if info.hasPermission(ResourceTypeSystem, Read) {
|
|
printMuxSystemResources(info)
|
|
}
|
|
|
|
fmt.Printf("%s: https://www.mux.com/docs/api-reference\n\n", color.GreenString("Ref"))
|
|
}
|
|
|
|
func printMuxVideoResources(info *secretInfo) {
|
|
t1 := table.NewWriter()
|
|
t1.SetTitle("Assets")
|
|
t1.AppendHeader(table.Row{"ID", "Title", "Duration", "Status", "Creator ID", "External ID", "Created At"})
|
|
|
|
t2 := table.NewWriter()
|
|
t2.SetTitle("Asset Tracks")
|
|
t2.AppendHeader(table.Row{"Asset ID", "ID", "Name", "Type", "Duration", "Status", "Primary"})
|
|
|
|
t3 := table.NewWriter()
|
|
t3.SetTitle("Asset Playback IDs")
|
|
t3.AppendHeader(table.Row{"Asset ID", "ID", "Policy"})
|
|
|
|
for _, asset := range info.Assets {
|
|
t1.AppendRow(table.Row{
|
|
color.GreenString(asset.ID),
|
|
color.GreenString(asset.Meta.Title),
|
|
color.GreenString(fmt.Sprintf("%.2fs", asset.Duration)),
|
|
color.GreenString(asset.Status),
|
|
color.GreenString(asset.Meta.CreatorID),
|
|
color.GreenString(asset.Meta.ExternalID),
|
|
color.GreenString(asset.CreatedAt),
|
|
})
|
|
t1.AppendSeparator()
|
|
for _, track := range asset.Tracks {
|
|
t2.AppendRow(table.Row{
|
|
color.GreenString(asset.ID),
|
|
color.GreenString(track.ID),
|
|
color.GreenString(track.Name),
|
|
color.GreenString(track.Type),
|
|
color.GreenString(fmt.Sprintf("%.2fs", track.Duration)),
|
|
color.GreenString(track.Status),
|
|
color.GreenString(fmt.Sprintf("%t", track.Primary)),
|
|
})
|
|
t2.AppendSeparator()
|
|
}
|
|
for _, playbackID := range asset.PlaybackIDs {
|
|
t3.AppendRow(table.Row{
|
|
color.GreenString(asset.ID),
|
|
color.GreenString(playbackID.ID),
|
|
color.GreenString(playbackID.Policy),
|
|
})
|
|
t3.AppendSeparator()
|
|
}
|
|
}
|
|
t1.SetOutputMirror(os.Stdout)
|
|
t1.Render()
|
|
|
|
t2.SetOutputMirror(os.Stdout)
|
|
t2.Render()
|
|
|
|
t3.SetOutputMirror(os.Stdout)
|
|
t3.Render()
|
|
}
|
|
|
|
func printMuxDataResources(info *secretInfo) {
|
|
t1 := table.NewWriter()
|
|
t1.SetTitle("Annotations")
|
|
t1.AppendHeader(table.Row{"ID", "Note", "Date", "Sub Property ID"})
|
|
for _, annotation := range info.Annotations {
|
|
t1.AppendRow(table.Row{
|
|
color.GreenString(annotation.ID),
|
|
color.GreenString(annotation.Note),
|
|
color.GreenString(annotation.Date),
|
|
color.GreenString(annotation.SubPropertyID),
|
|
})
|
|
}
|
|
t1.SetOutputMirror(os.Stdout)
|
|
t1.Render()
|
|
}
|
|
|
|
func printMuxSystemResources(info *secretInfo) {
|
|
t1 := table.NewWriter()
|
|
t1.SetTitle("Signing Keys")
|
|
t1.AppendHeader(table.Row{"ID", "Created At"})
|
|
for _, signingKey := range info.SigningKeys {
|
|
t1.AppendRow(table.Row{
|
|
color.GreenString(signingKey.ID),
|
|
color.GreenString(signingKey.CreatedAt),
|
|
})
|
|
}
|
|
t1.SetOutputMirror(os.Stdout)
|
|
t1.Render()
|
|
}
|
|
|
|
func getAccessLevelStringFromPermission(permission Permission) string {
|
|
switch permission {
|
|
case Read:
|
|
return "Read"
|
|
case FullAccess:
|
|
return "Read & Write"
|
|
default:
|
|
return "None"
|
|
}
|
|
}
|