Skip to content

Commit

Permalink
feat(SMTP-SASL-Auth): Now SMTP output may use any SASL mechanisms: No…
Browse files Browse the repository at this point in the history
…ne, Plain, OAuth-Bearer, External or Anonymous. Default to Plain.

Updates:
- Fixing the SMTP Format by adding the 'From' header

Signed-off-by: Lyonel Martinez <[email protected]>
  • Loading branch information
Lowaiz authored and poiana committed Jul 6, 2022
1 parent c5a74ce commit bbbce46
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 17 deletions.
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,12 @@ aws:

smtp:
# hostport: "" # host:port address of SMTP server, if not empty, SMTP output is enabled
# user: "" # user to access SMTP server
# password: "" # password to access SMTP server
# authmechanism: "plain" # SASL Mechanisms : plain, oauthbearer, external, anonymous or "" (disable SASL). Default: plain
# user: "" # user for Plain Mechanism
# password: "" # password for Plain Mechanism
# token: "" # OAuthBearer token for OAuthBearer Mechanism
# identity: "" # identity string for Plain and External Mechanisms
# trace: "" trace string for Anonymous Mechanism
# from: "" # Sender address (mandatory if SMTP output is enabled)
# to: "" # comma-separated list of Recipident addresses, can't be empty (mandatory if SMTP output is enabled)
# outputformat: "" # html (default), text
Expand Down Expand Up @@ -687,17 +691,20 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` :
`emergency|alert|critical|error|warning|notice|informational|debug or "" (default)`
- **SMTP_HOSTPORT** : "host:port" address of SMTP server, if not empty, SMTP
output is _enabled_
- **SMTP_USER** : user to access SMTP server
- **SMTP_PASSWORD** : password to access SMTP server
- **SMTP_FROM** : Sender address (mandatory if SMTP output is enabled)
- **SMTP_TO** : comma-separated list of Recipident addresses, can't be empty
(mandatory if SMTP output is enabled)
- **SMTP_OUTPUTFORMAT** : "" # html (default), text
- **SMTP_MINIMUMPRIORITY** : minimum priority of event for using this output,
order is
`emergency|alert|critical|error|warning|notice|informational|debug or "" (default)`
- **OPSGENIE_APIKEY** : Opsgenie API Key, if not empty, Opsgenie output is
_enabled_
- **SMTP_AUTHMECHANISM** : SASL Mechanisms `plain|oauthbearer|external|anonymous or "" (disable SASL)` Default to `plain`
- **SMTP_USER** : user for Plain Mechanism
- **SMTP_PASSWORD** : password for Plain Mechanism
- **SMTP_TOKEN** : # OAuthBearer token for OAuthBearer Mechanism
- **SMTP_IDENTITY** : identity string for Plain and External Mechanisms
- **SMTP_TRACE** : trace string for Anonymous Mechanism
- **OPSGENIE_APIKEY** : Opsgenie API Key, if not empty, Opsgenie output is _enabled_
- **OPSGENIE_REGION** : (us|eu) region of your domain (default is 'us')
- **OPSGENIE_MINIMUMPRIORITY** : minimum priority of event for using this
output, order is
Expand Down
9 changes: 7 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,18 @@ func getConfig() *types.Configuration {
v.SetDefault("AWS.Kinesis.MinimumPriority", "")

v.SetDefault("SMTP.HostPort", "")
v.SetDefault("SMTP.User", "")
v.SetDefault("SMTP.Password", "")
v.SetDefault("SMTP.From", "")
v.SetDefault("SMTP.To", "")
v.SetDefault("SMTP.OutputFormat", "html")
v.SetDefault("SMTP.MinimumPriority", "")

v.SetDefault("SMTP.AuthMechanism", "plain")
v.SetDefault("SMTP.User", "")
v.SetDefault("SMTP.Password", "")
v.SetDefault("SMTP.Token", "")
v.SetDefault("SMTP.Identity", "")
v.SetDefault("SMTP.Trace", "")

v.SetDefault("STAN.HostPort", "")
v.SetDefault("STAN.ClusterID", "")
v.SetDefault("STAN.ClientID", "")
Expand Down
8 changes: 6 additions & 2 deletions config_example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,12 @@ aws:

smtp:
# hostport: "" # host:port address of SMTP server, if not empty, SMTP output is enabled
# user: "" # user to access SMTP server
# password: "" # password to access SMTP server
# authmechanism: "plain" # SASL Mechanisms : plain, oauthbearer, external, anonymous or "" (disable SASL). Default: plain
# user: "" # user for Plain Mechanism
# password: "" # password for Plain Mechanism
# token: "" # OAuthBearer token for OAuthBearer Mechanism
# identity: "" # identity string for Plain and External Mechanisms
# trace: "" trace string for Anonymous Mechanism
# from: "" # Sender address (mandatory if SMTP output is enabled)
# to: "" # comma-separated list of Recipident addresses, can't be empty (mandatory if SMTP output is enabled)
# outputformat: "" # html (default), text
Expand Down
2 changes: 2 additions & 0 deletions outputs/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ var ErrTooManyRequest = errors.New("exceeding post rate limit")
// ErrClientCreation is returned if client can't be created
var ErrClientCreation = errors.New("client creation Error")

var ErrSASLAuthCreation = errors.New("sasl auth: wrong mechanism")

// EnabledOutputs list all enabled outputs
var EnabledOutputs []string

Expand Down
6 changes: 6 additions & 0 deletions outputs/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,10 @@ const (

UDP string = "udp"
TCP string = "tcp"

// SASL Auth mechanisms for SMTP
Plain string = "plain"
OAuthBearer string = "oauthbearer"
External string = "external"
Anonymous string = "anonymous"
)
56 changes: 49 additions & 7 deletions outputs/smtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"bytes"
htmlTemplate "html/template"
"log"
"net"
"regexp"
"strconv"
"strings"
textTemplate "text/template"

Expand All @@ -17,6 +19,7 @@ import (

// SMTPPayload is payload for SMTP Output
type SMTPPayload struct {
From string
To string
Subject string
Body string
Expand All @@ -42,6 +45,7 @@ func NewSMTPClient(config *types.Configuration, stats *types.Statistics, promSta

func newSMTPPayload(falcopayload types.FalcoPayload, config *types.Configuration) SMTPPayload {
s := SMTPPayload{
From: "From: " + config.SMTP.From,
To: "To: " + config.SMTP.To,
Subject: "Subject: [" + falcopayload.Priority.String() + "] " + falcopayload.Output,
}
Expand Down Expand Up @@ -83,24 +87,62 @@ func newSMTPPayload(falcopayload types.FalcoPayload, config *types.Configuration
return s
}

func (c *Client) ReportErr(message string, err error) {
go c.CountMetric("outputs", 1, []string{"output:smtp", "status:error"})
c.Stats.SMTP.Add(Error, 1)
log.Printf("[ERROR] : SMTP - %s : %v\n", message, err)
}

func (c *Client) GetAuth() (sasl.Client, error) {
if c.Config.SMTP.AuthMechanism == "" {
return nil, nil
}
var authClient sasl.Client
switch strings.ToLower(c.Config.SMTP.AuthMechanism) {
case Plain:
authClient = sasl.NewPlainClient(c.Config.SMTP.Identity, c.Config.SMTP.User, c.Config.SMTP.Password)
case OAuthBearer:
host, portString, _ := net.SplitHostPort(c.Config.SMTP.HostPort)
port, err := strconv.Atoi(portString)
if err != nil {
return nil, err
}
authClient = sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{Username: c.Config.SMTP.User, Token: c.Config.SMTP.Token, Host: host, Port: port})
case External:
authClient = sasl.NewExternalClient(c.Config.SMTP.Identity)
case Anonymous:
authClient = sasl.NewAnonymousClient(c.Config.SMTP.Trace)
default:
return nil, ErrSASLAuthCreation
}
return authClient, nil
}

// SendMail sends email to SMTP server
func (c *Client) SendMail(falcopayload types.FalcoPayload) {
sp := newSMTPPayload(falcopayload, c.Config)

to := strings.Split(strings.Replace(c.Config.SMTP.To, " ", "", -1), ",")
auth := sasl.NewPlainClient("", c.Config.SMTP.User, c.Config.SMTP.Password)
body := sp.To + "\n" + sp.Subject + "\n" + sp.Body
auth, err := c.GetAuth()
if err != nil {
c.ReportErr("SASL Authentication mechanisms", err)
return
}
body := sp.To + "\n" + sp.Subject + "\n" + sp.From + "\n" + sp.Body

if c.Config.Debug {
log.Printf("[DEBUG] : SMTP payload : \nServer: %v\nFrom: %v\nTo: %v\nSubject: %v\n", c.Config.SMTP.HostPort, c.Config.SMTP.From, sp.To, sp.Subject)
log.Printf("[DEBUG] : SMTP payload : \nServer: %v\n%v\n%v\nSubject: %v\n", c.Config.SMTP.HostPort, sp.From, sp.To, sp.Subject)
if c.Config.SMTP.AuthMechanism != "" {
log.Printf("[DEBUG] : SMTP - SASL Auth : \nMechanisms: %v\nUser: %v\nToken: %v\nIdentity: %v\nTrace: %v\n", c.Config.SMTP.AuthMechanism, c.Config.SMTP.User, c.Config.SMTP.Token, c.Config.SMTP.Identity, c.Config.SMTP.Trace)
} else {
log.Printf("[DEBUG] : SMTP - SASL Auth : Disabled\n")
}
}

c.Stats.SMTP.Add("total", 1)
err := smtp.SendMail(c.Config.SMTP.HostPort, auth, c.Config.SMTP.From, to, strings.NewReader(body))
err = smtp.SendMail(c.Config.SMTP.HostPort, auth, c.Config.SMTP.From, to, strings.NewReader(body))
if err != nil {
go c.CountMetric("outputs", 1, []string{"output:smtp", "status:error"})
c.Stats.SMTP.Add(Error, 1)
log.Printf("[ERROR] : SMTP - %v\n", err)
c.ReportErr("Send Mail failure", err)
return
}

Expand Down
4 changes: 4 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,12 @@ type awsKinesisConfig struct {

type smtpOutputConfig struct {
HostPort string
AuthMechanism string
User string
Password string
Token string
Identity string
Trace string
From string
To string
OutputFormat string
Expand Down

0 comments on commit bbbce46

Please sign in to comment.