From 109f7cf75e8db5d7523b8a2ae750a919dce09403 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 9 Oct 2015 16:35:19 -0400 Subject: [PATCH] Limit the number of contacts per registration --- cmd/boulder-ra/main.go | 3 ++- cmd/shell.go | 2 ++ ra/registration-authority.go | 21 ++++++++++++++------- ra/registration-authority_test.go | 22 +++++++++++++--------- test/boulder-config.json | 1 + wfe/web-front-end_test.go | 2 +- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/cmd/boulder-ra/main.go b/cmd/boulder-ra/main.go index 832cfac7f..e9facf68d 100644 --- a/cmd/boulder-ra/main.go +++ b/cmd/boulder-ra/main.go @@ -46,7 +46,8 @@ func main() { rateLimitPolicies, err := cmd.LoadRateLimitPolicies(c.RA.RateLimitPoliciesFilename) cmd.FailOnError(err, "Couldn't load rate limit policies file") - rai := ra.NewRegistrationAuthorityImpl(clock.Default(), auditlogger, stats, rateLimitPolicies) + rai := ra.NewRegistrationAuthorityImpl(clock.Default(), auditlogger, stats, + rateLimitPolicies, c.RA.MaxContactsPerRegistration) rai.PA = pa raDNSTimeout, err := time.ParseDuration(c.Common.DNSTimeout) cmd.FailOnError(err, "Couldn't parse RA DNS timeout") diff --git a/cmd/shell.go b/cmd/shell.go index 0469a950d..99d58d9f2 100644 --- a/cmd/shell.go +++ b/cmd/shell.go @@ -99,6 +99,8 @@ type Config struct { MaxConcurrentRPCServerRequests int64 + MaxContactsPerRegistration int + // DebugAddr is the address to run the /debug handlers on. DebugAddr string } diff --git a/ra/registration-authority.go b/ra/registration-authority.go index dd4990527..32ef80201 100644 --- a/ra/registration-authority.go +++ b/ra/registration-authority.go @@ -51,10 +51,11 @@ type RegistrationAuthorityImpl struct { tiMu *sync.RWMutex totalIssuedCache int lastIssuedCount *time.Time + maxContactsPerReg int } // NewRegistrationAuthorityImpl constructs a new RA object. -func NewRegistrationAuthorityImpl(clk clock.Clock, logger *blog.AuditLogger, stats statsd.Statter, policies cmd.RateLimitConfig) RegistrationAuthorityImpl { +func NewRegistrationAuthorityImpl(clk clock.Clock, logger *blog.AuditLogger, stats statsd.Statter, policies cmd.RateLimitConfig, maxContactsPerReg int) RegistrationAuthorityImpl { ra := RegistrationAuthorityImpl{ stats: stats, clk: clk, @@ -62,6 +63,7 @@ func NewRegistrationAuthorityImpl(clk clock.Clock, logger *blog.AuditLogger, sta authorizationLifetime: DefaultAuthorizationLifetime, rlPolicies: policies, tiMu: new(sync.RWMutex), + maxContactsPerReg: maxContactsPerReg, } return ra } @@ -176,7 +178,7 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (re // MergeUpdate. But we need to fill it in for new registrations. reg.InitialIP = init.InitialIP - err = validateContacts(reg.Contact, ra.DNSResolver, ra.stats) + err = ra.validateContacts(reg.Contact) if err != nil { return } @@ -193,15 +195,20 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (re return } -func validateContacts(contacts []*core.AcmeURL, resolver core.DNSResolver, stats statsd.Statter) (err error) { +func (ra *RegistrationAuthorityImpl) validateContacts(contacts []*core.AcmeURL) (err error) { + if ra.maxContactsPerReg > 0 && len(contacts) > ra.maxContactsPerReg { + return core.MalformedRequestError(fmt.Sprintf("Too many contacts provided: %d > %d", + len(contacts), ra.maxContactsPerReg)) + } + for _, contact := range contacts { switch contact.Scheme { case "tel": continue case "mailto": - rtt, err := validateEmail(contact.Opaque, resolver) - stats.TimingDuration("RA.DNS.RTT.MX", rtt, 1.0) - stats.Inc("RA.DNS.Rate", 1, 1.0) + rtt, err := validateEmail(contact.Opaque, ra.DNSResolver) + ra.stats.TimingDuration("RA.DNS.RTT.MX", rtt, 1.0) + ra.stats.Inc("RA.DNS.Rate", 1, 1.0) if err != nil { return err } @@ -574,7 +581,7 @@ func (ra *RegistrationAuthorityImpl) checkLimits(names []string, regID int64) er func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) { base.MergeUpdate(update) - err = validateContacts(base.Contact, ra.DNSResolver, ra.stats) + err = ra.validateContacts(base.Contact) if err != nil { return } diff --git a/ra/registration-authority_test.go b/ra/registration-authority_test.go index d2f2562c0..70e67ee6b 100644 --- a/ra/registration-authority_test.go +++ b/ra/registration-authority_test.go @@ -224,7 +224,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut Threshold: 100, Window: cmd.ConfigDuration{Duration: 24 * 90 * time.Hour}, }, - }) + }, 1) ra.SA = ssa ra.VA = va ra.CA = &ca @@ -259,30 +259,34 @@ func assertAuthzEqual(t *testing.T, a1, a2 core.Authorization) { } func TestValidateContacts(t *testing.T) { + _, _, ra, _, cleanUp := initAuthorities(t) + defer cleanUp() + tel, _ := core.ParseAcmeURL("tel:") ansible, _ := core.ParseAcmeURL("ansible:earth.sol.milkyway.laniakea/letsencrypt") validEmail, _ := core.ParseAcmeURL("mailto:admin@email.com") invalidEmail, _ := core.ParseAcmeURL("mailto:admin@example.com") malformedEmail, _ := core.ParseAcmeURL("mailto:admin.com") - nStats, _ := statsd.NewNoopClient() - - err := validateContacts([]*core.AcmeURL{}, &mocks.DNSResolver{}, nStats) + err := ra.validateContacts([]*core.AcmeURL{}) test.AssertNotError(t, err, "No Contacts") - err = validateContacts([]*core.AcmeURL{tel}, &mocks.DNSResolver{}, nStats) + err = ra.validateContacts([]*core.AcmeURL{tel, validEmail}) + test.AssertError(t, err, "Too Many Contacts") + + err = ra.validateContacts([]*core.AcmeURL{tel}) test.AssertNotError(t, err, "Simple Telephone") - err = validateContacts([]*core.AcmeURL{validEmail}, &mocks.DNSResolver{}, nStats) + err = ra.validateContacts([]*core.AcmeURL{validEmail}) test.AssertNotError(t, err, "Valid Email") - err = validateContacts([]*core.AcmeURL{invalidEmail}, &mocks.DNSResolver{}, nStats) + err = ra.validateContacts([]*core.AcmeURL{invalidEmail}) test.AssertError(t, err, "Invalid Email") - err = validateContacts([]*core.AcmeURL{malformedEmail}, &mocks.DNSResolver{}, nStats) + err = ra.validateContacts([]*core.AcmeURL{malformedEmail}) test.AssertError(t, err, "Malformed Email") - err = validateContacts([]*core.AcmeURL{ansible}, &mocks.DNSResolver{}, nStats) + err = ra.validateContacts([]*core.AcmeURL{ansible}) test.AssertError(t, err, "Unknown scehme") } diff --git a/test/boulder-config.json b/test/boulder-config.json index 6684deeee..d8272003c 100644 --- a/test/boulder-config.json +++ b/test/boulder-config.json @@ -121,6 +121,7 @@ "ra": { "rateLimitPoliciesFilename": "test/rate-limit-policies.yml", "maxConcurrentRPCServerRequests": 16, + "maxContactsPerRegistration": 100, "debugAddr": "localhost:8002" }, diff --git a/wfe/web-front-end_test.go b/wfe/web-front-end_test.go index a89d9d4fa..f37f1d3bc 100644 --- a/wfe/web-front-end_test.go +++ b/wfe/web-front-end_test.go @@ -547,7 +547,7 @@ func TestIssueCertificate(t *testing.T) { // TODO: Use a mock RA so we can test various conditions of authorized, not // authorized, etc. stats, _ := statsd.NewNoopClient(nil) - ra := ra.NewRegistrationAuthorityImpl(fakeClock, wfe.log, stats, cmd.RateLimitConfig{}) + ra := ra.NewRegistrationAuthorityImpl(fakeClock, wfe.log, stats, cmd.RateLimitConfig{}, 0) ra.SA = &mocks.StorageAuthority{} ra.CA = &MockCA{} ra.PA = &MockPA{}