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:
parent
f37c36205c
commit
e4668b4ca7
|
@ -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{
|
||||
|
|
|
@ -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`",
|
||||
|
|
|
@ -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
|
||||
|
|
10
mocks/sa.go
10
mocks/sa.go
|
@ -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
378
ra/ra.go
|
@ -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
|
||||
|
|
360
ra/ra_test.go
360
ra/ra_test.go
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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;
|
|
@ -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';
|
||||
|
|
2227
sa/proto/sa.pb.go
2227
sa/proto/sa.pb.go
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
67
sa/sa.go
67
sa/sa.go
|
@ -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
|
||||
}
|
||||
|
||||
|
|
284
sa/sa_test.go
284
sa/sa_test.go
|
@ -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()
|
||||
|
|
98
sa/saro.go
98
sa/saro.go
|
@ -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 = ×tamppb.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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -50,7 +50,6 @@
|
|||
"healthCheckInterval": "4s",
|
||||
"features": {
|
||||
"MultipleCertificateProfiles": true,
|
||||
"DisableLegacyLimitWrites": true,
|
||||
"InsertAuthzsIndividually": true
|
||||
}
|
||||
},
|
||||
|
|
|
@ -127,8 +127,7 @@
|
|||
"features": {
|
||||
"PropagateCancels": true,
|
||||
"ServeRenewalInfo": true,
|
||||
"CheckIdentifiersPaused": true,
|
||||
"UseKvLimitsForNewOrder": true
|
||||
"CheckIdentifiersPaused": true
|
||||
},
|
||||
"certProfiles": {
|
||||
"legacy": "The normal profile you know and love",
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -46,6 +46,9 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"UseKvLimitsForNewOrder": true
|
||||
}
|
||||
},
|
||||
"syslog": {
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
36
wfe2/wfe.go
36
wfe2/wfe.go
|
@ -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, ®)
|
||||
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)
|
||||
|
|
|
@ -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{
|
||||
|
|
Loading…
Reference in New Issue