[Feat] Added Mux API Analyzer (#4128)
* added mux analyzer * updated mux analyzer print function and mux detector * updated mux analyzer test expected output
This commit is contained in:
@@ -98,6 +98,7 @@ const (
|
||||
AnalyzerTypeFastly
|
||||
AnalyzerTypeMonday
|
||||
AnalyzerTypeNgrok
|
||||
AnalyzerTypeMux
|
||||
// Add new items here with AnalyzerType prefix
|
||||
)
|
||||
|
||||
@@ -141,6 +142,7 @@ var analyzerTypeStrings = map[AnalyzerType]string{
|
||||
AnalyzerTypeFastly: "Fastly",
|
||||
AnalyzerTypeMonday: "Monday",
|
||||
AnalyzerTypeNgrok: "Ngrok",
|
||||
AnalyzerTypeMux: "Mux",
|
||||
// Add new mappings here
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,142 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ResourceType string
|
||||
|
||||
const (
|
||||
ResourceTypeVideo ResourceType = "video"
|
||||
ResourceTypeData ResourceType = "data"
|
||||
ResourceTypeSystem ResourceType = "system"
|
||||
)
|
||||
|
||||
type permissionTestConfig struct {
|
||||
Tests []permissionTest `json:"tests"`
|
||||
}
|
||||
|
||||
type permissionTest struct {
|
||||
ResourceType ResourceType `json:"resource_type"`
|
||||
Permission string `json:"permission"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Method string `json:"method"`
|
||||
ValidStatusCode int `json:"valid_status_code"`
|
||||
}
|
||||
|
||||
func (test permissionTest) testPermission(client *http.Client, key string, secret string) (bool, error) {
|
||||
_, statusCode, err := makeAPIRequest(client, key, secret, test.Method, test.Endpoint)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case test.ValidStatusCode:
|
||||
return true, nil
|
||||
case http.StatusNotFound:
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf("unexpected status code: %d", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
type secretInfo struct {
|
||||
Permissions map[ResourceType]Permission
|
||||
Assets []asset
|
||||
Annotations []annotation
|
||||
SigningKeys []signingKey
|
||||
}
|
||||
|
||||
func (info *secretInfo) addPermission(resourceType ResourceType, permission string) {
|
||||
if info.Permissions == nil {
|
||||
info.Permissions = map[ResourceType]Permission{}
|
||||
}
|
||||
if perm := info.Permissions[resourceType]; perm == FullAccess {
|
||||
return
|
||||
}
|
||||
|
||||
if permission == "read" {
|
||||
info.Permissions[resourceType] = Read
|
||||
} else if permission == "write" {
|
||||
info.Permissions[resourceType] = FullAccess
|
||||
}
|
||||
}
|
||||
|
||||
func (info *secretInfo) hasPermission(resourceType ResourceType, permission Permission) bool {
|
||||
perm, exists := info.Permissions[resourceType]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
return perm == permission || perm == FullAccess
|
||||
}
|
||||
|
||||
// Resource structs
|
||||
|
||||
type track struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Duration float64 `json:"duration"`
|
||||
Status string `json:"status"`
|
||||
Primary bool `json:"primary"`
|
||||
|
||||
TextType string `json:"text_type"`
|
||||
TextSource string `json:"text_source"`
|
||||
LanguageCode string `json:"language_code"`
|
||||
|
||||
MaxWidth int `json:"max_width"`
|
||||
MaxHeight int `json:"max_height"`
|
||||
MaxFrameRate float64 `json:"max_frame_rate"`
|
||||
MaxChannels int `json:"max_channels"`
|
||||
}
|
||||
|
||||
type playbackID struct {
|
||||
ID string `json:"id"`
|
||||
Policy string `json:"policy"`
|
||||
}
|
||||
|
||||
type meta struct {
|
||||
Title string `json:"title"`
|
||||
ExternalID string `json:"external_id"`
|
||||
CreatorID string `json:"creator_id"`
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
ID string `json:"id"`
|
||||
Duration float64 `json:"duration"`
|
||||
Status string `json:"status"`
|
||||
VideoQuality string `json:"video_quality"`
|
||||
MP4Support string `json:"mp4_support"`
|
||||
AspectRatio string `json:"aspect_ratio"`
|
||||
Tracks []track `json:"tracks"`
|
||||
PlaybackIDs []playbackID `json:"playback_ids"`
|
||||
Meta meta `json:"meta"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type annotation struct {
|
||||
SubPropertyID string `json:"sub_property_id"`
|
||||
Note string `json:"note"`
|
||||
ID string `json:"id"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
type signingKey struct {
|
||||
ID string `json:"id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// API response structs
|
||||
|
||||
type assetListResponse struct {
|
||||
Data []asset `json:"data"`
|
||||
}
|
||||
|
||||
type annotationListResponse struct {
|
||||
Data []annotation `json:"data"`
|
||||
}
|
||||
|
||||
type signingKeyListResponse struct {
|
||||
Data []signingKey `json:"data"`
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
//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, errors.New("key not found in credentials info")
|
||||
}
|
||||
secret, exist := credInfo["secret"]
|
||||
if !exist {
|
||||
return nil, errors.New("secret not found in credentials info")
|
||||
}
|
||||
|
||||
info, err := AnalyzePermissions(a.Cfg, key, secret)
|
||||
if err != nil {
|
||||
return nil, 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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
//go:embed expected_output.json
|
||||
var expectedOutput []byte
|
||||
|
||||
func TestAnalyzer_Analyze(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
|
||||
key := testSecrets.MustGetField("MUX_KEY")
|
||||
secret := testSecrets.MustGetField("MUX_SECRET")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
secret string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid mux credentials",
|
||||
key: key,
|
||||
secret: secret,
|
||||
want: string(expectedOutput),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Analyzer{Cfg: &config.Config{}}
|
||||
got, err := a.Analyze(ctx, map[string]string{
|
||||
"key": tt.key,
|
||||
"secret": tt.secret,
|
||||
})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// marshal the actual result to JSON
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to JSON: %s", err)
|
||||
}
|
||||
|
||||
// compare the JSON strings
|
||||
if string(gotJSON) != string(tt.want) {
|
||||
// pretty-print both JSON strings for easier comparison
|
||||
var gotIndented, wantIndented []byte
|
||||
gotIndented, err = json.MarshalIndent(got, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to indented JSON: %s", err)
|
||||
}
|
||||
wantIndented, err = json.MarshalIndent(tt.want, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to indented JSON: %s", err)
|
||||
}
|
||||
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package mux
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
Invalid Permission = iota
|
||||
Read Permission = iota
|
||||
FullAccess Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
Read: "read",
|
||||
FullAccess: "full_access",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"read": Read,
|
||||
"full_access": FullAccess,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
Read: 1,
|
||||
FullAccess: 2,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
1: Read,
|
||||
2: FullAccess,
|
||||
}
|
||||
)
|
||||
|
||||
// ToString converts a Permission enum to its string representation
|
||||
func (p Permission) ToString() (string, error) {
|
||||
if str, ok := PermissionStrings[p]; ok {
|
||||
return str, nil
|
||||
}
|
||||
return "", errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// ToID converts a Permission enum to its ID
|
||||
func (p Permission) ToID() (int, error) {
|
||||
if id, ok := PermissionIDs[p]; ok {
|
||||
return id, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// PermissionFromString converts a string representation to its Permission enum
|
||||
func PermissionFromString(s string) (Permission, error) {
|
||||
if p, ok := StringToPermission[s]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission string")
|
||||
}
|
||||
|
||||
// PermissionFromID converts an ID to its Permission enum
|
||||
func PermissionFromID(id int) (Permission, error) {
|
||||
if p, ok := IdToPermission[id]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission ID")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
permissions:
|
||||
- read
|
||||
- full_access
|
||||
@@ -0,0 +1,148 @@
|
||||
package mux
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const muxAPIBaseURL = "https://api.mux.com"
|
||||
|
||||
func makeAPIRequest(client *http.Client, key, secret, method, endpoint string) ([]byte, int, error) {
|
||||
req, err := http.NewRequest(method, muxAPIBaseURL+"/"+endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(key, secret)
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer func() {
|
||||
_, _ = io.Copy(io.Discard, res.Body)
|
||||
_ = res.Body.Close()
|
||||
}()
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return body, res.StatusCode, nil
|
||||
}
|
||||
|
||||
func testAllPermissions(client *http.Client, info *secretInfo, key string, secret string) error {
|
||||
testsConfig, err := readTestsConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, test := range testsConfig.Tests {
|
||||
hasPermission, err := test.testPermission(client, key, secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasPermission {
|
||||
continue
|
||||
}
|
||||
info.addPermission(test.ResourceType, test.Permission)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func populateAllResources(client *http.Client, info *secretInfo, key string, secret string) error {
|
||||
if info.hasPermission(ResourceTypeVideo, Read) {
|
||||
if err := populateAssets(client, info, key, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if info.hasPermission(ResourceTypeData, Read) {
|
||||
if err := populateAnnotations(client, info, key, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if info.hasPermission(ResourceTypeSystem, Read) {
|
||||
if err := populateSigningKeys(client, info, key, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func populateAssets(client *http.Client, info *secretInfo, key string, secret string) error {
|
||||
const limit = 100
|
||||
|
||||
for page := 1; ; page++ {
|
||||
url := fmt.Sprintf("/video/v1/assets?limit=%d&page=%d&timeframe[]=100:days", limit, page)
|
||||
body, statusCode, err := makeAPIRequest(client, key, secret, http.MethodGet, url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if statusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: %d", statusCode)
|
||||
}
|
||||
|
||||
resp := assetListResponse{}
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal data: %w", err)
|
||||
}
|
||||
if len(resp.Data) == 0 {
|
||||
break
|
||||
}
|
||||
info.Assets = append(info.Assets, resp.Data...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func populateAnnotations(client *http.Client, info *secretInfo, key string, secret string) error {
|
||||
const limit = 100
|
||||
|
||||
for page := 1; ; page++ {
|
||||
url := fmt.Sprintf("/data/v1/annotations?limit=%d&page=%d&timeframe[]=100:days", limit, page)
|
||||
body, statusCode, err := makeAPIRequest(client, key, secret, http.MethodGet, url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if statusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: %d", statusCode)
|
||||
}
|
||||
|
||||
resp := annotationListResponse{}
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal data: %w", err)
|
||||
}
|
||||
if len(resp.Data) == 0 {
|
||||
break
|
||||
}
|
||||
info.Annotations = append(info.Annotations, resp.Data...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func populateSigningKeys(client *http.Client, info *secretInfo, key string, secret string) error {
|
||||
const limit = 100
|
||||
|
||||
for page := 1; ; page++ {
|
||||
url := fmt.Sprintf("/system/v1/signing-keys?limit=%d&page=%d&timeframe[]=100:days", limit, page)
|
||||
body, statusCode, err := makeAPIRequest(client, key, secret, http.MethodGet, url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if statusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: %d", statusCode)
|
||||
}
|
||||
|
||||
resp := signingKeyListResponse{}
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal data: %w", err)
|
||||
}
|
||||
if len(resp.Data) == 0 {
|
||||
break
|
||||
}
|
||||
info.SigningKeys = append(info.SigningKeys, resp.Data...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package mux
|
||||
|
||||
import "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
|
||||
var muxResourcesMap map[ResourceType][]string
|
||||
|
||||
func init() {
|
||||
muxResourcesMap = map[ResourceType][]string{
|
||||
ResourceTypeVideo: {
|
||||
"Transcription Vocabularies",
|
||||
"Web Inputs",
|
||||
"Assets",
|
||||
"Live Streams",
|
||||
"Uploads",
|
||||
"Playback Restrictions",
|
||||
"DRM Configurations",
|
||||
},
|
||||
ResourceTypeData: {
|
||||
"Video Views",
|
||||
"Filters",
|
||||
"Dimensions",
|
||||
"Export",
|
||||
"Metrics",
|
||||
"Monitoring",
|
||||
"Realtime",
|
||||
"Incidents",
|
||||
"Annotations",
|
||||
},
|
||||
ResourceTypeSystem: {
|
||||
"Signing Keys",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createAssetResource(asset asset) analyzers.Resource {
|
||||
return analyzers.Resource{
|
||||
Name: asset.ID,
|
||||
FullyQualifiedName: "asset/" + asset.ID,
|
||||
Type: "asset",
|
||||
Metadata: map[string]any{
|
||||
"duration": asset.Duration,
|
||||
"status": asset.Status,
|
||||
"videoQuality": asset.VideoQuality,
|
||||
"mp4Support": asset.MP4Support,
|
||||
"aspectRatio": asset.AspectRatio,
|
||||
"createdAt": asset.CreatedAt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createAssetTrackResources(asset asset, parent *analyzers.Resource) []analyzers.Resource {
|
||||
trackResources := []analyzers.Resource{}
|
||||
for _, track := range asset.Tracks {
|
||||
trackResources = append(trackResources, analyzers.Resource{
|
||||
Name: track.ID,
|
||||
FullyQualifiedName: "asset/" + asset.ID + "/track/" + track.ID,
|
||||
Type: "track",
|
||||
Metadata: map[string]any{
|
||||
"name": track.Name,
|
||||
"type": track.Type,
|
||||
"duration": track.Duration,
|
||||
"status": track.Status,
|
||||
"primary": track.Primary,
|
||||
"textType": track.TextType,
|
||||
"textSource": track.TextSource,
|
||||
"languageCode": track.LanguageCode,
|
||||
"maxWidth": track.MaxWidth,
|
||||
"maxHeight": track.MaxHeight,
|
||||
"maxFrameRate": track.MaxFrameRate,
|
||||
"maxChannels": track.MaxChannels,
|
||||
},
|
||||
Parent: parent,
|
||||
})
|
||||
}
|
||||
return trackResources
|
||||
}
|
||||
|
||||
func createAssetPlaybackIDResources(asset asset, parent *analyzers.Resource) []analyzers.Resource {
|
||||
playbackIDResources := []analyzers.Resource{}
|
||||
for _, playbackID := range asset.PlaybackIDs {
|
||||
playbackIDResources = append(playbackIDResources, analyzers.Resource{
|
||||
Name: playbackID.ID,
|
||||
FullyQualifiedName: "asset/" + asset.ID + "/playback_id/" + playbackID.ID,
|
||||
Type: "playback_id",
|
||||
Metadata: map[string]any{
|
||||
"policy": playbackID.Policy,
|
||||
},
|
||||
Parent: parent,
|
||||
})
|
||||
}
|
||||
return playbackIDResources
|
||||
}
|
||||
|
||||
func createAnnotationResource(annotation annotation) analyzers.Resource {
|
||||
return analyzers.Resource{
|
||||
Name: annotation.ID,
|
||||
FullyQualifiedName: "annotation/" + annotation.ID,
|
||||
Type: "annotation",
|
||||
Metadata: map[string]any{
|
||||
"subPropertyID": annotation.SubPropertyID,
|
||||
"note": annotation.Note,
|
||||
"date": annotation.Date,
|
||||
},
|
||||
}
|
||||
}
|
||||
func createSigningKeyResource(signingKey signingKey) analyzers.Resource {
|
||||
return analyzers.Resource{
|
||||
Name: signingKey.ID,
|
||||
FullyQualifiedName: "signing_key/" + signingKey.ID,
|
||||
Type: "signing_key",
|
||||
Metadata: map[string]any{
|
||||
"createdAt": signingKey.CreatedAt,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"tests": [
|
||||
{
|
||||
"resource_type": "video",
|
||||
"permission": "read",
|
||||
"endpoint": "/video/v1/assets?limit=1",
|
||||
"method": "GET",
|
||||
"valid_status_code": 200
|
||||
},
|
||||
{
|
||||
"resource_type": "video",
|
||||
"permission": "write",
|
||||
"endpoint": "/video/v1/assets",
|
||||
"method": "POST",
|
||||
"valid_status_code": 400
|
||||
},
|
||||
{
|
||||
"resource_type": "data",
|
||||
"permission": "read",
|
||||
"endpoint": "/data/v1/annotations?limit=1",
|
||||
"method": "GET",
|
||||
"valid_status_code": 200
|
||||
},
|
||||
{
|
||||
"resource_type": "data",
|
||||
"permission": "write",
|
||||
"endpoint": "/data/v1/annotations",
|
||||
"method": "POST",
|
||||
"valid_status_code": 400
|
||||
},
|
||||
{
|
||||
"resource_type": "system",
|
||||
"permission": "read",
|
||||
"endpoint": "/system/v1/signing-keys?limit=1",
|
||||
"method": "GET",
|
||||
"valid_status_code": 200
|
||||
},
|
||||
{
|
||||
"resource_type": "system",
|
||||
"permission": "write",
|
||||
"endpoint": "/system/v1/signing-keys",
|
||||
"method": "DELETE",
|
||||
"valid_status_code": 400
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mailchimp"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mailgun"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/monday"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mux"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mysql"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/netlify"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/ngrok"
|
||||
@@ -133,5 +134,7 @@ func Run(keyType string, secretInfo SecretInfo) {
|
||||
monday.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "ngrok":
|
||||
ngrok.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "mux":
|
||||
mux.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"], secretInfo.Parts["secret"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
s1.Verified = true
|
||||
}
|
||||
}
|
||||
if s1.Verified {
|
||||
s1.AnalysisInfo = map[string]string{
|
||||
"key": resMatch,
|
||||
"secret": resSecretMatch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
|
||||
@@ -95,6 +95,17 @@ func New(c common.Common, keyType string) *AnalyzeForm {
|
||||
Required: true,
|
||||
RedactInput: true,
|
||||
}}
|
||||
case "mux":
|
||||
inputs = []textinputs.InputConfig{{
|
||||
Label: "Secret",
|
||||
Key: "secret",
|
||||
Required: true,
|
||||
RedactInput: true,
|
||||
}, {
|
||||
Label: "Key",
|
||||
Key: "key",
|
||||
Required: true,
|
||||
}}
|
||||
default:
|
||||
inputs = []textinputs.InputConfig{{
|
||||
Label: "Secret",
|
||||
|
||||
Reference in New Issue
Block a user