Skip to content

Commit f3237c5

Browse files
optimized and updated mailgun analyzer (#3899)
1 parent 943daae commit f3237c5

File tree

3 files changed

+189
-83
lines changed

3 files changed

+189
-83
lines changed
Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
{
22
"AnalyzerType": 8,
33
"Bindings": [
4-
{
5-
"Resource": {
6-
"Name": "sandbox19e49763d44e498e850589ea7d54bd82.mailgun.org",
7-
"FullyQualifiedName": "mailgun/6478cb31d026c112819856cd/sandbox19e49763d44e498e850589ea7d54bd82.mailgun.org",
8-
"Type": "domain",
9-
"Metadata": {
10-
"created_at": "Thu, 01 Jun 2023 16:45:37 GMT",
11-
"is_disabled": false,
12-
"state": "active",
13-
"type": "sandbox"
14-
},
15-
"Parent": null
16-
},
17-
"Permission": {
18-
"Value": "full_access",
19-
"Parent": null
20-
}
21-
}
4+
{
5+
"Resource": {
6+
"Name": "sandbox19e49763d44e498e850589ea7d54bd82.mailgun.org",
7+
"FullyQualifiedName": "mailgun/6478cb31d026c112819856cd/sandbox19e49763d44e498e850589ea7d54bd82.mailgun.org",
8+
"Type": "domain",
9+
"Metadata": {
10+
"created_at": "Thu, 01 Jun 2023 16:45:37 GMT",
11+
"is_disabled": false,
12+
"state": "active",
13+
"type": "sandbox"
14+
},
15+
"Parent": null
16+
},
17+
"Permission": {
18+
"Value": "full_access",
19+
"Parent": null
20+
}
21+
}
2222
],
2323
"UnboundedResources": null,
2424
"Metadata": null
25-
}
25+
}

pkg/analyzer/analyzers/mailgun/mailgun.go

Lines changed: 46 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
package mailgun
33

44
import (
5-
"encoding/json"
65
"errors"
7-
"fmt"
8-
"net/http"
96
"os"
107
"strconv"
118

@@ -22,6 +19,15 @@ type Analyzer struct {
2219
Cfg *config.Config
2320
}
2421

22+
type SecretInfo struct {
23+
ID string // key id
24+
UserName string
25+
Type string // type of key
26+
Role string // key role
27+
ExpiresAt string // key expiry time if any
28+
Domains []Domain
29+
}
30+
2531
func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeMailgun }
2632

2733
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
@@ -34,19 +40,20 @@ func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analy
3440
if err != nil {
3541
return nil, err
3642
}
43+
3744
return secretInfoToAnalyzerResult(info), nil
3845
}
3946

