[FEATURE] Notifier Startup Checks (#889)

* implement SMTP notifier startup check
* check dial, starttls, auth, mail from, rcpt to, reset, and quit
* log the error on failure
* implement mock
* misc optimizations, adjustments, and refactoring
* implement validate_skip config option
* fix comments to end with period
* fix suites that used smtp notifier without a smtp container
* add docs
* add file notifier startup check
* move file mode into const.go
* disable gosec linting on insecureskipverify since it's intended, warned, and discouraged
* minor PR commentary adjustment
* apply suggestions from code review

Co-Authored-By: Amir Zarrinkafsh <nightah@me.com>
pull/892/head
James Elliott 2020-04-21 14:59:38 +10:00 committed by GitHub
parent a26ddf9c65
commit 9e9dee43ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 205 additions and 83 deletions

9
.github/probot.js vendored
View File

@ -8,10 +8,10 @@ on('pull_request.opened')
context => context =>
context.payload.pull_request.head.ref.slice(0, 11) !== 'dependabot/' context.payload.pull_request.head.ref.slice(0, 11) !== 'dependabot/'
) )
.comment(`# Artifacts .comment(`## Artifacts
These changes are published for testing on Buildkite and DockerHub. These changes are published for testing on Buildkite and DockerHub.
## Docker Container ### Docker Container
* \`docker pull authelia/authelia:{{ pull_request.head.ref }}\``) * \`docker pull authelia/authelia:{{ pull_request.head.ref }}\``)
// PR commentary for third party based contributions // PR commentary for third party based contributions
@ -21,10 +21,11 @@ on('pull_request.opened')
context.payload.pull_request.head.label.slice(0, 9) !== 'authelia:' context.payload.pull_request.head.label.slice(0, 9) !== 'authelia:'
) )
.comment(`Thanks for choosing to contribute. We lint all PR's with golangci-lint, autheliabot may add a review to your PR with some suggestions. .comment(`Thanks for choosing to contribute. We lint all PR's with golangci-lint, autheliabot may add a review to your PR with some suggestions.
You are free to apply the changes if you're comfortable, alternatively you are welcome to ask a team member for advice. You are free to apply the changes if you're comfortable, alternatively you are welcome to ask a team member for advice.
# Artifacts ## Artifacts
These changes once approved by a team member will be published for testing on Buildkite and DockerHub. These changes once approved by a team member will be published for testing on Buildkite and DockerHub.
## Docker Container ### Docker Container
* \`docker pull authelia/authelia:PR{{ pull_request.number }}\``) * \`docker pull authelia/authelia:PR{{ pull_request.number }}\``)

View File

@ -89,6 +89,12 @@ func startServer() {
} else { } else {
log.Fatalf("Unrecognized notifier") log.Fatalf("Unrecognized notifier")
} }
if !config.Notifier.DisableStartupCheck {
_, err := notifier.StartupCheck()
if err != nil {
log.Fatalf("Error during notifier startup check: %s", err)
}
}
clock := utils.RealClock{} clock := utils.RealClock{}
authorizer := authorization.NewAuthorizer(config.AccessControl) authorizer := authorization.NewAuthorizer(config.AccessControl)

View File

@ -350,8 +350,11 @@ storage:
# #
# Notifications are sent to users when they require a password reset, a u2f # Notifications are sent to users when they require a password reset, a u2f
# registration or a TOTP registration. # registration or a TOTP registration.
# Use only an available configuration: filesystem, gmail # Use only an available configuration: filesystem, smtp.
notifier: notifier:
# You can disable the notifier startup check by setting this to true.
disable_startup_check: false
# For testing purpose, notifications can be sent in a file # For testing purpose, notifications can be sent in a file
## filesystem: ## filesystem:
## filename: /tmp/authelia/notification.txt ## filename: /tmp/authelia/notification.txt
@ -377,9 +380,11 @@ notifier:
# Subject configuration of the emails sent. # Subject configuration of the emails sent.
# {title} is replaced by the text from the notifier # {title} is replaced by the text from the notifier
subject: "[Authelia] {title}" 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
## trusted_cert: ""
## disable_require_tls: false ## disable_require_tls: false
## disable_verify_cert: false ## disable_verify_cert: false
## trusted_cert: ""
# Sending an email using a Gmail account is as simple as the next section. # 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 # You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en

