Reload ratelimit policy automatically at runtime (#1894)
Resolves #1810 by automatically updating the RA ratelimit.RateLimitConfig whenever the backing config file is changed. Much like the Policy Authority uses a reloader instance to support updating the Hostname policy on the fly, this PR changes the Registration Authority to use a reloader for the rate limit policy file. Access to the ra.rlPolicies member is protected with a RWMutex now that there is a potential for the values to be reloaded while a reader is active. A test is introduced to ensure that writing a new policy YAML to the policy config file results in new values being set in the RA's rlPolicies instance. https://github.com/letsencrypt/boulder/pull/1894
This commit is contained in:
parent
87d7d787aa
commit
4c289f2a8f
|
|
@ -14,7 +14,6 @@ import (
|
|||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/policy"
|
||||
"github.com/letsencrypt/boulder/ra"
|
||||
"github.com/letsencrypt/boulder/ratelimit"
|
||||
"github.com/letsencrypt/boulder/rpc"
|
||||
)
|
||||
|
||||
|
|
@ -37,9 +36,6 @@ func main() {
|
|||
err = pa.SetHostnamePolicyFile(c.RA.HostnamePolicyFile)
|
||||
cmd.FailOnError(err, "Couldn't load hostname policy file")
|
||||
|
||||
rateLimitPolicies, err := ratelimit.LoadRateLimitPolicies(c.RA.RateLimitPoliciesFilename)
|
||||
cmd.FailOnError(err, "Couldn't load rate limit policies file")
|
||||
|
||||
go cmd.ProfileCmd("RA", stats)
|
||||
|
||||
amqpConf := c.RA.AMQP
|
||||
|
|
@ -60,8 +56,10 @@ func main() {
|
|||
cmd.FailOnError(err, "Unable to create SA client")
|
||||
|
||||
rai := ra.NewRegistrationAuthorityImpl(clock.Default(), logger, stats,
|
||||
rateLimitPolicies, c.RA.MaxContactsPerRegistration, c.KeyPolicy(),
|
||||
c.RA.MaxContactsPerRegistration, c.KeyPolicy(),
|
||||
c.RA.UseNewVARPC, c.RA.MaxNames, c.RA.DoNotForceCN)
|
||||
policyErr := rai.SetRateLimitPoliciesFile(c.RA.RateLimitPoliciesFilename)
|
||||
cmd.FailOnError(policyErr, "Couldn't load rate limit policies file")
|
||||
rai.PA = pa
|
||||
raDNSTimeout, err := time.ParseDuration(c.Common.DNSTimeout)
|
||||
cmd.FailOnError(err, "Couldn't parse RA DNS timeout")
|
||||
|
|
|
|||
46
ra/ra.go
46
ra/ra.go
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/letsencrypt/boulder/goodkey"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
"github.com/letsencrypt/boulder/reloader"
|
||||
"github.com/letsencrypt/net/publicsuffix"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ type RegistrationAuthorityImpl struct {
|
|||
// How long before a newly created authorization expires.
|
||||
authorizationLifetime time.Duration
|
||||
pendingAuthorizationLifetime time.Duration
|
||||
rlPolicies ratelimit.RateLimitConfig
|
||||
rlPolicies ratelimit.Limits
|
||||
tiMu *sync.RWMutex
|
||||
totalIssuedCache int
|
||||
lastIssuedCount *time.Time
|
||||
|
|
@ -76,7 +77,6 @@ func NewRegistrationAuthorityImpl(
|
|||
clk clock.Clock,
|
||||
logger blog.Logger,
|
||||
stats statsd.Statter,
|
||||
policies ratelimit.RateLimitConfig,
|
||||
maxContactsPerReg int,
|
||||
keyPolicy goodkey.KeyPolicy,
|
||||
newVARPC bool,
|
||||
|
|
@ -90,7 +90,7 @@ func NewRegistrationAuthorityImpl(
|
|||
log: logger,
|
||||
authorizationLifetime: DefaultAuthorizationLifetime,
|
||||
pendingAuthorizationLifetime: DefaultPendingAuthorizationLifetime,
|
||||
rlPolicies: policies,
|
||||
rlPolicies: ratelimit.New(),
|
||||
tiMu: new(sync.RWMutex),
|
||||
maxContactsPerReg: maxContactsPerReg,
|
||||
keyPolicy: keyPolicy,
|
||||
|
|
@ -104,6 +104,19 @@ func NewRegistrationAuthorityImpl(
|
|||
return ra
|
||||
}
|
||||
|
||||
func (ra *RegistrationAuthorityImpl) SetRateLimitPoliciesFile(filename string) error {
|
||||
_, err := reloader.New(filename, ra.rlPolicies.LoadPolicies, ra.rateLimitPoliciesLoadError)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ra *RegistrationAuthorityImpl) rateLimitPoliciesLoadError(err error) {
|
||||
ra.log.Err(fmt.Sprintf("error reloading rate limit policy: %s", err))
|
||||
}
|
||||
|
||||
const (
|
||||
unparseableEmailDetail = "not a valid e-mail address"
|
||||
emptyDNSResponseDetail = "empty DNS response"
|
||||
|
|
@ -193,11 +206,13 @@ func (ra *RegistrationAuthorityImpl) setIssuanceCount(ctx context.Context) (int,
|
|||
ra.tiMu.Lock()
|
||||
defer ra.tiMu.Unlock()
|
||||
|
||||
totalCertWindow := ra.rlPolicies.TotalCertificates().Window.Duration
|
||||
|
||||
now := ra.clk.Now()
|
||||
if ra.issuanceCountInvalid(now) {
|
||||
count, err := ra.SA.CountCertificatesRange(
|
||||
ctx,
|
||||
now.Add(-ra.rlPolicies.TotalCertificates.Window.Duration),
|
||||
now.Add(-totalCertWindow),
|
||||
now,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -214,7 +229,8 @@ func (ra *RegistrationAuthorityImpl) setIssuanceCount(ctx context.Context) (int,
|
|||
const noRegistrationID = -1
|
||||
|
||||
func (ra *RegistrationAuthorityImpl) checkRegistrationLimit(ctx context.Context, ip net.IP) error {
|
||||
limit := ra.rlPolicies.RegistrationsPerIP
|
||||
limit := ra.rlPolicies.RegistrationsPerIP()
|
||||
|
||||
if limit.Enabled() {
|
||||
now := ra.clk.Now()
|
||||
count, err := ra.SA.CountRegistrationsByIP(ctx, ip, limit.WindowBegin(now), now)
|
||||
|
|
@ -295,7 +311,7 @@ func (ra *RegistrationAuthorityImpl) validateContacts(ctx context.Context, conta
|
|||
}
|
||||
|
||||
func (ra *RegistrationAuthorityImpl) checkPendingAuthorizationLimit(ctx context.Context, regID int64) error {
|
||||
limit := ra.rlPolicies.PendingAuthorizationsPerAccount
|
||||
limit := ra.rlPolicies.PendingAuthorizationsPerAccount()
|
||||
if limit.Enabled() {
|
||||
count, err := ra.SA.CountPendingAuthorizations(ctx, regID)
|
||||
if err != nil {
|
||||
|
|
@ -687,13 +703,13 @@ func (ra *RegistrationAuthorityImpl) checkCertificatesPerFQDNSetLimit(ctx contex
|
|||
}
|
||||
|
||||
func (ra *RegistrationAuthorityImpl) checkLimits(ctx context.Context, names []string, regID int64) error {
|
||||
limits := ra.rlPolicies
|
||||
if limits.TotalCertificates.Enabled() {
|
||||
totalCertLimits := ra.rlPolicies.TotalCertificates()
|
||||
if totalCertLimits.Enabled() {
|
||||
totalIssued, err := ra.getIssuanceCount(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if totalIssued >= ra.rlPolicies.TotalCertificates.Threshold {
|
||||
if totalIssued >= totalCertLimits.Threshold {
|
||||
domains := strings.Join(names, ",")
|
||||
ra.totalCertsStats.Inc("Exceeded", 1)
|
||||
ra.log.Info(fmt.Sprintf("Rate limit exceeded, TotalCertificates, regID: %d, domains: %s, totalIssued: %d", regID, domains, totalIssued))
|
||||
|
|
@ -701,14 +717,18 @@ func (ra *RegistrationAuthorityImpl) checkLimits(ctx context.Context, names []st
|
|||
}
|
||||
ra.totalCertsStats.Inc("Pass", 1)
|
||||
}
|
||||
if limits.CertificatesPerName.Enabled() {
|
||||
err := ra.checkCertificatesPerNameLimit(ctx, names, limits.CertificatesPerName, regID)
|
||||
|
||||
certNameLimits := ra.rlPolicies.CertificatesPerName()
|
||||
if certNameLimits.Enabled() {
|
||||
err := ra.checkCertificatesPerNameLimit(ctx, names, certNameLimits, regID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if limits.CertificatesPerFQDNSet.Enabled() {
|
||||
err := ra.checkCertificatesPerFQDNSetLimit(ctx, names, limits.CertificatesPerFQDNSet, regID)
|
||||
|
||||
fqdnLimits := ra.rlPolicies.CertificatesPerFQDNSet()
|
||||
if fqdnLimits.Enabled() {
|
||||
err := ra.checkCertificatesPerFQDNSetLimit(ctx, names, fqdnLimits, regID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
103
ra/ra_test.go
103
ra/ra_test.go
|
|
@ -6,8 +6,10 @@ import (
|
|||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -149,6 +151,40 @@ var testKeyPolicy = goodkey.KeyPolicy{
|
|||
|
||||
var ctx = context.Background()
|
||||
|
||||
// dummyRateLimitConfig satisfies the ratelimit.RateLimitConfig interface while
|
||||
// allowing easy mocking of the individual RateLimitPolicy's
|
||||
type dummyRateLimitConfig struct {
|
||||
TotalCertificatesPolicy ratelimit.RateLimitPolicy
|
||||
CertificatesPerNamePolicy ratelimit.RateLimitPolicy
|
||||
RegistrationsPerIPPolicy ratelimit.RateLimitPolicy
|
||||
PendingAuthorizationsPerAccountPolicy ratelimit.RateLimitPolicy
|
||||
CertificatesPerFQDNSetPolicy ratelimit.RateLimitPolicy
|
||||
}
|
||||
|
||||
func (r *dummyRateLimitConfig) TotalCertificates() ratelimit.RateLimitPolicy {
|
||||
return r.TotalCertificatesPolicy
|
||||
}
|
||||
|
||||
func (r *dummyRateLimitConfig) CertificatesPerName() ratelimit.RateLimitPolicy {
|
||||
return r.CertificatesPerNamePolicy
|
||||
}
|
||||
|
||||
func (r *dummyRateLimitConfig) RegistrationsPerIP() ratelimit.RateLimitPolicy {
|
||||
return r.RegistrationsPerIPPolicy
|
||||
}
|
||||
|
||||
func (r *dummyRateLimitConfig) PendingAuthorizationsPerAccount() ratelimit.RateLimitPolicy {
|
||||
return r.PendingAuthorizationsPerAccountPolicy
|
||||
}
|
||||
|
||||
func (r *dummyRateLimitConfig) CertificatesPerFQDNSet() ratelimit.RateLimitPolicy {
|
||||
return r.CertificatesPerFQDNSetPolicy
|
||||
}
|
||||
|
||||
func (r *dummyRateLimitConfig) LoadPolicies(contents []byte) error {
|
||||
return nil // NOP - unrequired behaviour for this mock
|
||||
}
|
||||
|
||||
func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAuthority, *RegistrationAuthorityImpl, clock.FakeClock, func()) {
|
||||
err := json.Unmarshal(AccountKeyJSONA, &AccountKeyA)
|
||||
test.AssertNotError(t, err, "Failed to unmarshal public JWK")
|
||||
|
|
@ -203,12 +239,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
ra := NewRegistrationAuthorityImpl(fc,
|
||||
log,
|
||||
stats,
|
||||
ratelimit.RateLimitConfig{
|
||||
TotalCertificates: ratelimit.RateLimitPolicy{
|
||||
Threshold: 100,
|
||||
Window: cmd.ConfigDuration{Duration: 24 * 90 * time.Hour},
|
||||
},
|
||||
}, 1, testKeyPolicy, false, 0, true)
|
||||
1, testKeyPolicy, false, 0, true)
|
||||
ra.SA = ssa
|
||||
ra.VA = va
|
||||
ra.CA = ca
|
||||
|
|
@ -644,8 +675,8 @@ func TestTotalCertRateLimit(t *testing.T) {
|
|||
_, sa, ra, fc, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
|
||||
ra.rlPolicies = ratelimit.RateLimitConfig{
|
||||
TotalCertificates: ratelimit.RateLimitPolicy{
|
||||
ra.rlPolicies = &dummyRateLimitConfig{
|
||||
TotalCertificatesPolicy: ratelimit.RateLimitPolicy{
|
||||
Threshold: 1,
|
||||
Window: cmd.ConfigDuration{Duration: 24 * 90 * time.Hour},
|
||||
},
|
||||
|
|
@ -688,8 +719,8 @@ func TestAuthzRateLimiting(t *testing.T) {
|
|||
_, _, ra, fc, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
|
||||
ra.rlPolicies = ratelimit.RateLimitConfig{
|
||||
PendingAuthorizationsPerAccount: ratelimit.RateLimitPolicy{
|
||||
ra.rlPolicies = &dummyRateLimitConfig{
|
||||
PendingAuthorizationsPerAccountPolicy: ratelimit.RateLimitPolicy{
|
||||
Threshold: 1,
|
||||
Window: cmd.ConfigDuration{Duration: 24 * 90 * time.Hour},
|
||||
},
|
||||
|
|
@ -744,6 +775,58 @@ func TestDomainsForRateLimiting(t *testing.T) {
|
|||
test.AssertEquals(t, domains[0], "example.com")
|
||||
}
|
||||
|
||||
func TestRateLimitLiveReload(t *testing.T) {
|
||||
_, _, ra, _, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
|
||||
// We'll work with a temporary file as the reloader monitored rate limit
|
||||
// policy file
|
||||
policyFile, tempErr := ioutil.TempFile("", "rate-limit-policies.yml")
|
||||
test.AssertNotError(t, tempErr, "should not fail to create TempFile")
|
||||
filename := policyFile.Name()
|
||||
defer os.Remove(filename)
|
||||
|
||||
// Start with bodyOne in the temp file
|
||||
bodyOne, readErr := ioutil.ReadFile("../test/rate-limit-policies.yml")
|
||||
test.AssertNotError(t, readErr, "should not fail to read ../test/rate-limit-policies.yml")
|
||||
writeErr := ioutil.WriteFile(filename, bodyOne, 0644)
|
||||
test.AssertNotError(t, writeErr, "should not fail to write temp file")
|
||||
|
||||
// Configure the RA to use the monitored temp file as the policy file
|
||||
err := ra.SetRateLimitPoliciesFile(filename)
|
||||
test.AssertNotError(t, err, "failed to SetRateLimitPoliciesFile")
|
||||
|
||||
// Test some fields of the initial policy to ensure it loaded correctly
|
||||
test.AssertEquals(t, ra.rlPolicies.TotalCertificates().Threshold, 100000)
|
||||
test.AssertEquals(t, ra.rlPolicies.CertificatesPerName().Overrides["le.wtf"], 10000)
|
||||
test.AssertEquals(t, ra.rlPolicies.RegistrationsPerIP().Overrides["127.0.0.1"], 1000000)
|
||||
test.AssertEquals(t, ra.rlPolicies.PendingAuthorizationsPerAccount().Threshold, 3)
|
||||
test.AssertEquals(t, ra.rlPolicies.CertificatesPerFQDNSet().Overrides["le.wtf"], 10000)
|
||||
test.AssertEquals(t, ra.rlPolicies.CertificatesPerFQDNSet().Threshold, 5)
|
||||
|
||||
// Write a different policy YAML to the monitored file, expect a reload.
|
||||
// Sleep a few milliseconds before writing so the timestamp isn't identical to
|
||||
// when we wrote bodyOne to the file earlier.
|
||||
bodyTwo, readErr := ioutil.ReadFile("../test/rate-limit-policies-b.yml")
|
||||
test.AssertNotError(t, readErr, "should not fail to read ../test/rate-limit-policies-b.yml")
|
||||
time.Sleep(15 * time.Millisecond)
|
||||
writeErr = ioutil.WriteFile(filename, bodyTwo, 0644)
|
||||
test.AssertNotError(t, writeErr, "should not fail to write temp file")
|
||||
|
||||
// Sleep to allow the reloader a chance to catch that an update occurred
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Test fields of the policy to make sure writing the new policy to the monitored file
|
||||
// resulted in the runtime values being updated
|
||||
test.AssertEquals(t, ra.rlPolicies.TotalCertificates().Threshold, 99999)
|
||||
test.AssertEquals(t, ra.rlPolicies.CertificatesPerName().Overrides["le.wtf"], 9999)
|
||||
test.AssertEquals(t, ra.rlPolicies.CertificatesPerName().Overrides["le4.wtf"], 9999)
|
||||
test.AssertEquals(t, ra.rlPolicies.RegistrationsPerIP().Overrides["127.0.0.1"], 999990)
|
||||
test.AssertEquals(t, ra.rlPolicies.PendingAuthorizationsPerAccount().Threshold, 999)
|
||||
test.AssertEquals(t, ra.rlPolicies.CertificatesPerFQDNSet().Overrides["le.wtf"], 9999)
|
||||
test.AssertEquals(t, ra.rlPolicies.CertificatesPerFQDNSet().Threshold, 99999)
|
||||
}
|
||||
|
||||
type mockSAWithNameCounts struct {
|
||||
mocks.StorageAuthority
|
||||
nameCounts map[string]int
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package ratelimit
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
|
|
@ -9,8 +9,93 @@ import (
|
|||
"github.com/letsencrypt/boulder/cmd"
|
||||
)
|
||||
|
||||
// RateLimitConfig contains all application layer rate limiting policies
|
||||
type RateLimitConfig struct {
|
||||
// Limits is defined to allow mock implementations be provided during unit
|
||||
// testing
|
||||
type Limits interface {
|
||||
TotalCertificates() RateLimitPolicy
|
||||
CertificatesPerName() RateLimitPolicy
|
||||
RegistrationsPerIP() RateLimitPolicy
|
||||
PendingAuthorizationsPerAccount() RateLimitPolicy
|
||||
CertificatesPerFQDNSet() RateLimitPolicy
|
||||
LoadPolicies(contents []byte) error
|
||||
}
|
||||
|
||||
// limitsImpl is an unexported implementation of the Limits interface. It acts
|
||||
// as a container for a rateLimitConfig and a mutex. This allows the inner
|
||||
// rateLimitConfig pointer to be updated safely when the overall configuration
|
||||
// changes (e.g. due to a reload of the policy file)
|
||||
type limitsImpl struct {
|
||||
sync.RWMutex
|
||||
rlPolicy *rateLimitConfig
|
||||
}
|
||||
|
||||
func (r *limitsImpl) TotalCertificates() RateLimitPolicy {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
if r.rlPolicy == nil {
|
||||
return RateLimitPolicy{}
|
||||
}
|
||||
return r.rlPolicy.TotalCertificates
|
||||
}
|
||||
|
||||
func (r *limitsImpl) CertificatesPerName() RateLimitPolicy {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
if r.rlPolicy == nil {
|
||||
return RateLimitPolicy{}
|
||||
}
|
||||
return r.rlPolicy.CertificatesPerName
|
||||
}
|
||||
|
||||
func (r *limitsImpl) RegistrationsPerIP() RateLimitPolicy {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
if r.rlPolicy == nil {
|
||||
return RateLimitPolicy{}
|
||||
}
|
||||
return r.rlPolicy.RegistrationsPerIP
|
||||
}
|
||||
|
||||
func (r *limitsImpl) PendingAuthorizationsPerAccount() RateLimitPolicy {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
if r.rlPolicy == nil {
|
||||
return RateLimitPolicy{}
|
||||
}
|
||||
return r.rlPolicy.PendingAuthorizationsPerAccount
|
||||
}
|
||||
|
||||
func (r *limitsImpl) CertificatesPerFQDNSet() RateLimitPolicy {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
if r.rlPolicy == nil {
|
||||
return RateLimitPolicy{}
|
||||
}
|
||||
return r.rlPolicy.CertificatesPerFQDNSet
|
||||
}
|
||||
|
||||
// LoadPolicies loads various rate limiting policies from a byte array of
|
||||
// YAML configuration (typically read from disk by a reloader)
|
||||
func (r *limitsImpl) LoadPolicies(contents []byte) error {
|
||||
var newPolicy rateLimitConfig
|
||||
err := yaml.Unmarshal(contents, &newPolicy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Lock()
|
||||
r.rlPolicy = &newPolicy
|
||||
r.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func New() Limits {
|
||||
return &limitsImpl{}
|
||||
}
|
||||
|
||||
// rateLimitConfig contains all application layer rate limiting policies. It is
|
||||
// unexported and clients are expected to use the exported container struct
|
||||
type rateLimitConfig struct {
|
||||
// Total number of certificates that can be extant at any given time.
|
||||
// The 2160h window, 90 days, is chosen to match certificate lifetime, since the
|
||||
// main capacity factor is how many OCSP requests we can sign with available
|
||||
|
|
@ -76,18 +161,3 @@ func (rlp *RateLimitPolicy) GetThreshold(key string, regID int64) int {
|
|||
func (rlp *RateLimitPolicy) WindowBegin(windowEnd time.Time) time.Time {
|
||||
return windowEnd.Add(-1 * rlp.Window.Duration)
|
||||
}
|
||||
|
||||
// LoadRateLimitPolicies loads various rate limiting policies from a YAML
|
||||
// configuration file
|
||||
func LoadRateLimitPolicies(filename string) (RateLimitConfig, error) {
|
||||
contents, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return RateLimitConfig{}, err
|
||||
}
|
||||
var rlc RateLimitConfig
|
||||
err = yaml.Unmarshal(contents, &rlc)
|
||||
if err != nil {
|
||||
return RateLimitConfig{}, err
|
||||
}
|
||||
return rlc, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package ratelimit
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
func TestEnabled(t *testing.T) {
|
||||
|
|
@ -60,3 +62,85 @@ func TestWindowBegin(t *testing.T) {
|
|||
t.Errorf("Incorrect WindowBegin: %s, expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPolicies(t *testing.T) {
|
||||
policy := New()
|
||||
|
||||
policyContent, readErr := ioutil.ReadFile("../test/rate-limit-policies.yml")
|
||||
test.AssertNotError(t, readErr, "Failed to load rate-limit-policies.yml")
|
||||
|
||||
// Test that loading a good policy from YAML doesn't error
|
||||
err := policy.LoadPolicies(policyContent)
|
||||
test.AssertNotError(t, err, "Failed to parse rate-limit-policies.yml")
|
||||
|
||||
// Test that the TotalCertificates section parsed correctly
|
||||
totalCerts := policy.TotalCertificates()
|
||||
test.AssertEquals(t, totalCerts.Threshold, 100000)
|
||||
test.AssertEquals(t, len(totalCerts.Overrides), 0)
|
||||
test.AssertEquals(t, len(totalCerts.RegistrationOverrides), 0)
|
||||
|
||||
// Test that the CertificatesPerName section parsed correctly
|
||||
certsPerName := policy.CertificatesPerName()
|
||||
test.AssertEquals(t, certsPerName.Threshold, 2)
|
||||
test.AssertDeepEquals(t, certsPerName.Overrides, map[string]int{
|
||||
"ratelimit.me": 1,
|
||||
"le.wtf": 10000,
|
||||
"le1.wtf": 10000,
|
||||
"le2.wtf": 10000,
|
||||
"le3.wtf": 10000,
|
||||
"nginx.wtf": 10000,
|
||||
"good-caa-reserved.com": 10000,
|
||||
"bad-caa-reserved.com": 10000,
|
||||
})
|
||||
test.AssertDeepEquals(t, certsPerName.RegistrationOverrides, map[int64]int{
|
||||
101: 1000,
|
||||
})
|
||||
|
||||
// Test that the RegistrationsPerIP section parsed correctly
|
||||
regsPerIP := policy.RegistrationsPerIP()
|
||||
test.AssertEquals(t, regsPerIP.Threshold, 10000)
|
||||
test.AssertDeepEquals(t, regsPerIP.Overrides, map[string]int{
|
||||
"127.0.0.1": 1000000,
|
||||
})
|
||||
test.AssertEquals(t, len(regsPerIP.RegistrationOverrides), 0)
|
||||
|
||||
// Test that the PendingAuthorizationsPerAccount section parsed correctly
|
||||
pendingAuthsPerAcct := policy.PendingAuthorizationsPerAccount()
|
||||
test.AssertEquals(t, pendingAuthsPerAcct.Threshold, 3)
|
||||
test.AssertEquals(t, len(pendingAuthsPerAcct.Overrides), 0)
|
||||
test.AssertEquals(t, len(pendingAuthsPerAcct.RegistrationOverrides), 0)
|
||||
|
||||
// Test that the CertificatesPerFQDN section parsed correctly
|
||||
certsPerFQDN := policy.CertificatesPerFQDNSet()
|
||||
test.AssertEquals(t, certsPerFQDN.Threshold, 5)
|
||||
test.AssertDeepEquals(t, certsPerFQDN.Overrides, map[string]int{
|
||||
"le.wtf": 10000,
|
||||
"le1.wtf": 10000,
|
||||
"le2.wtf": 10000,
|
||||
"le3.wtf": 10000,
|
||||
"le.wtf,le1.wtf": 10000,
|
||||
"good-caa-reserved.com": 10000,
|
||||
"nginx.wtf": 10000,
|
||||
})
|
||||
test.AssertEquals(t, len(certsPerFQDN.RegistrationOverrides), 0)
|
||||
|
||||
// Test that loading invalid YAML generates an error
|
||||
err = policy.LoadPolicies([]byte("err"))
|
||||
test.AssertError(t, err, "Failed to generate error loading invalid yaml policy file")
|
||||
// Re-check a field of policy to make sure a LoadPolicies error doesn't
|
||||
// corrupt the existing policies
|
||||
test.AssertDeepEquals(t, policy.RegistrationsPerIP().Overrides, map[string]int{
|
||||
"127.0.0.1": 1000000,
|
||||
})
|
||||
|
||||
// Test that the RateLimitConfig accessors do not panic when there has been no
|
||||
// `LoadPolicy` call, and instead return empty RateLimitPolicy objects with default
|
||||
// values.
|
||||
emptyPolicy := New()
|
||||
test.AssertEquals(t, emptyPolicy.TotalCertificates().Threshold, 0)
|
||||
test.AssertEquals(t, emptyPolicy.CertificatesPerName().Threshold, 0)
|
||||
test.AssertEquals(t, emptyPolicy.RegistrationsPerIP().Threshold, 0)
|
||||
test.AssertEquals(t, emptyPolicy.RegistrationsPerIP().Threshold, 0)
|
||||
test.AssertEquals(t, emptyPolicy.PendingAuthorizationsPerAccount().Threshold, 0)
|
||||
test.AssertEquals(t, emptyPolicy.CertificatesPerFQDNSet().Threshold, 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
# See cmd/shell.go for definitions of these rate limits.
|
||||
totalCertificates:
|
||||
window: 9999h
|
||||
threshold: 99999
|
||||
certificatesPerName:
|
||||
window: 2160h
|
||||
threshold: 99
|
||||
overrides:
|
||||
ratelimit.me: 1
|
||||
# Hostnames used by the letsencrypt client integration test.
|
||||
le.wtf: 9999
|
||||
le1.wtf: 9999
|
||||
le2.wtf: 9999
|
||||
le3.wtf: 9999
|
||||
le4.wtf: 9999
|
||||
nginx.wtf: 9999
|
||||
good-caa-reserved.com: 9999
|
||||
bad-caa-reserved.com: 9999
|
||||
registrationOverrides:
|
||||
101: 1000
|
||||
registrationsPerIP:
|
||||
window: 168h # 1 week
|
||||
threshold: 9999
|
||||
overrides:
|
||||
127.0.0.1: 999990
|
||||
pendingAuthorizationsPerAccount:
|
||||
window: 168h # 1 week, should match pending authorization lifetime.
|
||||
threshold: 999
|
||||
certificatesPerFQDNSet:
|
||||
window: 24h
|
||||
threshold: 99999
|
||||
overrides:
|
||||
le.wtf: 9999
|
||||
le1.wtf: 9999
|
||||
le2.wtf: 9999
|
||||
le3.wtf: 9999
|
||||
le.wtf,le1.wtf: 9999
|
||||
good-caa-reserved.com: 9999
|
||||
nginx.wtf: 9999
|
||||
|
|
@ -33,7 +33,6 @@ import (
|
|||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/mocks"
|
||||
"github.com/letsencrypt/boulder/ra"
|
||||
"github.com/letsencrypt/boulder/ratelimit"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
|
|
@ -620,7 +619,6 @@ func TestIssueCertificate(t *testing.T) {
|
|||
fc,
|
||||
wfe.log,
|
||||
stats,
|
||||
ratelimit.RateLimitConfig{},
|
||||
0,
|
||||
testKeyPolicy,
|
||||
false,
|
||||
|
|
|
|||
Loading…
Reference in New Issue