Merge branch 'master' into more-revoker
This commit is contained in:
commit
c3a6a837a4
|
|
@ -1,9 +1,5 @@
|
|||
# Contributing to Boulder
|
||||
|
||||
> **Note:** We are currently in a *General Availability* only merge window, meaning
|
||||
> we will only be reviewing & merging patches which close an issue tagged with the *General
|
||||
> Availability* milestone.
|
||||
|
||||
Thanks for helping us build Boulder, if you haven't already had a chance to look
|
||||
over our patch submission guidelines take a minute to do so now.
|
||||
|
||||
|
|
|
|||
|
|
@ -99,6 +99,14 @@ func (mock *MockDNSResolver) LookupCAA(_ context.Context, domain string) ([]*dns
|
|||
fallthrough
|
||||
case "servfail.com":
|
||||
return results, fmt.Errorf("SERVFAIL")
|
||||
case "multi-crit-present.com":
|
||||
record.Flag = 1
|
||||
record.Tag = "issue"
|
||||
record.Value = "symantec.com"
|
||||
results = append(results, &record)
|
||||
secondRecord := record
|
||||
secondRecord.Value = "letsencrypt.org"
|
||||
results = append(results, &secondRecord)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
netmail "net/mail"
|
||||
"sort"
|
||||
"strings"
|
||||
|
|
@ -32,7 +34,7 @@ import (
|
|||
const defaultNagCheckInterval = 24 * time.Hour
|
||||
|
||||
type emailContent struct {
|
||||
ExpirationDate time.Time
|
||||
ExpirationDate string
|
||||
DaysToExpiration int
|
||||
DNSNames string
|
||||
}
|
||||
|
|
@ -54,35 +56,58 @@ type mailer struct {
|
|||
clk clock.Clock
|
||||
}
|
||||
|
||||
func (m *mailer) sendNags(parsedCert *x509.Certificate, contacts []*core.AcmeURL) error {
|
||||
expiresIn := int(parsedCert.NotAfter.Sub(m.clk.Now()).Hours() / 24)
|
||||
func (m *mailer) sendNags(contacts []*core.AcmeURL, certs []*x509.Certificate) error {
|
||||
if len(contacts) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(certs) == 0 {
|
||||
return errors.New("no certs given to send nags for")
|
||||
}
|
||||
emails := []string{}
|
||||
for _, contact := range contacts {
|
||||
if contact.Scheme == "mailto" {
|
||||
emails = append(emails, contact.Opaque)
|
||||
}
|
||||
}
|
||||
if len(emails) > 0 {
|
||||
email := emailContent{
|
||||
ExpirationDate: parsedCert.NotAfter,
|
||||
DaysToExpiration: expiresIn,
|
||||
DNSNames: strings.Join(parsedCert.DNSNames, ", "),
|
||||
}
|
||||
msgBuf := new(bytes.Buffer)
|
||||
err := m.emailTemplate.Execute(msgBuf, email)
|
||||
if err != nil {
|
||||
m.stats.Inc("Mailer.Expiration.Errors.SendingNag.TemplateFailure", 1, 1.0)
|
||||
return err
|
||||
}
|
||||
startSending := m.clk.Now()
|
||||
err = m.mailer.SendMail(emails, m.subject, msgBuf.String())
|
||||
if err != nil {
|
||||
m.stats.Inc("Mailer.Expiration.Errors.SendingNag.SendFailure", 1, 1.0)
|
||||
return err
|
||||
}
|
||||
m.stats.TimingDuration("Mailer.Expiration.SendLatency", time.Since(startSending), 1.0)
|
||||
m.stats.Inc("Mailer.Expiration.Sent", int64(len(emails)), 1.0)
|
||||
if len(emails) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
expiresIn := time.Duration(math.MaxInt64)
|
||||
expDate := m.clk.Now()
|
||||
domains := []string{}
|
||||
|
||||
// Pick out the expiration date that is closest to being hit.
|
||||
for _, cert := range certs {
|
||||
domains = append(domains, cert.DNSNames...)
|
||||
possible := cert.NotAfter.Sub(m.clk.Now())
|
||||
if possible < expiresIn {
|
||||
expiresIn = possible
|
||||
expDate = cert.NotAfter
|
||||
}
|
||||
}
|
||||
domains = core.UniqueLowerNames(domains)
|
||||
sort.Strings(domains)
|
||||
|
||||
email := emailContent{
|
||||
ExpirationDate: expDate.UTC().Format(time.RFC822Z),
|
||||
DaysToExpiration: int(expiresIn.Hours() / 24),
|
||||
DNSNames: strings.Join(domains, "\n"),
|
||||
}
|
||||
msgBuf := new(bytes.Buffer)
|
||||
err := m.emailTemplate.Execute(msgBuf, email)
|
||||
if err != nil {
|
||||
m.stats.Inc("Mailer.Expiration.Errors.SendingNag.TemplateFailure", 1, 1.0)
|
||||
return err
|
||||
}
|
||||
startSending := m.clk.Now()
|
||||
err = m.mailer.SendMail(emails, m.subject, msgBuf.String())
|
||||
if err != nil {
|
||||
m.stats.Inc("Mailer.Expiration.Errors.SendingNag.SendFailure", 1, 1.0)
|
||||
return err
|
||||
}
|
||||
m.stats.TimingDuration("Mailer.Expiration.SendLatency", time.Since(startSending), 1.0)
|
||||
m.stats.Inc("Mailer.Expiration.Sent", int64(len(emails)), 1.0)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -121,32 +146,49 @@ func (m *mailer) updateCertStatus(serial string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *mailer) processCerts(certs []core.Certificate) {
|
||||
m.log.Info(fmt.Sprintf("expiration-mailer: Found %d certificates, starting sending messages", len(certs)))
|
||||
func (m *mailer) processCerts(allCerts []core.Certificate) {
|
||||
m.log.Info(fmt.Sprintf("expiration-mailer: Found %d certificates, starting sending messages", len(allCerts)))
|
||||
|
||||
for _, cert := range certs {
|
||||
reg, err := m.rs.GetRegistration(cert.RegistrationID)
|
||||
regIDToCerts := make(map[int64][]core.Certificate)
|
||||
|
||||
for _, cert := range allCerts {
|
||||
cs := regIDToCerts[cert.RegistrationID]
|
||||
cs = append(cs, cert)
|
||||
regIDToCerts[cert.RegistrationID] = cs
|
||||
}
|
||||
|
||||
for regID, certs := range regIDToCerts {
|
||||
reg, err := m.rs.GetRegistration(regID)
|
||||
if err != nil {
|
||||
m.log.Err(fmt.Sprintf("Error fetching registration %d: %s", cert.RegistrationID, err))
|
||||
m.log.Err(fmt.Sprintf("Error fetching registration %d: %s", regID, err))
|
||||
m.stats.Inc("Mailer.Expiration.Errors.GetRegistration", 1, 1.0)
|
||||
continue
|
||||
}
|
||||
parsedCert, err := x509.ParseCertificate(cert.DER)
|
||||
if err != nil {
|
||||
m.log.Err(fmt.Sprintf("Error parsing certificate %s: %s", cert.Serial, err))
|
||||
m.stats.Inc("Mailer.Expiration.Errors.ParseCertificate", 1, 1.0)
|
||||
continue
|
||||
|
||||
parsedCerts := []*x509.Certificate{}
|
||||
for _, cert := range certs {
|
||||
parsedCert, err := x509.ParseCertificate(cert.DER)
|
||||
if err != nil {
|
||||
// TODO(#1420): tell registration about this error
|
||||
m.log.Err(fmt.Sprintf("Error parsing certificate %s: %s", cert.Serial, err))
|
||||
m.stats.Inc("Mailer.Expiration.Errors.ParseCertificate", 1, 1.0)
|
||||
continue
|
||||
}
|
||||
parsedCerts = append(parsedCerts, parsedCert)
|
||||
}
|
||||
err = m.sendNags(parsedCert, reg.Contact)
|
||||
|
||||
err = m.sendNags(reg.Contact, parsedCerts)
|
||||
if err != nil {
|
||||
m.log.Err(fmt.Sprintf("Error sending nag emails: %s", err))
|
||||
continue
|
||||
}
|
||||
err = m.updateCertStatus(cert.Serial)
|
||||
if err != nil {
|
||||
m.log.Err(fmt.Sprintf("Error updating certificate status for %s: %s", cert.Serial, err))
|
||||
m.stats.Inc("Mailer.Expiration.Errors.UpdateCertificateStatus", 1, 1.0)
|
||||
continue
|
||||
for _, cert := range certs {
|
||||
err = m.updateCertStatus(cert.Serial)
|
||||
if err != nil {
|
||||
m.log.Err(fmt.Sprintf("Error updating certificate status for %s: %s", cert.Serial, err))
|
||||
m.stats.Inc("Mailer.Expiration.Errors.UpdateCertificateStatus", 1, 1.0)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
m.log.Info("expiration-mailer: Finished sending messages")
|
||||
|
|
|
|||
|
|
@ -113,20 +113,20 @@ func TestSendNags(t *testing.T) {
|
|||
email, _ := core.ParseAcmeURL("mailto:rolandshoemaker@gmail.com")
|
||||
emailB, _ := core.ParseAcmeURL("mailto:test@gmail.com")
|
||||
|
||||
err := m.sendNags(cert, []*core.AcmeURL{email})
|
||||
err := m.sendNags([]*core.AcmeURL{email}, []*x509.Certificate{cert})
|
||||
test.AssertNotError(t, err, "Failed to send warning messages")
|
||||
test.AssertEquals(t, len(mc.Messages), 1)
|
||||
test.AssertEquals(t, fmt.Sprintf(`hi, cert for DNS names example.com is going to expire in 2 days (%s)`, cert.NotAfter), mc.Messages[0])
|
||||
test.AssertEquals(t, fmt.Sprintf(`hi, cert for DNS names example.com is going to expire in 2 days (%s)`, cert.NotAfter.Format(time.RFC822Z)), mc.Messages[0])
|
||||
|
||||
mc.Clear()
|
||||
err = m.sendNags(cert, []*core.AcmeURL{email, emailB})
|
||||
err = m.sendNags([]*core.AcmeURL{email, emailB}, []*x509.Certificate{cert})
|
||||
test.AssertNotError(t, err, "Failed to send warning messages")
|
||||
test.AssertEquals(t, len(mc.Messages), 2)
|
||||
test.AssertEquals(t, fmt.Sprintf(`hi, cert for DNS names example.com is going to expire in 2 days (%s)`, cert.NotAfter), mc.Messages[0])
|
||||
test.AssertEquals(t, fmt.Sprintf(`hi, cert for DNS names example.com is going to expire in 2 days (%s)`, cert.NotAfter), mc.Messages[1])
|
||||
test.AssertEquals(t, fmt.Sprintf(`hi, cert for DNS names example.com is going to expire in 2 days (%s)`, cert.NotAfter.Format(time.RFC822Z)), mc.Messages[0])
|
||||
test.AssertEquals(t, fmt.Sprintf(`hi, cert for DNS names example.com is going to expire in 2 days (%s)`, cert.NotAfter.Format(time.RFC822Z)), mc.Messages[1])
|
||||
|
||||
mc.Clear()
|
||||
err = m.sendNags(cert, []*core.AcmeURL{})
|
||||
err = m.sendNags([]*core.AcmeURL{}, []*x509.Certificate{cert})
|
||||
test.AssertNotError(t, err, "Not an error to pass no email contacts")
|
||||
test.AssertEquals(t, len(mc.Messages), 0)
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ func TestSendNags(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to parse templates")
|
||||
for _, template := range templates.Templates() {
|
||||
m.emailTemplate = template
|
||||
err = m.sendNags(cert, []*core.AcmeURL{})
|
||||
err = m.sendNags(nil, []*x509.Certificate{cert})
|
||||
test.AssertNotError(t, err, "failed to send nag")
|
||||
}
|
||||
}
|
||||
|
|
@ -278,8 +278,8 @@ func TestFindExpiringCertificates(t *testing.T) {
|
|||
// Should get 001 and 003
|
||||
test.AssertEquals(t, len(ctx.mc.Messages), 2)
|
||||
|
||||
test.AssertEquals(t, fmt.Sprintf(`hi, cert for DNS names example-a.com is going to expire in 0 days (%s)`, rawCertA.NotAfter.UTC().Format("2006-01-02 15:04:05 -0700 MST")), ctx.mc.Messages[0])
|
||||
test.AssertEquals(t, fmt.Sprintf(`hi, cert for DNS names example-c.com is going to expire in 7 days (%s)`, rawCertC.NotAfter.UTC().Format("2006-01-02 15:04:05 -0700 MST")), ctx.mc.Messages[1])
|
||||
test.AssertEquals(t, fmt.Sprintf(`hi, cert for DNS names example-a.com is going to expire in 0 days (%s)`, rawCertA.NotAfter.UTC().Format(time.RFC822Z)), ctx.mc.Messages[0])
|
||||
test.AssertEquals(t, fmt.Sprintf(`hi, cert for DNS names example-c.com is going to expire in 7 days (%s)`, rawCertC.NotAfter.UTC().Format(time.RFC822Z)), ctx.mc.Messages[1])
|
||||
|
||||
// A consecutive run shouldn't find anything
|
||||
ctx.mc.Clear()
|
||||
|
|
@ -452,6 +452,88 @@ func TestDontFindRevokedCert(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDedupOnRegistration(t *testing.T) {
|
||||
expiresIn := 96 * time.Hour
|
||||
ctx := setup(t, []time.Duration{expiresIn})
|
||||
|
||||
var keyA jose.JsonWebKey
|
||||
err := json.Unmarshal(jsonKeyA, &keyA)
|
||||
test.AssertNotError(t, err, "Failed to unmarshal public JWK")
|
||||
|
||||
regA := core.Registration{
|
||||
ID: 1,
|
||||
Contact: []*core.AcmeURL{
|
||||
email1,
|
||||
},
|
||||
Key: keyA,
|
||||
InitialIP: net.ParseIP("6.5.5.6"),
|
||||
}
|
||||
regA, err = ctx.ssa.NewRegistration(regA)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't store regA: %s", err)
|
||||
}
|
||||
rawCertA := newX509Cert("happy A",
|
||||
ctx.fc.Now().Add(72*time.Hour),
|
||||
[]string{"example-a.com", "shared-example.com"},
|
||||
1338,
|
||||
)
|
||||
|
||||
certDerA, _ := x509.CreateCertificate(rand.Reader, rawCertA, rawCertA, &testKey.PublicKey, &testKey)
|
||||
certA := &core.Certificate{
|
||||
RegistrationID: regA.ID,
|
||||
Serial: "001",
|
||||
Expires: rawCertA.NotAfter,
|
||||
DER: certDerA,
|
||||
}
|
||||
certStatusA := &core.CertificateStatus{
|
||||
Serial: "001",
|
||||
LastExpirationNagSent: time.Unix(0, 0),
|
||||
Status: core.OCSPStatusGood,
|
||||
}
|
||||
|
||||
rawCertB := newX509Cert("happy B",
|
||||
ctx.fc.Now().Add(48*time.Hour),
|
||||
[]string{"example-b.com", "shared-example.com"},
|
||||
1337,
|
||||
)
|
||||
certDerB, _ := x509.CreateCertificate(rand.Reader, rawCertB, rawCertB, &testKey.PublicKey, &testKey)
|
||||
certB := &core.Certificate{
|
||||
RegistrationID: regA.ID,
|
||||
Serial: "002",
|
||||
Expires: rawCertB.NotAfter,
|
||||
DER: certDerB,
|
||||
}
|
||||
certStatusB := &core.CertificateStatus{
|
||||
Serial: "002",
|
||||
LastExpirationNagSent: time.Unix(0, 0),
|
||||
Status: core.OCSPStatusGood,
|
||||
}
|
||||
|
||||
setupDBMap, err := sa.NewDbMap("mysql+tcp://test_setup@localhost:3306/boulder_sa_test")
|
||||
err = setupDBMap.Insert(certA)
|
||||
test.AssertNotError(t, err, "Couldn't add certA")
|
||||
err = setupDBMap.Insert(certB)
|
||||
test.AssertNotError(t, err, "Couldn't add certB")
|
||||
err = setupDBMap.Insert(certStatusA)
|
||||
test.AssertNotError(t, err, "Couldn't add certStatusA")
|
||||
err = setupDBMap.Insert(certStatusB)
|
||||
test.AssertNotError(t, err, "Couldn't add certStatusB")
|
||||
|
||||
err = ctx.m.findExpiringCertificates()
|
||||
test.AssertNotError(t, err, "error calling findExpiringCertificates")
|
||||
if len(ctx.mc.Messages) > 1 {
|
||||
t.Errorf("num of messages, want %d, got %d", 1, len(ctx.mc.Messages))
|
||||
}
|
||||
if len(ctx.mc.Messages) == 0 {
|
||||
t.Fatalf("no messages sent")
|
||||
}
|
||||
domains := "example-a.com\nexample-b.com\nshared-example.com"
|
||||
expected := fmt.Sprintf(`hi, cert for DNS names %s is going to expire in 1 days (%s)`,
|
||||
domains,
|
||||
rawCertB.NotAfter.Format(time.RFC822Z))
|
||||
test.AssertEquals(t, expected, ctx.mc.Messages[0])
|
||||
}
|
||||
|
||||
type testCtx struct {
|
||||
dbMap *gorp.DbMap
|
||||
ssa core.StorageAdder
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
var (
|
||||
email1 = mustParseAcmeURL("mailto:one@example.com")
|
||||
email2 = mustParseAcmeURL("mailto:two@example.com")
|
||||
)
|
||||
|
||||
func mustParseAcmeURL(acmeURL string) *core.AcmeURL {
|
||||
c, err := core.ParseAcmeURL(acmeURL)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to parse as AcmeURL %#v: %s", acmeURL, err))
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func TestSendEarliestCertInfo(t *testing.T) {
|
||||
expiresIn := 24 * time.Hour
|
||||
ctx := setup(t, []time.Duration{expiresIn})
|
||||
defer ctx.cleanUp()
|
||||
|
||||
rawCertA := newX509Cert("happy A",
|
||||
ctx.fc.Now().AddDate(0, 0, 5),
|
||||
[]string{"example-A.com", "SHARED-example.com"},
|
||||
1337,
|
||||
)
|
||||
rawCertB := newX509Cert("happy B",
|
||||
ctx.fc.Now().AddDate(0, 0, 2),
|
||||
[]string{"shared-example.com", "example-b.com"},
|
||||
1337,
|
||||
)
|
||||
|
||||
ctx.m.sendNags([]*core.AcmeURL{email1, email2}, []*x509.Certificate{rawCertA, rawCertB})
|
||||
if len(ctx.mc.Messages) != 2 {
|
||||
t.Errorf("num of messages, want %d, got %d", 2, len(ctx.mc.Messages))
|
||||
}
|
||||
if len(ctx.mc.Messages) == 0 {
|
||||
t.Fatalf("no message sent")
|
||||
}
|
||||
domains := "example-a.com\nexample-b.com\nshared-example.com"
|
||||
expected := fmt.Sprintf(`hi, cert for DNS names %s is going to expire in 2 days (%s)`,
|
||||
domains,
|
||||
rawCertB.NotAfter.Format(time.RFC822Z))
|
||||
test.AssertEquals(t, expected, ctx.mc.Messages[0])
|
||||
}
|
||||
|
||||
func newX509Cert(commonName string, notAfter time.Time, dnsNames []string, serial int64) *x509.Certificate {
|
||||
return &x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: commonName,
|
||||
},
|
||||
NotAfter: notAfter,
|
||||
DNSNames: dnsNames,
|
||||
SerialNumber: big.NewInt(serial),
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -363,6 +363,8 @@ func (m *Mailer) Clear() {
|
|||
|
||||
// SendMail is a mock
|
||||
func (m *Mailer) SendMail(to []string, subject, msg string) (err error) {
|
||||
|
||||
// TODO(1421): clean up this To: stuff
|
||||
for range to {
|
||||
m.Messages = append(m.Messages, msg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,10 @@ func (pa PolicyAuthorityImpl) WillingToIssue(id core.AcmeIdentifier, regID int64
|
|||
return errInvalidDNSCharacter
|
||||
}
|
||||
|
||||
if label[len(label)-1] == '-' {
|
||||
return errInvalidDNSCharacter
|
||||
}
|
||||
|
||||
if punycodeRegexp.MatchString(label) {
|
||||
return errIDNNotSupported
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,8 +66,9 @@ func TestWillingToIssue(t *testing.T) {
|
|||
{`www.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.com`, errLabelTooLong}, // Label too long (>63 characters)
|
||||
|
||||
{`www.-ombo.com`, errInvalidDNSCharacter}, // Label starts with '-'
|
||||
{`www.zomb-.com`, errInvalidDNSCharacter}, // Label ends with '-'
|
||||
{`xn--.net`, errInvalidDNSCharacter}, // Label ends with '-'
|
||||
{`www.xn--hmr.net`, errIDNNotSupported}, // Punycode (disallowed for now)
|
||||
{`xn--.net`, errIDNNotSupported}, // No punycode for now.
|
||||
{`0`, errTooFewLabels},
|
||||
{`1`, errTooFewLabels},
|
||||
{`*`, errInvalidDNSCharacter},
|
||||
|
|
@ -123,9 +124,7 @@ func TestWillingToIssue(t *testing.T) {
|
|||
"www.8675309.com",
|
||||
"8675309.com",
|
||||
"zom2bo.com",
|
||||
"zombo-.com",
|
||||
"www.zom-bo.com",
|
||||
"www.zombo-.com",
|
||||
}
|
||||
|
||||
pa, cleanup := paImpl(t)
|
||||
|
|
|
|||
|
|
@ -658,9 +658,6 @@ func (va *ValidationAuthorityImpl) checkCAARecords(ctx context.Context, identifi
|
|||
if caa.Value == va.IssuerDomain {
|
||||
valid = true
|
||||
return
|
||||
} else if caa.Flag > 0 {
|
||||
valid = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -679,6 +679,8 @@ func TestCAAChecking(t *testing.T) {
|
|||
CAATest{"example.co.uk", false, true},
|
||||
// Good (present)
|
||||
CAATest{"present.com", true, true},
|
||||
// Good (multiple critical, one matching)
|
||||
CAATest{"multi-crit-present.com", true, true},
|
||||
}
|
||||
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
|
|
|
|||
Loading…
Reference in New Issue