40-
func secretInfoToAnalyzerResult(info *DomainsJSON) *analyzers.AnalyzerResult {
47+
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
4148
if info == nil {
4249
return nil
4350
}
4451
result := analyzers.AnalyzerResult{
4552
AnalyzerType: analyzers.AnalyzerTypeMailgun,
46-
Bindings: make([]analyzers.Binding, len(info.Items)),
53+
Bindings: make([]analyzers.Binding, len(info.Domains)),
4754
}
4855

49-
for idx, domain := range info.Items {
56+
for idx, domain := range info.Domains {
5057
result.Bindings[idx] = analyzers.Binding{
5158
Resource: analyzers.Resource{
5259
Name: domain.URL,
@@ -59,6 +66,7 @@ func secretInfoToAnalyzerResult(info *DomainsJSON) *analyzers.AnalyzerResult {
5966
"is_disabled": domain.IsDisabled,
6067
},
6168
},
69+
6270
Permission: analyzers.Permission{
6371
Value: PermissionStrings[FullAccess],
6472
},
@@ -67,87 +75,60 @@ func secretInfoToAnalyzerResult(info *DomainsJSON) *analyzers.AnalyzerResult {
6775
return &result
6876
}
6977

70-
type Domain struct {
71-
ID string `json:"id"`
72-
URL string `json:"name"`
73-
IsDisabled bool `json:"is_disabled"`
74-
Type string `json:"type"`
75-
State string `json:"state"`
76-
CreatedAt string `json:"created_at"`
77-
}
78-
79-
type DomainsJSON struct {
80-
Items []Domain `json:"items"`
81-
TotalCount int `json:"total_count"`
82-
}
83-
84-
func getDomains(cfg *config.Config, apiKey string) (DomainsJSON, int, error) {
85-
var domainsJSON DomainsJSON
86-
87-
client := analyzers.NewAnalyzeClient(cfg)
88-
req, err := http.NewRequest("GET", "https://api.mailgun.net/v4/domains", nil)
78+
func AnalyzeAndPrintPermissions(cfg *config.Config, apiKey string) {
79+
info, err := AnalyzePermissions(cfg, apiKey)
8980
if err != nil {
90-
return domainsJSON, -1, err
81+
color.Red("[x] %s", err.Error())
82+
return
9183
}
9284

93-
req.SetBasicAuth("api", apiKey)
94-
resp, err := client.Do(req)
95-
if err != nil {
96-
return domainsJSON, -1, err
97-
}
85+
color.Green("[i] Valid Mailgun API key\n\n")
86+
printKeyInfo(info)
87+
printDomains(info.Domains)
88+
color.Yellow("[i] Permissions: Full Access\n\n")
89+
}
9890

99-
if resp.StatusCode != 200 {
100-
return domainsJSON, resp.StatusCode, nil
101-
}
91+
func AnalyzePermissions(cfg *config.Config, apiKey string) (*SecretInfo, error) {
92+
var secretInfo SecretInfo
10293

103-
defer resp.Body.Close()
94+
var client = analyzers.NewAnalyzeClient(cfg)
10495

105-
err = json.NewDecoder(resp.Body).Decode(&domainsJSON)
106-
if err != nil {
107-
return domainsJSON, resp.StatusCode, err
96+
if err := getDomains(client, apiKey, &secretInfo); err != nil {
97+
return &secretInfo, err
10898
}
109-
return domainsJSON, resp.StatusCode, nil
110-
}
11199

112-
func AnalyzeAndPrintPermissions(cfg *config.Config, apiKey string) {
113-
data, err := AnalyzePermissions(cfg, apiKey)
114-
if err != nil {
115-
color.Red("[x] %s", err.Error())
116-
return
100+
if err := getKeys(client, apiKey, &secretInfo); err != nil {
101+
return &secretInfo, err
117102
}
118103

119-
printMetadata(data)
104+
return &secretInfo, nil
120105
}
121106

122-
func AnalyzePermissions(cfg *config.Config, apiKey string) (*DomainsJSON, error) {
123-
// Get the domains associated with the API key
124-
domains, statusCode, err := getDomains(cfg, apiKey)
125-
if err != nil {
126-
return nil, fmt.Errorf("Error getting domains: %s", err)
107+
func printKeyInfo(info *SecretInfo) {
108+
if info.ID == "" {
109+
color.Red("[i] Key information not found")
110+
return
127111
}
128112

129-
if statusCode != 200 {
130-
return nil, fmt.Errorf("Invalid Mailgun API key.")
131-
}
132-
color.Green("[i] Valid Mailgun API key\n\n")
133-
color.Green("[i] Permissions: Full Access\n\n")
134-
135-
return &domains, nil
113+
t := table.NewWriter()
114+
t.SetOutputMirror(os.Stdout)
115+
t.AppendHeader(table.Row{"Key ID", "UserName/Requester", "Key Type", "Expires At", "Role"})
116+
t.AppendRow(table.Row{info.ID, info.UserName, info.Type, info.ExpiresAt, info.Role})
117+
t.Render()
136118
}
137-
138-
func printMetadata(domains *DomainsJSON) {
139-
if domains.TotalCount == 0 {
119+
func printDomains(domains []Domain) {
120+
if len(domains) == 0 {
140121
color.Red("[i] No domains found")
141122
return
142123
}
143-
color.Yellow("[i] Found %d domain(s)", domains.TotalCount)
124+
125+
color.Yellow("[i] Found %d domain(s)", len(domains))
144126

145127
t := table.NewWriter()
146128
t.SetOutputMirror(os.Stdout)
147129
t.AppendHeader(table.Row{"Domain", "Type", "State", "Created At", "Disabled"})
148130

149-
for _, domain := range domains.Items {
150-
131+
for _, domain := range domains {
151132
var colorFunc func(format string, a ...interface{}) string
152133
switch {
153134
case domain.IsDisabled:
@@ -166,5 +147,6 @@ func printMetadata(domains *DomainsJSON) {
166147
colorFunc(strconv.FormatBool(domain.IsDisabled)),
167148
})
168149
}
150+
169151
t.Render()
170152
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package mailgun
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"strings"
8+
)
9+
10+
// DomainsJSON is /domains API response
11+
type DomainsJSON struct {
12+
Items []Domain `json:"items"`
13+
TotalCount int `json:"total_count"`
14+
}
15+
16+
// Domain is a single mailgun domain details
17+
type Domain struct {
18+
ID string `json:"id"`
19+
URL string `json:"name"`
20+
IsDisabled bool `json:"is_disabled"`
21+
Type string `json:"type"`
22+
State string `json:"state"`
23+
CreatedAt string `json:"created_at"`
24+
}
25+
26+
// KeysJSON is /v1/keys API response
27+
type KeysJSON struct {
28+
Items []Key `json:"items"`
29+
TotalCount int `json:"total_count"`
30+
}
31+
32+
// Key is a single mailgun Key details
33+
type Key struct {
34+
ID string `json:"id"`
35+
Requester string `json:"requestor"`
36+
UserName string `json:"user_name"`
37+
Role string `json:"role"`
38+
Type string `json:"kind"`
39+
ExpiresAt string `json:"expires_at"`
40+
}
41+
42+
// getDomains list all domains
43+
func getDomains(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
44+
var domainsJSON DomainsJSON
45+
46+
req, err := http.NewRequest("GET", "https://api.mailgun.net/v4/domains", nil)
47+
if err != nil {
48+
return err
49+
}
50+
51+
req.SetBasicAuth("api", apiKey)
52+
resp, err := client.Do(req)
53+
if err != nil {
54+
return err
55+
}
56+
defer resp.Body.Close()
57+
58+
if resp.StatusCode != 200 {
59+
return fmt.Errorf("invalid Mailgun API key")
60+
}
61+
62+
err = json.NewDecoder(resp.Body).Decode(&domainsJSON)
63+
if err != nil {
64+
return err
65+
}
66+
67+
// populate secretInfo with domains
68+
secretInfo.Domains = append(secretInfo.Domains, domainsJSON.Items...)
69+
70+
return nil
71+
}
72+
73+
func getKeys(client *http.Client, apiKey string, secretInfo *SecretInfo) error {
74+
var keysJSON KeysJSON
75+
76+
req, err := http.NewRequest("GET", "https://api.mailgun.net/v1/keys", nil)
77+
if err != nil {
78+
return err
79+
}
80+
81+
req.SetBasicAuth("api", apiKey)
82+
resp, err := client.Do(req)
83+
if err != nil {
84+
return err
85+
}
86+
defer resp.Body.Close()
87+
88+
if resp.StatusCode != 200 {
89+
return fmt.Errorf("invalid Mailgun API key")
90+
}
91+
92+
err = json.NewDecoder(resp.Body).Decode(&keysJSON)
93+
if err != nil {
94+
return err
95+
}
96+
97+
// populate secretInfo with key details
98+
for _, key := range keysJSON.Items {
99+
// filter the exact key which we are analyzing
100+
// ID is actually the suffix of actual apiKeys
101+
if strings.Contains(apiKey, key.ID) {
102+
keyToSecretInfo(key, secretInfo)
103+
}
104+
}
105+
106+
return nil
107+
}
108+
109+
func keyToSecretInfo(key Key, secretInfo *SecretInfo) {
110+
secretInfo.ID = key.ID
111+
if key.UserName != "" {
112+
secretInfo.UserName = key.UserName
113+
} else {
114+
secretInfo.UserName = key.Requester
115+
}
116+
117+
secretInfo.Role = key.Role
118+
secretInfo.Type = key.Type
119+
if secretInfo.ExpiresAt != "" {
120+
secretInfo.ExpiresAt = key.ExpiresAt
121+
} else {
122+
secretInfo.ExpiresAt = "Never"
123+
}
124+
}

0 commit comments

Comments
 (0)