Add registration-based overrides.

This commit is contained in:
Jacob Hoffman-Andrews 2015-10-06 17:40:49 -07:00
parent bd842922ac
commit 1899866d6b
5 changed files with 94 additions and 24 deletions

View File

@ -25,15 +25,20 @@ type RateLimitPolicy struct {
Window ConfigDuration `yaml:"window"`
// The max number of items that can be present before triggering the rate
// limit. Zero means "no limit."
Threshold int64 `yaml:"threshold"`
// A per-key override granting higher limits. The key is defined on a
// per-limit basis and should match the key it counts on. For instance, a rate
// limit on the number of certificates per name uses name as a key, whilte a
// rate limit on the number of registrations per IP subnet would use subnet as
// a key.
Threshold int `yaml:"threshold"`
// A per-key override setting different limits than the default (higher or lower).
// The key is defined on a per-limit basis and should match the key it counts on.
// For instance, a rate limit on the number of certificates per name uses name as
// a key, while a rate limit on the number of registrations per IP subnet would
// use subnet as a key.
// Note that a zero entry in the overrides map does not mean "not limit," it
// means a limit of zero.
Overrides map[string]int64 `yaml:"overrides"`
Overrides map[string]int `yaml:"overrides"`
// A per-registration override setting. This can be used, e.g. if there are
// hosting providers that we would like to grant a higher rate of issuance
// than the default. If both key-based and registration-based overrides are
// available, the registration-based on takes priority.
RegistrationOverrides map[int64]int `yaml:"registrationOverrides"`
}
// Enabled returns true iff the RateLimitPolicy is enabled.
@ -43,7 +48,10 @@ func (rlp *RateLimitPolicy) Enabled() bool {
// GetThreshold returns the threshold for this rate limit, taking into account
// any overrides for `key`.
func (rlp *RateLimitPolicy) GetThreshold(key string) int64 {
func (rlp *RateLimitPolicy) GetThreshold(key string, regID int64) int {
if override, ok := rlp.RegistrationOverrides[regID]; ok {
return override
}
if override, ok := rlp.Overrides[key]; ok {
return override
}

60
cmd/rate-limits_test.go Normal file
View File

@ -0,0 +1,60 @@
package cmd
import (
"testing"
"time"
)
func TestEnabled(t *testing.T) {
policy := RateLimitPolicy{
Threshold: 10,
}
if !policy.Enabled() {
t.Errorf("Policy should have been enabled.")
}
}
func TestNotEnabled(t *testing.T) {
policy := RateLimitPolicy{
Threshold: 0,
}
if policy.Enabled() {
t.Errorf("Policy should not have been enabled.")
}
}
func TestGetThreshold(t *testing.T) {
policy := RateLimitPolicy{
Threshold: 1,
Overrides: map[string]int{
"key": 2,
},
RegistrationOverrides: map[int64]int{
101: 3,
},
}
if policy.GetThreshold("foo", 11) != 1 {
t.Errorf("threshold should have been 1")
}
if policy.GetThreshold("key", 11) != 2 {
t.Errorf("threshold should have been 2")
}
if policy.GetThreshold("key", 101) != 3 {
t.Errorf("threshold should have been 3")
}
if policy.GetThreshold("foo", 101) != 3 {
t.Errorf("threshold should have been 3")
}
}
func TestWindowBegin(t *testing.T) {
policy := RateLimitPolicy{
Window: ConfigDuration{Duration: 24 * time.Hour},
}
now := time.Date(2015, 9, 22, 0, 0, 0, 0, time.UTC)
expected := time.Date(2015, 9, 21, 0, 0, 0, 0, time.UTC)
actual := policy.WindowBegin(now)
if actual != expected {
t.Errorf("Incorrect WindowBegin: %s, expected %s", actual, expected)
}
}

View File

@ -48,7 +48,7 @@ type RegistrationAuthorityImpl struct {
authorizationLifetime time.Duration
rlPolicies cmd.RateLimitConfig
tiMu *sync.RWMutex
totalIssuedCache int64
totalIssuedCache int
lastIssuedCount *time.Time
}
@ -108,7 +108,7 @@ func (ra *RegistrationAuthorityImpl) issuanceCountInvalid(now time.Time) bool {
return ra.lastIssuedCount == nil || ra.lastIssuedCount.Add(issuanceCountCacheLife).Before(now)
}
func (ra *RegistrationAuthorityImpl) getIssuanceCount() (int64, error) {
func (ra *RegistrationAuthorityImpl) getIssuanceCount() (int, error) {
ra.tiMu.RLock()
if ra.issuanceCountInvalid(ra.clk.Now()) {
ra.tiMu.RUnlock()
@ -119,7 +119,7 @@ func (ra *RegistrationAuthorityImpl) getIssuanceCount() (int64, error) {
return count, nil
}
func (ra *RegistrationAuthorityImpl) setIssuanceCount() (int64, error) {
func (ra *RegistrationAuthorityImpl) setIssuanceCount() (int, error) {
ra.tiMu.Lock()
defer ra.tiMu.Unlock()
@ -132,7 +132,7 @@ func (ra *RegistrationAuthorityImpl) setIssuanceCount() (int64, error) {
if err != nil {
return 0, err
}
ra.totalIssuedCache = count
ra.totalIssuedCache = int(count)
ra.lastIssuedCount = &now
}
return ra.totalIssuedCache, nil
@ -430,7 +430,7 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
// Check rate limits before checking authorizations. If someone is unable to
// issue a cert due to rate limiting, we don't want to tell them to go get the
// necessary authorizations, only to later fail the rate limit check.
err = ra.checkLimits(names)
err = ra.checkLimits(names, registration.ID)
if err != nil {
logEvent.Error = err.Error()
return emptyCert, err
@ -497,7 +497,7 @@ func domainsForRateLimiting(names []string) ([]string, error) {
return domains, nil
}
func (ra *RegistrationAuthorityImpl) checkCertificatesPerNameLimit(names []string, limit cmd.RateLimitPolicy) error {
func (ra *RegistrationAuthorityImpl) checkCertificatesPerNameLimit(names []string, limit cmd.RateLimitPolicy, regID int64) error {
names, err := domainsForRateLimiting(names)
if err != nil {
return err
@ -515,7 +515,7 @@ func (ra *RegistrationAuthorityImpl) checkCertificatesPerNameLimit(names []strin
// Shouldn't happen, but let's be careful anyhow.
return errors.New("StorageAuthority failed to return a count for every name")
}
if int64(count) >= limit.GetThreshold(name) {
if count >= limit.GetThreshold(name, regID) {
badNames = append(badNames, name)
}
}
@ -527,7 +527,7 @@ func (ra *RegistrationAuthorityImpl) checkCertificatesPerNameLimit(names []strin
return nil
}
func (ra *RegistrationAuthorityImpl) checkLimits(names []string) error {
func (ra *RegistrationAuthorityImpl) checkLimits(names []string, regID int64) error {
limits := ra.rlPolicies
if limits.TotalCertificates.Enabled() {
totalIssued, err := ra.getIssuanceCount()
@ -539,7 +539,7 @@ func (ra *RegistrationAuthorityImpl) checkLimits(names []string) error {
}
}
if limits.CertificatesPerName.Enabled() {
err := ra.checkCertificatesPerNameLimit(names, limits.CertificatesPerName)
err := ra.checkCertificatesPerNameLimit(names, limits.CertificatesPerName, regID)
if err != nil {
return err
}

View File

@ -655,7 +655,7 @@ func TestCheckCertificatesPerNameLimit(t *testing.T) {
rlp := cmd.RateLimitPolicy{
Threshold: 3,
Window: cmd.ConfigDuration{Duration: 23 * time.Hour},
Overrides: map[string]int64{
Overrides: map[string]int{
"bigissuer.com": 100,
"smallissuer.co.uk": 1,
},
@ -672,31 +672,31 @@ func TestCheckCertificatesPerNameLimit(t *testing.T) {
ra.SA = mockSA
// One base domain, below threshold
err := ra.checkCertificatesPerNameLimit([]string{"www.example.com", "example.com"}, rlp)
err := ra.checkCertificatesPerNameLimit([]string{"www.example.com", "example.com"}, rlp, 99)
test.AssertNotError(t, err, "rate limited example.com incorrectly")
// One base domain, above threshold
mockSA.nameCounts["example.com"] = 10
err = ra.checkCertificatesPerNameLimit([]string{"www.example.com", "example.com"}, rlp)
err = ra.checkCertificatesPerNameLimit([]string{"www.example.com", "example.com"}, rlp, 99)
test.AssertError(t, err, "incorrectly failed to rate limit example.com")
if _, ok := err.(core.RateLimitedError); !ok {
t.Errorf("Incorrect error type %#v", err)
}
// SA misbehaved and didn't send back a count for every input name
err = ra.checkCertificatesPerNameLimit([]string{"zombo.com", "www.example.com", "example.com"}, rlp)
err = ra.checkCertificatesPerNameLimit([]string{"zombo.com", "www.example.com", "example.com"}, rlp, 99)
test.AssertError(t, err, "incorrectly failed to error on misbehaving SA")
// Two base domains, one above threshold but with an override.
mockSA.nameCounts["example.com"] = 0
mockSA.nameCounts["bigissuer.com"] = 50
err = ra.checkCertificatesPerNameLimit([]string{"www.example.com", "subdomain.bigissuer.com"}, rlp)
err = ra.checkCertificatesPerNameLimit([]string{"www.example.com", "subdomain.bigissuer.com"}, rlp, 99)
test.AssertNotError(t, err, "incorrectly rate limited bigissuer")
// Two base domains, one above its override
mockSA.nameCounts["example.com"] = 0
mockSA.nameCounts["bigissuer.com"] = 100
err = ra.checkCertificatesPerNameLimit([]string{"www.example.com", "subdomain.bigissuer.com"}, rlp)
err = ra.checkCertificatesPerNameLimit([]string{"www.example.com", "subdomain.bigissuer.com"}, rlp, 99)
test.AssertError(t, err, "incorrectly failed to rate limit bigissuer")
if _, ok := err.(core.RateLimitedError); !ok {
t.Errorf("Incorrect error type")
@ -704,7 +704,7 @@ func TestCheckCertificatesPerNameLimit(t *testing.T) {
// One base domain, above its override (which is below threshold)
mockSA.nameCounts["smallissuer.co.uk"] = 1
err = ra.checkCertificatesPerNameLimit([]string{"www.smallissuer.co.uk"}, rlp)
err = ra.checkCertificatesPerNameLimit([]string{"www.smallissuer.co.uk"}, rlp, 99)
test.AssertError(t, err, "incorrectly failed to rate limit smallissuer")
if _, ok := err.(core.RateLimitedError); !ok {
t.Errorf("Incorrect error type %#v", err)

View File

@ -13,3 +13,5 @@ certificatesPerName:
le2.wtf: 10000
le3.wtf: 10000
nginx.wtf: 10000
registrationOverrides:
101: 1000