Deprecate DisableLegacyLimitWrites & UseKvLimitsForNewOrder flags; remove code using certificatesPerName & newOrdersRL tables (#7858)

Remove code using `certificatesPerName` & `newOrdersRL` tables.

Deprecate `DisableLegacyLimitWrites` & `UseKvLimitsForNewOrder` flags.

Remove legacy `ratelimit` package.

Delete these RA test cases:

- `TestAuthzFailedRateLimitingNewOrder` (rl:
`FailedAuthorizationsPerDomainPerAccount`)
- `TestCheckCertificatesPerNameLimit` (rl: `CertificatesPerDomain`)
- `TestCheckExactCertificateLimit` (rl: `CertificatesPerFQDNSet`)
- `TestExactPublicSuffixCertLimit` (rl: `CertificatesPerDomain`)

Rate limits in NewOrder are now enforced by the WFE, starting here:
5a9b4c4b18/wfe2/wfe.go (L781)

We collect a batch of transactions to check limits, check them all at
once, go through and find which one(s) failed, and serve the failure
with the Retry-After that's furthest in the future. All this code
doesn't really need to be tested again; what needs to be tested is that
we're returning the correct failure. That code is
`NewOrderLimitTransactions`, and the `ratelimits` package's tests cover
this.

The public suffix handling behavior is tested by
`TestFQDNsToETLDsPlusOne`:
5a9b4c4b18/ratelimits/utilities_test.go (L9)

Some other RA rate limit tests were deleted earlier, in #7869.

Part of #7671.
This commit is contained in:
James Renken 2025-01-10 12:50:57 -08:00 committed by GitHub
parent f37c36205c
commit e4668b4ca7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 1190 additions and 3413 deletions

View File

@ -34,7 +34,8 @@ type Config struct {
cmd.ServiceConfig
cmd.HostnamePolicyConfig
RateLimitPoliciesFilename string `validate:"required"`
// RateLimitPoliciesFilename is deprecated.
RateLimitPoliciesFilename string
MaxContactsPerRegistration int
@ -297,8 +298,6 @@ func main() {
)
defer rai.Drain()
policyErr := rai.LoadRateLimitPoliciesFile(c.RA.RateLimitPoliciesFilename)
cmd.FailOnError(policyErr, "Couldn't load rate limit policies file")
rai.PA = pa
rai.VA = va.RemoteClients{

View File

@ -10,6 +10,7 @@ import (
"github.com/letsencrypt/borp"
"github.com/go-sql-driver/mysql"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/test"
"github.com/letsencrypt/boulder/test/vars"
@ -185,10 +186,6 @@ func TestTableFromQuery(t *testing.T) {
query: "insert into `certificates` (`registrationID`,`serial`,`digest`,`der`,`issued`,`expires`) values (?,?,?,?,?,?);",
expectedTable: "`certificates`",
},
{
query: "INSERT INTO certificatesPerName (eTLDPlusOne, time, count) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE count=count+1;",
expectedTable: "certificatesPerName",
},
{
query: "insert into `fqdnSets` (`ID`,`SetHash`,`Serial`,`Issued`,`Expires`) values (null,?,?,?,?);",
expectedTable: "`fqdnSets`",

View File

@ -16,7 +16,9 @@ import (
// package's global Config.
type Config struct {
// Deprecated flags.
IncrementRateLimits bool
IncrementRateLimits bool
UseKvLimitsForNewOrder bool
DisableLegacyLimitWrites bool
// ServeRenewalInfo exposes the renewalInfo endpoint in the directory and for
// GET requests. WARNING: This feature is a draft and highly unstable.
@ -67,21 +69,6 @@ type Config struct {
// until the paused identifiers are unpaused and the order is resubmitted.
CheckIdentifiersPaused bool
// UseKvLimitsForNewOrder when enabled, causes the key-value rate limiter to
// be the authoritative source of rate limiting information for new-order
// callers and disables the legacy rate limiting checks.
//
// Note: this flag does not disable writes to the certificatesPerName or
// fqdnSets tables at Finalize time.
UseKvLimitsForNewOrder bool
// DisableLegacyLimitWrites when enabled, disables writes to:
// - the newOrdersRL table at new-order time, and
// - the certificatesPerName table at finalize time.
//
// This flag should only be used in conjunction with UseKvLimitsForNewOrder.
DisableLegacyLimitWrites bool
// PropagateCancels controls whether the WFE and ocsp-responder allows
// cancellation of an inbound request to cancel downstream gRPC and other
// queries. In practice, cancellation of an inbound request is achieved by

View File

@ -311,16 +311,6 @@ func (sa *StorageAuthorityReadOnly) FQDNSetExists(_ context.Context, _ *sapb.FQD
return &sapb.Exists{Exists: false}, nil
}
// CountCertificatesByNames is a mock
func (sa *StorageAuthorityReadOnly) CountCertificatesByNames(_ context.Context, _ *sapb.CountCertificatesByNamesRequest, _ ...grpc.CallOption) (*sapb.CountByNames, error) {
return &sapb.CountByNames{}, nil
}
// CountOrders is a mock
func (sa *StorageAuthorityReadOnly) CountOrders(_ context.Context, _ *sapb.CountOrdersRequest, _ ...grpc.CallOption) (*sapb.Count, error) {
return &sapb.Count{}, nil
}
// DeactivateRegistration is a mock
func (sa *StorageAuthority) DeactivateRegistration(_ context.Context, _ *sapb.RegistrationID, _ ...grpc.CallOption) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil

378
ra/ra.go
View File

@ -11,7 +11,6 @@ import (
"math/big"
"net"
"net/url"
"os"
"slices"
"sort"
"strconv"
@ -47,7 +46,6 @@ import (
"github.com/letsencrypt/boulder/probs"
pubpb "github.com/letsencrypt/boulder/publisher/proto"
rapb "github.com/letsencrypt/boulder/ra/proto"
"github.com/letsencrypt/boulder/ratelimit"
"github.com/letsencrypt/boulder/ratelimits"
"github.com/letsencrypt/boulder/revocation"
sapb "github.com/letsencrypt/boulder/sa/proto"
@ -87,7 +85,6 @@ type RegistrationAuthorityImpl struct {
// How long before a newly created authorization expires.
authorizationLifetime time.Duration
pendingAuthorizationLifetime time.Duration
rlPolicies ratelimit.Limits
maxContactsPerReg int
limiter *ratelimits.Limiter
txnBuilder *ratelimits.TransactionBuilder
@ -248,7 +245,6 @@ func NewRegistrationAuthorityImpl(
log: logger,
authorizationLifetime: authorizationLifetime,
pendingAuthorizationLifetime: pendingAuthorizationLifetime,
rlPolicies: ratelimit.New(),
maxContactsPerReg: maxContactsPerReg,
keyPolicy: keyPolicy,
limiter: limiter,
@ -277,19 +273,6 @@ func NewRegistrationAuthorityImpl(
return ra
}
func (ra *RegistrationAuthorityImpl) LoadRateLimitPoliciesFile(filename string) error {
configBytes, err := os.ReadFile(filename)
if err != nil {
return err
}
err = ra.rlPolicies.LoadPolicies(configBytes)
if err != nil {
return err
}
return nil
}
// certificateRequestAuthz is a struct for holding information about a valid
// authz referenced during a certificateRequestEvent. It holds both the
// authorization ID and the challenge type that made the authorization valid. We
@ -489,109 +472,6 @@ func (ra *RegistrationAuthorityImpl) validateContacts(contacts []string) error {
return nil
}
func (ra *RegistrationAuthorityImpl) checkPendingAuthorizationLimit(ctx context.Context, regID int64, limit ratelimit.RateLimitPolicy) error {
// This rate limit's threshold can only be overridden on a per-regID basis,
// not based on any other key.
threshold, overrideKey := limit.GetThreshold("", regID)
if threshold == -1 {
return nil
}
countPB, err := ra.SA.CountPendingAuthorizations2(ctx, &sapb.RegistrationID{
Id: regID,
})
if err != nil {
return err
}
if countPB.Count >= threshold {
ra.log.Infof("Rate limit exceeded, PendingAuthorizationsByRegID, regID: %d", regID)
return berrors.RateLimitError(0, "too many currently pending authorizations: %d", countPB.Count)
}
if overrideKey != "" {
utilization := float64(countPB.Count) / float64(threshold)
ra.rlOverrideUsageGauge.WithLabelValues(ratelimit.PendingAuthorizationsPerAccount, overrideKey).Set(utilization)
}
return nil
}
// checkInvalidAuthorizationLimits checks the failed validation limit for each
// of the provided hostnames. It returns the first error.
func (ra *RegistrationAuthorityImpl) checkInvalidAuthorizationLimits(ctx context.Context, regID int64, hostnames []string, limits ratelimit.RateLimitPolicy) error {
results := make(chan error, len(hostnames))
for _, hostname := range hostnames {
go func(hostname string) {
results <- ra.checkInvalidAuthorizationLimit(ctx, regID, hostname, limits)
}(hostname)
}
// We don't have to wait for all of the goroutines to finish because there's
// enough capacity in the chan for them all to write their result even if
// nothing is reading off the chan anymore.
for range len(hostnames) {
err := <-results
if err != nil {
return err
}
}
return nil
}
func (ra *RegistrationAuthorityImpl) checkInvalidAuthorizationLimit(ctx context.Context, regID int64, hostname string, limit ratelimit.RateLimitPolicy) error {
latest := ra.clk.Now().Add(ra.pendingAuthorizationLifetime)
earliest := latest.Add(-limit.Window.Duration)
req := &sapb.CountInvalidAuthorizationsRequest{
RegistrationID: regID,
DnsName: hostname,
Range: &sapb.Range{
Earliest: timestamppb.New(earliest),
Latest: timestamppb.New(latest),
},
}
count, err := ra.SA.CountInvalidAuthorizations2(ctx, req)
if err != nil {
return err
}
// Most rate limits have a key for overrides, but there is no meaningful key
// here.
noKey := ""
threshold, overrideKey := limit.GetThreshold(noKey, regID)
if count.Count >= threshold {
ra.log.Infof("Rate limit exceeded, InvalidAuthorizationsByRegID, regID: %d", regID)
return berrors.FailedAuthorizationsPerDomainPerAccountError(0, "too many failed authorizations recently")
}
if overrideKey != "" {
utilization := float64(count.Count) / float64(threshold)
ra.rlOverrideUsageGauge.WithLabelValues(ratelimit.InvalidAuthorizationsPerAccount, overrideKey).Set(utilization)
}
return nil
}
// checkNewOrdersPerAccountLimit enforces the rlPolicies `NewOrdersPerAccount`
// rate limit. This rate limit ensures a client can not create more than the
// specified threshold of new orders within the specified time window.
func (ra *RegistrationAuthorityImpl) checkNewOrdersPerAccountLimit(ctx context.Context, acctID int64, limit ratelimit.RateLimitPolicy) error {
now := ra.clk.Now()
count, err := ra.SA.CountOrders(ctx, &sapb.CountOrdersRequest{
AccountID: acctID,
Range: &sapb.Range{
Earliest: timestamppb.New(now.Add(-limit.Window.Duration)),
Latest: timestamppb.New(now),
},
})
if err != nil {
return err
}
// There is no meaningful override key to use for this rate limit
noKey := ""
threshold, overrideKey := limit.GetThreshold(noKey, acctID)
if count.Count >= threshold {
return berrors.NewOrdersPerAccountError(0, "too many new orders recently")
}
if overrideKey != "" {
utilization := float64(count.Count+1) / float64(threshold)
ra.rlOverrideUsageGauge.WithLabelValues(ratelimit.NewOrdersPerAccount, overrideKey).Set(utilization)
}
return nil
}
// matchesCSR tests the contents of a generated certificate to make sure
// that the PublicKey, CommonName, and DNSNames match those provided in
// the CSR that was used to generate the certificate. It also checks the
@ -1211,11 +1091,6 @@ func (ra *RegistrationAuthorityImpl) issueCertificateOuter(
// errors from this function to the Subscriber, spends against these limit are
// best effort.
func (ra *RegistrationAuthorityImpl) countCertificateIssued(ctx context.Context, regId int64, orderDomains []string, isRenewal bool) {
if ra.limiter == nil || ra.txnBuilder == nil {
// Limiter is disabled.
return
}
var transactions []ratelimits.Transaction
if !isRenewal {
txns, err := ra.txnBuilder.CertificatesPerDomainSpendOnlyTransactions(regId, orderDomains)
@ -1364,226 +1239,6 @@ func (ra *RegistrationAuthorityImpl) getSCTs(ctx context.Context, cert []byte, e
return scts, nil
}
// enforceNameCounts uses the provided count RPC to find a count of certificates
// for each of the names. If the count for any of the names exceeds the limit
// for the given registration then the names out of policy are returned to be
// used for a rate limit error.
func (ra *RegistrationAuthorityImpl) enforceNameCounts(ctx context.Context, names []string, limit ratelimit.RateLimitPolicy, regID int64) ([]string, time.Time, error) {
now := ra.clk.Now()
req := &sapb.CountCertificatesByNamesRequest{
DnsNames: names,
Range: &sapb.Range{
Earliest: timestamppb.New(limit.WindowBegin(now)),
Latest: timestamppb.New(now),
},
}
response, err := ra.SA.CountCertificatesByNames(ctx, req)
if err != nil {
return nil, time.Time{}, err
}
if len(response.Counts) == 0 {
return nil, time.Time{}, errIncompleteGRPCResponse
}
var badNames []string
var metricsData []struct {
overrideKey string
utilization float64
}
// Find the names that have counts at or over the threshold. Range
// over the names slice input to ensure the order of badNames will
// return the badNames in the same order they were input.
for _, name := range names {
threshold, overrideKey := limit.GetThreshold(name, regID)
if response.Counts[name] >= threshold {
badNames = append(badNames, name)
}
if overrideKey != "" {
// Name is under threshold due to an override.
utilization := float64(response.Counts[name]+1) / float64(threshold)
metricsData = append(metricsData, struct {
overrideKey string
utilization float64
}{overrideKey, utilization})
}
}
if len(badNames) == 0 {
// All names were under the threshold, emit override utilization metrics.
for _, data := range metricsData {
ra.rlOverrideUsageGauge.WithLabelValues(ratelimit.CertificatesPerName, data.overrideKey).Set(data.utilization)
}
}
return badNames, response.Earliest.AsTime(), nil
}
func (ra *RegistrationAuthorityImpl) checkCertificatesPerNameLimit(ctx context.Context, names []string, limit ratelimit.RateLimitPolicy, regID int64) error {
tldNames := ratelimits.FQDNsToETLDsPlusOne(names)
namesOutOfLimit, earliest, err := ra.enforceNameCounts(ctx, tldNames, limit, regID)
if err != nil {
return fmt.Errorf("checking certificates per name limit for %q: %s",
names, err)
}
if len(namesOutOfLimit) > 0 {
// Determine the amount of time until the earliest event would fall out
// of the window.
retryAfter := earliest.Add(limit.Window.Duration).Sub(ra.clk.Now())
retryString := earliest.Add(limit.Window.Duration).Format(time.RFC3339)
ra.log.Infof("Rate limit exceeded, CertificatesForDomain, regID: %d, domains: %s", regID, strings.Join(namesOutOfLimit, ", "))
if len(namesOutOfLimit) > 1 {
var subErrors []berrors.SubBoulderError
for _, name := range namesOutOfLimit {
subErrors = append(subErrors, berrors.SubBoulderError{
Identifier: identifier.NewDNS(name),
BoulderError: berrors.NewOrdersPerAccountError(retryAfter, "too many certificates already issued. Retry after %s", retryString).(*berrors.BoulderError),
})
}
return berrors.NewOrdersPerAccountError(retryAfter, "too many certificates already issued for multiple names (%q and %d others). Retry after %s", namesOutOfLimit[0], len(namesOutOfLimit), retryString).(*berrors.BoulderError).WithSubErrors(subErrors)
}
return berrors.NewOrdersPerAccountError(retryAfter, "too many certificates already issued for %q. Retry after %s", namesOutOfLimit[0], retryString)
}
return nil
}
func (ra *RegistrationAuthorityImpl) checkCertificatesPerFQDNSetLimit(ctx context.Context, names []string, limit ratelimit.RateLimitPolicy, regID int64) error {
names = core.UniqueLowerNames(names)
threshold, overrideKey := limit.GetThreshold(strings.Join(names, ","), regID)
if threshold <= 0 {
// No limit configured.
return nil
}
prevIssuances, err := ra.SA.FQDNSetTimestampsForWindow(ctx, &sapb.CountFQDNSetsRequest{
DnsNames: names,
Window: durationpb.New(limit.Window.Duration),
})
if err != nil {
return fmt.Errorf("checking duplicate certificate limit for %q: %s", names, err)
}
if overrideKey != "" {
utilization := float64(len(prevIssuances.Timestamps)) / float64(threshold)
ra.rlOverrideUsageGauge.WithLabelValues(ratelimit.CertificatesPerFQDNSet, overrideKey).Set(utilization)
}
issuanceCount := int64(len(prevIssuances.Timestamps))
if issuanceCount < threshold {
// Issuance in window is below the threshold, no need to limit.
if overrideKey != "" {
utilization := float64(issuanceCount+1) / float64(threshold)
ra.rlOverrideUsageGauge.WithLabelValues(ratelimit.CertificatesPerFQDNSet, overrideKey).Set(utilization)
}
return nil
} else {
// Evaluate the rate limit using a token bucket algorithm. The bucket
// has a capacity of threshold and is refilled at a rate of 1 token per
// limit.Window/threshold from the time of each issuance timestamp. The
// timestamps start from the most recent issuance and go back in time.
now := ra.clk.Now()
nsPerToken := limit.Window.Nanoseconds() / threshold
for i, timestamp := range prevIssuances.Timestamps {
tokensGeneratedSince := now.Add(-time.Duration(int64(i+1) * nsPerToken))
if timestamp.AsTime().Before(tokensGeneratedSince) {
// We know `i+1` tokens were generated since `tokenGeneratedSince`,
// and only `i` certificates were issued, so there's room to allow
// for an additional issuance.
if overrideKey != "" {
utilization := float64(issuanceCount) / float64(threshold)
ra.rlOverrideUsageGauge.WithLabelValues(ratelimit.CertificatesPerFQDNSet, overrideKey).Set(utilization)
}
return nil
}
}
retryTime := prevIssuances.Timestamps[0].AsTime().Add(time.Duration(nsPerToken))
retryAfter := retryTime.Sub(now)
return berrors.CertificatesPerFQDNSetError(
retryAfter,
"too many certificates (%d) already issued for this exact set of domains in the last %.0f hours: %s, retry after %s",
threshold, limit.Window.Duration.Hours(), strings.Join(names, ","), retryTime.Format(time.RFC3339),
)
}
}
func (ra *RegistrationAuthorityImpl) checkNewOrderLimits(ctx context.Context, names []string, regID int64, isRenewal bool) error {
newOrdersPerAccountLimits := ra.rlPolicies.NewOrdersPerAccount()
if newOrdersPerAccountLimits.Enabled() && !isRenewal {
started := ra.clk.Now()
err := ra.checkNewOrdersPerAccountLimit(ctx, regID, newOrdersPerAccountLimits)
elapsed := ra.clk.Since(started)
if err != nil {
if errors.Is(err, berrors.RateLimit) {
ra.rlCheckLatency.WithLabelValues(ratelimit.NewOrdersPerAccount, ratelimits.Denied).Observe(elapsed.Seconds())
}
return err
}
ra.rlCheckLatency.WithLabelValues(ratelimit.NewOrdersPerAccount, ratelimits.Allowed).Observe(elapsed.Seconds())
}
certNameLimits := ra.rlPolicies.CertificatesPerName()
if certNameLimits.Enabled() && !isRenewal {
started := ra.clk.Now()
err := ra.checkCertificatesPerNameLimit(ctx, names, certNameLimits, regID)
elapsed := ra.clk.Since(started)
if err != nil {
if errors.Is(err, berrors.RateLimit) {
ra.rlCheckLatency.WithLabelValues(ratelimit.CertificatesPerName, ratelimits.Denied).Observe(elapsed.Seconds())
}
return err
}
ra.rlCheckLatency.WithLabelValues(ratelimit.CertificatesPerName, ratelimits.Allowed).Observe(elapsed.Seconds())
}
fqdnLimitsFast := ra.rlPolicies.CertificatesPerFQDNSetFast()
if fqdnLimitsFast.Enabled() {
started := ra.clk.Now()
err := ra.checkCertificatesPerFQDNSetLimit(ctx, names, fqdnLimitsFast, regID)
elapsed := ra.clk.Since(started)
if err != nil {
if errors.Is(err, berrors.RateLimit) {
ra.rlCheckLatency.WithLabelValues(ratelimit.CertificatesPerFQDNSetFast, ratelimits.Denied).Observe(elapsed.Seconds())
}
return err
}
ra.rlCheckLatency.WithLabelValues(ratelimit.CertificatesPerFQDNSetFast, ratelimits.Allowed).Observe(elapsed.Seconds())
}
fqdnLimits := ra.rlPolicies.CertificatesPerFQDNSet()
if fqdnLimits.Enabled() {
started := ra.clk.Now()
err := ra.checkCertificatesPerFQDNSetLimit(ctx, names, fqdnLimits, regID)
elapsed := ra.clk.Since(started)
if err != nil {
if errors.Is(err, berrors.RateLimit) {
ra.rlCheckLatency.WithLabelValues(ratelimit.CertificatesPerFQDNSet, ratelimits.Denied).Observe(elapsed.Seconds())
}
return err
}
ra.rlCheckLatency.WithLabelValues(ratelimit.CertificatesPerFQDNSet, ratelimits.Allowed).Observe(elapsed.Seconds())
}
invalidAuthzPerAccountLimits := ra.rlPolicies.InvalidAuthorizationsPerAccount()
if invalidAuthzPerAccountLimits.Enabled() {
started := ra.clk.Now()
err := ra.checkInvalidAuthorizationLimits(ctx, regID, names, invalidAuthzPerAccountLimits)
elapsed := ra.clk.Since(started)
if err != nil {
if errors.Is(err, berrors.RateLimit) {
ra.rlCheckLatency.WithLabelValues(ratelimit.InvalidAuthorizationsPerAccount, ratelimits.Denied).Observe(elapsed.Seconds())
}
return err
}
ra.rlCheckLatency.WithLabelValues(ratelimit.InvalidAuthorizationsPerAccount, ratelimits.Allowed).Observe(elapsed.Seconds())
}
return nil
}
// UpdateRegistration updates an existing Registration with new values. Caller
// is responsible for making sure that update.Key is only different from base.Key
// if it is being called from the WFE key change endpoint.
@ -1778,11 +1433,6 @@ func (ra *RegistrationAuthorityImpl) recordValidation(ctx context.Context, authI
// countFailedValidations increments the FailedAuthorizationsPerDomainPerAccount limit.
// and the FailedAuthorizationsForPausingPerDomainPerAccountTransaction limit.
func (ra *RegistrationAuthorityImpl) countFailedValidations(ctx context.Context, regId int64, ident identifier.ACMEIdentifier) error {
if ra.limiter == nil || ra.txnBuilder == nil {
// Limiter is disabled.
return nil
}
txn, err := ra.txnBuilder.FailedAuthorizationsPerDomainPerAccountSpendOnlyTransaction(regId, ident.Value)
if err != nil {
return fmt.Errorf("building rate limit transaction for the %s rate limit: %w", ratelimits.FailedAuthorizationsPerDomainPerAccount, err)
@ -2589,15 +2239,6 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
}
}
// Renewal orders, indicated by ARI, are exempt from NewOrder rate limits.
if !req.IsARIRenewal && !features.Get().UseKvLimitsForNewOrder {
// Check if there is rate limit space for issuing a certificate.
err = ra.checkNewOrderLimits(ctx, newOrder.DnsNames, newOrder.RegistrationID, req.IsRenewal)
if err != nil {
return nil, err
}
}
// An order's lifetime is effectively bound by the shortest remaining lifetime
// of its associated authorizations. For that reason it would be Uncool if
// `sa.GetAuthorizations` returned an authorization that was very close to
@ -2678,25 +2319,6 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
missingAuthzIdents = append(missingAuthzIdents, ident)
}
// Renewal orders, indicated by ARI, are exempt from NewOrder rate limits.
if len(missingAuthzIdents) > 0 && !req.IsARIRenewal && !features.Get().UseKvLimitsForNewOrder {
pendingAuthzLimits := ra.rlPolicies.PendingAuthorizationsPerAccount()
if pendingAuthzLimits.Enabled() {
// The order isn't fully authorized we need to check that the client
// has rate limit room for more pending authorizations.
started := ra.clk.Now()
err := ra.checkPendingAuthorizationLimit(ctx, newOrder.RegistrationID, pendingAuthzLimits)
elapsed := ra.clk.Since(started)
if err != nil {
if errors.Is(err, berrors.RateLimit) {
ra.rlCheckLatency.WithLabelValues(ratelimit.PendingAuthorizationsPerAccount, ratelimits.Denied).Observe(elapsed.Seconds())
}
return nil, err
}
ra.rlCheckLatency.WithLabelValues(ratelimit.PendingAuthorizationsPerAccount, ratelimits.Allowed).Observe(elapsed.Seconds())
}
}
// Loop through each of the names missing authzs and create a new pending
// authorization for each.
var newAuthzs []*sapb.NewAuthzRequest

View File

@ -7,7 +7,6 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
@ -30,7 +29,6 @@ import (
ctpkix "github.com/google/certificate-transparency-go/x509/pkix"
"github.com/jmhodges/clock"
"github.com/prometheus/client_golang/prometheus"
"github.com/weppos/publicsuffix-go/publicsuffix"
"golang.org/x/crypto/ocsp"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
@ -57,7 +55,6 @@ import (
"github.com/letsencrypt/boulder/policy"
pubpb "github.com/letsencrypt/boulder/publisher/proto"
rapb "github.com/letsencrypt/boulder/ra/proto"
"github.com/letsencrypt/boulder/ratelimit"
"github.com/letsencrypt/boulder/ratelimits"
"github.com/letsencrypt/boulder/sa"
sapb "github.com/letsencrypt/boulder/sa/proto"
@ -1012,261 +1009,6 @@ func TestCertificateKeyNotEqualAccountKey(t *testing.T) {
test.AssertEquals(t, err.Error(), "certificate public key must be different than account key")
}
// mockInvalidAuthorizationsAuthority is a mock which claims that the given
// domain has one invalid authorization.
type mockInvalidAuthorizationsAuthority struct {
sapb.StorageAuthorityClient
domainWithFailures string
}
func (sa *mockInvalidAuthorizationsAuthority) CountInvalidAuthorizations2(ctx context.Context, req *sapb.CountInvalidAuthorizationsRequest, _ ...grpc.CallOption) (*sapb.Count, error) {
if req.DnsName == sa.domainWithFailures {
return &sapb.Count{Count: 1}, nil
} else {
return &sapb.Count{}, nil
}
}
func TestAuthzFailedRateLimitingNewOrder(t *testing.T) {
_, _, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()
txnBuilder, err := ratelimits.NewTransactionBuilder(ratelimits.LimitConfigs{
ratelimits.FailedAuthorizationsForPausingPerDomainPerAccount.String(): &ratelimits.LimitConfig{
Burst: 1,
Count: 1,
Period: config.Duration{Duration: time.Hour * 1}},
})
test.AssertNotError(t, err, "making transaction composer")
ra.txnBuilder = txnBuilder
limit := ra.rlPolicies.InvalidAuthorizationsPerAccount()
ra.SA = &mockInvalidAuthorizationsAuthority{domainWithFailures: "all.i.do.is.lose.com"}
err = ra.checkInvalidAuthorizationLimits(ctx, Registration.Id,
[]string{"charlie.brown.com", "all.i.do.is.lose.com"}, limit)
test.AssertError(t, err, "checkInvalidAuthorizationLimits did not encounter expected rate limit error")
test.AssertEquals(t, err.Error(), "too many failed authorizations recently: see https://letsencrypt.org/docs/rate-limits/#authorization-failures-per-hostname-per-account")
}
type mockSAWithNameCounts struct {
sapb.StorageAuthorityClient
nameCounts *sapb.CountByNames
t *testing.T
clk clock.FakeClock
}
func (m *mockSAWithNameCounts) CountCertificatesByNames(ctx context.Context, req *sapb.CountCertificatesByNamesRequest, _ ...grpc.CallOption) (*sapb.CountByNames, error) {
expectedLatest := m.clk.Now()
if req.Range.Latest.AsTime() != expectedLatest {
m.t.Errorf("incorrect latest: got '%v', expected '%v'", req.Range.Latest.AsTime(), expectedLatest)
}
expectedEarliest := m.clk.Now().Add(-23 * time.Hour)
if req.Range.Earliest.AsTime() != expectedEarliest {
m.t.Errorf("incorrect earliest: got '%v', expected '%v'", req.Range.Earliest.AsTime(), expectedEarliest)
}
counts := make(map[string]int64)
for _, name := range req.DnsNames {
if count, ok := m.nameCounts.Counts[name]; ok {
counts[name] = count
}
}
return &sapb.CountByNames{Counts: counts}, nil
}
// FQDNSetExists is a mock which always returns false, so the test requests
// aren't considered to be renewals.
func (m *mockSAWithNameCounts) FQDNSetExists(ctx context.Context, req *sapb.FQDNSetExistsRequest, _ ...grpc.CallOption) (*sapb.Exists, error) {
return &sapb.Exists{Exists: false}, nil
}
func TestCheckCertificatesPerNameLimit(t *testing.T) {
_, _, ra, _, fc, cleanUp := initAuthorities(t)
defer cleanUp()
rlp := ratelimit.RateLimitPolicy{
Threshold: 3,
Window: config.Duration{Duration: 23 * time.Hour},
Overrides: map[string]int64{
"bigissuer.com": 100,
"smallissuer.co.uk": 1,
},
}
mockSA := &mockSAWithNameCounts{
nameCounts: &sapb.CountByNames{Counts: map[string]int64{"example.com": 1}},
clk: fc,
t: t,
}
ra.SA = mockSA
// One base domain, below threshold
err := ra.checkCertificatesPerNameLimit(ctx, []string{"www.example.com", "example.com"}, rlp, 99)
test.AssertNotError(t, err, "rate limited example.com incorrectly")
// Two base domains, one above threshold, one below
mockSA.nameCounts.Counts["example.com"] = 10
mockSA.nameCounts.Counts["good-example.com"] = 1
err = ra.checkCertificatesPerNameLimit(ctx, []string{"www.example.com", "example.com", "good-example.com"}, rlp, 99)
test.AssertError(t, err, "incorrectly failed to rate limit example.com")
test.AssertErrorIs(t, err, berrors.RateLimit)
// There are no overrides for "example.com", so the override usage gauge
// should contain 0 entries with labels matching it.
test.AssertMetricWithLabelsEquals(t, ra.rlOverrideUsageGauge, prometheus.Labels{"limit": ratelimit.CertificatesPerName, "override_key": "example.com"}, 0)
// Verify it has no sub errors as there is only one bad name
test.AssertEquals(t, err.Error(), "too many certificates already issued for \"example.com\". Retry after 1970-01-01T23:00:00Z: see https://letsencrypt.org/docs/rate-limits/#new-orders-per-account")
var bErr *berrors.BoulderError
test.AssertErrorWraps(t, err, &bErr)
test.AssertEquals(t, len(bErr.SubErrors), 0)
// Three base domains, two above threshold, one below
mockSA.nameCounts.Counts["example.com"] = 10
mockSA.nameCounts.Counts["other-example.com"] = 10
mockSA.nameCounts.Counts["good-example.com"] = 1
err = ra.checkCertificatesPerNameLimit(ctx, []string{"example.com", "other-example.com", "good-example.com"}, rlp, 99)
test.AssertError(t, err, "incorrectly failed to rate limit example.com, other-example.com")
test.AssertErrorIs(t, err, berrors.RateLimit)
// Verify it has two sub errors as there are two bad names
test.AssertEquals(t, err.Error(), "too many certificates already issued for multiple names (\"example.com\" and 2 others). Retry after 1970-01-01T23:00:00Z: see https://letsencrypt.org/docs/rate-limits/#new-orders-per-account")
test.AssertErrorWraps(t, err, &bErr)
test.AssertEquals(t, len(bErr.SubErrors), 2)
// SA misbehaved and didn't send back a count for every input name
err = ra.checkCertificatesPerNameLimit(ctx, []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.Counts["example.com"] = 0
mockSA.nameCounts.Counts["bigissuer.com"] = 50
ra.rlOverrideUsageGauge.WithLabelValues(ratelimit.CertificatesPerName, "bigissuer.com").Set(.5)
err = ra.checkCertificatesPerNameLimit(ctx, []string{"www.example.com", "subdomain.bigissuer.com"}, rlp, 99)
test.AssertNotError(t, err, "incorrectly rate limited bigissuer")
// "bigissuer.com" has an override of 100 and they've issued 50. Accounting
// for the anticipated issuance, we expect to see 51% utilization.
test.AssertMetricWithLabelsEquals(t, ra.rlOverrideUsageGauge, prometheus.Labels{"limit": ratelimit.CertificatesPerName, "override_key": "bigissuer.com"}, .51)
// Two base domains, one above its override
mockSA.nameCounts.Counts["example.com"] = 10
mockSA.nameCounts.Counts["bigissuer.com"] = 100
ra.rlOverrideUsageGauge.WithLabelValues(ratelimit.CertificatesPerName, "bigissuer.com").Set(1)
err = ra.checkCertificatesPerNameLimit(ctx, []string{"www.example.com", "subdomain.bigissuer.com"}, rlp, 99)
test.AssertError(t, err, "incorrectly failed to rate limit bigissuer")
test.AssertErrorIs(t, err, berrors.RateLimit)
// "bigissuer.com" has an override of 100 and they've issued 100. They're
// already at 100% utilization, so we expect to see 100% utilization.
test.AssertMetricWithLabelsEquals(t, ra.rlOverrideUsageGauge, prometheus.Labels{"limit": ratelimit.CertificatesPerName, "override_key": "bigissuer.com"}, 1)
// One base domain, above its override (which is below threshold)
mockSA.nameCounts.Counts["smallissuer.co.uk"] = 1
ra.rlOverrideUsageGauge.WithLabelValues(ratelimit.CertificatesPerName, "smallissuer.co.uk").Set(1)
err = ra.checkCertificatesPerNameLimit(ctx, []string{"www.smallissuer.co.uk"}, rlp, 99)
test.AssertError(t, err, "incorrectly failed to rate limit smallissuer")
test.AssertErrorIs(t, err, berrors.RateLimit)
// "smallissuer.co.uk" has an override of 1 and they've issued 1. They're
// already at 100% utilization, so we expect to see 100% utilization.
test.AssertMetricWithLabelsEquals(t, ra.rlOverrideUsageGauge, prometheus.Labels{"limit": ratelimit.CertificatesPerName, "override_key": "smallissuer.co.uk"}, 1)
}
// TestCheckExactCertificateLimit tests that the duplicate certificate limit
// applied to FQDN sets is respected.
func TestCheckExactCertificateLimit(t *testing.T) {
_, _, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()
// Create a rate limit with a small threshold
const dupeCertLimit = 3
rlp := ratelimit.RateLimitPolicy{
Threshold: dupeCertLimit,
Window: config.Duration{Duration: 24 * time.Hour},
}
// Create a mock SA that has a count of already issued certificates for some
// test names
firstIssuanceTimestamp := ra.clk.Now().Add(-rlp.Window.Duration)
fITS2 := firstIssuanceTimestamp.Add(time.Hour * 23)
fITS3 := firstIssuanceTimestamp.Add(time.Hour * 16)
fITS4 := firstIssuanceTimestamp.Add(time.Hour * 8)
issuanceTimestampsNS := []int64{
fITS2.UnixNano(),
fITS3.UnixNano(),
fITS4.UnixNano(),
firstIssuanceTimestamp.UnixNano(),
}
issuanceTimestamps := []*timestamppb.Timestamp{
timestamppb.New(fITS2),
timestamppb.New(fITS3),
timestamppb.New(fITS4),
timestamppb.New(firstIssuanceTimestamp),
}
// Our window is 24 hours and our threshold is 3 issuance. If our most
// recent issuance was 1 hour ago, we expect the next token to be available
// 8 hours from issuance time or 7 hours from now.
expectRetryAfterNS := time.Unix(0, issuanceTimestampsNS[0]).Add(time.Hour * 8).Format(time.RFC3339)
expectRetryAfter := issuanceTimestamps[0].AsTime().Add(time.Hour * 8).Format(time.RFC3339)
test.AssertEquals(t, expectRetryAfterNS, expectRetryAfter)
ra.SA = &mockSAWithFQDNSet{
issuanceTimestamps: map[string]*sapb.Timestamps{
"none.example.com": {Timestamps: []*timestamppb.Timestamp{}},
"under.example.com": {Timestamps: issuanceTimestamps[3:3]},
"equalbutvalid.example.com": {Timestamps: issuanceTimestamps[1:3]},
"over.example.com": {Timestamps: issuanceTimestamps[0:3]},
},
t: t,
}
testCases := []struct {
Name string
Domain string
ExpectedErr error
}{
{
Name: "FQDN set issuances none",
Domain: "none.example.com",
ExpectedErr: nil,
},
{
Name: "FQDN set issuances less than limit",
Domain: "under.example.com",
ExpectedErr: nil,
},
{
Name: "FQDN set issuances equal to limit",
Domain: "equalbutvalid.example.com",
ExpectedErr: nil,
},
{
Name: "FQDN set issuances above limit NS",
Domain: "over.example.com",
ExpectedErr: fmt.Errorf(
"too many certificates (3) already issued for this exact set of domains in the last 24 hours: over.example.com, retry after %s: see https://letsencrypt.org/docs/rate-limits/#new-certificates-per-exact-set-of-hostnames",
expectRetryAfterNS,
),
},
{
Name: "FQDN set issuances above limit",
Domain: "over.example.com",
ExpectedErr: fmt.Errorf(
"too many certificates (3) already issued for this exact set of domains in the last 24 hours: over.example.com, retry after %s: see https://letsencrypt.org/docs/rate-limits/#new-certificates-per-exact-set-of-hostnames",
expectRetryAfter,
),
},
}
// For each test case we check that the certificatesPerFQDNSetLimit is applied
// as we expect
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := ra.checkCertificatesPerFQDNSetLimit(ctx, []string{tc.Domain}, rlp, 0)
if tc.ExpectedErr == nil {
test.AssertNotError(t, result, fmt.Sprintf("Expected no error for %q", tc.Domain))
} else {
test.AssertError(t, result, fmt.Sprintf("Expected error for %q", tc.Domain))
test.AssertEquals(t, result.Error(), tc.ExpectedErr.Error())
}
})
}
}
func TestRegistrationUpdate(t *testing.T) {
oldURL := "http://old.invalid"
newURL := "http://new.invalid"
@ -1356,108 +1098,6 @@ func TestRegistrationKeyUpdate(t *testing.T) {
test.AssertByteEquals(t, res.Key, update.Key)
}
// A mockSAWithFQDNSet is a mock StorageAuthority that supports
// CountCertificatesByName as well as FQDNSetExists. This allows testing
// checkCertificatesPerNameRateLimit's FQDN exemption logic.
type mockSAWithFQDNSet struct {
sapb.StorageAuthorityClient
fqdnSet map[string]bool
issuanceTimestamps map[string]*sapb.Timestamps
t *testing.T
}
// Construct the FQDN Set key the same way as the SA (by using
// `core.UniqueLowerNames`, joining the names with a `,` and hashing them)
// but return a string so it can be used as a key in m.fqdnSet.
func (m mockSAWithFQDNSet) hashNames(names []string) string {
names = core.UniqueLowerNames(names)
hash := sha256.Sum256([]byte(strings.Join(names, ",")))
return string(hash[:])
}
// Search for a set of domain names in the FQDN set map
func (m mockSAWithFQDNSet) FQDNSetExists(_ context.Context, req *sapb.FQDNSetExistsRequest, _ ...grpc.CallOption) (*sapb.Exists, error) {
hash := m.hashNames(req.DnsNames)
if _, exists := m.fqdnSet[hash]; exists {
return &sapb.Exists{Exists: true}, nil
}
return &sapb.Exists{Exists: false}, nil
}
// Return a map of domain -> certificate count.
func (m mockSAWithFQDNSet) CountCertificatesByNames(ctx context.Context, req *sapb.CountCertificatesByNamesRequest, _ ...grpc.CallOption) (*sapb.CountByNames, error) {
counts := make(map[string]int64)
for _, name := range req.DnsNames {
entry, ok := m.issuanceTimestamps[name]
if ok {
counts[name] = int64(len(entry.Timestamps))
}
}
return &sapb.CountByNames{Counts: counts}, nil
}
func (m mockSAWithFQDNSet) FQDNSetTimestampsForWindow(_ context.Context, req *sapb.CountFQDNSetsRequest, _ ...grpc.CallOption) (*sapb.Timestamps, error) {
if len(req.DnsNames) == 1 {
return m.issuanceTimestamps[req.DnsNames[0]], nil
} else {
return nil, fmt.Errorf("FQDNSetTimestampsForWindow mock only supports a single domain")
}
}
// TestExactPublicSuffixCertLimit tests the behaviour of issue #2681.
// See https://github.com/letsencrypt/boulder/issues/2681
func TestExactPublicSuffixCertLimit(t *testing.T) {
_, _, ra, _, fc, cleanUp := initAuthorities(t)
defer cleanUp()
// Simple policy that only allows 2 certificates per name.
certsPerNamePolicy := ratelimit.RateLimitPolicy{
Threshold: 2,
Window: config.Duration{Duration: 23 * time.Hour},
}
// We use "dedyn.io" and "dynv6.net" domains for the test on the implicit
// assumption that both domains are present on the public suffix list.
// Quickly verify that this is true before continuing with the rest of the test.
_, err := publicsuffix.Domain("dedyn.io")
test.AssertError(t, err, "dedyn.io was not on the public suffix list, invaliding the test")
_, err = publicsuffix.Domain("dynv6.net")
test.AssertError(t, err, "dynv6.net was not on the public suffix list, invaliding the test")
// Back the mock SA with counts as if so far we have issued the following
// certificates for the following domains:
// - test.dedyn.io (once)
// - test2.dedyn.io (once)
// - dynv6.net (twice)
mockSA := &mockSAWithNameCounts{
nameCounts: &sapb.CountByNames{
Counts: map[string]int64{
"test.dedyn.io": 1,
"test2.dedyn.io": 1,
"test3.dedyn.io": 0,
"dedyn.io": 0,
"dynv6.net": 2,
},
},
clk: fc,
t: t,
}
ra.SA = mockSA
// Trying to issue for "test3.dedyn.io" and "dedyn.io" should succeed because
// test3.dedyn.io has no certificates and "dedyn.io" is an exact public suffix
// match with no certificates issued for it.
err = ra.checkCertificatesPerNameLimit(ctx, []string{"test3.dedyn.io", "dedyn.io"}, certsPerNamePolicy, 99)
test.AssertNotError(t, err, "certificate per name rate limit not applied correctly")
// Trying to issue for "test3.dedyn.io" and "dynv6.net" should fail because
// "dynv6.net" is an exact public suffix match with 2 certificates issued for
// it.
err = ra.checkCertificatesPerNameLimit(ctx, []string{"test3.dedyn.io", "dynv6.net"}, certsPerNamePolicy, 99)
test.AssertError(t, err, "certificate per name rate limit not applied correctly")
}
func TestDeactivateAuthorization(t *testing.T) {
_, sa, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()

View File

@ -1,237 +0,0 @@
package ratelimit
import (
"strconv"
"time"
"github.com/letsencrypt/boulder/config"
"github.com/letsencrypt/boulder/strictyaml"
)
const (
// CertificatesPerName is the name of the CertificatesPerName rate limit
// when referenced in metric labels.
CertificatesPerName = "certificates_per_domain"
// RegistrationsPerIP is the name of the RegistrationsPerIP rate limit when
// referenced in metric labels.
RegistrationsPerIP = "registrations_per_ip"
// RegistrationsPerIPRange is the name of the RegistrationsPerIPRange rate
// limit when referenced in metric labels.
RegistrationsPerIPRange = "registrations_per_ipv6_range"
// PendingAuthorizationsPerAccount is the name of the
// PendingAuthorizationsPerAccount rate limit when referenced in metric
// labels.
PendingAuthorizationsPerAccount = "pending_authorizations_per_account"
// InvalidAuthorizationsPerAccount is the name of the
// InvalidAuthorizationsPerAccount rate limit when referenced in metric
// labels.
InvalidAuthorizationsPerAccount = "failed_authorizations_per_account"
// CertificatesPerFQDNSet is the name of the CertificatesPerFQDNSet rate
// limit when referenced in metric labels.
CertificatesPerFQDNSet = "certificates_per_fqdn_set"
// CertificatesPerFQDNSetFast is the name of the CertificatesPerFQDNSetFast
// rate limit when referenced in metric labels.
CertificatesPerFQDNSetFast = "certificates_per_fqdn_set_fast"
// NewOrdersPerAccount is the name of the NewOrdersPerAccount rate limit
// when referenced in metric labels.
NewOrdersPerAccount = "new_orders_per_account"
)
// Limits is defined to allow mock implementations be provided during unit
// testing
type Limits interface {
CertificatesPerName() RateLimitPolicy
RegistrationsPerIP() RateLimitPolicy
RegistrationsPerIPRange() RateLimitPolicy
PendingAuthorizationsPerAccount() RateLimitPolicy
InvalidAuthorizationsPerAccount() RateLimitPolicy
CertificatesPerFQDNSet() RateLimitPolicy
CertificatesPerFQDNSetFast() RateLimitPolicy
NewOrdersPerAccount() RateLimitPolicy
LoadPolicies(contents []byte) error
}
// limitsImpl is an unexported implementation of the Limits interface. It acts
// as a container for a rateLimitConfig.
type limitsImpl struct {
rlPolicy *rateLimitConfig
}
func (r *limitsImpl) CertificatesPerName() RateLimitPolicy {
if r.rlPolicy == nil {
return RateLimitPolicy{}
}
return r.rlPolicy.CertificatesPerName
}
func (r *limitsImpl) RegistrationsPerIP() RateLimitPolicy {
if r.rlPolicy == nil {
return RateLimitPolicy{}
}
return r.rlPolicy.RegistrationsPerIP
}
func (r *limitsImpl) RegistrationsPerIPRange() RateLimitPolicy {
if r.rlPolicy == nil {
return RateLimitPolicy{}
}
return r.rlPolicy.RegistrationsPerIPRange
}
func (r *limitsImpl) PendingAuthorizationsPerAccount() RateLimitPolicy {
if r.rlPolicy == nil {
return RateLimitPolicy{}
}
return r.rlPolicy.PendingAuthorizationsPerAccount
}
func (r *limitsImpl) InvalidAuthorizationsPerAccount() RateLimitPolicy {
if r.rlPolicy == nil {
return RateLimitPolicy{}
}
return r.rlPolicy.InvalidAuthorizationsPerAccount
}
func (r *limitsImpl) CertificatesPerFQDNSet() RateLimitPolicy {
if r.rlPolicy == nil {
return RateLimitPolicy{}
}
return r.rlPolicy.CertificatesPerFQDNSet
}
func (r *limitsImpl) CertificatesPerFQDNSetFast() RateLimitPolicy {
if r.rlPolicy == nil {
return RateLimitPolicy{}
}
return r.rlPolicy.CertificatesPerFQDNSetFast
}
func (r *limitsImpl) NewOrdersPerAccount() RateLimitPolicy {
if r.rlPolicy == nil {
return RateLimitPolicy{}
}
return r.rlPolicy.NewOrdersPerAccount
}
// LoadPolicies loads various rate limiting policies from a byte array of
// YAML configuration.
func (r *limitsImpl) LoadPolicies(contents []byte) error {
var newPolicy rateLimitConfig
err := strictyaml.Unmarshal(contents, &newPolicy)
if err != nil {
return err
}
r.rlPolicy = &newPolicy
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 {
// Number of certificates that can be extant containing any given name.
// These are counted by "base domain" aka eTLD+1, so any entries in the
// overrides section must be an eTLD+1 according to the publicsuffix package.
CertificatesPerName RateLimitPolicy `yaml:"certificatesPerName"`
// Number of registrations that can be created per IP.
// Note: Since this is checked before a registration is created, setting a
// RegistrationOverride on it has no effect.
RegistrationsPerIP RateLimitPolicy `yaml:"registrationsPerIP"`
// Number of registrations that can be created per fuzzy IP range. Unlike
// RegistrationsPerIP this will apply to a /48 for IPv6 addresses to help curb
// abuse from easily obtained IPv6 ranges.
// Note: Like RegistrationsPerIP, setting a RegistrationOverride has no
// effect here.
RegistrationsPerIPRange RateLimitPolicy `yaml:"registrationsPerIPRange"`
// Number of pending authorizations that can exist per account. Overrides by
// key are not applied, but overrides by registration are.
PendingAuthorizationsPerAccount RateLimitPolicy `yaml:"pendingAuthorizationsPerAccount"`
// Number of invalid authorizations that can be failed per account within the
// given window. Overrides by key are not applied, but overrides by registration are.
// Note that this limit is actually "per account, per hostname," but that
// is too long for the variable name.
InvalidAuthorizationsPerAccount RateLimitPolicy `yaml:"invalidAuthorizationsPerAccount"`
// Number of new orders that can be created per account within the given
// window. Overrides by key are not applied, but overrides by registration are.
NewOrdersPerAccount RateLimitPolicy `yaml:"newOrdersPerAccount"`
// Number of certificates that can be extant containing a specific set
// of DNS names.
CertificatesPerFQDNSet RateLimitPolicy `yaml:"certificatesPerFQDNSet"`
// Same as above, but intended to both trigger and reset faster (i.e. a
// lower threshold and smaller window), so that clients don't have to wait
// a long time after a small burst of accidental duplicate issuance.
CertificatesPerFQDNSetFast RateLimitPolicy `yaml:"certificatesPerFQDNSetFast"`
}
// RateLimitPolicy describes a general limiting policy
type RateLimitPolicy struct {
// How long to count items for
Window config.Duration `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 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 "no limit," it means a limit of zero. An entry of -1 means
// "no limit", only for the pending authorizations rate limit.
Overrides map[string]int64 `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, whichever is larger takes priority. Note that a zero entry in
// the overrides map does not mean "no limit", it means a limit of zero.
RegistrationOverrides map[int64]int64 `yaml:"registrationOverrides"`
}
// Enabled returns true iff the RateLimitPolicy is enabled.
func (rlp *RateLimitPolicy) Enabled() bool {
return rlp.Threshold != 0
}
// GetThreshold returns the threshold for this rate limit and the override
// Id/Key if that threshold is the result of an override for the default limit,
// empty-string otherwise. The threshold returned takes into account any
// overrides for `key` or `regID`. If both `key` and `regID` have an override
// the largest of the two will be used.
func (rlp *RateLimitPolicy) GetThreshold(key string, regID int64) (int64, string) {
regOverride, regOverrideExists := rlp.RegistrationOverrides[regID]
keyOverride, keyOverrideExists := rlp.Overrides[key]
if regOverrideExists && !keyOverrideExists {
// If there is a regOverride and no keyOverride use the regOverride
return regOverride, strconv.FormatInt(regID, 10)
} else if !regOverrideExists && keyOverrideExists {
// If there is a keyOverride and no regOverride use the keyOverride
return keyOverride, key
} else if regOverrideExists && keyOverrideExists {
// If there is both a regOverride and a keyOverride use whichever is larger.
if regOverride > keyOverride {
return regOverride, strconv.FormatInt(regID, 10)
} else {
return keyOverride, key
}
}
// Otherwise there was no regOverride and no keyOverride, use the base
// Threshold
return rlp.Threshold, ""
}
// WindowBegin returns the time that a RateLimitPolicy's window begins, given a
// particular end time (typically the current time).
func (rlp *RateLimitPolicy) WindowBegin(windowEnd time.Time) time.Time {
return windowEnd.Add(-1 * rlp.Window.Duration)
}

View File

@ -1,187 +0,0 @@
package ratelimit
import (
"os"
"testing"
"time"
"github.com/letsencrypt/boulder/config"
"github.com/letsencrypt/boulder/test"
)
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]int64{
"key": 2,
"baz": 99,
},
RegistrationOverrides: map[int64]int64{
101: 3,
},
}
testCases := []struct {
Name string
Key string
RegID int64
Expected int64
}{
{
Name: "No key or reg overrides",
Key: "foo",
RegID: 11,
Expected: 1,
},
{
Name: "Key override, no reg override",
Key: "key",
RegID: 11,
Expected: 2,
},
{
Name: "No key override, reg override",
Key: "foo",
RegID: 101,
Expected: 3,
},
{
Name: "Key override, larger reg override",
Key: "foo",
RegID: 101,
Expected: 3,
},
{
Name: "Key override, smaller reg override",
Key: "baz",
RegID: 101,
Expected: 99,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
threshold, _ := policy.GetThreshold(tc.Key, tc.RegID)
test.AssertEquals(t,
threshold,
tc.Expected)
})
}
}
func TestWindowBegin(t *testing.T) {
policy := RateLimitPolicy{
Window: config.Duration{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)
}
}
func TestLoadPolicies(t *testing.T) {
policy := New()
policyContent, readErr := os.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 CertificatesPerName section parsed correctly
certsPerName := policy.CertificatesPerName()
test.AssertEquals(t, certsPerName.Threshold, int64(2))
test.AssertDeepEquals(t, certsPerName.Overrides, map[string]int64{
"ratelimit.me": 1,
"lim.it": 0,
"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,
"ecdsa.le.wtf": 10000,
"must-staple.le.wtf": 10000,
})
test.AssertDeepEquals(t, certsPerName.RegistrationOverrides, map[int64]int64{
101: 1000,
})
// Test that the RegistrationsPerIP section parsed correctly
regsPerIP := policy.RegistrationsPerIP()
test.AssertEquals(t, regsPerIP.Threshold, int64(10000))
test.AssertDeepEquals(t, regsPerIP.Overrides, map[string]int64{
"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, int64(150))
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, int64(6))
test.AssertDeepEquals(t, certsPerFQDN.Overrides, map[string]int64{
"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,
"ecdsa.le.wtf": 10000,
"must-staple.le.wtf": 10000,
})
test.AssertEquals(t, len(certsPerFQDN.RegistrationOverrides), 0)
certsPerFQDNFast := policy.CertificatesPerFQDNSetFast()
test.AssertEquals(t, certsPerFQDNFast.Threshold, int64(2))
test.AssertDeepEquals(t, certsPerFQDNFast.Overrides, map[string]int64{
"le.wtf": 100,
})
test.AssertEquals(t, len(certsPerFQDNFast.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]int64{
"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.CertificatesPerName().Threshold, int64(0))
test.AssertEquals(t, emptyPolicy.RegistrationsPerIP().Threshold, int64(0))
test.AssertEquals(t, emptyPolicy.RegistrationsPerIP().Threshold, int64(0))
test.AssertEquals(t, emptyPolicy.PendingAuthorizationsPerAccount().Threshold, int64(0))
test.AssertEquals(t, emptyPolicy.CertificatesPerFQDNSet().Threshold, int64(0))
}

View File

@ -0,0 +1,27 @@
-- +migrate Up
DROP TABLE certificatesPerName;
DROP TABLE newOrdersRL;
-- +migrate Down
DROP TABLE certificatesPerName;
DROP TABLE newOrdersRL;
CREATE TABLE `certificatesPerName` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`eTLDPlusOne` varchar(255) NOT NULL,
`time` datetime NOT NULL,
`count` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `eTLDPlusOne_time_idx` (`eTLDPlusOne`,`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `newOrdersRL` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`regID` bigint(20) NOT NULL,
`time` datetime NOT NULL,
`count` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `regID_time_idx` (`regID`,`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -18,7 +18,6 @@ CREATE USER IF NOT EXISTS 'proxysql'@'localhost';
GRANT SELECT,INSERT ON certificates TO 'sa'@'localhost';
GRANT SELECT,INSERT,UPDATE ON certificateStatus TO 'sa'@'localhost';
GRANT SELECT,INSERT ON issuedNames TO 'sa'@'localhost';
GRANT SELECT,INSERT,UPDATE ON certificatesPerName TO 'sa'@'localhost';
GRANT SELECT,INSERT,UPDATE ON registrations TO 'sa'@'localhost';
GRANT SELECT,INSERT on fqdnSets TO 'sa'@'localhost';
GRANT SELECT,INSERT,UPDATE ON orders TO 'sa'@'localhost';
@ -29,7 +28,6 @@ GRANT INSERT,SELECT ON serials TO 'sa'@'localhost';
GRANT SELECT,INSERT ON precertificates TO 'sa'@'localhost';
GRANT SELECT,INSERT ON keyHashToSerial TO 'sa'@'localhost';
GRANT SELECT,INSERT ON blockedKeys TO 'sa'@'localhost';
GRANT SELECT,INSERT,UPDATE ON newOrdersRL TO 'sa'@'localhost';
GRANT SELECT ON incidents TO 'sa'@'localhost';
GRANT SELECT,INSERT,UPDATE ON crlShards TO 'sa'@'localhost';
GRANT SELECT,INSERT,UPDATE ON revokedCertificates TO 'sa'@'localhost';
@ -40,7 +38,6 @@ GRANT SELECT,INSERT,UPDATE,DROP ON paused TO 'sa'@'localhost';
GRANT SELECT ON certificates TO 'sa_ro'@'localhost';
GRANT SELECT ON certificateStatus TO 'sa_ro'@'localhost';
GRANT SELECT ON issuedNames TO 'sa_ro'@'localhost';
GRANT SELECT ON certificatesPerName TO 'sa_ro'@'localhost';
GRANT SELECT ON registrations TO 'sa_ro'@'localhost';
GRANT SELECT on fqdnSets TO 'sa_ro'@'localhost';
GRANT SELECT ON orders TO 'sa_ro'@'localhost';
@ -51,7 +48,6 @@ GRANT SELECT ON serials TO 'sa_ro'@'localhost';
GRANT SELECT ON precertificates TO 'sa_ro'@'localhost';
GRANT SELECT ON keyHashToSerial TO 'sa_ro'@'localhost';
GRANT SELECT ON blockedKeys TO 'sa_ro'@'localhost';
GRANT SELECT ON newOrdersRL TO 'sa_ro'@'localhost';
GRANT SELECT ON incidents TO 'sa_ro'@'localhost';
GRANT SELECT ON crlShards TO 'sa_ro'@'localhost';
GRANT SELECT ON revokedCertificates TO 'sa_ro'@'localhost';

File diff suppressed because it is too large Load Diff

View File

@ -10,9 +10,7 @@ import "google/protobuf/duration.proto";
// StorageAuthorityReadOnly exposes only those SA methods which are read-only.
service StorageAuthorityReadOnly {
rpc CountCertificatesByNames(CountCertificatesByNamesRequest) returns (CountByNames) {}
rpc CountInvalidAuthorizations2(CountInvalidAuthorizationsRequest) returns (Count) {}
rpc CountOrders(CountOrdersRequest) returns (Count) {}
rpc CountPendingAuthorizations2(RegistrationID) returns (Count) {}
rpc FQDNSetExists(FQDNSetExistsRequest) returns (Exists) {}
rpc FQDNSetTimestampsForWindow(CountFQDNSetsRequest) returns (Timestamps) {}
@ -44,9 +42,7 @@ service StorageAuthorityReadOnly {
// StorageAuthority provides full read/write access to the database.
service StorageAuthority {
// Getters: this list must be identical to the StorageAuthorityReadOnly rpcs.
rpc CountCertificatesByNames(CountCertificatesByNamesRequest) returns (CountByNames) {}
rpc CountInvalidAuthorizations2(CountInvalidAuthorizationsRequest) returns (Count) {}
rpc CountOrders(CountOrdersRequest) returns (Count) {}
rpc CountPendingAuthorizations2(RegistrationID) returns (Count) {}
rpc FQDNSetExists(FQDNSetExistsRequest) returns (Exists) {}
rpc FQDNSetTimestampsForWindow(CountFQDNSetsRequest) returns (Timestamps) {}
@ -150,16 +146,6 @@ message Timestamps {
repeated google.protobuf.Timestamp timestamps = 2;
}
message CountCertificatesByNamesRequest {
Range range = 1;
repeated string dnsNames = 2;
}
message CountByNames {
map<string, int64> counts = 1;
google.protobuf.Timestamp earliest = 2; // Unix timestamp (nanoseconds)
}
message CountInvalidAuthorizationsRequest {
int64 registrationID = 1;
string dnsName = 2;
@ -167,11 +153,6 @@ message CountInvalidAuthorizationsRequest {
Range range = 3;
}
message CountOrdersRequest {
int64 accountID = 1;
Range range = 2;
}
message CountFQDNSetsRequest {
// Next unused field number: 4
reserved 1; // Previously windowNS

View File

@ -22,9 +22,7 @@ import (
const _ = grpc.SupportPackageIsVersion9
const (
StorageAuthorityReadOnly_CountCertificatesByNames_FullMethodName = "/sa.StorageAuthorityReadOnly/CountCertificatesByNames"
StorageAuthorityReadOnly_CountInvalidAuthorizations2_FullMethodName = "/sa.StorageAuthorityReadOnly/CountInvalidAuthorizations2"
StorageAuthorityReadOnly_CountOrders_FullMethodName = "/sa.StorageAuthorityReadOnly/CountOrders"
StorageAuthorityReadOnly_CountPendingAuthorizations2_FullMethodName = "/sa.StorageAuthorityReadOnly/CountPendingAuthorizations2"
StorageAuthorityReadOnly_FQDNSetExists_FullMethodName = "/sa.StorageAuthorityReadOnly/FQDNSetExists"
StorageAuthorityReadOnly_FQDNSetTimestampsForWindow_FullMethodName = "/sa.StorageAuthorityReadOnly/FQDNSetTimestampsForWindow"
@ -57,9 +55,7 @@ const (
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type StorageAuthorityReadOnlyClient interface {
CountCertificatesByNames(ctx context.Context, in *CountCertificatesByNamesRequest, opts ...grpc.CallOption) (*CountByNames, error)
CountInvalidAuthorizations2(ctx context.Context, in *CountInvalidAuthorizationsRequest, opts ...grpc.CallOption) (*Count, error)
CountOrders(ctx context.Context, in *CountOrdersRequest, opts ...grpc.CallOption) (*Count, error)
CountPendingAuthorizations2(ctx context.Context, in *RegistrationID, opts ...grpc.CallOption) (*Count, error)
FQDNSetExists(ctx context.Context, in *FQDNSetExistsRequest, opts ...grpc.CallOption) (*Exists, error)
FQDNSetTimestampsForWindow(ctx context.Context, in *CountFQDNSetsRequest, opts ...grpc.CallOption) (*Timestamps, error)
@ -96,16 +92,6 @@ func NewStorageAuthorityReadOnlyClient(cc grpc.ClientConnInterface) StorageAutho
return &storageAuthorityReadOnlyClient{cc}
}
func (c *storageAuthorityReadOnlyClient) CountCertificatesByNames(ctx context.Context, in *CountCertificatesByNamesRequest, opts ...grpc.CallOption) (*CountByNames, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CountByNames)
err := c.cc.Invoke(ctx, StorageAuthorityReadOnly_CountCertificatesByNames_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *storageAuthorityReadOnlyClient) CountInvalidAuthorizations2(ctx context.Context, in *CountInvalidAuthorizationsRequest, opts ...grpc.CallOption) (*Count, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Count)
@ -116,16 +102,6 @@ func (c *storageAuthorityReadOnlyClient) CountInvalidAuthorizations2(ctx context
return out, nil
}
func (c *storageAuthorityReadOnlyClient) CountOrders(ctx context.Context, in *CountOrdersRequest, opts ...grpc.CallOption) (*Count, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Count)
err := c.cc.Invoke(ctx, StorageAuthorityReadOnly_CountOrders_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *storageAuthorityReadOnlyClient) CountPendingAuthorizations2(ctx context.Context, in *RegistrationID, opts ...grpc.CallOption) (*Count, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Count)
@ -426,9 +402,7 @@ func (c *storageAuthorityReadOnlyClient) GetPausedIdentifiers(ctx context.Contex
// All implementations must embed UnimplementedStorageAuthorityReadOnlyServer
// for forward compatibility
type StorageAuthorityReadOnlyServer interface {
CountCertificatesByNames(context.Context, *CountCertificatesByNamesRequest) (*CountByNames, error)
CountInvalidAuthorizations2(context.Context, *CountInvalidAuthorizationsRequest) (*Count, error)
CountOrders(context.Context, *CountOrdersRequest) (*Count, error)
CountPendingAuthorizations2(context.Context, *RegistrationID) (*Count, error)
FQDNSetExists(context.Context, *FQDNSetExistsRequest) (*Exists, error)
FQDNSetTimestampsForWindow(context.Context, *CountFQDNSetsRequest) (*Timestamps, error)
@ -462,15 +436,9 @@ type StorageAuthorityReadOnlyServer interface {
type UnimplementedStorageAuthorityReadOnlyServer struct {
}
func (UnimplementedStorageAuthorityReadOnlyServer) CountCertificatesByNames(context.Context, *CountCertificatesByNamesRequest) (*CountByNames, error) {
return nil, status.Errorf(codes.Unimplemented, "method CountCertificatesByNames not implemented")
}
func (UnimplementedStorageAuthorityReadOnlyServer) CountInvalidAuthorizations2(context.Context, *CountInvalidAuthorizationsRequest) (*Count, error) {
return nil, status.Errorf(codes.Unimplemented, "method CountInvalidAuthorizations2 not implemented")
}
func (UnimplementedStorageAuthorityReadOnlyServer) CountOrders(context.Context, *CountOrdersRequest) (*Count, error) {
return nil, status.Errorf(codes.Unimplemented, "method CountOrders not implemented")
}
func (UnimplementedStorageAuthorityReadOnlyServer) CountPendingAuthorizations2(context.Context, *RegistrationID) (*Count, error) {
return nil, status.Errorf(codes.Unimplemented, "method CountPendingAuthorizations2 not implemented")
}
@ -563,24 +531,6 @@ func RegisterStorageAuthorityReadOnlyServer(s grpc.ServiceRegistrar, srv Storage
s.RegisterService(&StorageAuthorityReadOnly_ServiceDesc, srv)
}
func _StorageAuthorityReadOnly_CountCertificatesByNames_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CountCertificatesByNamesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StorageAuthorityReadOnlyServer).CountCertificatesByNames(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StorageAuthorityReadOnly_CountCertificatesByNames_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StorageAuthorityReadOnlyServer).CountCertificatesByNames(ctx, req.(*CountCertificatesByNamesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StorageAuthorityReadOnly_CountInvalidAuthorizations2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CountInvalidAuthorizationsRequest)
if err := dec(in); err != nil {
@ -599,24 +549,6 @@ func _StorageAuthorityReadOnly_CountInvalidAuthorizations2_Handler(srv interface
return interceptor(ctx, in, info, handler)
}
func _StorageAuthorityReadOnly_CountOrders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CountOrdersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StorageAuthorityReadOnlyServer).CountOrders(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StorageAuthorityReadOnly_CountOrders_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StorageAuthorityReadOnlyServer).CountOrders(ctx, req.(*CountOrdersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StorageAuthorityReadOnly_CountPendingAuthorizations2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RegistrationID)
if err := dec(in); err != nil {
@ -1064,18 +996,10 @@ var StorageAuthorityReadOnly_ServiceDesc = grpc.ServiceDesc{
ServiceName: "sa.StorageAuthorityReadOnly",
HandlerType: (*StorageAuthorityReadOnlyServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CountCertificatesByNames",
Handler: _StorageAuthorityReadOnly_CountCertificatesByNames_Handler,
},
{
MethodName: "CountInvalidAuthorizations2",
Handler: _StorageAuthorityReadOnly_CountInvalidAuthorizations2_Handler,
},
{
MethodName: "CountOrders",
Handler: _StorageAuthorityReadOnly_CountOrders_Handler,
},
{
MethodName: "CountPendingAuthorizations2",
Handler: _StorageAuthorityReadOnly_CountPendingAuthorizations2_Handler,
@ -1191,9 +1115,7 @@ var StorageAuthorityReadOnly_ServiceDesc = grpc.ServiceDesc{
}
const (
StorageAuthority_CountCertificatesByNames_FullMethodName = "/sa.StorageAuthority/CountCertificatesByNames"
StorageAuthority_CountInvalidAuthorizations2_FullMethodName = "/sa.StorageAuthority/CountInvalidAuthorizations2"
StorageAuthority_CountOrders_FullMethodName = "/sa.StorageAuthority/CountOrders"
StorageAuthority_CountPendingAuthorizations2_FullMethodName = "/sa.StorageAuthority/CountPendingAuthorizations2"
StorageAuthority_FQDNSetExists_FullMethodName = "/sa.StorageAuthority/FQDNSetExists"
StorageAuthority_FQDNSetTimestampsForWindow_FullMethodName = "/sa.StorageAuthority/FQDNSetTimestampsForWindow"
@ -1249,9 +1171,7 @@ const (
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type StorageAuthorityClient interface {
// Getters: this list must be identical to the StorageAuthorityReadOnly rpcs.
CountCertificatesByNames(ctx context.Context, in *CountCertificatesByNamesRequest, opts ...grpc.CallOption) (*CountByNames, error)
CountInvalidAuthorizations2(ctx context.Context, in *CountInvalidAuthorizationsRequest, opts ...grpc.CallOption) (*Count, error)
CountOrders(ctx context.Context, in *CountOrdersRequest, opts ...grpc.CallOption) (*Count, error)
CountPendingAuthorizations2(ctx context.Context, in *RegistrationID, opts ...grpc.CallOption) (*Count, error)
FQDNSetExists(ctx context.Context, in *FQDNSetExistsRequest, opts ...grpc.CallOption) (*Exists, error)
FQDNSetTimestampsForWindow(ctx context.Context, in *CountFQDNSetsRequest, opts ...grpc.CallOption) (*Timestamps, error)
@ -1311,16 +1231,6 @@ func NewStorageAuthorityClient(cc grpc.ClientConnInterface) StorageAuthorityClie
return &storageAuthorityClient{cc}
}
func (c *storageAuthorityClient) CountCertificatesByNames(ctx context.Context, in *CountCertificatesByNamesRequest, opts ...grpc.CallOption) (*CountByNames, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CountByNames)
err := c.cc.Invoke(ctx, StorageAuthority_CountCertificatesByNames_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *storageAuthorityClient) CountInvalidAuthorizations2(ctx context.Context, in *CountInvalidAuthorizationsRequest, opts ...grpc.CallOption) (*Count, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Count)
@ -1331,16 +1241,6 @@ func (c *storageAuthorityClient) CountInvalidAuthorizations2(ctx context.Context
return out, nil
}
func (c *storageAuthorityClient) CountOrders(ctx context.Context, in *CountOrdersRequest, opts ...grpc.CallOption) (*Count, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Count)
err := c.cc.Invoke(ctx, StorageAuthority_CountOrders_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *storageAuthorityClient) CountPendingAuthorizations2(ctx context.Context, in *RegistrationID, opts ...grpc.CallOption) (*Count, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Count)
@ -1862,9 +1762,7 @@ func (c *storageAuthorityClient) UnpauseAccount(ctx context.Context, in *Registr
// for forward compatibility
type StorageAuthorityServer interface {
// Getters: this list must be identical to the StorageAuthorityReadOnly rpcs.
CountCertificatesByNames(context.Context, *CountCertificatesByNamesRequest) (*CountByNames, error)
CountInvalidAuthorizations2(context.Context, *CountInvalidAuthorizationsRequest) (*Count, error)
CountOrders(context.Context, *CountOrdersRequest) (*Count, error)
CountPendingAuthorizations2(context.Context, *RegistrationID) (*Count, error)
FQDNSetExists(context.Context, *FQDNSetExistsRequest) (*Exists, error)
FQDNSetTimestampsForWindow(context.Context, *CountFQDNSetsRequest) (*Timestamps, error)
@ -1921,15 +1819,9 @@ type StorageAuthorityServer interface {
type UnimplementedStorageAuthorityServer struct {
}
func (UnimplementedStorageAuthorityServer) CountCertificatesByNames(context.Context, *CountCertificatesByNamesRequest) (*CountByNames, error) {
return nil, status.Errorf(codes.Unimplemented, "method CountCertificatesByNames not implemented")
}
func (UnimplementedStorageAuthorityServer) CountInvalidAuthorizations2(context.Context, *CountInvalidAuthorizationsRequest) (*Count, error) {
return nil, status.Errorf(codes.Unimplemented, "method CountInvalidAuthorizations2 not implemented")
}
func (UnimplementedStorageAuthorityServer) CountOrders(context.Context, *CountOrdersRequest) (*Count, error) {
return nil, status.Errorf(codes.Unimplemented, "method CountOrders not implemented")
}
func (UnimplementedStorageAuthorityServer) CountPendingAuthorizations2(context.Context, *RegistrationID) (*Count, error) {
return nil, status.Errorf(codes.Unimplemented, "method CountPendingAuthorizations2 not implemented")
}
@ -2087,24 +1979,6 @@ func RegisterStorageAuthorityServer(s grpc.ServiceRegistrar, srv StorageAuthorit
s.RegisterService(&StorageAuthority_ServiceDesc, srv)
}
func _StorageAuthority_CountCertificatesByNames_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CountCertificatesByNamesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StorageAuthorityServer).CountCertificatesByNames(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StorageAuthority_CountCertificatesByNames_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StorageAuthorityServer).CountCertificatesByNames(ctx, req.(*CountCertificatesByNamesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StorageAuthority_CountInvalidAuthorizations2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CountInvalidAuthorizationsRequest)
if err := dec(in); err != nil {
@ -2123,24 +1997,6 @@ func _StorageAuthority_CountInvalidAuthorizations2_Handler(srv interface{}, ctx
return interceptor(ctx, in, info, handler)
}
func _StorageAuthority_CountOrders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CountOrdersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StorageAuthorityServer).CountOrders(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StorageAuthority_CountOrders_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StorageAuthorityServer).CountOrders(ctx, req.(*CountOrdersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StorageAuthority_CountPendingAuthorizations2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RegistrationID)
if err := dec(in); err != nil {
@ -2984,18 +2840,10 @@ var StorageAuthority_ServiceDesc = grpc.ServiceDesc{
ServiceName: "sa.StorageAuthority",
HandlerType: (*StorageAuthorityServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CountCertificatesByNames",
Handler: _StorageAuthority_CountCertificatesByNames_Handler,
},
{
MethodName: "CountInvalidAuthorizations2",
Handler: _StorageAuthority_CountInvalidAuthorizations2_Handler,
},
{
MethodName: "CountOrders",
Handler: _StorageAuthority_CountOrders_Handler,
},
{
MethodName: "CountPendingAuthorizations2",
Handler: _StorageAuthority_CountPendingAuthorizations2_Handler,

View File

@ -1,146 +0,0 @@
package sa
import (
"context"
"strings"
"time"
"github.com/letsencrypt/boulder/db"
sapb "github.com/letsencrypt/boulder/sa/proto"
"github.com/weppos/publicsuffix-go/publicsuffix"
)
// baseDomain returns the eTLD+1 of a domain name for the purpose of rate
// limiting. For a domain name that is itself an eTLD, it returns its input.
func baseDomain(name string) string {
eTLDPlusOne, err := publicsuffix.Domain(name)
if err != nil {
// publicsuffix.Domain will return an error if the input name is itself a
// public suffix. In that case we use the input name as the key for rate
// limiting. Since all of its subdomains will have separate keys for rate
// limiting (e.g. "foo.bar.publicsuffix.com" will have
// "bar.publicsuffix.com", this means that domains exactly equal to a
// public suffix get their own rate limit bucket. This is important
// because otherwise they might be perpetually unable to issue, assuming
// the rate of issuance from their subdomains was high enough.
return name
}
return eTLDPlusOne
}
// addCertificatesPerName adds 1 to the rate limit count for the provided
// domains, in a specific time bucket. It must be executed in a transaction, and
// the input timeToTheHour must be a time rounded to an hour.
func (ssa *SQLStorageAuthority) addCertificatesPerName(ctx context.Context, db db.SelectExecer, names []string, timeToTheHour time.Time) error {
// De-duplicate the base domains.
baseDomainsMap := make(map[string]bool)
var qmarks []string
var values []interface{}
for _, name := range names {
base := baseDomain(name)
if !baseDomainsMap[base] {
baseDomainsMap[base] = true
values = append(values, base, timeToTheHour, 1)
qmarks = append(qmarks, "(?, ?, ?)")
}
}
_, err := db.ExecContext(ctx, `INSERT INTO certificatesPerName (eTLDPlusOne, time, count) VALUES `+
strings.Join(qmarks, ", ")+` ON DUPLICATE KEY UPDATE count=count+1;`,
values...)
if err != nil {
return err
}
return nil
}
// countCertificates returns the count of certificates issued for a domain's
// eTLD+1 (aka base domain), during a given time range.
func (ssa *SQLStorageAuthorityRO) countCertificates(ctx context.Context, dbMap db.Selector, domain string, timeRange *sapb.Range) (int64, time.Time, error) {
latest := timeRange.Latest.AsTime()
var results []struct {
Count int64
Time time.Time
}
_, err := dbMap.Select(
ctx,
&results,
`SELECT count, time FROM certificatesPerName
WHERE eTLDPlusOne = :baseDomain AND
time > :earliest AND
time <= :latest`,
map[string]interface{}{
"baseDomain": baseDomain(domain),
"earliest": timeRange.Earliest.AsTime(),
"latest": latest,
})
if err != nil {
if db.IsNoRows(err) {
return 0, time.Time{}, nil
}
return 0, time.Time{}, err
}
// Set earliest to the latest possible time, so that we can find the
// earliest certificate in the results.
var earliest = latest
var total int64
for _, r := range results {
total += r.Count
if r.Time.Before(earliest) {
earliest = r.Time
}
}
if total <= 0 && earliest == latest {
// If we didn't find any certificates, return a zero time.
return total, time.Time{}, nil
}
return total, earliest, nil
}
// addNewOrdersRateLimit adds 1 to the rate limit count for the provided ID, in
// a specific time bucket. It must be executed in a transaction, and the input
// timeToTheMinute must be a time rounded to a minute.
func addNewOrdersRateLimit(ctx context.Context, dbMap db.SelectExecer, regID int64, timeToTheMinute time.Time) error {
_, err := dbMap.ExecContext(ctx, `INSERT INTO newOrdersRL
(regID, time, count)
VALUES (?, ?, 1)
ON DUPLICATE KEY UPDATE count=count+1;`,
regID,
timeToTheMinute,
)
if err != nil {
return err
}
return nil
}
// countNewOrders returns the count of orders created in the given time range
// for the given registration ID.
func countNewOrders(ctx context.Context, dbMap db.Selector, req *sapb.CountOrdersRequest) (*sapb.Count, error) {
var counts []int64
_, err := dbMap.Select(
ctx,
&counts,
`SELECT count FROM newOrdersRL
WHERE regID = :regID AND
time > :earliest AND
time <= :latest`,
map[string]interface{}{
"regID": req.AccountID,
"earliest": req.Range.Earliest.AsTime(),
"latest": req.Range.Latest.AsTime(),
},
)
if err != nil {
if db.IsNoRows(err) {
return &sapb.Count{Count: 0}, nil
}
return nil, err
}
var total int64
for _, count := range counts {
total += count
}
return &sapb.Count{Count: total}, nil
}

View File

@ -1,141 +0,0 @@
package sa
import (
"context"
"fmt"
"testing"
"time"
"google.golang.org/protobuf/types/known/timestamppb"
sapb "github.com/letsencrypt/boulder/sa/proto"
"github.com/letsencrypt/boulder/test"
)
func TestCertsPerNameRateLimitTable(t *testing.T) {
ctx := context.Background()
sa, _, cleanUp := initSA(t)
defer cleanUp()
aprilFirst, err := time.Parse(time.RFC3339, "2019-04-01T00:00:00Z")
if err != nil {
t.Fatal(err)
}
type inputCase struct {
time time.Time
names []string
}
inputs := []inputCase{
{aprilFirst, []string{"example.com"}},
{aprilFirst, []string{"example.com", "www.example.com"}},
{aprilFirst, []string{"example.com", "other.example.com"}},
{aprilFirst, []string{"dyndns.org"}},
{aprilFirst, []string{"mydomain.dyndns.org"}},
{aprilFirst, []string{"mydomain.dyndns.org"}},
{aprilFirst, []string{"otherdomain.dyndns.org"}},
}
// For each hour in a week, add an entry for a certificate that has
// progressively more names.
var manyNames []string
for i := range 7 * 24 {
manyNames = append(manyNames, fmt.Sprintf("%d.manynames.example.net", i))
inputs = append(inputs, inputCase{aprilFirst.Add(time.Duration(i) * time.Hour), manyNames})
}
for _, input := range inputs {
tx, err := sa.dbMap.BeginTx(ctx)
if err != nil {
t.Fatal(err)
}
err = sa.addCertificatesPerName(ctx, tx, input.names, input.time)
if err != nil {
t.Fatal(err)
}
err = tx.Commit()
if err != nil {
t.Fatal(err)
}
}
const aWeek = time.Duration(7*24) * time.Hour
testCases := []struct {
caseName string
domainName string
expected int64
}{
{"name doesn't exist", "non.example.org", 0},
{"base name gets dinged for all certs including it", "example.com", 3},
{"subdomain gets dinged for neighbors", "www.example.com", 3},
{"other subdomain", "other.example.com", 3},
{"many subdomains", "1.manynames.example.net", 168},
{"public suffix gets its own bucket", "dyndns.org", 1},
{"subdomain of public suffix gets its own bucket", "mydomain.dyndns.org", 2},
{"subdomain of public suffix gets its own bucket 2", "otherdomain.dyndns.org", 1},
}
for _, tc := range testCases {
t.Run(tc.caseName, func(t *testing.T) {
timeRange := &sapb.Range{
Earliest: timestamppb.New(aprilFirst.Add(-1 * time.Second)),
Latest: timestamppb.New(aprilFirst.Add(aWeek)),
}
count, earliest, err := sa.countCertificatesByName(ctx, sa.dbMap, tc.domainName, timeRange)
if err != nil {
t.Fatal(err)
}
if count != tc.expected {
t.Errorf("Expected count of %d for %q, got %d", tc.expected, tc.domainName, count)
}
if earliest.IsZero() {
// The count should always be zero if earliest is nil.
test.AssertEquals(t, count, int64(0))
} else {
test.AssertEquals(t, earliest, aprilFirst)
}
})
}
}
func TestNewOrdersRateLimitTable(t *testing.T) {
sa, _, cleanUp := initSA(t)
defer cleanUp()
manyCountRegID := int64(2)
start := time.Now().Truncate(time.Minute)
req := &sapb.CountOrdersRequest{
AccountID: 1,
Range: &sapb.Range{
Earliest: timestamppb.New(start),
Latest: timestamppb.New(start.Add(time.Minute * 10)),
},
}
for i := 0; i <= 10; i++ {
tx, err := sa.dbMap.BeginTx(ctx)
test.AssertNotError(t, err, "failed to open tx")
for j := 0; j < i+1; j++ {
err = addNewOrdersRateLimit(ctx, tx, manyCountRegID, start.Add(time.Minute*time.Duration(i)))
}
test.AssertNotError(t, err, "addNewOrdersRateLimit failed")
test.AssertNotError(t, tx.Commit(), "failed to commit tx")
}
count, err := countNewOrders(ctx, sa.dbMap, req)
test.AssertNotError(t, err, "countNewOrders failed")
test.AssertEquals(t, count.Count, int64(0))
req.AccountID = manyCountRegID
count, err = countNewOrders(ctx, sa.dbMap, req)
test.AssertNotError(t, err, "countNewOrders failed")
test.AssertEquals(t, count.Count, int64(65))
req.Range.Earliest = timestamppb.New(start.Add(time.Minute * 5))
req.Range.Latest = timestamppb.New(start.Add(time.Minute * 10))
count, err = countNewOrders(ctx, sa.dbMap, req)
test.AssertNotError(t, err, "countNewOrders failed")
test.AssertEquals(t, count.Count, int64(45))
}

View File

@ -441,7 +441,7 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.Ad
Expires: parsedCertificate.NotAfter,
}
isRenewalRaw, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
// Select to see if cert exists
var row struct {
Count int64
@ -460,54 +460,19 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.Ad
return nil, err
}
// NOTE(@cpu): When we collect up names to check if an FQDN set exists (e.g.
// that it is a renewal) we use just the DNSNames from the certificate and
// ignore the Subject Common Name (if any). This is a safe assumption because
// if a certificate we issued were to have a Subj. CN not present as a SAN it
// would be a misissuance and miscalculating whether the cert is a renewal or
// not for the purpose of rate limiting is the least of our troubles.
isRenewal, err := ssa.checkFQDNSetExists(
ctx,
tx.SelectOne,
parsedCertificate.DNSNames)
if err != nil {
return nil, err
}
return isRenewal, err
return nil, err
})
if overallError != nil {
return nil, overallError
}
// Recast the interface{} return from db.WithTransaction as a bool, returning
// an error if we can't.
var isRenewal bool
if boolVal, ok := isRenewalRaw.(bool); !ok {
return nil, fmt.Errorf(
"AddCertificate db.WithTransaction returned %T out var, expected bool",
isRenewalRaw)
} else {
isRenewal = boolVal
}
// In a separate transaction perform the work required to update tables used
// for rate limits. Since the effects of failing these writes is slight
// miscalculation of rate limits we choose to not fail the AddCertificate
// operation if the rate limit update transaction fails.
_, rlTransactionErr := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
// Add to the rate limit table, but only for new certificates. Renewals
// don't count against the certificatesPerName limit.
if !isRenewal && !features.Get().DisableLegacyLimitWrites {
timeToTheHour := parsedCertificate.NotBefore.Round(time.Hour)
err := ssa.addCertificatesPerName(ctx, tx, parsedCertificate.DNSNames, timeToTheHour)
if err != nil {
return nil, err
}
}
// Update the FQDN sets now that there is a final certificate to ensure rate
// limits are calculated correctly.
// In a separate transaction, perform the work required to update the table
// used for order reuse. Since the effect of failing the write is just a
// missed opportunity to reuse an order, we choose to not fail the
// AddCertificate operation if this update transaction fails.
_, fqdnTransactionErr := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
// Update the FQDN sets now that there is a final certificate to ensure
// reuse is determined correctly.
err = addFQDNSet(
ctx,
tx,
@ -522,11 +487,11 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.Ad
return nil, nil
})
// If the ratelimit transaction failed increment a stat and log a warning
// If the FQDN sets transaction failed, increment a stat and log a warning
// but don't return an error from AddCertificate.
if rlTransactionErr != nil {
if fqdnTransactionErr != nil {
ssa.rateLimitWriteErrors.Inc()
ssa.log.AuditErrf("failed AddCertificate ratelimit update transaction: %v", rlTransactionErr)
ssa.log.AuditErrf("failed AddCertificate FQDN sets insert transaction: %v", fqdnTransactionErr)
}
return &emptypb.Empty{}, nil
@ -746,14 +711,6 @@ func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb
return nil, fmt.Errorf("casting error in NewOrderAndAuthzs")
}
if !features.Get().DisableLegacyLimitWrites {
// Increment the order creation count
err = addNewOrdersRateLimit(ctx, ssa.dbMap, req.NewOrder.RegistrationID, ssa.clk.Now().Truncate(time.Minute))
if err != nil {
return nil, err
}
}
return order, nil
}

View File

@ -6,7 +6,6 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"database/sql"
@ -23,7 +22,6 @@ import (
"reflect"
"slices"
"strings"
"sync"
"testing"
"time"
@ -616,138 +614,6 @@ func TestAddCertificateDuplicate(t *testing.T) {
}
func TestCountCertificatesByNamesTimeRange(t *testing.T) {
sa, clk, cleanUp := initSA(t)
defer cleanUp()
reg := createWorkingRegistration(t, sa)
_, testCert := test.ThrowAwayCert(t, clk)
_, err := sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
Der: testCert.Raw,
RegID: reg.Id,
Issued: timestamppb.New(testCert.NotBefore),
})
test.AssertNotError(t, err, "Couldn't add test cert")
name := testCert.DNSNames[0]
// Move time forward, so the cert was issued slightly in the past.
clk.Add(time.Hour)
now := clk.Now()
yesterday := clk.Now().Add(-24 * time.Hour)
twoDaysAgo := clk.Now().Add(-48 * time.Hour)
tomorrow := clk.Now().Add(24 * time.Hour)
// Count for a name that doesn't have any certs
counts, err := sa.CountCertificatesByNames(ctx, &sapb.CountCertificatesByNamesRequest{
DnsNames: []string{"does.not.exist"},
Range: &sapb.Range{
Earliest: timestamppb.New(yesterday),
Latest: timestamppb.New(now),
},
})
test.AssertNotError(t, err, "Error counting certs.")
test.AssertEquals(t, len(counts.Counts), 1)
test.AssertEquals(t, counts.Counts["does.not.exist"], int64(0))
// Time range including now should find the cert.
counts, err = sa.CountCertificatesByNames(ctx, &sapb.CountCertificatesByNamesRequest{
DnsNames: testCert.DNSNames,
Range: &sapb.Range{
Earliest: timestamppb.New(yesterday),
Latest: timestamppb.New(now),
},
})
test.AssertNotError(t, err, "sa.CountCertificatesByName failed")
test.AssertEquals(t, len(counts.Counts), 1)
test.AssertEquals(t, counts.Counts[name], int64(1))
// Time range between two days ago and yesterday should not find the cert.
counts, err = sa.CountCertificatesByNames(ctx, &sapb.CountCertificatesByNamesRequest{
DnsNames: testCert.DNSNames,
Range: &sapb.Range{
Earliest: timestamppb.New(twoDaysAgo),
Latest: timestamppb.New(yesterday),
},
})
test.AssertNotError(t, err, "Error counting certs.")
test.AssertEquals(t, len(counts.Counts), 1)
test.AssertEquals(t, counts.Counts[name], int64(0))
// Time range between now and tomorrow also should not (time ranges are
// inclusive at the tail end, but not the beginning end).
counts, err = sa.CountCertificatesByNames(ctx, &sapb.CountCertificatesByNamesRequest{
DnsNames: testCert.DNSNames,
Range: &sapb.Range{
Earliest: timestamppb.New(now),
Latest: timestamppb.New(tomorrow),
},
})
test.AssertNotError(t, err, "Error counting certs.")
test.AssertEquals(t, len(counts.Counts), 1)
test.AssertEquals(t, counts.Counts[name], int64(0))
}
func TestCountCertificatesByNamesParallel(t *testing.T) {
sa, clk, cleanUp := initSA(t)
defer cleanUp()
// Create two certs with different names and add them both to the database.
reg := createWorkingRegistration(t, sa)
_, testCert := test.ThrowAwayCert(t, clk)
_, err := sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
Der: testCert.Raw,
RegID: reg.Id,
Issued: timestamppb.New(testCert.NotBefore),
})
test.AssertNotError(t, err, "Couldn't add test cert")
_, testCert2 := test.ThrowAwayCert(t, clk)
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
Der: testCert2.Raw,
RegID: reg.Id,
Issued: timestamppb.New(testCert2.NotBefore),
})
test.AssertNotError(t, err, "Couldn't add test cert")
// Override countCertificatesByName with an implementation of certCountFunc
// that will block forever if it's called in serial, but will succeed if
// called in parallel.
names := []string{"does.not.exist", testCert.DNSNames[0], testCert2.DNSNames[0]}
var interlocker sync.WaitGroup
interlocker.Add(len(names))
sa.parallelismPerRPC = len(names)
oldCertCountFunc := sa.countCertificatesByName
sa.countCertificatesByName = func(ctx context.Context, sel db.Selector, domain string, timeRange *sapb.Range) (int64, time.Time, error) {
interlocker.Done()
interlocker.Wait()
return oldCertCountFunc(ctx, sel, domain, timeRange)
}
counts, err := sa.CountCertificatesByNames(ctx, &sapb.CountCertificatesByNamesRequest{
DnsNames: names,
Range: &sapb.Range{
Earliest: timestamppb.New(clk.Now().Add(-time.Hour)),
Latest: timestamppb.New(clk.Now().Add(time.Hour)),
},
})
test.AssertNotError(t, err, "Error counting certs.")
test.AssertEquals(t, len(counts.Counts), 3)
// We expect there to be two of each of the names that do exist, because
// test.ThrowAwayCert creates certs for subdomains of example.com, and
// CountCertificatesByNames counts all certs under the same registered domain.
expected := map[string]int64{
"does.not.exist": 0,
testCert.DNSNames[0]: 2,
testCert2.DNSNames[0]: 2,
}
for name, count := range expected {
test.AssertEquals(t, count, counts.Counts[name])
}
}
func TestFQDNSetTimestampsForWindow(t *testing.T) {
sa, fc, cleanUp := initSA(t)
defer cleanUp()
@ -1632,58 +1498,6 @@ func TestGetAuthorizations2(t *testing.T) {
test.AssertEquals(t, len(authz.Authzs), 2)
}
func TestCountOrders(t *testing.T) {
sa, _, cleanUp := initSA(t)
defer cleanUp()
reg := createWorkingRegistration(t, sa)
now := sa.clk.Now()
expires := now.Add(24 * time.Hour)
req := &sapb.CountOrdersRequest{
AccountID: 12345,
Range: &sapb.Range{
Earliest: timestamppb.New(now.Add(-time.Hour)),
Latest: timestamppb.New(now.Add(time.Second)),
},
}
// Counting new orders for a reg ID that doesn't exist should return 0
count, err := sa.CountOrders(ctx, req)
test.AssertNotError(t, err, "Couldn't count new orders for fake reg ID")
test.AssertEquals(t, count.Count, int64(0))
// Add a pending authorization
authzID := createPendingAuthorization(t, sa, "example.com", expires)
// Add one pending order
order, err := sa.NewOrderAndAuthzs(ctx, &sapb.NewOrderAndAuthzsRequest{
NewOrder: &sapb.NewOrderRequest{
RegistrationID: reg.Id,
Expires: timestamppb.New(expires),
DnsNames: []string{"example.com"},
V2Authorizations: []int64{authzID},
},
})
test.AssertNotError(t, err, "Couldn't create new pending order")
// Counting new orders for the reg ID should now yield 1
req.AccountID = reg.Id
count, err = sa.CountOrders(ctx, req)
test.AssertNotError(t, err, "Couldn't count new orders for reg ID")
test.AssertEquals(t, count.Count, int64(1))
// Moving the count window to after the order was created should return the
// count to 0
earliest := order.Created.AsTime().Add(time.Minute)
latest := earliest.Add(time.Hour)
req.Range.Earliest = timestamppb.New(earliest)
req.Range.Latest = timestamppb.New(latest)
count, err = sa.CountOrders(ctx, req)
test.AssertNotError(t, err, "Couldn't count new orders for reg ID")
test.AssertEquals(t, count.Count, int64(0))
}
func TestFasterGetOrderForNames(t *testing.T) {
sa, fc, cleanUp := initSA(t)
defer cleanUp()
@ -2437,104 +2251,6 @@ func TestAddCertificateRenewalBit(t *testing.T) {
}
}
func TestCountCertificatesRenewalBit(t *testing.T) {
sa, fc, cleanUp := initSA(t)
defer cleanUp()
// Create a test registration
reg := createWorkingRegistration(t, sa)
// Create a small throw away key for the test certificates.
testKey, err := rsa.GenerateKey(rand.Reader, 512)
test.AssertNotError(t, err, "error generating test key")
// Create an initial test certificate for a set of domain names, issued an
// hour ago.
template := &x509.Certificate{
SerialNumber: big.NewInt(1337),
DNSNames: []string{"www.not-example.com", "not-example.com", "admin.not-example.com"},
NotBefore: fc.Now().Add(-time.Hour),
BasicConstraintsValid: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
}
certADER, err := x509.CreateCertificate(rand.Reader, template, template, testKey.Public(), testKey)
test.AssertNotError(t, err, "Failed to create test cert A")
certA, _ := x509.ParseCertificate(certADER)
// Update the template with a new serial number and a not before of now and
// create a second test cert for the same names. This will be a renewal.
template.SerialNumber = big.NewInt(7331)
template.NotBefore = fc.Now()
certBDER, err := x509.CreateCertificate(rand.Reader, template, template, testKey.Public(), testKey)
test.AssertNotError(t, err, "Failed to create test cert B")
certB, _ := x509.ParseCertificate(certBDER)
// Update the template with a third serial number and a partially overlapping
// set of names. This will not be a renewal but will help test the exact name
// counts.
template.SerialNumber = big.NewInt(0xC0FFEE)
template.DNSNames = []string{"www.not-example.com"}
certCDER, err := x509.CreateCertificate(rand.Reader, template, template, testKey.Public(), testKey)
test.AssertNotError(t, err, "Failed to create test cert C")
countName := func(t *testing.T, expectedName string) int64 {
earliest := fc.Now().Add(-5 * time.Hour)
latest := fc.Now().Add(5 * time.Hour)
req := &sapb.CountCertificatesByNamesRequest{
DnsNames: []string{expectedName},
Range: &sapb.Range{
Earliest: timestamppb.New(earliest),
Latest: timestamppb.New(latest),
},
}
counts, err := sa.CountCertificatesByNames(context.Background(), req)
test.AssertNotError(t, err, "Unexpected err from CountCertificatesByNames")
for name, count := range counts.Counts {
if name == expectedName {
return count
}
}
return 0
}
// Add the first certificate - it won't be considered a renewal.
issued := certA.NotBefore
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
Der: certADER,
RegID: reg.Id,
Issued: timestamppb.New(issued),
})
test.AssertNotError(t, err, "Failed to add CertA test certificate")
// The count for the base domain should be 1 - just certA has been added.
test.AssertEquals(t, countName(t, "not-example.com"), int64(1))
// Add the second certificate - it should be considered a renewal
issued = certB.NotBefore
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
Der: certBDER,
RegID: reg.Id,
Issued: timestamppb.New(issued),
})
test.AssertNotError(t, err, "Failed to add CertB test certificate")
// The count for the base domain should still be 1, just certA. CertB should
// be ignored.
test.AssertEquals(t, countName(t, "not-example.com"), int64(1))
// Add the third certificate - it should not be considered a renewal
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
Der: certCDER,
RegID: reg.Id,
Issued: timestamppb.New(issued),
})
test.AssertNotError(t, err, "Failed to add CertC test certificate")
// The count for the base domain should be 2 now: certA and certC.
// CertB should be ignored.
test.AssertEquals(t, countName(t, "not-example.com"), int64(2))
}
func TestFinalizeAuthorization2(t *testing.T) {
sa, fc, cleanUp := initSA(t)
defer cleanUp()

View File

@ -9,7 +9,6 @@ import (
"net"
"regexp"
"strings"
"sync"
"time"
"github.com/go-jose/go-jose/v4"
@ -34,8 +33,6 @@ var (
validIncidentTableRegexp = regexp.MustCompile(`^incident_[0-9a-zA-Z_]{1,100}$`)
)
type certCountFunc func(ctx context.Context, db db.Selector, domain string, timeRange *sapb.Range) (int64, time.Time, error)
// SQLStorageAuthorityRO defines a read-only subset of a Storage Authority
type SQLStorageAuthorityRO struct {
sapb.UnsafeStorageAuthorityReadOnlyServer
@ -57,10 +54,6 @@ type SQLStorageAuthorityRO struct {
// as, the observed database replication lag.
lagFactor time.Duration
// We use function types here so we can mock out this internal function in
// unittests.
countCertificatesByName certCountFunc
clk clock.Clock
log blog.Logger
@ -101,8 +94,6 @@ func NewSQLStorageAuthorityRO(
lagFactorCounter: lagFactorCounter,
}
ssaro.countCertificatesByName = ssaro.countCertificates
return ssaro, nil
}
@ -212,87 +203,6 @@ func ipRange(ip net.IP) (net.IP, net.IP) {
return begin, end
}
// CountCertificatesByNames counts, for each input domain, the number of
// certificates issued in the given time range for that domain and its
// subdomains. It returns a map from domains to counts and a timestamp. The map
// of domains to counts is guaranteed to contain an entry for each input domain,
// so long as err is nil. The timestamp is the earliest time a certificate was
// issued for any of the domains during the provided range of time. Queries will
// be run in parallel. If any of them error, only one error will be returned.
func (ssa *SQLStorageAuthorityRO) CountCertificatesByNames(ctx context.Context, req *sapb.CountCertificatesByNamesRequest) (*sapb.CountByNames, error) {
if core.IsAnyNilOrZero(req.DnsNames, req.Range.Earliest, req.Range.Latest) {
return nil, errIncompleteRequest
}
work := make(chan string, len(req.DnsNames))
type result struct {
err error
count int64
earliest time.Time
domain string
}
results := make(chan result, len(req.DnsNames))
for _, domain := range req.DnsNames {
work <- domain
}
close(work)
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// We may perform up to 100 queries, depending on what's in the certificate
// request. Parallelize them so we don't hit our timeout, but limit the
// parallelism so we don't consume too many threads on the database.
for range ssa.parallelismPerRPC {
wg.Add(1)
go func() {
defer wg.Done()
for domain := range work {
select {
case <-ctx.Done():
results <- result{err: ctx.Err()}
return
default:
}
count, earliest, err := ssa.countCertificatesByName(ctx, ssa.dbReadOnlyMap, domain, req.Range)
if err != nil {
results <- result{err: err}
// Skip any further work
cancel()
return
}
results <- result{
count: count,
earliest: earliest,
domain: domain,
}
}
}()
}
wg.Wait()
close(results)
// Set earliest to the latest possible time, so that we can find the
// earliest certificate in the results.
earliest := req.Range.Latest
counts := make(map[string]int64)
for r := range results {
if r.err != nil {
return nil, r.err
}
counts[r.domain] = r.count
if !r.earliest.IsZero() && r.earliest.Before(earliest.AsTime()) {
earliest = timestamppb.New(r.earliest)
}
}
// If we didn't find any certificates in the range, earliest should be set
// to a zero value.
if len(counts) == 0 {
earliest = &timestamppb.Timestamp{}
}
return &sapb.CountByNames{Counts: counts, Earliest: earliest}, nil
}
func ReverseName(domain string) string {
labels := strings.Split(domain, ".")
for i, j := 0, len(labels)-1; i < j; i, j = i+1, j-1 {
@ -422,14 +332,6 @@ func (ssa *SQLStorageAuthorityRO) GetRevocationStatus(ctx context.Context, req *
return status, nil
}
func (ssa *SQLStorageAuthorityRO) CountOrders(ctx context.Context, req *sapb.CountOrdersRequest) (*sapb.Count, error) {
if core.IsAnyNilOrZero(req.AccountID, req.Range.Earliest, req.Range.Latest) {
return nil, errIncompleteRequest
}
return countNewOrders(ctx, ssa.dbReadOnlyMap, req)
}
// FQDNSetTimestampsForWindow returns the issuance timestamps for each
// certificate, issued for a set of domains, during a given window of time,
// starting from the most recent issuance.

View File

@ -1,6 +1,5 @@
{
"ra": {
"rateLimitPoliciesFilename": "test/rate-limit-policies.yml",
"limiter": {
"redis": {
"username": "boulder-wfe",
@ -127,7 +126,6 @@
},
"features": {
"AsyncFinalize": true,
"UseKvLimitsForNewOrder": true,
"AutomaticallyPauseZombieClients": true,
"NoPendingAuthzReuse": true,
"EnforceMPIC": true

View File

@ -50,7 +50,6 @@
"healthCheckInterval": "4s",
"features": {
"MultipleCertificateProfiles": true,
"DisableLegacyLimitWrites": true,
"InsertAuthzsIndividually": true
}
},

View File

@ -127,8 +127,7 @@
"features": {
"PropagateCancels": true,
"ServeRenewalInfo": true,
"CheckIdentifiersPaused": true,
"UseKvLimitsForNewOrder": true
"CheckIdentifiersPaused": true
},
"certProfiles": {
"legacy": "The normal profile you know and love",

View File

@ -1,6 +1,30 @@
{
"ra": {
"rateLimitPoliciesFilename": "test/rate-limit-policies.yml",
"limiter": {
"redis": {
"username": "boulder-wfe",
"passwordFile": "test/secrets/wfe_ratelimits_redis_password",
"lookups": [
{
"Service": "redisratelimits",
"Domain": "service.consul"
}
],
"lookupDNSAuthority": "consul.service.consul",
"readTimeout": "250ms",
"writeTimeout": "250ms",
"poolSize": 100,
"routeRandomly": true,
"tls": {
"caCertFile": "test/certs/ipki/minica.pem",
"certFile": "test/certs/ipki/wfe.boulder/cert.pem",
"keyFile": "test/certs/ipki/wfe.boulder/key.pem"
}
},
"Defaults": "test/config/wfe2-ratelimit-defaults.yml",
"Overrides": "test/config/wfe2-ratelimit-overrides.yml"
},
"maxContactsPerRegistration": 3,
"debugAddr": ":8002",
"hostnamePolicyFile": "test/hostname-policy.yaml",
@ -105,6 +129,7 @@
}
},
"features": {
"UseKvLimitsForNewOrder": true,
"IncrementRateLimits": true
},
"ctLogs": {

View File

@ -46,6 +46,9 @@
]
}
}
},
"features": {
"UseKvLimitsForNewOrder": true
}
},
"syslog": {

View File

@ -0,0 +1,36 @@
NewRegistrationsPerIPAddress:
count: 10000
burst: 10000
period: 168h
NewRegistrationsPerIPv6Range:
count: 99999
burst: 99999
period: 168h
CertificatesPerDomain:
count: 2
burst: 2
period: 2160h
FailedAuthorizationsPerDomainPerAccount:
count: 3
burst: 3
period: 5m
# The burst represents failing 40 times per day for 90 days. The count and
# period grant one "freebie" failure per day. In combination, these parameters
# mean that:
# - Failing 120 times per day results in being paused after 30.25 days
# - Failing 40 times per day results in being paused after 92.3 days
# - Failing 20 times per day results in being paused after 6.2 months
# - Failing 4 times per day results in being paused after 3.3 years
# - Failing once per day results in never being paused
FailedAuthorizationsForPausingPerDomainPerAccount:
count: 1
burst: 3600
period: 24h
NewOrdersPerAccount:
count: 1500
burst: 1500
period: 3h
CertificatesPerFQDNSet:
count: 2
burst: 2
period: 3h

View File

@ -0,0 +1,64 @@
- NewRegistrationsPerIPAddress:
burst: 1000000
count: 1000000
period: 168h
ids:
- id: 127.0.0.1
comment: localhost
- id: 10.77.77.77
comment: test
- id: 10.88.88.88
comment: test
- CertificatesPerDomain:
burst: 1
count: 1
period: 2160h
ids:
- id: ratelimit.me
comment: Rate Limit Test Domain
- CertificatesPerDomain:
burst: 10000
count: 10000
period: 2160h
ids:
- id: le.wtf
comment: Let's Encrypt Test Domain
- id: le1.wtf
comment: Let's Encrypt Test Domain 1
- id: le2.wtf
comment: Let's Encrypt Test Domain 2
- id: le3.wtf
comment: Let's Encrypt Test Domain 3
- id: nginx.wtf
comment: Nginx Test Domain
- id: good-caa-reserved.com
comment: Good CAA Reserved Domain
- id: bad-caa-reserved.com
comment: Bad CAA Reserved Domain
- id: ecdsa.le.wtf
comment: ECDSA Let's Encrypt Test Domain
- id: must-staple.le.wtf
comment: Must-Staple Let's Encrypt Test Domain
- CertificatesPerFQDNSet:
burst: 10000
count: 10000
period: 168h
ids:
- id: le.wtf
comment: Let's Encrypt Test Domain
- id: le1.wtf
comment: Let's Encrypt Test Domain 1
- id: le2.wtf
comment: Let's Encrypt Test Domain 2
- id: le3.wtf
comment: Let's Encrypt Test Domain 3
- id: le.wtf,le1.wtf
comment: Let's Encrypt Test Domain, Let's Encrypt Test Domain 1
- id: good-caa-reserved.com
comment: Good CAA Reserved Domain
- id: nginx.wtf
comment: Nginx Test Domain
- id: ecdsa.le.wtf
comment: ECDSA Let's Encrypt Test Domain
- id: must-staple.le.wtf
comment: Must-Staple Let's Encrypt Test Domain

View File

@ -102,7 +102,32 @@
"staleTimeout": "5m",
"authorizationLifetimeDays": 30,
"pendingAuthorizationLifetimeDays": 7,
"limiter": {
"redis": {
"username": "boulder-wfe",
"passwordFile": "test/secrets/wfe_ratelimits_redis_password",
"lookups": [
{
"Service": "redisratelimits",
"Domain": "service.consul"
}
],
"lookupDNSAuthority": "consul.service.consul",
"readTimeout": "250ms",
"writeTimeout": "250ms",
"poolSize": 100,
"routeRandomly": true,
"tls": {
"caCertFile": "test/certs/ipki/minica.pem",
"certFile": "test/certs/ipki/wfe.boulder/cert.pem",
"keyFile": "test/certs/ipki/wfe.boulder/key.pem"
}
},
"Defaults": "test/config/wfe2-ratelimit-defaults.yml",
"Overrides": "test/config/wfe2-ratelimit-overrides.yml"
},
"features": {
"UseKvLimitsForNewOrder": true,
"ServeRenewalInfo": true,
"IncrementRateLimits": true,
"CheckIdentifiersPaused": true

View File

@ -73,10 +73,6 @@ func (sa SA) GetOrderForNames(ctx context.Context, req *sapb.GetOrderForNamesReq
return sa.Impl.GetOrderForNames(ctx, req)
}
func (sa SA) CountOrders(ctx context.Context, req *sapb.CountOrdersRequest, _ ...grpc.CallOption) (*sapb.Count, error) {
return sa.Impl.CountOrders(ctx, req)
}
func (sa SA) SetOrderError(ctx context.Context, req *sapb.SetOrderErrorRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
return sa.Impl.SetOrderError(ctx, req)
}
@ -97,10 +93,6 @@ func (sa SA) AddCertificate(ctx context.Context, req *sapb.AddCertificateRequest
return sa.Impl.AddCertificate(ctx, req)
}
func (sa SA) CountCertificatesByNames(ctx context.Context, req *sapb.CountCertificatesByNamesRequest, _ ...grpc.CallOption) (*sapb.CountByNames, error) {
return sa.Impl.CountCertificatesByNames(ctx, req)
}
func (sa SA) RevokeCertificate(ctx context.Context, req *sapb.RevokeCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
return sa.Impl.RevokeCertificate(ctx, req)
}

View File

@ -6,8 +6,6 @@ import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"strings"
"testing"
"github.com/letsencrypt/boulder/test"
@ -27,13 +25,7 @@ func TestDuplicateFQDNRateLimit(t *testing.T) {
_, err = authAndIssue(nil, nil, []string{domain}, true)
test.AssertError(t, err, "Somehow managed to issue third certificate")
if strings.Contains(os.Getenv("BOULDER_CONFIG_DIR"), "test/config-next") {
// Error should be served from key-value rate limits implementation.
test.AssertContains(t, err.Error(), "too many certificates (2) already issued for this exact set of domains in the last 3h0m0s")
} else {
// Error should be served from legacy rate limits implementation.
test.AssertContains(t, err.Error(), "too many certificates (2) already issued for this exact set of domains in the last 3 hours")
}
test.AssertContains(t, err.Error(), "too many certificates (2) already issued for this exact set of domains in the last 3h0m0s")
}
func TestCertificatesPerDomain(t *testing.T) {
@ -56,13 +48,7 @@ func TestCertificatesPerDomain(t *testing.T) {
_, err = authAndIssue(nil, nil, []string{randomSubDomain()}, true)
test.AssertError(t, err, "Somehow managed to issue third certificate")
if strings.Contains(os.Getenv("BOULDER_CONFIG_DIR"), "test/config-next") {
// Error should be served from key-value rate limits implementation.
test.AssertContains(t, err.Error(), fmt.Sprintf("too many certificates (2) already issued for %q in the last 2160h0m0s", randomDomain))
} else {
// Error should be served from legacy rate limits implementation.
test.AssertContains(t, err.Error(), fmt.Sprintf("too many certificates already issued for %q", randomDomain))
}
test.AssertContains(t, err.Error(), fmt.Sprintf("too many certificates (2) already issued for %q in the last 2160h0m0s", randomDomain))
// Issue a certificate for the first subdomain, which should succeed because
// it's a renewal.

View File

@ -180,7 +180,7 @@ def test_failed_validation_limit():
"""
Fail a challenge repeatedly for the same domain, with the same account. Once
we reach the rate limit we should get a rateLimitedError. Note that this
depends on the specific threshold configured in rate-limit-policies.yml.
depends on the specific threshold configured.
This also incidentally tests a fix for
https://github.com/letsencrypt/boulder/issues/4329. We expect to get
@ -1503,17 +1503,6 @@ def test_renewal_exemption():
chisel2.expect_problem("urn:ietf:params:acme:error:rateLimited",
lambda: chisel2.auth_and_issue(["mail." + base_domain]))
# TODO(#5545) Remove this test once key-value rate limits are authoritative in
# production.
def test_certificates_per_name():
if CONFIG_NEXT:
# This test is replaced by TestCertificatesPerDomain in the Go
# integration tests because key-value rate limits does not support
# override limits of 0.
return
chisel2.expect_problem("urn:ietf:params:acme:error:rateLimited",
lambda: chisel2.auth_and_issue([random_domain() + ".lim.it"]))
def test_oversized_csr():
# Number of names is chosen to be one greater than the configured RA/CA maxNames
numNames = 101

View File

@ -639,11 +639,6 @@ func link(url, relation string) string {
// creation fails, the func will be nil if any error was encountered during the
// check.
func (wfe *WebFrontEndImpl) checkNewAccountLimits(ctx context.Context, ip net.IP) (func(), error) {
if wfe.limiter == nil && wfe.txnBuilder == nil {
// Key-value rate limiting is disabled.
return nil, nil
}
txns, err := wfe.txnBuilder.NewAccountLimitTransactions(ip)
if err != nil {
return nil, fmt.Errorf("building new account limit transactions: %w", err)
@ -795,9 +790,8 @@ func (wfe *WebFrontEndImpl) NewAccount(
}
var newRegistrationSuccessful bool
var errIsRateLimit bool
defer func() {
if !newRegistrationSuccessful && !errIsRateLimit && refundLimits != nil {
if !newRegistrationSuccessful && refundLimits != nil {
go refundLimits()
}
}()
@ -805,15 +799,6 @@ func (wfe *WebFrontEndImpl) NewAccount(
// Send the registration to the RA via grpc
acctPB, err := wfe.ra.NewRegistration(ctx, &reg)
if err != nil {
if errors.Is(err, berrors.RateLimit) {
// Request was denied by a legacy rate limit. In this error case we
// do not want to refund the quota consumed by the request because
// repeated requests would result in unearned refunds.
//
// TODO(#5545): Once key-value rate limits are authoritative this
// can be removed.
errIsRateLimit = true
}
if errors.Is(err, berrors.Duplicate) {
existingAcct, err := wfe.sa.GetRegistrationByKey(ctx, &sapb.JSONWebKey{Jwk: keyBytes})
if err == nil {
@ -2021,11 +2006,6 @@ func (wfe *WebFrontEndImpl) orderToOrderJSON(request *http.Request, order *corep
// function is returned that can be used to refund the quota if the order is not
// created, the func will be nil if any error was encountered during the check.
func (wfe *WebFrontEndImpl) checkNewOrderLimits(ctx context.Context, regId int64, names []string, isRenewal bool) (func(), error) {
if wfe.limiter == nil && wfe.txnBuilder == nil {
// Key-value rate limiting is disabled.
return nil, nil
}
txns, err := wfe.txnBuilder.NewOrderLimitTransactions(regId, names, isRenewal)
if err != nil {
return nil, fmt.Errorf("building new order limit transactions: %w", err)
@ -2368,7 +2348,7 @@ func (wfe *WebFrontEndImpl) NewOrder(
refundLimits := func() {}
if !isARIRenewal {
refundLimits, err = wfe.checkNewOrderLimits(ctx, acct.ID, names, isRenewal || isARIRenewal)
if err != nil && features.Get().UseKvLimitsForNewOrder {
if err != nil {
if errors.Is(err, berrors.RateLimit) {
wfe.sendError(response, logEvent, probs.RateLimited(err.Error()), err)
return
@ -2380,14 +2360,13 @@ func (wfe *WebFrontEndImpl) NewOrder(
}
var newOrderSuccessful bool
var errIsRateLimit bool
defer func() {
wfe.stats.ariReplacementOrders.With(prometheus.Labels{
"isReplacement": fmt.Sprintf("%t", replaces != ""),
"limitsExempt": fmt.Sprintf("%t", isARIRenewal),
}).Inc()
if !newOrderSuccessful && !errIsRateLimit && refundLimits != nil {
if !newOrderSuccessful && refundLimits != nil {
go refundLimits()
}
}()
@ -2402,15 +2381,6 @@ func (wfe *WebFrontEndImpl) NewOrder(
})
if err != nil || core.IsAnyNilOrZero(order, order.Id, order.RegistrationID, order.DnsNames, order.Created, order.Expires) {
wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Error creating new order"), err)
if errors.Is(err, berrors.RateLimit) {
// Request was denied by a legacy rate limit. In this error case we
// do not want to refund the quota consumed by the request because
// repeated requests would result in unearned refunds.
//
// TODO(#5545): Once key-value rate limits are authoritative this
// can be removed.
errIsRateLimit = true
}
return
}
logEvent.Created = fmt.Sprintf("%d", order.Id)

View File

@ -4108,9 +4108,6 @@ func TestCountNewOrderWithReplaces(t *testing.T) {
func TestNewOrderRateLimits(t *testing.T) {
wfe, fc, signer := setupWFE(t)
features.Set(features.Config{UseKvLimitsForNewOrder: true})
defer features.Reset()
// Set the default ratelimits to only allow one new order per account per 24
// hours.
txnBuilder, err := ratelimits.NewTransactionBuilder(ratelimits.LimitConfigs{