diff --git a/README.md b/README.md index 25eb7a41d..9a2495983 100644 --- a/README.md +++ b/README.md @@ -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 @@ -687,8 +691,6 @@ 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) @@ -696,8 +698,13 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : - **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 diff --git a/config.go b/config.go index 5865fbba6..8efe7cba3 100644 --- a/config.go +++ b/config.go @@ -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", "") diff --git a/config_example.yaml b/config_example.yaml index 5f6b7b5cb..35ab72691 100644 --- a/config_example.yaml +++ b/config_example.yaml @@ -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 diff --git a/outputs/client.go b/outputs/client.go index dcde24ae5..3f7f480bd 100644 --- a/outputs/client.go +++ b/outputs/client.go @@ -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 diff --git a/outputs/constants.go b/outputs/constants.go index a447879ec..2242d10b4 100644 --- a/outputs/constants.go +++ b/outputs/constants.go @@ -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" ) diff --git a/outputs/smtp.go b/outputs/smtp.go index f0c07f35d..1dde52525 100644 --- a/outputs/smtp.go +++ b/outputs/smtp.go @@ -4,7 +4,9 @@ import ( "bytes" htmlTemplate "html/template" "log" + "net" "regexp" + "strconv" "strings" textTemplate "text/template" @@ -17,6 +19,7 @@ import ( // SMTPPayload is payload for SMTP Output type SMTPPayload struct { + From string To string Subject string Body string @@ -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, } @@ -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 } diff --git a/types/types.go b/types/types.go index fec332844..a817cdb5f 100644 --- a/types/types.go +++ b/types/types.go @@ -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