ratelimits: Fix transaction building for Failed Authorizations Limit (#7344)

- Update the failed authorizations limit to use 'enum:regId:domain' for
transactions while maintaining 'enum:regId' for overrides.
- Modify the failed authorizations transaction builder to generate a
transaction for each order name.
- Rename the `FailedAuthorizationsPerAccount` enum to
`FailedAuthorizationsPerDomainPerAccount` to align with its corrected
implementation. This change is possible because the limit isn't yet
deployed in staging or production.

Blocks #7346
Part of #5545
This commit is contained in:
Samantha 2024-03-06 13:48:32 -05:00 committed by GitHub
parent 51231a3942
commit 529157ce56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 106 additions and 43 deletions

View File

@ -235,39 +235,84 @@ func (builder *TransactionBuilder) OrdersPerAccountTransaction(regId int64) (Tra
return newTransaction(limit, bucketKey, 1) return newTransaction(limit, bucketKey, 1)
} }
// FailedAuthorizationsPerAccountCheckOnlyTransaction returns a check-only // FailedAuthorizationsPerDomainPerAccountCheckOnlyTransactions returns a slice
// Transaction for the provided ACME registration Id for the // of Transactions for the provided order domain names. An error is returned if
// FailedAuthorizationsPerAccount limit. // any of the order domain names are invalid. This method should be used for
func (builder *TransactionBuilder) FailedAuthorizationsPerAccountCheckOnlyTransaction(regId int64) (Transaction, error) { // checking capacity, before allowing more authorizations to be created.
bucketKey, err := newRegIdBucketKey(FailedAuthorizationsPerAccount, regId) func (builder *TransactionBuilder) FailedAuthorizationsPerDomainPerAccountCheckOnlyTransactions(regId int64, orderDomains []string, maxNames int) ([]Transaction, error) {
if err != nil { if len(orderDomains) > maxNames {
return Transaction{}, err return nil, fmt.Errorf("order contains more than %d DNS names", maxNames)
} }
limit, err := builder.getLimit(FailedAuthorizationsPerAccount, bucketKey)
// FailedAuthorizationsPerDomainPerAccount limit uses the 'enum:regId'
// bucket key format for overrides.
perAccountBucketKey, err := newRegIdBucketKey(FailedAuthorizationsPerDomainPerAccount, regId)
if err != nil { if err != nil {
if errors.Is(err, errLimitDisabled) { return nil, err
return newAllowOnlyTransaction() }
limit, err := builder.getLimit(FailedAuthorizationsPerDomainPerAccount, perAccountBucketKey)
if err != nil && !errors.Is(err, errLimitDisabled) {
return nil, err
}
var txns []Transaction
for _, name := range DomainsForRateLimiting(orderDomains) {
// FailedAuthorizationsPerDomainPerAccount limit uses the
// 'enum:regId:domain' bucket key format for transactions.
perDomainPerAccountBucketKey, err := newRegIdDomainBucketKey(FailedAuthorizationsPerDomainPerAccount, regId, name)
if err != nil {
return nil, err
} }
return Transaction{}, err
// Add a check-only transaction for each per domain per account bucket.
// The cost is 0, as we are only checking that the account and domain
// pair aren't already over the limit.
txn, err := newCheckOnlyTransaction(limit, perDomainPerAccountBucketKey, 0)
if err != nil {
return nil, err
}
txns = append(txns, txn)
} }
return newCheckOnlyTransaction(limit, bucketKey, 1) return txns, nil
} }
// FailedAuthorizationsPerAccountTransaction returns a Transaction for the // FailedAuthorizationsPerDomainPerAccountSpendOnlyTransactions returns a slice
// FailedAuthorizationsPerAccount limit for the provided ACME registration Id. // of Transactions for the provided order domain names. An error is returned if
func (builder *TransactionBuilder) FailedAuthorizationsPerAccountTransaction(regId int64) (Transaction, error) { // any of the order domain names are invalid. This method should be used for
bucketKey, err := newRegIdBucketKey(FailedAuthorizationsPerAccount, regId) // spending capacity, as a result of a failed authorization.
if err != nil { func (builder *TransactionBuilder) FailedAuthorizationsPerDomainPerAccountSpendOnlyTransactions(regId int64, orderDomains []string, maxNames int) ([]Transaction, error) {
return Transaction{}, err if len(orderDomains) > maxNames {
return nil, fmt.Errorf("order contains more than %d DNS names", maxNames)
} }
limit, err := builder.getLimit(FailedAuthorizationsPerAccount, bucketKey)
// FailedAuthorizationsPerDomainPerAccount limit uses the 'enum:regId'
// bucket key format for overrides.
perAccountBucketKey, err := newRegIdBucketKey(FailedAuthorizationsPerDomainPerAccount, regId)
if err != nil { if err != nil {
if errors.Is(err, errLimitDisabled) { return nil, err
return newAllowOnlyTransaction() }
limit, err := builder.getLimit(FailedAuthorizationsPerDomainPerAccount, perAccountBucketKey)
if err != nil && !errors.Is(err, errLimitDisabled) {
return nil, err
}
var txns []Transaction
for _, name := range DomainsForRateLimiting(orderDomains) {
// FailedAuthorizationsPerDomainPerAccount limit uses the
// 'enum:regId:domain' bucket key format for transactions.
perDomainPerAccountBucketKey, err := newRegIdDomainBucketKey(FailedAuthorizationsPerDomainPerAccount, regId, name)
if err != nil {
return nil, err
} }
return Transaction{}, err
// Add an allow-only transaction for each per domain per account bucket.
txn, err := newSpendOnlyTransaction(limit, perDomainPerAccountBucketKey, 1)
if err != nil {
return nil, err
}
txns = append(txns, txn)
} }
return newTransaction(limit, bucketKey, 1) return txns, nil
} }
// CertificatesPerDomainTransactions returns a slice of Transactions for the // CertificatesPerDomainTransactions returns a slice of Transactions for the

