Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(SASL-Auth): Now SMTP output may use any SASL Auth mechanisms #341

Merged
merged 1 commit into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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