Merge pull request #958 from bifurcation/limit-contacts

Limit the number of contacts per registration
This commit is contained in:
bifurcation 2015-10-09 16:49:39 -04:00
commit 0e034fcda4
6 changed files with 33 additions and 18 deletions

View File

@ -46,7 +46,8 @@ func main() {
rateLimitPolicies, err := cmd.LoadRateLimitPolicies(c.RA.RateLimitPoliciesFilename) rateLimitPolicies, err := cmd.LoadRateLimitPolicies(c.RA.RateLimitPoliciesFilename)
cmd.FailOnError(err, "Couldn't load rate limit policies file") 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 rai.PA = pa
raDNSTimeout, err := time.ParseDuration(c.Common.DNSTimeout) raDNSTimeout, err := time.ParseDuration(c.Common.DNSTimeout)
cmd.FailOnError(err, "Couldn't parse RA DNS timeout") cmd.FailOnError(err, "Couldn't parse RA DNS timeout")

View File

@ -99,6 +99,8 @@ type Config struct {
MaxConcurrentRPCServerRequests int64 MaxConcurrentRPCServerRequests int64
MaxContactsPerRegistration int
// DebugAddr is the address to run the /debug handlers on. // DebugAddr is the address to run the /debug handlers on.
DebugAddr string DebugAddr string
} }

View File

@ -51,10 +51,11 @@ type RegistrationAuthorityImpl struct {
tiMu *sync.RWMutex tiMu *sync.RWMutex
totalIssuedCache int totalIssuedCache int
lastIssuedCount *time.Time lastIssuedCount *time.Time
maxContactsPerReg int
} }
// NewRegistrationAuthorityImpl constructs a new RA object. // 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{ ra := RegistrationAuthorityImpl{
stats: stats, stats: stats,
clk: clk, clk: clk,
@ -62,6 +63,7 @@ func NewRegistrationAuthorityImpl(clk clock.Clock, logger *blog.AuditLogger, sta
authorizationLifetime: DefaultAuthorizationLifetime, authorizationLifetime: DefaultAuthorizationLifetime,
rlPolicies: policies, rlPolicies: policies,
tiMu: new(sync.RWMutex), tiMu: new(sync.RWMutex),
maxContactsPerReg: maxContactsPerReg,
} }
return ra 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. // MergeUpdate. But we need to fill it in for new registrations.
reg.InitialIP = init.InitialIP reg.InitialIP = init.InitialIP
err = validateContacts(reg.Contact, ra.DNSResolver, ra.stats) err = ra.validateContacts(reg.Contact)
if err != nil { if err != nil {
return return
} }
@ -193,15 +195,20 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (re
return 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 { for _, contact := range contacts {
switch contact.Scheme { switch contact.Scheme {
case "tel": case "tel":
continue continue
case "mailto": case "mailto":
rtt, err := validateEmail(contact.Opaque, resolver) rtt, err := validateEmail(contact.Opaque, ra.DNSResolver)
stats.TimingDuration("RA.DNS.RTT.MX", rtt, 1.0) ra.stats.TimingDuration("RA.DNS.RTT.MX", rtt, 1.0)
stats.Inc("RA.DNS.Rate", 1, 1.0) ra.stats.Inc("RA.DNS.Rate", 1, 1.0)
if err != nil { if err != nil {
return err return err
} }
@ -576,7 +583,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) { func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) {
base.MergeUpdate(update) base.MergeUpdate(update)
err = validateContacts(base.Contact, ra.DNSResolver, ra.stats) err = ra.validateContacts(base.Contact)
if err != nil { if err != nil {
return return
} }

View File

@ -224,7 +224,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
Threshold: 100, Threshold: 100,
Window: cmd.ConfigDuration{Duration: 24 * 90 * time.Hour}, Window: cmd.ConfigDuration{Duration: 24 * 90 * time.Hour},
}, },
}) }, 1)
ra.SA = ssa ra.SA = ssa
ra.VA = va ra.VA = va
ra.CA = &ca ra.CA = &ca
@ -259,30 +259,34 @@ func assertAuthzEqual(t *testing.T, a1, a2 core.Authorization) {
} }
func TestValidateContacts(t *testing.T) { func TestValidateContacts(t *testing.T) {
_, _, ra, _, cleanUp := initAuthorities(t)
defer cleanUp()
tel, _ := core.ParseAcmeURL("tel:") tel, _ := core.ParseAcmeURL("tel:")
ansible, _ := core.ParseAcmeURL("ansible:earth.sol.milkyway.laniakea/letsencrypt") ansible, _ := core.ParseAcmeURL("ansible:earth.sol.milkyway.laniakea/letsencrypt")
validEmail, _ := core.ParseAcmeURL("mailto:admin@email.com") validEmail, _ := core.ParseAcmeURL("mailto:admin@email.com")
invalidEmail, _ := core.ParseAcmeURL("mailto:admin@example.com") invalidEmail, _ := core.ParseAcmeURL("mailto:admin@example.com")
malformedEmail, _ := core.ParseAcmeURL("mailto:admin.com") malformedEmail, _ := core.ParseAcmeURL("mailto:admin.com")
nStats, _ := statsd.NewNoopClient() err := ra.validateContacts([]*core.AcmeURL{})
err := validateContacts([]*core.AcmeURL{}, &mocks.DNSResolver{}, nStats)
test.AssertNotError(t, err, "No Contacts") 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") 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") 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") 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") 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") test.AssertError(t, err, "Unknown scehme")
} }

View File

@ -121,6 +121,7 @@
"ra": { "ra": {
"rateLimitPoliciesFilename": "test/rate-limit-policies.yml", "rateLimitPoliciesFilename": "test/rate-limit-policies.yml",
"maxConcurrentRPCServerRequests": 16, "maxConcurrentRPCServerRequests": 16,
"maxContactsPerRegistration": 100,
"debugAddr": "localhost:8002" "debugAddr": "localhost:8002"
}, },

View File

@ -547,7 +547,7 @@ func TestIssueCertificate(t *testing.T) {
// TODO: Use a mock RA so we can test various conditions of authorized, not // TODO: Use a mock RA so we can test various conditions of authorized, not
// authorized, etc. // authorized, etc.
stats, _ := statsd.NewNoopClient(nil) 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.SA = &mocks.StorageAuthority{}
ra.CA = &MockCA{} ra.CA = &MockCA{}
ra.PA = &MockPA{} ra.PA = &MockPA{}