View File

@ -40,11 +40,14 @@ const (
// NewOrdersPerAccount uses bucket key 'enum:regId'. // NewOrdersPerAccount uses bucket key 'enum:regId'.
NewOrdersPerAccount NewOrdersPerAccount
// FailedAuthorizationsPerAccount uses bucket key 'enum:regId', where regId // FailedAuthorizationsPerDomainPerAccount uses two different bucket keys
// is the ACME registration Id of the account. Cost MUST be consumed from // depending on the context:
// this bucket only when the authorization is considered "failed". It SHOULD // - When referenced in an overrides file: uses bucket key 'enum:regId',
// be checked before new authorizations are created. // where regId is the ACME registration Id of the account.
FailedAuthorizationsPerAccount // - When referenced in a transaction: uses bucket key 'enum:regId:domain',
// where regId is the ACME registration Id of the account and domain is a
// domain name in the certificate.
FailedAuthorizationsPerDomainPerAccount
// CertificatesPerDomain uses bucket key 'enum:domain', where domain is a // CertificatesPerDomain uses bucket key 'enum:domain', where domain is a
// domain name in the certificate. // domain name in the certificate.
@ -96,14 +99,14 @@ func (n Name) EnumString() string {
// nameToString is a map of Name values to string names. // nameToString is a map of Name values to string names.
var nameToString = map[Name]string{ var nameToString = map[Name]string{
Unknown: "Unknown", Unknown: "Unknown",
NewRegistrationsPerIPAddress: "NewRegistrationsPerIPAddress", NewRegistrationsPerIPAddress: "NewRegistrationsPerIPAddress",
NewRegistrationsPerIPv6Range: "NewRegistrationsPerIPv6Range", NewRegistrationsPerIPv6Range: "NewRegistrationsPerIPv6Range",
NewOrdersPerAccount: "NewOrdersPerAccount", NewOrdersPerAccount: "NewOrdersPerAccount",
FailedAuthorizationsPerAccount: "FailedAuthorizationsPerAccount", FailedAuthorizationsPerDomainPerAccount: "FailedAuthorizationsPerDomainPerAccount",
CertificatesPerDomain: "CertificatesPerDomain", CertificatesPerDomain: "CertificatesPerDomain",
CertificatesPerDomainPerAccount: "CertificatesPerDomainPerAccount", CertificatesPerDomainPerAccount: "CertificatesPerDomainPerAccount",
CertificatesPerFQDNSet: "CertificatesPerFQDNSet", CertificatesPerFQDNSet: "CertificatesPerFQDNSet",
} }
// validIPAddress validates that the provided string is a valid IP address. // validIPAddress validates that the provided string is a valid IP address.
@ -202,10 +205,19 @@ func validateIdForName(name Name, id string) error {
// 'enum:ipv6rangeCIDR' // 'enum:ipv6rangeCIDR'
return validIPv6RangeCIDR(id) return validIPv6RangeCIDR(id)
case NewOrdersPerAccount, FailedAuthorizationsPerAccount: case NewOrdersPerAccount:
// 'enum:regId' // 'enum:regId'
return validateRegId(id) return validateRegId(id)
case FailedAuthorizationsPerDomainPerAccount:
// 'enum:regId:domain' for transaction
err := validateRegIdDomain(id)
if err == nil {
return nil
}
// 'enum:regId' for overrides
return validateRegId(id)
case CertificatesPerDomainPerAccount: case CertificatesPerDomainPerAccount:
// 'enum:regId:domain' for transaction // 'enum:regId:domain' for transaction
err := validateRegIdDomain(id) err := validateRegIdDomain(id)

View File

@ -8,3 +8,9 @@
count: 50 count: 50
period: 2s period: 2s
ids: [2001:0db8:0000::/48] ids: [2001:0db8:0000::/48]
- FailedAuthorizationsPerDomainPerAccount:
burst: 60
count: 60
period: 3s
ids: [1234, 5678]

View File

@ -10,7 +10,7 @@ CertificatesPerDomain:
count: 2 count: 2
burst: 2 burst: 2
period: 2160h period: 2160h
FailedAuthorizationsPerAccount: FailedAuthorizationsPerDomainPerAccount:
count: 3 count: 3
burst: 3 burst: 3
period: 5m period: 5m

View File

@ -2064,19 +2064,19 @@ func (wfe *WebFrontEndImpl) newNewOrderLimitTransactions(regId int64, names []st
} }
transactions = append(transactions, txn) transactions = append(transactions, txn)
txn, err = wfe.txnBuilder.FailedAuthorizationsPerAccountCheckOnlyTransaction(regId) failedAuthzTxns, err := wfe.txnBuilder.FailedAuthorizationsPerDomainPerAccountCheckOnlyTransactions(regId, names, wfe.maxNames)
if err != nil { if err != nil {
logTxnErr(err, ratelimits.FailedAuthorizationsPerAccount) logTxnErr(err, ratelimits.FailedAuthorizationsPerDomainPerAccount)
return nil return nil
} }
transactions = append(transactions, txn) transactions = append(transactions, failedAuthzTxns...)
txns, err := wfe.txnBuilder.CertificatesPerDomainTransactions(regId, names, wfe.maxNames) certsPerDomainTxns, err := wfe.txnBuilder.CertificatesPerDomainTransactions(regId, names, wfe.maxNames)
if err != nil { if err != nil {
logTxnErr(err, ratelimits.CertificatesPerDomain) logTxnErr(err, ratelimits.CertificatesPerDomain)
return nil return nil
} }
transactions = append(transactions, txns...) transactions = append(transactions, certsPerDomainTxns...)
txn, err = wfe.txnBuilder.CertificatesPerFQDNSetTransaction(names) txn, err = wfe.txnBuilder.CertificatesPerFQDNSetTransaction(names)
if err != nil { if err != nil {