122 lines
4.0 KiB
Go
122 lines
4.0 KiB
Go
package ratelimits
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
"strings"
|
|
|
|
"github.com/weppos/publicsuffix-go/publicsuffix"
|
|
|
|
"github.com/letsencrypt/boulder/core"
|
|
"github.com/letsencrypt/boulder/identifier"
|
|
)
|
|
|
|
// joinWithColon joins the provided args with a colon.
|
|
func joinWithColon(args ...string) string {
|
|
return strings.Join(args, ":")
|
|
}
|
|
|
|
// coveringIdentifiers returns the set of "covering" identifiers used to enforce
|
|
// the CertificatesPerDomain rate limit. For DNS names, this is the eTLD+1 as
|
|
// determined by the Public Suffix List; exact public suffix matches are
|
|
// preserved. For IP addresses, the covering prefix is /32 for IPv4 and /64 for
|
|
// IPv6. This groups requests by registered domain or address block to match the
|
|
// scope of the limit. The result is deduplicated and lowercased. If the
|
|
// identifier type is unsupported, an error is returned.
|
|
func coveringIdentifiers(idents identifier.ACMEIdentifiers) ([]string, error) {
|
|
var covers []string
|
|
for _, ident := range idents {
|
|
cover, err := coveringIdentifier(CertificatesPerDomain, ident)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
covers = append(covers, cover)
|
|
}
|
|
return core.UniqueLowerNames(covers), nil
|
|
}
|
|
|
|
// coveringIdentifier returns the "covering" identifier used to enforce the
|
|
// CertificatesPerDomain, CertificatesPerDomainPerAccount, and
|
|
// NewRegistrationsPerIPv6Range rate limits. For DNS names, this is the eTLD+1
|
|
// as determined by the Public Suffix List; exact public suffix matches are
|
|
// preserved. For IP addresses, the covering prefix depends on the limit:
|
|
//
|
|
// - CertificatesPerDomain and CertificatesPerDomainPerAccount:
|
|
// - /32 for IPv4
|
|
// - /64 for IPv6
|
|
//
|
|
// - NewRegistrationsPerIPv6Range:
|
|
// - /48 for IPv6 only
|
|
//
|
|
// This groups requests by registered domain or address block to match the scope
|
|
// of each limit. The result is deduplicated and lowercased. If the identifier
|
|
// type or limit is unsupported, an error is returned.
|
|
func coveringIdentifier(limit Name, ident identifier.ACMEIdentifier) (string, error) {
|
|
switch ident.Type {
|
|
case identifier.TypeDNS:
|
|
domain, err := publicsuffix.Domain(ident.Value)
|
|
if err != nil {
|
|
if err.Error() == fmt.Sprintf("%s is a suffix", ident.Value) {
|
|
// If the public suffix is the domain itself, that's fine.
|
|
// Include the original name in the result.
|
|
return ident.Value, nil
|
|
}
|
|
return "", err
|
|
}
|
|
return domain, nil
|
|
case identifier.TypeIP:
|
|
ip, err := netip.ParseAddr(ident.Value)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
prefix, err := coveringIPPrefix(limit, ip)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return prefix.String(), nil
|
|
}
|
|
return "", fmt.Errorf("unsupported identifier type: %s", ident.Type)
|
|
}
|
|
|
|
// coveringIPPrefix returns the "covering" IP prefix used to enforce the
|
|
// CertificatesPerDomain, CertificatesPerDomainPerAccount, and
|
|
// NewRegistrationsPerIPv6Range rate limits. The prefix length depends on the
|
|
// limit and IP version:
|
|
//
|
|
// - CertificatesPerDomain and CertificatesPerDomainPerAccount:
|
|
// - /32 for IPv4
|
|
// - /64 for IPv6
|
|
//
|
|
// - NewRegistrationsPerIPv6Range:
|
|
// - /48 for IPv6 only
|
|
//
|
|
// This groups requests by address block to match the scope of each limit. If
|
|
// the limit does not require a covering prefix, an error is returned.
|
|
func coveringIPPrefix(limit Name, addr netip.Addr) (netip.Prefix, error) {
|
|
switch limit {
|
|
case CertificatesPerDomain, CertificatesPerDomainPerAccount:
|
|
var bits int
|
|
if addr.Is4() {
|
|
bits = 32
|
|
} else {
|
|
bits = 64
|
|
}
|
|
prefix, err := addr.Prefix(bits)
|
|
if err != nil {
|
|
return netip.Prefix{}, fmt.Errorf("building covering prefix for %s: %w", addr, err)
|
|
}
|
|
return prefix, nil
|
|
|
|
case NewRegistrationsPerIPv6Range:
|
|
if !addr.Is6() {
|
|
return netip.Prefix{}, fmt.Errorf("limit %s requires an IPv6 address, got %s", limit, addr)
|
|
}
|
|
prefix, err := addr.Prefix(48)
|
|
if err != nil {
|
|
return netip.Prefix{}, fmt.Errorf("building covering prefix for %s: %w", addr, err)
|
|
}
|
|
return prefix, nil
|
|
}
|
|
return netip.Prefix{}, fmt.Errorf("limit %s does not require a covering prefix", limit)
|
|
}
|