mirror of
https://github.com/outbackdingo/labca.git
synced 2026-01-27 18:19:33 +00:00
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:
@@ -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
|
||||
|
||||
83
gui/main.go
83
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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user