github.com/letsencrypt/boulder@v0.20251208.0/ratelimits/names.go (about)

     1  package ratelimits
     2  
     3  import (
     4  	"fmt"
     5  	"net/netip"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/letsencrypt/boulder/iana"
    10  	"github.com/letsencrypt/boulder/identifier"
    11  	"github.com/letsencrypt/boulder/policy"
    12  )
    13  
    14  // Name is an enumeration of all rate limit names. It is used to intern rate
    15  // limit names as strings and to provide a type-safe way to refer to rate
    16  // limits.
    17  //
    18  // IMPORTANT: If you add or remove a limit Name, you MUST update:
    19  //   - the string representation of the Name in nameToString,
    20  //   - the validators for that name in validateIdForName(),
    21  //   - the transaction constructors for that name in transaction.go
    22  //   - the Subscriber facing error message in Decision.Result(), and
    23  //   - the case in BuildBucketKey() for that name.
    24  type Name int
    25  
    26  const (
    27  	// Unknown is the zero value of Name and is used to indicate an unknown
    28  	// limit name.
    29  	Unknown Name = iota
    30  
    31  	// NewRegistrationsPerIPAddress uses bucket key 'enum:ipAddress'.
    32  	NewRegistrationsPerIPAddress
    33  
    34  	// NewRegistrationsPerIPv6Range uses bucket key 'enum:ipv6rangeCIDR'. The
    35  	// address range must be a /48. RFC 3177, which was published in 2001,
    36  	// advised operators to allocate a /48 block of IPv6 addresses for most end
    37  	// sites. RFC 6177, which was published in 2011 and obsoletes RFC 3177,
    38  	// advises allocating a smaller /56 block. We've chosen to use the larger
    39  	// /48 block for our IPv6 rate limiting. See:
    40  	//   1. https://tools.ietf.org/html/rfc3177#section-3
    41  	//   2. https://datatracker.ietf.org/doc/html/rfc6177#section-2
    42  	NewRegistrationsPerIPv6Range
    43  
    44  	// NewOrdersPerAccount uses bucket key 'enum:regId'.
    45  	NewOrdersPerAccount
    46  
    47  	// FailedAuthorizationsPerDomainPerAccount uses two different bucket keys
    48  	// depending on the context:
    49  	//  - When referenced in an overrides file: uses bucket key 'enum:regId',
    50  	//    where regId is the ACME registration Id of the account.
    51  	//  - When referenced in a transaction: uses bucket key
    52  	//    'enum:regId:identValue', where regId is the ACME registration Id of
    53  	//    the account and identValue is the value of an identifier in the
    54  	//    certificate.
    55  	FailedAuthorizationsPerDomainPerAccount
    56  
    57  	// CertificatesPerDomain uses bucket key 'enum:domainOrCIDR', where
    58  	// domainOrCIDR is a domain name or IP address in the certificate. It uses
    59  	// two different IP address formats depending on the context:
    60  	//  - When referenced in an overrides file: uses a single IP address.
    61  	//  - When referenced in a transaction: uses an IP address prefix in CIDR
    62  	//    notation. IPv4 prefixes must be /32, and IPv6 prefixes must be /64.
    63  	// In both cases, IPv6 addresses must be the lowest address in their /64;
    64  	// i.e. their last 64 bits must be zero.
    65  	CertificatesPerDomain
    66  
    67  	// CertificatesPerDomainPerAccount is only used for per-account overrides to
    68  	// the CertificatesPerDomain rate limit. If this limit is referenced in the
    69  	// default limits file, it will be ignored. It uses two different bucket
    70  	// keys depending on the context:
    71  	//  - When referenced in an overrides file: uses bucket key 'enum:regId',
    72  	//    where regId is the ACME registration Id of the account.
    73  	//  - When referenced in a transaction: uses bucket key
    74  	//   'enum:regId:domainOrCIDR', where regId is the ACME registration Id of
    75  	//    the account and domainOrCIDR is either a domain name in the
    76  	//    certificate or an IP prefix in CIDR notation.
    77  	//     - IP address formats vary by context, as for CertificatesPerDomain.
    78  	//
    79  	// When overrides to the CertificatesPerDomainPerAccount are configured for a
    80  	// subscriber, the cost:
    81  	//   - MUST be consumed from each CertificatesPerDomainPerAccount bucket and
    82  	//   - SHOULD be consumed from each CertificatesPerDomain bucket, if possible.
    83  	CertificatesPerDomainPerAccount
    84  
    85  	// CertificatesPerFQDNSet uses bucket key 'enum:fqdnSet', where fqdnSet is a
    86  	// hashed set of unique identifier values in the certificate.
    87  	//
    88  	// Note: When this is referenced in an overrides file, the fqdnSet MUST be
    89  	// passed as a comma-separated list of identifier values.
    90  	CertificatesPerFQDNSet
    91  
    92  	// FailedAuthorizationsForPausingPerDomainPerAccount is similar to
    93  	// FailedAuthorizationsPerDomainPerAccount in that it uses two different
    94  	// bucket keys depending on the context:
    95  	//  - When referenced in an overrides file: uses bucket key 'enum:regId',
    96  	//    where regId is the ACME registration Id of the account.
    97  	//  - When referenced in a transaction: uses bucket key
    98  	//    'enum:regId:identValue', where regId is the ACME registration Id of
    99  	//    the account and identValue is the value of an identifier in the
   100  	//    certificate.
   101  	FailedAuthorizationsForPausingPerDomainPerAccount
   102  
   103  	// LimitOverrideRequestsPerIPAddress is used to limit the number of requests
   104  	// to the rate limit override request endpoint per IP address. It uses
   105  	// bucket key 'enum:ipAddress'.
   106  	LimitOverrideRequestsPerIPAddress
   107  )
   108  
   109  // nameToString is a map of Name values to string names.
   110  var nameToString = map[Name]string{
   111  	Unknown:                                           "Unknown",
   112  	NewRegistrationsPerIPAddress:                      "NewRegistrationsPerIPAddress",
   113  	NewRegistrationsPerIPv6Range:                      "NewRegistrationsPerIPv6Range",
   114  	NewOrdersPerAccount:                               "NewOrdersPerAccount",
   115  	FailedAuthorizationsPerDomainPerAccount:           "FailedAuthorizationsPerDomainPerAccount",
   116  	CertificatesPerDomain:                             "CertificatesPerDomain",
   117  	CertificatesPerDomainPerAccount:                   "CertificatesPerDomainPerAccount",
   118  	CertificatesPerFQDNSet:                            "CertificatesPerFQDNSet",
   119  	FailedAuthorizationsForPausingPerDomainPerAccount: "FailedAuthorizationsForPausingPerDomainPerAccount",
   120  	LimitOverrideRequestsPerIPAddress:                 "LimitOverrideRequestsPerIPAddress",
   121  }
   122  
   123  // isValid returns true if the Name is a valid rate limit name.
   124  func (n Name) isValid() bool {
   125  	return n > Unknown && n < Name(len(nameToString))
   126  }
   127  
   128  // String returns the string representation of the Name. It allows Name to
   129  // satisfy the fmt.Stringer interface.
   130  func (n Name) String() string {
   131  	if !n.isValid() {
   132  		return nameToString[Unknown]
   133  	}
   134  	return nameToString[n]
   135  }
   136  
   137  // EnumString returns the string representation of the Name enumeration.
   138  func (n Name) EnumString() string {
   139  	if !n.isValid() {
   140  		return nameToString[Unknown]
   141  	}
   142  	return strconv.Itoa(int(n))
   143  }
   144  
   145  // validIPAddress validates that the provided string is a valid IP address.
   146  func validIPAddress(id string) error {
   147  	ip, err := netip.ParseAddr(id)
   148  	if err != nil {
   149  		return fmt.Errorf("invalid IP address, %q must be an IP address", id)
   150  	}
   151  	canon := ip.String()
   152  	if canon != id {
   153  		return fmt.Errorf(
   154  			"invalid IP address, %q must be in canonical form (%q)", id, canon)
   155  	}
   156  	return iana.IsReservedAddr(ip)
   157  }
   158  
   159  // validIPv6RangeCIDR validates that the provided string is formatted as an IPv6
   160  // prefix in CIDR notation, with a /48 mask.
   161  func validIPv6RangeCIDR(id string) error {
   162  	prefix, err := netip.ParsePrefix(id)
   163  	if err != nil {
   164  		return fmt.Errorf(
   165  			"invalid CIDR, %q must be an IPv6 CIDR range", id)
   166  	}
   167  	if prefix.Bits() != 48 {
   168  		// This also catches the case where the range is an IPv4 CIDR, since an
   169  		// IPv4 CIDR can't have a /48 subnet mask - the maximum is /32.
   170  		return fmt.Errorf(
   171  			"invalid CIDR, %q must be /48", id)
   172  	}
   173  	canon := prefix.Masked().String()
   174  	if canon != id {
   175  		return fmt.Errorf(
   176  			"invalid CIDR, %q must be in canonical form (%q)", id, canon)
   177  	}
   178  	return iana.IsReservedPrefix(prefix)
   179  }
   180  
   181  // validateRegId validates that the provided string is a valid ACME regId.
   182  func validateRegId(id string) error {
   183  	_, err := strconv.ParseUint(id, 10, 64)
   184  	if err != nil {
   185  		return fmt.Errorf("invalid regId, %q must be an ACME registration Id", id)
   186  	}
   187  	return nil
   188  }
   189  
   190  // validateRegIdIdentValue validates that the provided string is formatted
   191  // 'regId:identValue', where regId is an ACME registration Id and identValue is
   192  // a valid identifier value.
   193  func validateRegIdIdentValue(id string) error {
   194  	regIdIdentValue := strings.Split(id, ":")
   195  	if len(regIdIdentValue) != 2 {
   196  		return fmt.Errorf(
   197  			"invalid regId:identValue, %q must be formatted 'regId:identValue'", id)
   198  	}
   199  	err := validateRegId(regIdIdentValue[0])
   200  	if err != nil {
   201  		return fmt.Errorf(
   202  			"invalid regId, %q must be formatted 'regId:identValue'", id)
   203  	}
   204  	domainErr := policy.ValidDomain(regIdIdentValue[1])
   205  	if domainErr != nil {
   206  		ipErr := policy.ValidIP(regIdIdentValue[1])
   207  		if ipErr != nil {
   208  			return fmt.Errorf("invalid identValue, %q must be formatted 'regId:identValue': %w as domain, %w as IP", id, domainErr, ipErr)
   209  		}
   210  	}
   211  	return nil
   212  }
   213  
   214  // validateDomainOrCIDR validates that the provided string is either a domain
   215  // name or an IP address. IPv6 addresses must be the lowest address in their
   216  // /64, i.e. their last 64 bits must be zero.
   217  func validateDomainOrCIDR(limit Name, id string) error {
   218  	domainErr := policy.ValidDomain(id)
   219  	if domainErr == nil {
   220  		// This is a valid domain.
   221  		return nil
   222  	}
   223  
   224  	ip, ipErr := netip.ParseAddr(id)
   225  	if ipErr != nil {
   226  		return fmt.Errorf("%q is neither a domain (%w) nor an IP address (%w)", id, domainErr, ipErr)
   227  	}
   228  
   229  	if ip.String() != id {
   230  		return fmt.Errorf("invalid IP address %q, must be in canonical form (%q)", id, ip.String())
   231  	}
   232  
   233  	prefix, prefixErr := coveringIPPrefix(limit, ip)
   234  	if prefixErr != nil {
   235  		return fmt.Errorf("invalid IP address %q, couldn't determine prefix: %w", id, prefixErr)
   236  	}
   237  	if prefix.Addr() != ip {
   238  		return fmt.Errorf("invalid IP address %q, must be the lowest address in its prefix (%q)", id, prefix.Addr().String())
   239  	}
   240  	return iana.IsReservedPrefix(prefix)
   241  }
   242  
   243  // validateRegIdDomainOrCIDR validates that the provided string is formatted
   244  // 'regId:domainOrCIDR', where domainOrCIDR is either a domain name or an IP
   245  // address. IPv6 addresses must be the lowest address in their /64, i.e. their
   246  // last 64 bits must be zero.
   247  func validateRegIdDomainOrCIDR(limit Name, id string) error {
   248  	regIdDomainOrCIDR := strings.Split(id, ":")
   249  	if len(regIdDomainOrCIDR) != 2 {
   250  		return fmt.Errorf(
   251  			"invalid regId:domainOrCIDR, %q must be formatted 'regId:domainOrCIDR'", id)
   252  	}
   253  	err := validateRegId(regIdDomainOrCIDR[0])
   254  	if err != nil {
   255  		return fmt.Errorf(
   256  			"invalid regId, %q must be formatted 'regId:domainOrCIDR'", id)
   257  	}
   258  	err = validateDomainOrCIDR(limit, regIdDomainOrCIDR[1])
   259  	if err != nil {
   260  		return fmt.Errorf("invalid domainOrCIDR, %q must be formatted 'regId:domainOrCIDR': %w", id, err)
   261  	}
   262  	return nil
   263  }
   264  
   265  // validateFQDNSet validates that the provided string is formatted 'fqdnSet',
   266  // where fqdnSet is a comma-separated list of identifier values.
   267  func validateFQDNSet(id string) error {
   268  	values := strings.Split(id, ",")
   269  	if len(values) == 0 {
   270  		return fmt.Errorf(
   271  			"invalid fqdnSet, %q must be formatted 'fqdnSet'", id)
   272  	}
   273  	for _, value := range values {
   274  		domainErr := policy.ValidDomain(value)
   275  		if domainErr != nil {
   276  			ipErr := policy.ValidIP(value)
   277  			if ipErr != nil {
   278  				return fmt.Errorf("invalid fqdnSet member %q: %w as domain, %w as IP", id, domainErr, ipErr)
   279  			}
   280  		}
   281  	}
   282  	return nil
   283  }
   284  
   285  func validateIdForName(name Name, id string) error {
   286  	switch name {
   287  	case NewRegistrationsPerIPAddress, LimitOverrideRequestsPerIPAddress:
   288  		// 'enum:ipaddress'
   289  		return validIPAddress(id)
   290  
   291  	case NewRegistrationsPerIPv6Range:
   292  		// 'enum:ipv6rangeCIDR'
   293  		return validIPv6RangeCIDR(id)
   294  
   295  	case NewOrdersPerAccount:
   296  		// 'enum:regId'
   297  		return validateRegId(id)
   298  
   299  	case FailedAuthorizationsPerDomainPerAccount:
   300  		if strings.Contains(id, ":") {
   301  			// 'enum:regId:identValue' for transaction
   302  			return validateRegIdIdentValue(id)
   303  		} else {
   304  			// 'enum:regId' for overrides
   305  			return validateRegId(id)
   306  		}
   307  
   308  	case CertificatesPerDomainPerAccount:
   309  		if strings.Contains(id, ":") {
   310  			// 'enum:regId:domainOrCIDR' for transaction
   311  			return validateRegIdDomainOrCIDR(name, id)
   312  		} else {
   313  			// 'enum:regId' for overrides
   314  			return validateRegId(id)
   315  		}
   316  
   317  	case CertificatesPerDomain:
   318  		// 'enum:domainOrCIDR'
   319  		return validateDomainOrCIDR(name, id)
   320  
   321  	case CertificatesPerFQDNSet:
   322  		// 'enum:fqdnSet'
   323  		return validateFQDNSet(id)
   324  
   325  	case FailedAuthorizationsForPausingPerDomainPerAccount:
   326  		if strings.Contains(id, ":") {
   327  			// 'enum:regId:identValue' for transaction
   328  			return validateRegIdIdentValue(id)
   329  		} else {
   330  			// 'enum:regId' for overrides
   331  			return validateRegId(id)
   332  		}
   333  
   334  	case Unknown:
   335  		fallthrough
   336  
   337  	default:
   338  		// This should never happen.
   339  		return fmt.Errorf("unknown limit enum %q", name)
   340  	}
   341  }
   342  
   343  // StringToName is a map of string names to Name values.
   344  var StringToName = func() map[string]Name {
   345  	m := make(map[string]Name, len(nameToString))
   346  	for k, v := range nameToString {
   347  		m[v] = k
   348  	}
   349  	return m
   350  }()
   351  
   352  // LimitNames is a slice of all rate limit names.
   353  var LimitNames = func() []string {
   354  	names := make([]string, 0, len(nameToString))
   355  	for _, v := range nameToString {
   356  		names = append(names, v)
   357  	}
   358  	return names
   359  }()
   360  
   361  // BuildBucketKey builds a bucketKey for the given rate limit name from the
   362  // provided components. It returns an error if the name is not valid or if the
   363  // components are not valid for the given name.
   364  func BuildBucketKey(name Name, regId int64, singleIdent identifier.ACMEIdentifier, setOfIdents identifier.ACMEIdentifiers, subscriberIP netip.Addr) (string, error) {
   365  	makeMissingErr := func(field string) error {
   366  		return fmt.Errorf("%s is required for limit %s (enum: %s)", field, name, name.EnumString())
   367  	}
   368  
   369  	switch name {
   370  	case NewRegistrationsPerIPAddress, LimitOverrideRequestsPerIPAddress:
   371  		if !subscriberIP.IsValid() {
   372  			return "", makeMissingErr("subscriberIP")
   373  		}
   374  		return newIPAddressBucketKey(name, subscriberIP), nil
   375  
   376  	case NewRegistrationsPerIPv6Range:
   377  		if !subscriberIP.IsValid() {
   378  			return "", makeMissingErr("subscriberIP")
   379  		}
   380  		prefix, err := coveringIPPrefix(name, subscriberIP)
   381  		if err != nil {
   382  			return "", err
   383  		}
   384  		return newIPv6RangeCIDRBucketKey(name, prefix), nil
   385  
   386  	case NewOrdersPerAccount:
   387  		if regId == 0 {
   388  			return "", makeMissingErr("regId")
   389  		}
   390  		return newRegIdBucketKey(name, regId), nil
   391  
   392  	case CertificatesPerDomain:
   393  		if singleIdent.Value == "" {
   394  			return "", makeMissingErr("singleIdent")
   395  		}
   396  		coveringIdent, err := coveringIdentifier(name, singleIdent)
   397  		if err != nil {
   398  			return "", err
   399  		}
   400  		return newDomainOrCIDRBucketKey(name, coveringIdent), nil
   401  
   402  	case CertificatesPerDomainPerAccount:
   403  		if singleIdent.Value != "" {
   404  			if regId == 0 {
   405  				return "", makeMissingErr("regId")
   406  			}
   407  			// Default: use 'enum:regId:identValue' bucket key format.
   408  			coveringIdent, err := coveringIdentifier(name, singleIdent)
   409  			if err != nil {
   410  				return "", err
   411  			}
   412  			return newRegIdIdentValueBucketKey(name, regId, coveringIdent), nil
   413  		}
   414  		if regId == 0 {
   415  			return "", makeMissingErr("regId")
   416  		}
   417  		// Override: use 'enum:regId' bucket key format.
   418  		return newRegIdBucketKey(name, regId), nil
   419  
   420  	case CertificatesPerFQDNSet:
   421  		if len(setOfIdents) == 0 {
   422  			return "", makeMissingErr("setOfIdents")
   423  		}
   424  		return newFQDNSetBucketKey(name, setOfIdents), nil
   425  
   426  	case FailedAuthorizationsPerDomainPerAccount, FailedAuthorizationsForPausingPerDomainPerAccount:
   427  		if singleIdent.Value != "" {
   428  			if regId == 0 {
   429  				return "", makeMissingErr("regId")
   430  			}
   431  			// Default: use 'enum:regId:identValue' bucket key format.
   432  			return newRegIdIdentValueBucketKey(name, regId, singleIdent.Value), nil
   433  		}
   434  		if regId == 0 {
   435  			return "", makeMissingErr("regId")
   436  		}
   437  		// Override: use 'enum:regId' bucket key format.
   438  		return newRegIdBucketKey(name, regId), nil
   439  	}
   440  
   441  	return "", fmt.Errorf("unknown limit enum %s", name.EnumString())
   442  }