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

     1  package ratelimits
     2  
     3  import (
     4  	"fmt"
     5  	"net/netip"
     6  	"strings"
     7  
     8  	"github.com/weppos/publicsuffix-go/publicsuffix"
     9  
    10  	"github.com/letsencrypt/boulder/core"
    11  	"github.com/letsencrypt/boulder/identifier"
    12  )
    13  
    14  // joinWithColon joins the provided args with a colon.
    15  func joinWithColon(args ...string) string {
    16  	return strings.Join(args, ":")
    17  }
    18  
    19  // coveringIdentifiers returns the set of "covering" identifiers used to enforce
    20  // the CertificatesPerDomain rate limit. For DNS names, this is the eTLD+1 as
    21  // determined by the Public Suffix List; exact public suffix matches are
    22  // preserved. For IP addresses, the covering prefix is /32 for IPv4 and /64 for
    23  // IPv6. This groups requests by registered domain or address block to match the
    24  // scope of the limit. The result is deduplicated and lowercased. If the
    25  // identifier type is unsupported, an error is returned.
    26  func coveringIdentifiers(idents identifier.ACMEIdentifiers) ([]string, error) {
    27  	var covers []string
    28  	for _, ident := range idents {
    29  		cover, err := coveringIdentifier(CertificatesPerDomain, ident)
    30  		if err != nil {
    31  			return nil, err
    32  		}
    33  		covers = append(covers, cover)
    34  	}
    35  	return core.UniqueLowerNames(covers), nil
    36  }
    37  
    38  // coveringIdentifier returns the "covering" identifier used to enforce the
    39  // CertificatesPerDomain, CertificatesPerDomainPerAccount, and
    40  // NewRegistrationsPerIPv6Range rate limits. For DNS names, this is the eTLD+1
    41  // as determined by the Public Suffix List; exact public suffix matches are
    42  // preserved. For IP addresses, the covering prefix depends on the limit:
    43  //
    44  // - CertificatesPerDomain and CertificatesPerDomainPerAccount:
    45  //   - /32 for IPv4
    46  //   - /64 for IPv6
    47  //
    48  // - NewRegistrationsPerIPv6Range:
    49  //   - /48 for IPv6 only
    50  //
    51  // This groups requests by registered domain or address block to match the scope
    52  // of each limit. The result is deduplicated and lowercased. If the identifier
    53  // type or limit is unsupported, an error is returned.
    54  func coveringIdentifier(limit Name, ident identifier.ACMEIdentifier) (string, error) {
    55  	switch ident.Type {
    56  	case identifier.TypeDNS:
    57  		domain, err := publicsuffix.Domain(ident.Value)
    58  		if err != nil {
    59  			if err.Error() == fmt.Sprintf("%s is a suffix", ident.Value) {
    60  				// If the public suffix is the domain itself, that's fine.
    61  				// Include the original name in the result.
    62  				return ident.Value, nil
    63  			}
    64  			return "", err
    65  		}
    66  		return domain, nil
    67  	case identifier.TypeIP:
    68  		ip, err := netip.ParseAddr(ident.Value)
    69  		if err != nil {
    70  			return "", err
    71  		}
    72  		prefix, err := coveringIPPrefix(limit, ip)
    73  		if err != nil {
    74  			return "", err
    75  		}
    76  		return prefix.String(), nil
    77  	}
    78  	return "", fmt.Errorf("unsupported identifier type: %s", ident.Type)
    79  }
    80  
    81  // coveringIPPrefix returns the "covering" IP prefix used to enforce the
    82  // CertificatesPerDomain, CertificatesPerDomainPerAccount, and
    83  // NewRegistrationsPerIPv6Range rate limits. The prefix length depends on the
    84  // limit and IP version:
    85  //
    86  // - CertificatesPerDomain and CertificatesPerDomainPerAccount:
    87  //   - /32 for IPv4
    88  //   - /64 for IPv6
    89  //
    90  // - NewRegistrationsPerIPv6Range:
    91  //   - /48 for IPv6 only
    92  //
    93  // This groups requests by address block to match the scope of each limit. If
    94  // the limit does not require a covering prefix, an error is returned.
    95  func coveringIPPrefix(limit Name, addr netip.Addr) (netip.Prefix, error) {
    96  	switch limit {
    97  	case CertificatesPerDomain, CertificatesPerDomainPerAccount:
    98  		var bits int
    99  		if addr.Is4() {
   100  			bits = 32
   101  		} else {
   102  			bits = 64
   103  		}
   104  		prefix, err := addr.Prefix(bits)
   105  		if err != nil {
   106  			return netip.Prefix{}, fmt.Errorf("building covering prefix for %s: %w", addr, err)
   107  		}
   108  		return prefix, nil
   109  
   110  	case NewRegistrationsPerIPv6Range:
   111  		if !addr.Is6() {
   112  			return netip.Prefix{}, fmt.Errorf("limit %s requires an IPv6 address, got %s", limit, addr)
   113  		}
   114  		prefix, err := addr.Prefix(48)
   115  		if err != nil {
   116  			return netip.Prefix{}, fmt.Errorf("building covering prefix for %s: %w", addr, err)
   117  		}
   118  		return prefix, nil
   119  	}
   120  	return netip.Prefix{}, fmt.Errorf("limit %s does not require a covering prefix", limit)
   121  }