Remove logging of contact email addresses (#7833)
Fixes https://github.com/letsencrypt/boulder/issues/7801
This commit is contained in:
parent
c3948314ff
commit
ded2e5e610
|
@ -284,7 +284,7 @@ func (bkr *badKeyRevoker) revokeCerts(revokerEmails []string, emailToCerts map[s
|
||||||
err := bkr.sendMessage(email, revokedSerials)
|
err := bkr.sendMessage(email, revokedSerials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mailErrors.Inc()
|
mailErrors.Inc()
|
||||||
bkr.logger.Errf("failed to send message to %q: %s", email, err)
|
bkr.logger.Errf("failed to send message: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,9 +370,11 @@ func (bkr *badKeyRevoker) invoke(ctx context.Context) (bool, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
revokerEmails := idToEmails[unchecked.RevokedBy]
|
var serials []string
|
||||||
bkr.logger.AuditInfo(fmt.Sprintf("revoking certs. revoked emails=%v, emailsToCerts=%s",
|
for _, cert := range unrevokedCerts {
|
||||||
revokerEmails, emailsToCerts))
|
serials = append(serials, cert.Serial)
|
||||||
|
}
|
||||||
|
bkr.logger.AuditInfo(fmt.Sprintf("revoking serials %v for key with hash %s", serials, unchecked.KeyHash))
|
||||||
|
|
||||||
// revoke each certificate and send emails to their owners
|
// revoke each certificate and send emails to their owners
|
||||||
err = bkr.revokeCerts(idToEmails[unchecked.RevokedBy], emailsToCerts)
|
err = bkr.revokeCerts(idToEmails[unchecked.RevokedBy], emailsToCerts)
|
||||||
|
|
|
@ -106,7 +106,7 @@ func (lim *limiter) check(address string) error {
|
||||||
|
|
||||||
lim.maybeBumpDay()
|
lim.maybeBumpDay()
|
||||||
if lim.counts[address] >= lim.limit {
|
if lim.counts[address] >= lim.limit {
|
||||||
return fmt.Errorf("daily mail limit exceeded for %q", address)
|
return errors.New("daily mail limit exceeded for this email address")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ func (m *mailer) sendNags(conn bmail.Conn, contacts []string, certs []*x509.Cert
|
||||||
for _, contact := range contacts {
|
for _, contact := range contacts {
|
||||||
parsed, err := url.Parse(contact)
|
parsed, err := url.Parse(contact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Errf("parsing contact email %s: %s", contact, err)
|
m.log.Errf("parsing contact email: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if parsed.Scheme != "mailto" {
|
if parsed.Scheme != "mailto" {
|
||||||
|
@ -164,7 +164,7 @@ func (m *mailer) sendNags(conn bmail.Conn, contacts []string, certs []*x509.Cert
|
||||||
address := parsed.Opaque
|
address := parsed.Opaque
|
||||||
err = policy.ValidEmail(address)
|
err = policy.ValidEmail(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Debugf("skipping invalid email %q: %s", address, err)
|
m.log.Debugf("skipping invalid email: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = m.addressLimiter.check(address)
|
err = m.addressLimiter.check(address)
|
||||||
|
@ -249,28 +249,24 @@ func (m *mailer) sendNags(conn bmail.Conn, contacts []string, certs []*x509.Cert
|
||||||
}
|
}
|
||||||
|
|
||||||
logItem := struct {
|
logItem := struct {
|
||||||
Rcpt []string
|
|
||||||
DaysToExpiration int
|
DaysToExpiration int
|
||||||
TruncatedDNSNames []string
|
TruncatedDNSNames []string
|
||||||
TruncatedSerials []string
|
TruncatedSerials []string
|
||||||
}{
|
}{
|
||||||
Rcpt: emails,
|
|
||||||
DaysToExpiration: email.DaysToExpiration,
|
DaysToExpiration: email.DaysToExpiration,
|
||||||
TruncatedDNSNames: truncatedDomains,
|
TruncatedDNSNames: truncatedDomains,
|
||||||
TruncatedSerials: truncatedSerials,
|
TruncatedSerials: truncatedSerials,
|
||||||
}
|
}
|
||||||
logStr, err := json.Marshal(logItem)
|
logStr, err := json.Marshal(logItem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Errf("logItem could not be serialized to JSON. Raw: %+v", logItem)
|
return fmt.Errorf("failed to serialize log line: %w", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
m.log.Infof("attempting send JSON=%s", string(logStr))
|
m.log.Infof("attempting send for JSON=%s", string(logStr))
|
||||||
|
|
||||||
startSending := m.clk.Now()
|
startSending := m.clk.Now()
|
||||||
err = conn.SendMail(emails, subjBuf.String(), msgBuf.String())
|
err = conn.SendMail(emails, subjBuf.String(), msgBuf.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Errf("failed send JSON=%s err=%s", string(logStr), err)
|
return fmt.Errorf("failed send for %s: %w", string(logStr), err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
finishSending := m.clk.Now()
|
finishSending := m.clk.Now()
|
||||||
elapsed := finishSending.Sub(startSending)
|
elapsed := finishSending.Sub(startSending)
|
||||||
|
|
|
@ -180,13 +180,10 @@ func TestSendNags(t *testing.T) {
|
||||||
test.AssertErrorIs(t, err, errNoValidEmail)
|
test.AssertErrorIs(t, err, errNoValidEmail)
|
||||||
test.AssertEquals(t, len(mc.Messages), 0)
|
test.AssertEquals(t, len(mc.Messages), 0)
|
||||||
|
|
||||||
sendLogs := log.GetAllMatching("INFO: attempting send JSON=.*")
|
sendLogs := log.GetAllMatching("INFO: attempting send for JSON=.*")
|
||||||
if len(sendLogs) != 2 {
|
if len(sendLogs) != 2 {
|
||||||
t.Errorf("expected 2 'attempting send' log line, got %d: %s", len(sendLogs), strings.Join(sendLogs, "\n"))
|
t.Errorf("expected 2 'attempting send' log line, got %d: %s", len(sendLogs), strings.Join(sendLogs, "\n"))
|
||||||
}
|
}
|
||||||
if !strings.Contains(sendLogs[0], `"Rcpt":["rolandshoemaker@gmail.com"]`) {
|
|
||||||
t.Errorf("expected first 'attempting send' log line to have one address, got %q", sendLogs[0])
|
|
||||||
}
|
|
||||||
if !strings.Contains(sendLogs[0], `"TruncatedSerials":["000000000000000000000000000000000304"]`) {
|
if !strings.Contains(sendLogs[0], `"TruncatedSerials":["000000000000000000000000000000000304"]`) {
|
||||||
t.Errorf("expected first 'attempting send' log line to have one serial, got %q", sendLogs[0])
|
t.Errorf("expected first 'attempting send' log line to have one serial, got %q", sendLogs[0])
|
||||||
}
|
}
|
||||||
|
@ -196,6 +193,9 @@ func TestSendNags(t *testing.T) {
|
||||||
if !strings.Contains(sendLogs[0], `"TruncatedDNSNames":["example.com"]`) {
|
if !strings.Contains(sendLogs[0], `"TruncatedDNSNames":["example.com"]`) {
|
||||||
t.Errorf("expected first 'attempting send' log line to have 1 domain, 'example.com', got %q", sendLogs[0])
|
t.Errorf("expected first 'attempting send' log line to have 1 domain, 'example.com', got %q", sendLogs[0])
|
||||||
}
|
}
|
||||||
|
if strings.Contains(sendLogs[0], `"@gmail.com"`) {
|
||||||
|
t.Errorf("log line should not contain email address, got %q", sendLogs[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSendNagsAddressLimited(t *testing.T) {
|
func TestSendNagsAddressLimited(t *testing.T) {
|
||||||
|
|
15
policy/pa.go
15
policy/pa.go
|
@ -336,23 +336,18 @@ var forbiddenMailDomains = map[string]bool{
|
||||||
func ValidEmail(address string) error {
|
func ValidEmail(address string) error {
|
||||||
email, err := mail.ParseAddress(address)
|
email, err := mail.ParseAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(address) > 254 {
|
return berrors.InvalidEmailError("unable to parse email address")
|
||||||
address = address[:254] + "..."
|
|
||||||
}
|
|
||||||
return berrors.InvalidEmailError("%q is not a valid e-mail address", address)
|
|
||||||
}
|
}
|
||||||
splitEmail := strings.SplitN(email.Address, "@", -1)
|
splitEmail := strings.SplitN(email.Address, "@", -1)
|
||||||
domain := strings.ToLower(splitEmail[len(splitEmail)-1])
|
domain := strings.ToLower(splitEmail[len(splitEmail)-1])
|
||||||
err = validNonWildcardDomain(domain)
|
err = validNonWildcardDomain(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return berrors.InvalidEmailError(
|
return berrors.InvalidEmailError("contact email has invalid domain: %s", err)
|
||||||
"contact email %q has invalid domain : %s",
|
|
||||||
email.Address, err)
|
|
||||||
}
|
}
|
||||||
if forbiddenMailDomains[domain] {
|
if forbiddenMailDomains[domain] {
|
||||||
return berrors.InvalidEmailError(
|
// We're okay including the domain in the error message here because this
|
||||||
"invalid contact domain. Contact emails @%s are forbidden",
|
// case occurs only for a small block-list of domains listed above.
|
||||||
domain)
|
return berrors.InvalidEmailError("contact email has forbidden domain %q", domain)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -473,16 +473,16 @@ func TestMalformedExactBlocklist(t *testing.T) {
|
||||||
|
|
||||||
func TestValidEmailError(t *testing.T) {
|
func TestValidEmailError(t *testing.T) {
|
||||||
err := ValidEmail("(๑•́ ω •̀๑)")
|
err := ValidEmail("(๑•́ ω •̀๑)")
|
||||||
test.AssertEquals(t, err.Error(), "\"(๑•́ ω •̀๑)\" is not a valid e-mail address")
|
test.AssertEquals(t, err.Error(), "unable to parse email address")
|
||||||
|
|
||||||
err = ValidEmail("john.smith@gmail.com #replace with real email")
|
err = ValidEmail("john.smith@gmail.com #replace with real email")
|
||||||
test.AssertEquals(t, err.Error(), "\"john.smith@gmail.com #replace with real email\" is not a valid e-mail address")
|
test.AssertEquals(t, err.Error(), "unable to parse email address")
|
||||||
|
|
||||||
err = ValidEmail("example@example.com")
|
err = ValidEmail("example@example.com")
|
||||||
test.AssertEquals(t, err.Error(), "invalid contact domain. Contact emails @example.com are forbidden")
|
test.AssertEquals(t, err.Error(), "contact email has forbidden domain \"example.com\"")
|
||||||
|
|
||||||
err = ValidEmail("example@-foobar.com")
|
err = ValidEmail("example@-foobar.com")
|
||||||
test.AssertEquals(t, err.Error(), "contact email \"example@-foobar.com\" has invalid domain : Domain name contains an invalid character")
|
test.AssertEquals(t, err.Error(), "contact email has invalid domain: Domain name contains an invalid character")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckAuthzChallenges(t *testing.T) {
|
func TestCheckAuthzChallenges(t *testing.T) {
|
||||||
|
|
16
ra/ra.go
16
ra/ra.go
|
@ -464,19 +464,16 @@ func (ra *RegistrationAuthorityImpl) validateContacts(contacts []string) error {
|
||||||
return berrors.InvalidEmailError("invalid contact")
|
return berrors.InvalidEmailError("invalid contact")
|
||||||
}
|
}
|
||||||
if parsed.Scheme != "mailto" {
|
if parsed.Scheme != "mailto" {
|
||||||
return berrors.UnsupportedContactError("contact method %q is not supported", parsed.Scheme)
|
return berrors.UnsupportedContactError("only contact scheme 'mailto:' is supported")
|
||||||
}
|
}
|
||||||
if parsed.RawQuery != "" || contact[len(contact)-1] == '?' {
|
if parsed.RawQuery != "" || contact[len(contact)-1] == '?' {
|
||||||
return berrors.InvalidEmailError("contact email %q contains a question mark", contact)
|
return berrors.InvalidEmailError("contact email contains a question mark")
|
||||||
}
|
}
|
||||||
if parsed.Fragment != "" || contact[len(contact)-1] == '#' {
|
if parsed.Fragment != "" || contact[len(contact)-1] == '#' {
|
||||||
return berrors.InvalidEmailError("contact email %q contains a '#'", contact)
|
return berrors.InvalidEmailError("contact email contains a '#'")
|
||||||
}
|
}
|
||||||
if !core.IsASCII(contact) {
|
if !core.IsASCII(contact) {
|
||||||
return berrors.InvalidEmailError(
|
return berrors.InvalidEmailError("contact email contains non-ASCII characters")
|
||||||
"contact email [%q] contains non-ASCII characters",
|
|
||||||
contact,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
err = policy.ValidEmail(parsed.Opaque)
|
err = policy.ValidEmail(parsed.Opaque)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -490,10 +487,7 @@ func (ra *RegistrationAuthorityImpl) validateContacts(contacts []string) error {
|
||||||
// That means the largest marshalled JSON value we can store is 191 bytes.
|
// That means the largest marshalled JSON value we can store is 191 bytes.
|
||||||
const maxContactBytes = 191
|
const maxContactBytes = 191
|
||||||
if jsonBytes, err := json.Marshal(contacts); err != nil {
|
if jsonBytes, err := json.Marshal(contacts); err != nil {
|
||||||
// This shouldn't happen with a simple []string but if it does we want the
|
return fmt.Errorf("failed to marshal reg.Contact to JSON: %w", err)
|
||||||
// error to be logged internally but served as a 500 to the user so we
|
|
||||||
// return a bare error and not a berror here.
|
|
||||||
return fmt.Errorf("failed to marshal reg.Contact to JSON: %#v", contacts)
|
|
||||||
} else if len(jsonBytes) >= maxContactBytes {
|
} else if len(jsonBytes) >= maxContactBytes {
|
||||||
return berrors.InvalidEmailError(
|
return berrors.InvalidEmailError(
|
||||||
"too many/too long contact(s). Please use shorter or fewer email addresses")
|
"too many/too long contact(s). Please use shorter or fewer email addresses")
|
||||||
|
|
|
@ -66,19 +66,19 @@ func TestAccountEmailError(t *testing.T) {
|
||||||
name: "empty proto",
|
name: "empty proto",
|
||||||
contacts: []string{"mailto:valid@valid.com", " "},
|
contacts: []string{"mailto:valid@valid.com", " "},
|
||||||
expectedProbType: "urn:ietf:params:acme:error:unsupportedContact",
|
expectedProbType: "urn:ietf:params:acme:error:unsupportedContact",
|
||||||
expectedProbDetail: `contact method "" is not supported`,
|
expectedProbDetail: `only contact scheme 'mailto:' is supported`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty mailto",
|
name: "empty mailto",
|
||||||
contacts: []string{"mailto:valid@valid.com", "mailto:"},
|
contacts: []string{"mailto:valid@valid.com", "mailto:"},
|
||||||
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
||||||
expectedProbDetail: `"" is not a valid e-mail address`,
|
expectedProbDetail: `unable to parse email address`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-ascii mailto",
|
name: "non-ascii mailto",
|
||||||
contacts: []string{"mailto:valid@valid.com", "mailto:cpu@l̴etsencrypt.org"},
|
contacts: []string{"mailto:valid@valid.com", "mailto:cpu@l̴etsencrypt.org"},
|
||||||
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
||||||
expectedProbDetail: `contact email ["mailto:cpu@l̴etsencrypt.org"] contains non-ASCII characters`,
|
expectedProbDetail: `contact email contains non-ASCII characters`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "too many contacts",
|
name: "too many contacts",
|
||||||
|
@ -90,25 +90,25 @@ func TestAccountEmailError(t *testing.T) {
|
||||||
name: "invalid contact",
|
name: "invalid contact",
|
||||||
contacts: []string{"mailto:valid@valid.com", "mailto:a@"},
|
contacts: []string{"mailto:valid@valid.com", "mailto:a@"},
|
||||||
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
||||||
expectedProbDetail: `"a@" is not a valid e-mail address`,
|
expectedProbDetail: `unable to parse email address`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "forbidden contact domain",
|
name: "forbidden contact domain",
|
||||||
contacts: []string{"mailto:valid@valid.com", "mailto:a@example.com"},
|
contacts: []string{"mailto:valid@valid.com", "mailto:a@example.com"},
|
||||||
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
||||||
expectedProbDetail: "invalid contact domain. Contact emails @example.com are forbidden",
|
expectedProbDetail: "contact email has forbidden domain \"example.com\"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "contact domain invalid TLD",
|
name: "contact domain invalid TLD",
|
||||||
contacts: []string{"mailto:valid@valid.com", "mailto:a@example.cpu"},
|
contacts: []string{"mailto:valid@valid.com", "mailto:a@example.cpu"},
|
||||||
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
||||||
expectedProbDetail: `contact email "a@example.cpu" has invalid domain : Domain name does not end with a valid public suffix (TLD)`,
|
expectedProbDetail: `contact email has invalid domain: Domain name does not end with a valid public suffix (TLD)`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "contact domain invalid",
|
name: "contact domain invalid",
|
||||||
contacts: []string{"mailto:valid@valid.com", "mailto:a@example./.com"},
|
contacts: []string{"mailto:valid@valid.com", "mailto:a@example./.com"},
|
||||||
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
expectedProbType: "urn:ietf:params:acme:error:invalidContact",
|
||||||
expectedProbDetail: "contact email \"a@example./.com\" has invalid domain : Domain name contains an invalid character",
|
expectedProbDetail: "contact email has invalid domain: Domain name contains an invalid character",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "too long contact",
|
name: "too long contact",
|
||||||
|
|
Loading…
Reference in New Issue