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 }