From 295cd0001190e81d6a88fbdfb2f6b370b107aa70 Mon Sep 17 00:00:00 2001 From: Arjan H Date: Sat, 28 Sep 2024 16:00:21 +0200 Subject: [PATCH] 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. --- gui/apply-boulder | 15 ++++- gui/main.go | 83 +++++++++++++++------------- gui/templates/views/manage.tmpl | 7 +++ mail-tester.go | 22 +++++++- patches/bad-key-revoker_main.patch | 15 +++-- patches/config_notify-mailer.patch | 4 +- patches/expiration-mailer_main.patch | 15 +++-- patches/mail_mailer.patch | 56 +++++++++++++------ patches/notify-mailer_main.patch | 50 +++++++++++++---- 9 files changed, 188 insertions(+), 79 deletions(-) diff --git a/gui/apply-boulder b/gui/apply-boulder index 958b38d..257d834 100755 --- a/gui/apply-boulder +++ b/gui/apply-boulder @@ -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 " + 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 diff --git a/gui/main.go b/gui/main.go index 4f8618e..b8cdf03 100644 --- a/gui/main.go +++ b/gui/main.go @@ -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 } diff --git a/gui/templates/views/manage.tmpl b/gui/templates/views/manage.tmpl index c3fb821..315da7b 100644 --- a/gui/templates/views/manage.tmpl +++ b/gui/templates/views/manage.tmpl @@ -348,6 +348,12 @@ +
+
+ Trust standard public root CAs
+ Trust the LabCA root CA
+ Do not check server certificate
+
@@ -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) { diff --git a/mail-tester.go b/mail-tester.go index 6dd204c..c78839c 100644 --- a/mail-tester.go +++ b/mail-tester.go @@ -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, diff --git a/patches/bad-key-revoker_main.patch b/patches/bad-key-revoker_main.patch index 96b323d..cd7b570 100644 --- a/patches/bad-key-revoker_main.patch +++ b/patches/bad-key-revoker_main.patch @@ -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, diff --git a/patches/config_notify-mailer.patch b/patches/config_notify-mailer.patch index 10accad..98238ce 100644 --- a/patches/config_notify-mailer.patch +++ b/patches/config_notify-mailer.patch @@ -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 ", "passwordFile": "test/secrets/smtp_password", ++ "SMTPTrustedRootFile": "test/certs/ipki/minica.pem", "db": { "dbConnectFile": "test/secrets/mailer_dburl", "maxOpenConns": 10 diff --git a/patches/expiration-mailer_main.patch b/patches/expiration-mailer_main.patch index ba0f065..4049a91 100644 --- a/patches/expiration-mailer_main.patch +++ b/patches/expiration-mailer_main.patch @@ -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, diff --git a/patches/mail_mailer.patch b/patches/mail_mailer.patch index d14c4c2..6909c9b 100644 --- a/patches/mail_mailer.patch +++ b/patches/mail_mailer.patch @@ -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 diff --git a/patches/notify-mailer_main.patch b/patches/notify-mailer_main.patch index a257bd1..4677e96 100644 --- a/patches/notify-mailer_main.patch +++ b/patches/notify-mailer_main.patch @@ -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,