View File

@ -12,7 +12,16 @@ With this configuration, the message will be sent to a file. This option
should only be used for testing purposes. should only be used for testing purposes.
```yaml ```yaml
# Configuration of the notification system.
#
# Notifications are sent to users when they require a password reset, a U2F
# registration or a TOTP registration.
# Use only an available configuration: filesystem, smtp.
notifier: notifier:
# You can disable the notifier startup check by setting this to true.
disable_startup_check: false
# For testing purpose, notifications can be sent in a file.
filesystem: filesystem:
filename: /tmp/authelia/notification.txt filename: /tmp/authelia/notification.txt
``` ```

View File

@ -10,3 +10,20 @@ has_children: true
**Authelia** sometimes needs to send messages to users in order to **Authelia** sometimes needs to send messages to users in order to
verify their identity. verify their identity.
## Startup Check
The notifier has a startup check which validates the specified provider
configuration is correct and will be able to send emails. This can be
disabled with the `disable_startup_check` option:
```yaml
# Configuration of the notification system.
#
# Notifications are sent to users when they require a password reset, a u2f
# registration or a TOTP registration.
# Use only an available configuration: filesystem, smtp.
notifier:
# You can disable the notifier startup check by setting this to true
disable_startup_check: false
```

View File

@ -16,9 +16,12 @@ It can be configured as described below.
# #
# Notifications are sent to users when they require a password reset, a u2f # Notifications are sent to users when they require a password reset, a u2f
# registration or a TOTP registration. # registration or a TOTP registration.
# Use only an available configuration: filesystem, smtp # Use only an available configuration: filesystem, smtp.
notifier: notifier:
# For testing purpose, notifications can be sent in a file # You can disable the notifier startup check by setting this to true.
disable_startup_check: false
# For testing purpose, notifications can be sent in a file.
## filesystem: ## filesystem:
## filename: /tmp/authelia/notification.txt ## filename: /tmp/authelia/notification.txt
@ -43,9 +46,11 @@ notifier:
# Subject configuration of the emails sent. # Subject configuration of the emails sent.
# {title} is replaced by the text from the notifier # {title} is replaced by the text from the notifier
subject: "[Authelia] {title}" 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
## trusted_cert: ""
## disable_require_tls: false ## disable_require_tls: false
## disable_verify_cert: false ## disable_verify_cert: false
## trusted_cert: ""
``` ```
## Using Gmail ## Using Gmail

View File

