Skip to content

Commit

Permalink
[analyze] Add Analyzer for Mailgun (#3206)
Browse files Browse the repository at this point in the history
* implement analyzer interface, add unit test and link with detector for mailgun

* [chore] moved expected output of test in json file to neat the code.
corrected variable name for test in detector bucket

* append domain id in fully qualified name of domain resources

* [Fixes]
domains will be added as resource in bindings and permissions.
updated the test.

---------

Co-authored-by: Abdul Basit <[email protected]>
  • Loading branch information
abmussani and abasit-folio3 authored Sep 12, 2024
1 parent b0318a9 commit 57e5812
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 4 deletions.
25 changes: 25 additions & 0 deletions pkg/analyzer/analyzers/mailgun/expected_output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"AnalyzerType": 8,
"Bindings": [
{
"Resource": {
"Name": "sandbox19e49763d44e498e850589ea7d54bd82.mailgun.org",
"FullyQualifiedName": "mailgun/6478cb31d026c112819856cd/sandbox19e49763d44e498e850589ea7d54bd82.mailgun.org",
"Type": "domain",
"Metadata": {
"created_at": "Thu, 01 Jun 2023 16:45:37 GMT",
"is_disabled": false,
"state": "active",
"type": "sandbox"
},
"Parent": null
},
"Permission": {
"Value": "full_access",
"Parent": null
}
}
],
"UnboundedResources": null,
"Metadata": null
}
42 changes: 40 additions & 2 deletions pkg/analyzer/analyzers/mailgun/mailgun.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//go:generate generate_permissions permissions.yaml permissions.go mailgun
package mailgun

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
Expand All @@ -24,14 +26,50 @@ type Analyzer struct {
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Mailgun }

func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
_, err := AnalyzePermissions(a.Cfg, credInfo["key"])
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 nil, fmt.Errorf("not implemented")
return secretInfoToAnalyzerResult(info), nil
}

func secretInfoToAnalyzerResult(info *DomainsJSON) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzerpb.AnalyzerType_Mailgun,
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"`
Expand Down
100 changes: 100 additions & 0 deletions pkg/analyzer/analyzers/mailgun/mailgun_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package mailgun

import (
_ "embed"
"encoding/json"
"sort"
"testing"
"time"

"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"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.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}

tests := []struct {
name string
key string
want string // JSON string
wantErr bool
}{
{
name: "valid Mailgun key",
key: testSecrets.MustGetField("NEW_MAILGUN_TOKEN_ACTIVE"),
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})
if (err != nil) != tt.wantErr {
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
return
}

// bindings need to be in the same order to be comparable
sortBindings(got.Bindings)

// Marshal the actual result to JSON
gotJSON, err := json.Marshal(got)
if err != nil {
t.Fatalf("could not marshal got to JSON: %s", err)
}

// Parse the expected JSON string
var wantObj analyzers.AnalyzerResult
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
t.Fatalf("could not unmarshal want JSON string: %s", err)
}

// bindings need to be in the same order to be comparable
sortBindings(wantObj.Bindings)

// Marshal the expected result to JSON (to normalize)
wantJSON, err := json.Marshal(wantObj)
if err != nil {
t.Fatalf("could not marshal want to JSON: %s", err)
}

// Compare the JSON strings
if string(gotJSON) != string(wantJSON) {
// 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(wantObj, "", " ")
if err != nil {
t.Fatalf("could not marshal want to indented JSON: %s", err)
}
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
}
})
}
}

// Helper function to sort bindings
func sortBindings(bindings []analyzers.Binding) {
sort.SliceStable(bindings, func(i, j int) bool {
if bindings[i].Resource.Name == bindings[j].Resource.Name {
return bindings[i].Permission.Value < bindings[j].Permission.Value
}
return bindings[i].Resource.Name < bindings[j].Resource.Name
})
}
71 changes: 71 additions & 0 deletions pkg/analyzer/analyzers/mailgun/permissions.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pkg/analyzer/analyzers/mailgun/permissions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
permissions:
- read
- write
- full_access
6 changes: 4 additions & 2 deletions pkg/detectors/mailgun/mailgun.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ package mailgun
import (
"context"
"fmt"
regexp "github.com/wasilibs/go-re2"
"net/http"
"strings"

regexp "github.com/wasilibs/go-re2"

"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)

type Scanner struct{
type Scanner struct {
detectors.DefaultMultiPartCredentialProvider
}

Expand Down Expand Up @@ -72,6 +73,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1.Verified = true
}
}
s1.AnalysisInfo = map[string]string{"key": resMatch}

}

Expand Down
1 change: 1 addition & 0 deletions pkg/detectors/mailgun/mailgun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func TestMailgun_FromChunk(t *testing.T) {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
got[i].Raw = nil
got[i].AnalysisInfo = nil
}
if diff := pretty.Compare(got, tt.want); diff != "" {
t.Errorf("Mailgun.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
Expand Down

0 comments on commit 57e5812

Please sign in to comment.