SMTP server can now use LabCA issued certificate (#139)

LabCA can optionally be configured to send emails. Until now it was only possible to send to SMTP
servers that use a certificate signed by a public root CA (e.g. gmail). Now this can also be an
internal server using a LabCA issued certificate, or you can skip TLS verification completely.
This commit is contained in:
Arjan H
2024-09-28 16:00:21 +02:00
parent 514c9116dc
commit 295cd00011
9 changed files with 188 additions and 79 deletions

View File

@@ -43,13 +43,22 @@ if [ "$enabled" == "true," ]; then
pwd=$([ -e ] && $baseDir/bin/labca-gui_prev -d $PKI_EMAIL_PASS || echo "")
fi
PKI_EMAIL_PASS=$pwd
PKI_EMAIL_FROM=$(grep from $dataDir/config.json | head -1 | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g')
PKI_EMAIL_FROM=$(grep from $dataDir/config.json | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g')
PKI_EMAIL_TRUST=$(grep trust_root $dataDir/config.json | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g')
if [ "$PKI_EMAIL_TRUST" == "private" ]; then
PKI_EMAIL_TRUST="labca/test-root.pem"
elif [ "$PKI_EMAIL_TRUST" == "skip" ]; then
PKI_EMAIL_TRUST="InsecureSkipVerify"
else
PKI_EMAIL_TRUST=""
fi
else
PKI_EMAIL_SERVER="localhost"
PKI_EMAIL_PORT="9380"
PKI_EMAIL_USER="cert-manager@example.com"
PKI_EMAIL_PASS="password"
PKI_EMAIL_FROM="Expiry bot <test@example.com>"
PKI_EMAIL_TRUST="labca/certs/ipki/minica.pem"
fi
@@ -188,14 +197,18 @@ sed -i -e "s/\"server\": \".*\"/\"server\": \"$PKI_EMAIL_SERVER\"/" config/bad-k
sed -i -e "s/\"port\": \".*\"/\"port\": \"$PKI_EMAIL_PORT\"/" config/bad-key-revoker.json
sed -i -e "s/\"username\": \".*\"/\"username\": \"$PKI_EMAIL_USER\"/" config/bad-key-revoker.json
sed -i -e "s/\"from\": \".*\"/\"from\": \"$PKI_EMAIL_FROM\"/" config/bad-key-revoker.json
sed -i -e "s|\"SMTPTrustedRootFile\": \".*\"|\"SMTPTrustedRootFile\": \"$PKI_EMAIL_TRUST\"|" config/bad-key-revoker.json
sed -i -e "s/\"server\": \".*\"/\"server\": \"$PKI_EMAIL_SERVER\"/" config/expiration-mailer.json
sed -i -e "s/\"port\": \".*\"/\"port\": \"$PKI_EMAIL_PORT\"/" config/expiration-mailer.json
sed -i -e "s/\"username\": \".*\"/\"username\": \"$PKI_EMAIL_USER\"/" config/expiration-mailer.json
sed -i -e "s/\"from\": \".*\"/\"from\": \"$PKI_EMAIL_FROM\"/" config/expiration-mailer.json
sed -i -e "s|\"SMTPTrustedRootFile\": \".*\"|\"SMTPTrustedRootFile\": \"$PKI_EMAIL_TRUST\"|" config/expiration-mailer.json
sed -i -e "s/\"server\": \".*\"/\"server\": \"$PKI_EMAIL_SERVER\"/" config/notify-mailer.json
sed -i -e "s/\"port\": \".*\"/\"port\": \"$PKI_EMAIL_PORT\"/" config/notify-mailer.json
sed -i -e "s/\"username\": \".*\"/\"username\": \"$PKI_EMAIL_USER\"/" config/notify-mailer.json
sed -i -e "s/\"from\": \".*\"/\"from\": \"$PKI_EMAIL_FROM\"/" config/notify-mailer.json
sed -i -e "s|\"SMTPTrustedRootFile\": \".*\"|\"SMTPTrustedRootFile\": \"$PKI_EMAIL_TRUST\"|" config/notify-mailer.json
sed -i -e "s/\"purgeInterval\": \".*\"/\"purgeInterval\": \"1s\"/" config/akamai-purger.json
for fl in $(grep -Rl maxOpenConns config/); do

View File

@@ -582,6 +582,15 @@ func _backupHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(res)
}
type ErrorsResponse struct {
Success bool
Errors map[string]string
}
func makeErrorsResponse(success bool) ErrorsResponse {
return ErrorsResponse{Success: success, Errors: make(map[string]string)}
}
func _accountUpdateHandler(w http.ResponseWriter, r *http.Request) {
reg := &User{
Name: r.Form.Get("username"),
@@ -591,10 +600,7 @@ func _accountUpdateHandler(w http.ResponseWriter, r *http.Request) {
Password: r.Form.Get("password"),
}
res := struct {
Success bool
Errors map[string]string
}{Success: true}
res := makeErrorsResponse(true)
if reg.Validate(false, true) {
viper.Set("user.name", reg.Name)
@@ -642,10 +648,7 @@ func backendUpdateHandler(w http.ResponseWriter, r *http.Request) {
RequestBase: r.Header.Get("X-Request-Base"),
}
res := struct {
Success bool
Errors map[string]string
}{Success: true}
res := makeErrorsResponse(true)
if cfg.Validate() {
writeStandaloneConfig(cfg)
@@ -671,10 +674,7 @@ func _configUpdateHandler(w http.ResponseWriter, r *http.Request) {
ExtendedTimeout: (r.Form.Get("extended_timeout") == "true"),
}
res := struct {
Success bool
Errors map[string]string
}{Success: true}
res := makeErrorsResponse(true)
if cfg.Validate(true) {
delta := false
@@ -772,10 +772,7 @@ func _configUpdateHandler(w http.ResponseWriter, r *http.Request) {
}
func _crlIntervalUpdateHandler(w http.ResponseWriter, r *http.Request) {
res := struct {
Success bool
Errors map[string]string
}{Success: true, Errors: make(map[string]string)}
res := makeErrorsResponse(true)
delta := false
crlInterval := r.Form.Get("crl_interval")
@@ -821,6 +818,7 @@ type EmailConfig struct {
EmailUser string
EmailPwd []byte
From string
TrustRoot string
Errors map[string]string
}
@@ -872,6 +870,10 @@ func (cfg *EmailConfig) Validate() bool {
cfg.Errors["From"] = "Please enter the from email address"
}
if strings.TrimSpace(cfg.TrustRoot) == "" {
cfg.Errors["From"] = "Please select what root CA to trust for validating the email server certificate"
}
return len(cfg.Errors) == 0
}
@@ -883,12 +885,10 @@ func _emailUpdateHandler(w http.ResponseWriter, r *http.Request) {
EmailUser: r.Form.Get("email_user"),
EmailPwd: []byte(r.Form.Get("email_pwd")),
From: r.Form.Get("from"),
TrustRoot: r.Form.Get("trust_root"),
}
res := struct {
Success bool
Errors map[string]string
}{Success: true}
res := makeErrorsResponse(true)
if cfg.Validate() {
delta := false
@@ -931,6 +931,11 @@ func _emailUpdateHandler(w http.ResponseWriter, r *http.Request) {
viper.Set("labca.email.from", cfg.From)
}
if cfg.TrustRoot != viper.GetString("labca.email.trust_root") {
delta = true
viper.Set("labca.email.trust_root", cfg.TrustRoot)
}
if delta {
viper.WriteConfig()
@@ -956,19 +961,14 @@ func _emailUpdateHandler(w http.ResponseWriter, r *http.Request) {
}
func _emailSendHandler(w http.ResponseWriter, r *http.Request) {
res := struct {
Success bool
Errors map[string]string
}{Success: true, Errors: make(map[string]string)}
res := makeErrorsResponse(true)
recipient := viper.GetString("user.email")
if !_hostCommand(w, r, "test-email", recipient) {
res.Success = false
res.Errors["EmailSend"] = "Failed to send email - see logs"
if _hostCommand(w, r, "test-email", recipient) {
// Only on success, as when this returns false for this case the response has already been sent!
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}
func _exportHandler(w http.ResponseWriter, r *http.Request) {
@@ -1110,10 +1110,7 @@ func _checkUpdatesHandler(w http.ResponseWriter, r *http.Request) {
}
func generateCRLHandler(w http.ResponseWriter, r *http.Request, isRoot bool) {
res := struct {
Success bool
Errors map[string]string
}{Success: true, Errors: make(map[string]string)}
res := makeErrorsResponse(true)
if isRoot {
path := "data/"
@@ -1178,10 +1175,7 @@ func generateCRLHandler(w http.ResponseWriter, r *http.Request, isRoot bool) {
}
func uploadCRLHandler(w http.ResponseWriter, r *http.Request) {
res := struct {
Success bool
Errors map[string]string
}{Success: true, Errors: make(map[string]string)}
res := makeErrorsResponse(true)
rootci := &CertificateInfo{
IsRoot: true,
@@ -1628,6 +1622,7 @@ func _manageGet(w http.ResponseWriter, r *http.Request) {
}
}
manageData["From"] = viper.GetString("labca.email.from")
manageData["TrustRoot"] = viper.GetString("labca.email.trust_root")
}
manageData["Name"] = viper.GetString("user.name")
@@ -2275,6 +2270,18 @@ func _hostCommand(w http.ResponseWriter, r *http.Request, command string, params
}
log.Printf("ERROR: Message from server: '%s'", message)
if command == "test-email" {
// Want special error handling for this case
res := makeErrorsResponse(false)
if strings.Contains(string(message), "certificate signed by unknown authority") {
res.Errors["EmailSend"] = "Error: SMTP server certificate signed by unknown authority"
} else {
res.Errors["EmailSend"] = "Failed to send email - see logs"
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
return false
}
errorHandler(w, r, errors.New(string(message)), http.StatusInternalServerError)
return false
}

View File

@@ -348,6 +348,12 @@
<input class="form-control non-fluid" type="text" id="from" name="from" value="{{ .From }}">
<span class="error email-error hidden" id="from-error"></span>
</div>
<div class="form-group">
<label for="rootfile">Root CAs for validating email server certficate:</label><br/>
<input type="radio" class="rootfile-rd" id="rootfile-public" name="rootfile-type" value="public" {{ if and (ne .TrustRoot "private") (ne .TrustRoot "skip")}}checked{{ end }}/> Trust standard public root CAs<br/>
<input type="radio" class="rootfile-rd" id="rootfile-private" name="rootfile-type" value="private" {{ if eq .TrustRoot "private"}}checked{{ end }}/> Trust the LabCA root CA<br/>
<input type="radio" class="rootfile-rd" id="rootfile-skip" name="rootfile-type" value="skip" {{ if eq .TrustRoot "skip"}}checked{{ end }}/> Do not check server certificate<br/>
</div>
<div class="form-group">
<span class="hidden" id="update-email-result"></span>
<button class="btn btn-default" type="button" id="update-email" title="Update the email configuration">Update</button>
@@ -1038,6 +1044,7 @@
email_user: $("#emailuser").val(),
email_pwd: $("#emailpwd").val(),
from: $("#from").val(),
trust_root: $("#rootfile-private").prop('checked') ? "private" : ($("#rootfile-skip").prop('checked') ? "skip" : "public"),
},
})
.done(function(data) {

View File

@@ -2,6 +2,7 @@ package notmain
import (
"context"
"crypto/x509"
"flag"
"fmt"
netmail "net/mail"
@@ -88,6 +89,9 @@ func main() {
defer oTelShutdown(context.Background())
logger.Info(cmd.VersionString())
tlsConfig, err := c.Mailer.TLS.Load(scope)
cmd.FailOnError(err, "TLS config")
clk := cmd.Clock()
dnsTimeout, err := time.ParseDuration(c.Mailer.DNSTimeout)
@@ -99,8 +103,6 @@ func main() {
var resolver bdns.Client
servers, err := bdns.NewStaticProvider(c.Mailer.DNSStaticResolvers)
cmd.FailOnError(err, "Couldn't start static DNS server resolver")
tlsConfig, err := c.Mailer.TLS.Load(scope)
cmd.FailOnError(err, "TLS config")
if !c.Mailer.DNSAllowLoopbackAddresses {
r := bdns.New(
dnsTimeout,
@@ -116,6 +118,19 @@ func main() {
resolver = r
}
var smtpRoots *x509.CertPool
smtpSkipVerify := false
if c.Mailer.SMTPTrustedRootFile == "InsecureSkipVerify" {
smtpSkipVerify = true
} else if c.Mailer.SMTPTrustedRootFile != "" {
pem, err := os.ReadFile(c.Mailer.SMTPTrustedRootFile)
cmd.FailOnError(err, "Loading trusted roots file")
smtpRoots = x509.NewCertPool()
if !smtpRoots.AppendCertsFromPEM(pem) {
cmd.FailOnError(nil, "Failed to parse root certs PEM")
}
}
fromAddress, err := netmail.ParseAddress(c.Mailer.From)
cmd.FailOnError(err, fmt.Sprintf("Could not parse from address: %s", c.Mailer.From))
@@ -126,7 +141,8 @@ func main() {
c.Mailer.Port,
c.Mailer.Username,
smtpPassword,
nil,
smtpRoots,
smtpSkipVerify,
resolver,
*fromAddress,
logger,

View File

@@ -1,5 +1,5 @@
diff --git a/cmd/bad-key-revoker/main.go b/cmd/bad-key-revoker/main.go
index e7015e0c8..5e4e73a12 100644
index e7015e0c8..9e226d2fa 100644
--- a/cmd/bad-key-revoker/main.go
+++ b/cmd/bad-key-revoker/main.go
@@ -18,6 +18,7 @@ import (
@@ -22,7 +22,7 @@ index e7015e0c8..5e4e73a12 100644
// MaximumRevocations specifies the maximum number of certificates associated with
// a key hash that bad-key-revoker will attempt to revoke. If the number of certificates
// is higher than MaximumRevocations bad-key-revoker will error out and refuse to
@@ -467,6 +473,30 @@ func main() {
@@ -467,8 +473,35 @@ func main() {
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA")
rac := rapb.NewRegistrationAuthorityClient(conn)
@@ -51,12 +51,19 @@ index e7015e0c8..5e4e73a12 100644
+ }
+
var smtpRoots *x509.CertPool
if config.BadKeyRevoker.Mailer.SMTPTrustedRootFile != "" {
- if config.BadKeyRevoker.Mailer.SMTPTrustedRootFile != "" {
+ smtpSkipVerify := false
+ if config.BadKeyRevoker.Mailer.SMTPTrustedRootFile == "skipVerify" {
+ smtpSkipVerify = true
+ } else if config.BadKeyRevoker.Mailer.SMTPTrustedRootFile != "" {
pem, err := os.ReadFile(config.BadKeyRevoker.Mailer.SMTPTrustedRootFile)
@@ -488,6 +518,7 @@ func main() {
cmd.FailOnError(err, "Loading trusted roots file")
smtpRoots = x509.NewCertPool()
@@ -488,6 +521,8 @@ func main() {
config.BadKeyRevoker.Mailer.Username,
smtpPassword,
smtpRoots,
+ smtpSkipVerify,
+ resolver,
*fromAddress,
logger,

View File

@@ -1,5 +1,5 @@
diff --git a/test/config/notify-mailer.json b/test/config/notify-mailer.json
index 261b689e4..15b2be0b8 100644
index f6813a696..115d5b150 100644
--- a/test/config/notify-mailer.json
+++ b/test/config/notify-mailer.json
@@ -2,13 +2,22 @@
@@ -8,8 +8,8 @@ index 261b689e4..15b2be0b8 100644
"port": "9380",
+ "hostnamePolicyFile": "test/hostname-policy.yaml",
"username": "cert-manager@example.com",
+ "from": "notify mailer <test@example.com>",
"passwordFile": "test/secrets/smtp_password",
+ "SMTPTrustedRootFile": "test/certs/ipki/minica.pem",
"db": {
"dbConnectFile": "test/secrets/mailer_dburl",
"maxOpenConns": 10

View File

@@ -1,5 +1,5 @@
diff --git a/cmd/expiration-mailer/main.go b/cmd/expiration-mailer/main.go
index 46fa939a6..03a9e6ae3 100644
index 46fa939a6..43f7c11b5 100644
--- a/cmd/expiration-mailer/main.go
+++ b/cmd/expiration-mailer/main.go
@@ -23,6 +23,7 @@ import (
@@ -45,7 +45,7 @@ index 46fa939a6..03a9e6ae3 100644
// Path to a file containing a list of trusted root certificates for use
// during the SMTP connection (as opposed to the gRPC connections).
SMTPTrustedRootFile string
@@ -854,6 +864,30 @@ func main() {
@@ -854,8 +864,35 @@ func main() {
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
sac := sapb.NewStorageAuthorityClient(conn)
@@ -74,12 +74,19 @@ index 46fa939a6..03a9e6ae3 100644
+ }
+
var smtpRoots *x509.CertPool
if c.Mailer.SMTPTrustedRootFile != "" {
- if c.Mailer.SMTPTrustedRootFile != "" {
+ smtpSkipVerify := false
+ if c.Mailer.SMTPTrustedRootFile == "InsecureSkipVerify" {
+ smtpSkipVerify = true
+ } else if c.Mailer.SMTPTrustedRootFile != "" {
pem, err := os.ReadFile(c.Mailer.SMTPTrustedRootFile)
@@ -889,6 +923,7 @@ func main() {
cmd.FailOnError(err, "Loading trusted roots file")
smtpRoots = x509.NewCertPool()
@@ -889,6 +926,8 @@ func main() {
c.Mailer.Username,
smtpPassword,
smtpRoots,
+ smtpSkipVerify,
+ resolver,
*fromAddress,
logger,

View File

@@ -1,5 +1,5 @@
diff --git a/mail/mailer.go b/mail/mailer.go
index 31ebd40b1..d8ab68969 100644
index 31ebd40b1..61add3ec2 100644
--- a/mail/mailer.go
+++ b/mail/mailer.go
@@ -2,6 +2,7 @@ package mail
@@ -10,34 +10,47 @@ index 31ebd40b1..d8ab68969 100644
"crypto/rand"
"crypto/tls"
"crypto/x509"
@@ -24,7 +25,10 @@ import (
@@ -23,8 +24,11 @@ import (
"github.com/jmhodges/clock"
"github.com/prometheus/client_golang/prometheus"
"github.com/letsencrypt/boulder/core"
+ "github.com/letsencrypt/boulder/bdns"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/core"
+ berrors "github.com/letsencrypt/boulder/errors"
blog "github.com/letsencrypt/boulder/log"
+ "github.com/letsencrypt/boulder/probs"
)
type idGenerator interface {
@@ -139,6 +143,7 @@ func New(
@@ -139,6 +143,8 @@ func New(
username,
password string,
rootCAs *x509.CertPool,
+ insecureSkipVerify bool,
+ resolver bdns.Client,
from mail.Address,
logger blog.Logger,
stats prometheus.Registerer,
@@ -159,6 +164,7 @@ func New(
server: server,
port: port,
rootCAs: rootCAs,
+ dnsClient: resolver,
@@ -154,11 +160,13 @@ func New(
return &mailerImpl{
config: config{
dialer: &dialerImpl{
- username: username,
- password: password,
- server: server,
- port: port,
- rootCAs: rootCAs,
+ username: username,
+ password: password,
+ server: server,
+ port: port,
+ rootCAs: rootCAs,
+ insecureSkipVerify: insecureSkipVerify,
+ dnsClient: resolver,
},
log: logger,
from: from,
@@ -202,7 +208,7 @@ func (c config) generateMessage(to []string, subject, body string) ([]byte, erro
@@ -202,7 +210,7 @@ func (c config) generateMessage(to []string, subject, body string) ([]byte, erro
fmt.Sprintf("To: %s", strings.Join(addrs, ", ")),
fmt.Sprintf("From: %s", c.from.String()),
fmt.Sprintf("Subject: %s", subject),
@@ -46,10 +59,11 @@ index 31ebd40b1..d8ab68969 100644
fmt.Sprintf("Message-Id: <%s.%s.%s>", now.Format("20060102T150405"), mid.String(), c.from.Address),
"MIME-Version: 1.0",
"Content-Type: text/plain; charset=UTF-8",
@@ -259,23 +265,32 @@ func (m *mailerImpl) Connect() (Conn, error) {
@@ -259,23 +267,41 @@ func (m *mailerImpl) Connect() (Conn, error) {
type dialerImpl struct {
username, password, server, port string
rootCAs *x509.CertPool
+ insecureSkipVerify bool
+ dnsClient bdns.Client
}
@@ -69,10 +83,20 @@ index 31ebd40b1..d8ab68969 100644
- return nil, err
+ problem := probs.DNS("%v")
+ return nil, problem
}
- client, err := smtp.NewClient(conn, di.server)
+ }
+
+ if len(addrs) == 0 {
+ return nil, berrors.DNSError("No valid IP addresses found for %s", di.server)
}
- client, err := smtp.NewClient(conn, di.server)
+
+ tlsconf := &tls.Config{
+ ServerName: di.server,
+ }
+ if di.insecureSkipVerify {
+ tlsconf.InsecureSkipVerify = true
+ } else {
+ tlsconf.RootCAs = di.rootCAs
+ }
+
+ hostport := net.JoinHostPort(addrs[0].String(), di.port)
@@ -81,9 +105,7 @@ index 31ebd40b1..d8ab68969 100644
return nil, err
}
- auth := smtp.PlainAuth("", di.username, di.password, di.server)
+ client.StartTLS( &tls.Config{
+ ServerName: di.server,
+ })
+ client.StartTLS(tlsconf)
+ auth := smtp.PlainAuth("", di.username, di.password, addrs[0].String())
if err = client.Auth(auth); err != nil {
return nil, err

View File

@@ -1,8 +1,16 @@
diff --git a/cmd/notify-mailer/main.go b/cmd/notify-mailer/main.go
index a05366c3..da9d78c8 100644
index 6c01efd64..23b1f4f9d 100644
--- a/cmd/notify-mailer/main.go
+++ b/cmd/notify-mailer/main.go
@@ -37,6 +37,7 @@ type mailer struct {
@@ -2,6 +2,7 @@ package notmain
import (
"context"
+ "crypto/x509"
"encoding/csv"
"encoding/json"
"errors"
@@ -37,6 +38,7 @@ type mailer struct {
recipients []recipient
targetRange interval
sleepInterval time.Duration
@@ -10,7 +18,7 @@ index a05366c3..da9d78c8 100644
parallelSends uint
}
@@ -201,7 +202,7 @@ func (m *mailer) run(ctx context.Context) error {
@@ -201,7 +203,7 @@ func (m *mailer) run(ctx context.Context) error {
continue
}
@@ -19,17 +27,20 @@ index a05366c3..da9d78c8 100644
if err != nil {
m.log.Infof("Skipping %q due to policy violation: %s", w.address, err)
continue
@@ -502,7 +503,9 @@ type Config struct {
@@ -502,7 +504,12 @@ type Config struct {
NotifyMailer struct {
DB cmd.DBConfig
cmd.SMTPConfig
+ // Path to a file containing a list of trusted root certificates for use
+ // during the SMTP connection (as opposed to the gRPC connections).
+ SMTPTrustedRootFile string
+ cmd.HostnamePolicyConfig
}
+ PA cmd.PAConfig
+ PA cmd.PAConfig
Syslog cmd.SyslogConfig
}
@@ -570,6 +573,15 @@ func main() {
@@ -570,6 +577,15 @@ func main() {
log.Infof("While reading the recipient list file %s", probs)
}
@@ -45,15 +56,34 @@ index a05366c3..da9d78c8 100644
var mailClient bmail.Mailer
if *dryRun {
log.Infof("Starting %s in dry-run mode", cmd.VersionString())
@@ -585,6 +597,7 @@ func main() {
@@ -579,11 +595,26 @@ func main() {
smtpPassword, err := cfg.NotifyMailer.PasswordConfig.Pass()
cmd.FailOnError(err, "Couldn't load SMTP password from file")
+ var smtpRoots *x509.CertPool
+ smtpSkipVerify := false
+ if cfg.NotifyMailer.SMTPTrustedRootFile == "InsecureSkipVerify" {
+ smtpSkipVerify = true
+ } else if cfg.NotifyMailer.SMTPTrustedRootFile != "" {
+ pem, err := os.ReadFile(cfg.NotifyMailer.SMTPTrustedRootFile)
+ cmd.FailOnError(err, "Loading trusted roots file")
+ smtpRoots = x509.NewCertPool()
+ if !smtpRoots.AppendCertsFromPEM(pem) {
+ cmd.FailOnError(nil, "Failed to parse root certs PEM")
+ }
+ }
+
mailClient = bmail.New(
cfg.NotifyMailer.Server,
cfg.NotifyMailer.Port,
cfg.NotifyMailer.Username,
smtpPassword,
+ smtpRoots,
+ smtpSkipVerify,
nil,
+ nil,
*address,
log,
metrics.NoopRegisterer,
@@ -605,6 +618,7 @@ func main() {
@@ -605,6 +636,7 @@ func main() {
end: *end,
},
sleepInterval: *sleep,