@ -7,19 +7,21 @@ type FileSystemNotifierConfiguration struct {
// SMTPNotifierConfiguration represents the configuration of the SMTP server to send emails with. // SMTPNotifierConfiguration represents the configuration of the SMTP server to send emails with.
type SMTPNotifierConfiguration struct { type SMTPNotifierConfiguration struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Username string `mapstructure:"username"` Username string `mapstructure:"username"`
Password string `mapstructure:"password"` Password string `mapstructure:"password"`
Sender string `mapstructure:"sender"` Sender string `mapstructure:"sender"`
Subject string `mapstructure:"subject"` Subject string `mapstructure:"subject"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
TrustedCert string `mapstructure:"trusted_cert"` TrustedCert string `mapstructure:"trusted_cert"`
StartupCheckAddress string `mapstructure:"startup_check_address"`
DisableVerifyCert bool `mapstructure:"disable_verify_cert"` DisableVerifyCert bool `mapstructure:"disable_verify_cert"`
DisableRequireTLS bool `mapstructure:"disable_require_tls"` DisableRequireTLS bool `mapstructure:"disable_require_tls"`
} }
// NotifierConfiguration represents the configuration of the notifier to use when sending notifications to users. // NotifierConfiguration represents the configuration of the notifier to use when sending notifications to users.
type NotifierConfiguration struct { type NotifierConfiguration struct {
DisableStartupCheck bool `mapstructure:"disable_startup_check"`
FileSystem *FileSystemNotifierConfiguration `mapstructure:"filesystem"` FileSystem *FileSystemNotifierConfiguration `mapstructure:"filesystem"`
SMTP *SMTPNotifierConfiguration `mapstructure:"smtp"` SMTP *SMTPNotifierConfiguration `mapstructure:"smtp"`
} }

View File

@ -26,6 +26,9 @@ func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *sc
} }
if configuration.SMTP != nil { if configuration.SMTP != nil {
if configuration.SMTP.StartupCheckAddress == "" {
configuration.SMTP.StartupCheckAddress = "test@authelia.com"
}
if configuration.SMTP.Host == "" { if configuration.SMTP.Host == "" {
validator.Push(fmt.Errorf("Host of SMTP notifier must be provided")) validator.Push(fmt.Errorf("Host of SMTP notifier must be provided"))
} }

View File

@ -10,30 +10,35 @@ import (
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
) )
// MockNotifier is a mock of Notifier interface // MockNotifier is a mock of Notifier interface.
type MockNotifier struct { type MockNotifier struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockNotifierMockRecorder recorder *MockNotifierMockRecorder
} }
// MockNotifierMockRecorder is the mock recorder for MockNotifier // MockNotifierMockRecorder is the mock recorder for MockNotifier.
type MockNotifierMockRecorder struct { type MockNotifierMockRecorder struct {
mock *MockNotifier mock *MockNotifier
} }
// NewMockNotifier creates a new mock instance // NewMockNotifier creates a new mock instance.
func NewMockNotifier(ctrl *gomock.Controller) *MockNotifier { func NewMockNotifier(ctrl *gomock.Controller) *MockNotifier {
mock := &MockNotifier{ctrl: ctrl} mock := &MockNotifier{ctrl: ctrl}
mock.recorder = &MockNotifierMockRecorder{mock} mock.recorder = &MockNotifierMockRecorder{mock}
return mock return mock
} }
// EXPECT returns an object that allows the caller to indicate expected use // EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockNotifier) EXPECT() *MockNotifierMockRecorder { func (m *MockNotifier) EXPECT() *MockNotifierMockRecorder {
return m.recorder return m.recorder
} }
// Send mocks base method // StartupCheck mocks base method.
func (m *MockNotifier) StartupCheck() (bool, error) {
return true, nil
}
// Send mocks base method.
func (m *MockNotifier) Send(arg0, arg1, arg2 string) error { func (m *MockNotifier) Send(arg0, arg1, arg2 string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Send", arg0, arg1, arg2) ret := m.ctrl.Call(m, "Send", arg0, arg1, arg2)
@ -41,7 +46,7 @@ func (m *MockNotifier) Send(arg0, arg1, arg2 string) error {
return ret0 return ret0
} }
// Send indicates an expected call of Send // Send indicates an expected call of Send.
func (mr *MockNotifierMockRecorder) Send(arg0, arg1, arg2 interface{}) *gomock.Call { func (mr *MockNotifierMockRecorder) Send(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockNotifier)(nil).Send), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockNotifier)(nil).Send), arg0, arg1, arg2)

View File

@ -0,0 +1,3 @@
package notification
const fileNotifierMode = 0755

View File

@ -3,6 +3,8 @@ package notification
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"path/filepath"
"time" "time"
"github.com/authelia/authelia/internal/configuration/schema" "github.com/authelia/authelia/internal/configuration/schema"
@ -20,11 +22,44 @@ func NewFileNotifier(configuration schema.FileSystemNotifierConfiguration) *File
} }
} }
// StartupCheck checks the file provider can write to the specified file
func (n *FileNotifier) StartupCheck() (ok bool, err error) {
ok = true
dir := filepath.Dir(n.path)
if _, err = os.Stat(dir); err != nil {
if os.IsNotExist(err) {
if err = os.MkdirAll(dir, fileNotifierMode); err != nil {
ok = false
return
}
if err = ioutil.WriteFile(n.path, []byte(""), fileNotifierMode); err != nil {
ok = false
return
}
} else {
ok = false
return
}
} else if _, err = os.Stat(n.path); err != nil {
if os.IsNotExist(err) {
if err = ioutil.WriteFile(n.path, []byte(""), fileNotifierMode); err != nil {
ok = false
return
}
} else {
ok = false
return
}
}
err = nil
return
}
// Send send a identity verification link to a user. // Send send a identity verification link to a user.
func (n *FileNotifier) Send(recipient, subject, body string) error { func (n *FileNotifier) Send(recipient, subject, body string) error {
content := fmt.Sprintf("Date: %s\nRecipient: %s\nSubject: %s\nBody: %s", time.Now(), recipient, subject, body) content := fmt.Sprintf("Date: %s\nRecipient: %s\nSubject: %s\nBody: %s", time.Now(), recipient, subject, body)
err := ioutil.WriteFile(n.path, []byte(content), 0755) err := ioutil.WriteFile(n.path, []byte(content), fileNotifierMode)
if err != nil { if err != nil {
return err return err

View File

@ -3,4 +3,5 @@ package notification
// Notifier interface for sending the identity verification link. // Notifier interface for sending the identity verification link.
type Notifier interface { type Notifier interface {
Send(recipient, subject, body string) error Send(recipient, subject, body string) error
StartupCheck() (bool, error)
} }

View File

@ -15,7 +15,7 @@ import (
"github.com/authelia/authelia/internal/utils" "github.com/authelia/authelia/internal/utils"
) )
// SMTPNotifier a notifier to send emails to SMTP servers // SMTPNotifier a notifier to send emails to SMTP servers.
type SMTPNotifier struct { type SMTPNotifier struct {
username string username string
password string password string
@ -27,11 +27,12 @@ type SMTPNotifier struct {
disableRequireTLS bool disableRequireTLS bool
address string address string
subject string subject string
startupCheckAddress string
client *smtp.Client client *smtp.Client
tlsConfig *tls.Config tlsConfig *tls.Config
} }
// NewSMTPNotifier create an SMTPNotifier targeting a given address // NewSMTPNotifier creates a SMTPNotifier using the notifier configuration.
func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifier { func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifier {
notifier := &SMTPNotifier{ notifier := &SMTPNotifier{
username: configuration.Username, username: configuration.Username,
@ -44,6 +45,7 @@ func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifi
disableRequireTLS: configuration.DisableRequireTLS, disableRequireTLS: configuration.DisableRequireTLS,
address: fmt.Sprintf("%s:%d", configuration.Host, configuration.Port), address: fmt.Sprintf("%s:%d", configuration.Host, configuration.Port),
subject: configuration.Subject, subject: configuration.Subject,
startupCheckAddress: configuration.StartupCheckAddress,
} }
notifier.initializeTLSConfig() notifier.initializeTLSConfig()
return notifier return notifier
@ -53,10 +55,6 @@ func (n *SMTPNotifier) initializeTLSConfig() {
// Do not allow users to disable verification of certs if they have also set a trusted cert that was loaded // Do not allow users to disable verification of certs if they have also set a trusted cert that was loaded
// The second part of this check happens in the Configure Cert Pool code block // The second part of this check happens in the Configure Cert Pool code block
log.Debug("Notifier SMTP client initializing TLS configuration") log.Debug("Notifier SMTP client initializing TLS configuration")
insecureSkipVerify := false
if n.disableVerifyCert {
insecureSkipVerify = true
}
//Configure Cert Pool //Configure Cert Pool
certPool, err := x509.SystemCertPool() certPool, err := x509.SystemCertPool()
@ -77,7 +75,7 @@ func (n *SMTPNotifier) initializeTLSConfig() {
log.Debug("Notifier SMTP successfully loaded certificate") log.Debug("Notifier SMTP successfully loaded certificate")
if n.disableVerifyCert { if n.disableVerifyCert {
log.Warn("Notifier SMTP when trusted_cert is specified we force disable_verify_cert to false, if you want to disable certificate validation please comment/delete trusted_cert from your config") log.Warn("Notifier SMTP when trusted_cert is specified we force disable_verify_cert to false, if you want to disable certificate validation please comment/delete trusted_cert from your config")
insecureSkipVerify = false n.disableVerifyCert = false
} }
} }
} }
@ -86,13 +84,13 @@ func (n *SMTPNotifier) initializeTLSConfig() {
} }
} }
n.tlsConfig = &tls.Config{ n.tlsConfig = &tls.Config{
InsecureSkipVerify: insecureSkipVerify, InsecureSkipVerify: n.disableVerifyCert, //nolint:gosec // This is an intended config, we never default true, provide alternate options, and we constantly warn the user.
ServerName: n.host, ServerName: n.host,
RootCAs: certPool, RootCAs: certPool,
} }
} }
// Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred) // Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred).
func (n *SMTPNotifier) startTLS() (bool, error) { func (n *SMTPNotifier) startTLS() (bool, error) {
// Only start if not already encrypted // Only start if not already encrypted
if _, ok := n.client.TLSConnectionState(); ok { if _, ok := n.client.TLSConnectionState(); ok {
@ -117,23 +115,23 @@ func (n *SMTPNotifier) startTLS() (bool, error) {
return ok, nil return ok, nil
} }
// Attempt Authentication // Attempt Authentication.
func (n *SMTPNotifier) auth() (bool, error) { func (n *SMTPNotifier) auth() (bool, error) {
// Attempt AUTH if password is specified only // Attempt AUTH if password is specified only.
if n.password != "" { if n.password != "" {
_, ok := n.client.TLSConnectionState() _, ok := n.client.TLSConnectionState()
if !ok { if !ok {
return false, errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text") return false, errors.New("Notifier SMTP client does not support authentication over plain text and the connection is currently plain text")
} }
// Check the server supports AUTH, and get the mechanisms // Check the server supports AUTH, and get the mechanisms.
ok, m := n.client.Extension("AUTH") ok, m := n.client.Extension("AUTH")
if ok { if ok {
log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m) log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m)
mechanisms := strings.Split(m, " ") mechanisms := strings.Split(m, " ")
var auth smtp.Auth var auth smtp.Auth
// Adaptively select the AUTH mechanism to use based on what the server advertised // Adaptively select the AUTH mechanism to use based on what the server advertised.
if utils.IsStringInSlice("PLAIN", mechanisms) { if utils.IsStringInSlice("PLAIN", mechanisms) {
auth = smtp.PlainAuth("", n.username, n.password, n.host) auth = smtp.PlainAuth("", n.username, n.password, n.host)
log.Debug("Notifier SMTP client attempting AUTH PLAIN with server") log.Debug("Notifier SMTP client attempting AUTH PLAIN with server")
@ -142,12 +140,12 @@ func (n *SMTPNotifier) auth() (bool, error) {
log.Debug("Notifier SMTP client attempting AUTH LOGIN with server") log.Debug("Notifier SMTP client attempting AUTH LOGIN with server")
} }
// Throw error since AUTH extension is not supported // Throw error since AUTH extension is not supported.
if auth == nil { if auth == nil {
return false, fmt.Errorf("notifier SMTP server does not advertise a AUTH mechanism that are supported by Authelia (PLAIN or LOGIN are supported, but server advertised %s mechanisms)", m) return false, fmt.Errorf("notifier SMTP server does not advertise a AUTH mechanism that are supported by Authelia (PLAIN or LOGIN are supported, but server advertised %s mechanisms)", m)
} }
// Authenticate // Authenticate.
err := n.client.Auth(auth) err := n.client.Auth(auth)
if err != nil { if err != nil {
return false, err return false, err
@ -195,7 +193,7 @@ func (n *SMTPNotifier) compose(recipient, subject, body string) error {
return nil return nil
} }
// Dial the SMTP server with the SMTPNotifier config // Dial the SMTP server with the SMTPNotifier config.
func (n *SMTPNotifier) dial() error { func (n *SMTPNotifier) dial() error {
log.Debugf("Notifier SMTP client attempting connection to %s", n.address) log.Debugf("Notifier SMTP client attempting connection to %s", n.address)
if n.port == 465 { if n.port == 465 {
@ -220,7 +218,7 @@ func (n *SMTPNotifier) dial() error {
return nil return nil
} }
// Closes the connection properly // Closes the connection properly.
func (n *SMTPNotifier) cleanup() { func (n *SMTPNotifier) cleanup() {
err := n.client.Quit() err := n.client.Quit()
if err != nil { if err != nil {
@ -228,17 +226,56 @@ func (n *SMTPNotifier) cleanup() {
} }
} }
// Send an email // StartupCheck checks the server is functioning correctly and the configuration is correct.
func (n *SMTPNotifier) StartupCheck() (ok bool, err error) {
ok = true
if err = n.dial(); err != nil {
ok = false
return
}
defer n.cleanup()
if _, err = n.startTLS(); err != nil {
ok = false
return
}
if _, err = n.auth(); err != nil {
ok = false
return
}
if err = n.client.Mail(n.sender); err != nil {
ok = false
return
}
if err = n.client.Rcpt(n.startupCheckAddress); err != nil {
ok = false
return
}
if err = n.client.Reset(); err != nil {
ok = false
return
}
return
}
// Send is used to send an email to a recipient.
func (n *SMTPNotifier) Send(recipient, title, body string) error { func (n *SMTPNotifier) Send(recipient, title, body string) error {
subject := strings.ReplaceAll(n.subject, "{title}", title) subject := strings.ReplaceAll(n.subject, "{title}", title)
if err := n.dial(); err != nil { if err := n.dial(); err != nil {
return err return err
} }
// Always execute QUIT at the end once we're connected // Always execute QUIT at the end once we're connected.
defer n.cleanup() defer n.cleanup()
// Start TLS and then Authenticate // Start TLS and then Authenticate.
if _, err := n.startTLS(); err != nil { if _, err := n.startTLS(); err != nil {
return err return err
} }
@ -246,7 +283,7 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error {
return err return err
} }
// Set the sender and recipient first // Set the sender and recipient first.
if err := n.client.Mail(n.sender); err != nil { if err := n.client.Mail(n.sender); err != nil {
log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err) log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err)
return err return err
@ -256,7 +293,7 @@ func (n *SMTPNotifier) Send(recipient, title, body string) error {
return err return err
} }
// Compose and send the email body to the server // Compose and send the email body to the server.
if err := n.compose(recipient, subject, body); err != nil { if err := n.compose(recipient, subject, body); err != nil {
return err return err
} }

View File

@ -97,9 +97,5 @@ regulation:
ban_time: 900 ban_time: 900
notifier: notifier:
# Use a SMTP server for sending notifications filesystem:
smtp: filename: /tmp/notifier.html
host: smtp
port: 1025
sender: admin@example.com
disable_require_tls: true

View File

@ -40,8 +40,5 @@ access_control:
policy: bypass policy: bypass
notifier: notifier:
smtp: filesystem:
host: smtp filename: /tmp/notifier.html
port: 1025
sender: admin@example.com
disable_require_tls: true