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:
Daniel McCarney 2016-06-08 15:11:46 -04:00 committed by Roland Bracewell Shoemaker
parent 87d7d787aa
commit 4c289f2a8f
7 changed files with 340 additions and 48 deletions

View File

@ -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")

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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,