fix(notifier): use sane default connection timeout (#2273)

This commit is contained in:
James Elliott 2021-08-10 10:52:41 +10:00 committed by GitHub
parent e2ebdb7e41
commit c0ebe3eb8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 144 additions and 116 deletions

View File

@ -541,20 +541,39 @@ notifier:
## - validate the SMTP server x509 certificate during the TLS handshake against the hosts trusted certificates
## (configure in tls section)
smtp:
username: test
## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
password: password
## The SMTP host to connect to.
host: 127.0.0.1
## The port to connect to the SMTP host on.
port: 1025
## The connection timeout.
timeout: 5s
## The username used for SMTP authentication.
username: test
## The password used for SMTP authentication.
## Can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
password: password
## The address to send the email FROM.
sender: admin@example.com
## HELO/EHLO Identifier. Some SMTP Servers may reject the default of localhost.
identifier: localhost
## Subject configuration of the emails sent. {title} is replaced by the text from the notifier.
subject: "[Authelia] {title}"
## This address is used during the startup check to verify the email configuration is correct.
## It's not important what it is except if your email server only allows local delivery.
startup_check_address: test@authelia.com
## By default we require some form of TLS. This disables this check though is not advised.
disable_require_tls: false
## Disables sending HTML formatted emails.
disable_html_emails: false
tls:
@ -569,16 +588,6 @@ notifier:
## Minimum TLS version for either StartTLS or SMTPS.
minimum_version: TLS1.2
## Sending an email using a Gmail account is as simple as the next section.
## You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en
# smtp:
# username: myaccount@gmail.com
# ## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
# password: yourapppassword
# sender: admin@example.com
# host: smtp.gmail.com
# port: 587
##
## Identity Providers
##

View File

@ -16,10 +16,11 @@ It can be configured as described below.
notifier:
disable_startup_check: false
smtp:
username: test
password: password
host: 127.0.0.1
port: 1025
timeout: 5s
username: test
password: password
sender: admin@example.com
identifier: localhost
subject: "[Authelia] {title}"
@ -34,27 +35,6 @@ notifier:
## Options
### username
<div markdown="1">
type: string
{: .label .label-config .label-purple }
required: no
{: .label .label-config .label-green }
</div>
The username sent for authentication with the SMTP server. Paired with the password.
### password
<div markdown="1">
type: string
{: .label .label-config .label-purple }
required: no
{: .label .label-config .label-green }
</div>
The password sent for authentication with the SMTP server. Paired with the username. Can also be defined using a
[secret](../secrets.md) which is the recommended for containerized deployments.
### host
<div markdown="1">
type: integer
@ -82,6 +62,39 @@ required: yes
The port the SMTP service is listening on.
### timeout
<div markdown="1">
type: duration
{: .label .label-config .label-purple }
default: 5s
{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-green }
</div>
The SMTP connection timeout.
### username
<div markdown="1">
type: string
{: .label .label-config .label-purple }
required: no
{: .label .label-config .label-green }
</div>
The username sent for authentication with the SMTP server. Paired with the password.
### password
<div markdown="1">
type: string
{: .label .label-config .label-purple }
required: no
{: .label .label-config .label-green }
</div>
The password sent for authentication with the SMTP server. Paired with the username. Can also be defined using a
[secret](../secrets.md) which is the recommended for containerized deployments.
### sender
<div markdown="1">
type: string
@ -93,7 +106,7 @@ required: no
The address sent in the FROM header for the email. Basically who the email appears to come from. It should be noted
that some SMTP servers require the username provided to have access to send from the specific address listed here.
### identifer
### identifier
<div markdown="1">
type: string
{: .label .label-config .label-purple }

View File

@ -120,7 +120,7 @@ func getProviders(config *schema.Configuration) (providers middlewares.Providers
switch {
case config.Notifier.SMTP != nil:
notifier = notification.NewSMTPNotifier(*config.Notifier.SMTP, autheliaCertPool)
notifier = notification.NewSMTPNotifier(config.Notifier.SMTP, autheliaCertPool)
case config.Notifier.FileSystem != nil:
notifier = notification.NewFileNotifier(*config.Notifier.FileSystem)
default:

View File

@ -541,20 +541,39 @@ notifier:
## - validate the SMTP server x509 certificate during the TLS handshake against the hosts trusted certificates
## (configure in tls section)
smtp:
username: test
## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
password: password
## The SMTP host to connect to.
host: 127.0.0.1
## The port to connect to the SMTP host on.
port: 1025
## The connection timeout.
timeout: 5s
## The username used for SMTP authentication.
username: test
## The password used for SMTP authentication.
## Can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
password: password
## The address to send the email FROM.
sender: admin@example.com
## HELO/EHLO Identifier. Some SMTP Servers may reject the default of localhost.
identifier: localhost
## Subject configuration of the emails sent. {title} is replaced by the text from the notifier.
subject: "[Authelia] {title}"
## This address is used during the startup check to verify the email configuration is correct.
## It's not important what it is except if your email server only allows local delivery.
startup_check_address: test@authelia.com
## By default we require some form of TLS. This disables this check though is not advised.
disable_require_tls: false
## Disables sending HTML formatted emails.
disable_html_emails: false
tls:
@ -569,16 +588,6 @@ notifier:
## Minimum TLS version for either StartTLS or SMTPS.
minimum_version: TLS1.2
## Sending an email using a Gmail account is as simple as the next section.
## You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en
# smtp:
# username: myaccount@gmail.com
# ## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
# password: yourapppassword
# sender: admin@example.com
# host: smtp.gmail.com
# port: 587
##
## Identity Providers
##

View File

@ -1,5 +1,7 @@
package schema
import "time"
// FileSystemNotifierConfiguration represents the configuration of the notifier writing emails in a file.
type FileSystemNotifierConfiguration struct {
Filename string `koanf:"filename"`
@ -7,17 +9,18 @@ type FileSystemNotifierConfiguration struct {
// SMTPNotifierConfiguration represents the configuration of the SMTP server to send emails with.
type SMTPNotifierConfiguration struct {
Host string `koanf:"host"`
Port int `koanf:"port"`
Username string `koanf:"username"`
Password string `koanf:"password"`
Identifier string `koanf:"identifier"`
Sender string `koanf:"sender"`
Subject string `koanf:"subject"`
StartupCheckAddress string `koanf:"startup_check_address"`
DisableRequireTLS bool `koanf:"disable_require_tls"`
DisableHTMLEmails bool `koanf:"disable_html_emails"`
TLS *TLSConfig `koanf:"tls"`
Host string `koanf:"host"`
Port int `koanf:"port"`
Timeout time.Duration `koanf:"timeout"`
Username string `koanf:"username"`
Password string `koanf:"password"`
Identifier string `koanf:"identifier"`
Sender string `koanf:"sender"`
Subject string `koanf:"subject"`
StartupCheckAddress string `koanf:"startup_check_address"`
DisableRequireTLS bool `koanf:"disable_require_tls"`
DisableHTMLEmails bool `koanf:"disable_html_emails"`
TLS *TLSConfig `koanf:"tls"`
}
// NotifierConfiguration represents the configuration of the notifier to use when sending notifications to users.
@ -29,6 +32,7 @@ type NotifierConfiguration struct {
// DefaultSMTPNotifierConfiguration represents default configuration parameters for the SMTP notifier.
var DefaultSMTPNotifierConfiguration = SMTPNotifierConfiguration{
Timeout: time.Second * 5,
Subject: "[Authelia] {title}",
Identifier: "localhost",
TLS: &TLSConfig{

View File

@ -232,6 +232,7 @@ var ValidKeys = []string{
// SMTP Notifier Keys.
"notifier.smtp.host",
"notifier.smtp.port",
"notifier.smtp.timeout",
"notifier.smtp.username",
"notifier.smtp.password",
"notifier.smtp.identifier",

View File

@ -42,6 +42,10 @@ func validateSMTPNotifier(configuration *schema.SMTPNotifierConfiguration, valid
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "port"))
}
if configuration.Timeout == 0 {
configuration.Timeout = schema.DefaultSMTPNotifierConfiguration.Timeout
}
if configuration.Sender == "" {
validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "sender"))
}

View File

@ -5,6 +5,7 @@ import (
"crypto/x509"
"errors"
"fmt"
"net"
"net/smtp"
"strings"
"time"
@ -16,34 +17,16 @@ import (
// SMTPNotifier a notifier to send emails to SMTP servers.
type SMTPNotifier struct {
username string
password string
sender string
identifier string
host string
port int
disableRequireTLS bool
address string
subject string
startupCheckAddress string
client *smtp.Client
tlsConfig *tls.Config
configuration *schema.SMTPNotifierConfiguration
client *smtp.Client
tlsConfig *tls.Config
}
// NewSMTPNotifier creates a SMTPNotifier using the notifier configuration.
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration, certPool *x509.CertPool) *SMTPNotifier {
func NewSMTPNotifier(configuration *schema.SMTPNotifierConfiguration, certPool *x509.CertPool) *SMTPNotifier {
notifier := &SMTPNotifier{
username: configuration.Username,
password: configuration.Password,
sender: configuration.Sender,
identifier: configuration.Identifier,
host: configuration.Host,
port: configuration.Port,
disableRequireTLS: configuration.DisableRequireTLS,
address: fmt.Sprintf("%s:%d", configuration.Host, configuration.Port),
subject: configuration.Subject,
startupCheckAddress: configuration.StartupCheckAddress,
tlsConfig: utils.NewTLSConfig(configuration.TLS, tls.VersionTLS12, certPool),
configuration: configuration,
tlsConfig: utils.NewTLSConfig(configuration.TLS, tls.VersionTLS12, certPool),
}
return notifier
@ -68,7 +51,7 @@ func (n *SMTPNotifier) startTLS() error {
logger.Debug("Notifier SMTP STARTTLS completed without error")
default:
switch n.disableRequireTLS {
switch n.configuration.DisableRequireTLS {
case true:
logger.Warn("Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)")
default:
@ -83,7 +66,7 @@ func (n *SMTPNotifier) startTLS() error {
func (n *SMTPNotifier) auth() error {
logger := logging.Logger()
// Attempt AUTH if password is specified only.
if n.password != "" {
if n.configuration.Password != "" {
_, ok := n.client.TLSConnectionState()
if !ok {
return errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text")
@ -99,11 +82,11 @@ func (n *SMTPNotifier) auth() error {
// Adaptively select the AUTH mechanism to use based on what the server advertised.
if utils.IsStringInSlice("PLAIN", mechanisms) {
auth = smtp.PlainAuth("", n.username, n.password, n.host)
auth = smtp.PlainAuth("", n.configuration.Username, n.configuration.Password, n.configuration.Host)
logger.Debug("Notifier SMTP client attempting AUTH PLAIN with server")
} else if utils.IsStringInSlice("LOGIN", mechanisms) {
auth = newLoginAuth(n.username, n.password, n.host)
auth = newLoginAuth(n.configuration.Username, n.configuration.Password, n.configuration.Host)
logger.Debug("Notifier SMTP client attempting AUTH LOGIN with server")
}
@ -135,7 +118,7 @@ func (n *SMTPNotifier) compose(recipient, subject, body, htmlBody string) error
logger := logging.Logger()
logger.Debugf("Notifier SMTP client attempting to send email body to %s", recipient)
if !n.disableRequireTLS {
if !n.configuration.DisableRequireTLS {
_, ok := n.client.TLSConnectionState()
if !ok {
return errors.New("Notifier SMTP client can't send an email over plain text connection")
@ -153,7 +136,7 @@ func (n *SMTPNotifier) compose(recipient, subject, body, htmlBody string) error
now := time.Now()
msg := "Date:" + now.Format(rfc5322DateTimeLayout) + "\n" +
"From: " + n.sender + "\n" +
"From: " + n.configuration.Sender + "\n" +
"To: " + recipient + "\n" +
"Subject: " + subject + "\n" +
"MIME-version: 1.0\n" +
@ -188,33 +171,40 @@ func (n *SMTPNotifier) compose(recipient, subject, body, htmlBody string) error
}
// Dial the SMTP server with the SMTPNotifier config.
func (n *SMTPNotifier) dial() error {
func (n *SMTPNotifier) dial() (err error) {
logger := logging.Logger()
logger.Debugf("Notifier SMTP client attempting connection to %s", n.address)
logger.Debugf("Notifier SMTP client attempting connection to %s:%d", n.configuration.Host, n.configuration.Port)
if n.port == 465 {
var (
client *smtp.Client
conn net.Conn
)
dialer := &net.Dialer{
Timeout: n.configuration.Timeout,
}
if n.configuration.Port == 465 {
logger.Infof("Notifier SMTP client using submissions port 465. Make sure the mail server you are connecting to is configured for submissions and not SMTPS.")
conn, err := tls.Dial("tcp", n.address, n.tlsConfig)
conn, err = tls.DialWithDialer(dialer, "tcp", fmt.Sprintf("%s:%d", n.configuration.Host, n.configuration.Port), n.tlsConfig)
if err != nil {
return err
}
client, err := smtp.NewClient(conn, n.host)
if err != nil {
return err
}
n.client = client
} else {
client, err := smtp.Dial(n.address)
conn, err = dialer.Dial("tcp", fmt.Sprintf("%s:%d", n.configuration.Host, n.configuration.Port))
if err != nil {
return err
}
n.client = client
}
client, err = smtp.NewClient(conn, n.configuration.Host)
if err != nil {
return err
}
n.client = client
logger.Debug("Notifier SMTP client connected successfully")
return nil
@ -238,7 +228,7 @@ func (n *SMTPNotifier) StartupCheck() (bool, error) {
defer n.cleanup()
if err := n.client.Hello(n.identifier); err != nil {
if err := n.client.Hello(n.configuration.Identifier); err != nil {
return false, err
}
@ -250,11 +240,11 @@ func (n *SMTPNotifier) StartupCheck() (bool, error) {
return false, err
}
if err := n.client.Mail(n.sender); err != nil {
if err := n.client.Mail(n.configuration.Sender); err != nil {
return false, err
}
if err := n.client.Rcpt(n.startupCheckAddress); err != nil {
if err := n.client.Rcpt(n.configuration.StartupCheckAddress); err != nil {
return false, err
}
@ -268,7 +258,7 @@ func (n *SMTPNotifier) StartupCheck() (bool, error) {
// Send is used to send an email to a recipient.
func (n *SMTPNotifier) Send(recipient, title, body, htmlBody string) error {
logger := logging.Logger()
subject := strings.ReplaceAll(n.subject, "{title}", title)
subject := strings.ReplaceAll(n.configuration.Subject, "{title}", title)
if err := n.dial(); err != nil {
return err
@ -277,7 +267,7 @@ func (n *SMTPNotifier) Send(recipient, title, body, htmlBody string) error {
// Always execute QUIT at the end once we're connected.
defer n.cleanup()
if err := n.client.Hello(n.identifier); err != nil {
if err := n.client.Hello(n.configuration.Identifier); err != nil {
return err
}
@ -291,7 +281,7 @@ func (n *SMTPNotifier) Send(recipient, title, body, htmlBody string) error {
}
// Set the sender and recipient first.
if err := n.client.Mail(n.sender); err != nil {
if err := n.client.Mail(n.configuration.Sender); err != nil {
logger.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err)
return err
}

View File

@ -25,12 +25,11 @@ func TestShouldConfigureSMTPNotifierWithTLS11AndDefaultHostname(t *testing.T) {
sv := schema.NewStructValidator()
validator.ValidateNotifier(config, sv)
notifier := NewSMTPNotifier(*config.SMTP, nil)
notifier := NewSMTPNotifier(config.SMTP, nil)
assert.Equal(t, "smtp.example.com", notifier.tlsConfig.ServerName)
assert.Equal(t, uint16(tls.VersionTLS11), notifier.tlsConfig.MinVersion)
assert.False(t, notifier.tlsConfig.InsecureSkipVerify)
assert.Equal(t, "smtp.example.com:25", notifier.address)
}
func TestShouldConfigureSMTPNotifierWithServerNameOverrideAndDefaultTLS12(t *testing.T) {
@ -48,10 +47,9 @@ func TestShouldConfigureSMTPNotifierWithServerNameOverrideAndDefaultTLS12(t *tes
sv := schema.NewStructValidator()
validator.ValidateNotifier(config, sv)
notifier := NewSMTPNotifier(*config.SMTP, nil)
notifier := NewSMTPNotifier(config.SMTP, nil)
assert.Equal(t, "smtp.golang.org", notifier.tlsConfig.ServerName)
assert.Equal(t, uint16(tls.VersionTLS12), notifier.tlsConfig.MinVersion)
assert.False(t, notifier.tlsConfig.InsecureSkipVerify)
assert.Equal(t, "smtp.example.com:25", notifier.address)
}