498 lines
14 KiB
Go
498 lines
14 KiB
Go
package ratelimits
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/letsencrypt/boulder/identifier"
|
|
"github.com/letsencrypt/boulder/test"
|
|
)
|
|
|
|
func TestNameIsValid(t *testing.T) {
|
|
t.Parallel()
|
|
type args struct {
|
|
name Name
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{name: "Unknown", args: args{name: Unknown}, want: false},
|
|
{name: "9001", args: args{name: 9001}, want: false},
|
|
{name: "NewRegistrationsPerIPAddress", args: args{name: NewRegistrationsPerIPAddress}, want: true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.args.name.isValid()
|
|
test.AssertEquals(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateIdForName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
limit Name
|
|
desc string
|
|
id string
|
|
err string
|
|
}{
|
|
{
|
|
limit: NewRegistrationsPerIPAddress,
|
|
desc: "valid IPv4 address",
|
|
id: "64.112.117.1",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPAddress,
|
|
desc: "reserved IPv4 address",
|
|
id: "10.0.0.1",
|
|
err: "in a reserved address block",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPAddress,
|
|
desc: "valid IPv6 address",
|
|
id: "2602:80a:6000::42:42",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPAddress,
|
|
desc: "IPv6 address in non-canonical form",
|
|
id: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
|
err: "must be in canonical form",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPAddress,
|
|
desc: "empty string",
|
|
id: "",
|
|
err: "must be an IP address",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPAddress,
|
|
desc: "one space",
|
|
id: " ",
|
|
err: "must be an IP address",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPAddress,
|
|
desc: "invalid IPv4 address",
|
|
id: "10.0.0.9000",
|
|
err: "must be an IP address",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPAddress,
|
|
desc: "invalid IPv6 address",
|
|
id: "2001:0db8:85a3:0000:0000:8a2e:0370:7334:9000",
|
|
err: "must be an IP address",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPv6Range,
|
|
desc: "valid IPv6 address range",
|
|
id: "2602:80a:6000::/48",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPv6Range,
|
|
desc: "IPv6 address range in non-canonical form",
|
|
id: "2602:080a:6000::/48",
|
|
err: "must be in canonical form",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPv6Range,
|
|
desc: "IPv6 address range with low bits set",
|
|
id: "2602:080a:6000::1/48",
|
|
err: "must be in canonical form",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPv6Range,
|
|
desc: "invalid IPv6 CIDR range",
|
|
id: "2001:0db8:0000::/128",
|
|
err: "must be /48",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPv6Range,
|
|
desc: "invalid IPv6 CIDR",
|
|
id: "2001:0db8:0000::/48/48",
|
|
err: "must be an IPv6 CIDR range",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPv6Range,
|
|
desc: "IPv4 CIDR when we expect IPv6 CIDR range",
|
|
id: "10.0.0.0/16",
|
|
err: "must be /48",
|
|
},
|
|
{
|
|
limit: NewRegistrationsPerIPv6Range,
|
|
desc: "IPv4 CIDR with invalid long mask",
|
|
id: "10.0.0.0/48",
|
|
err: "must be an IPv6 CIDR range",
|
|
},
|
|
{
|
|
limit: NewOrdersPerAccount,
|
|
desc: "valid regId",
|
|
id: "1234567890",
|
|
},
|
|
{
|
|
limit: NewOrdersPerAccount,
|
|
desc: "invalid regId",
|
|
id: "lol",
|
|
err: "must be an ACME registration Id",
|
|
},
|
|
{
|
|
limit: FailedAuthorizationsPerDomainPerAccount,
|
|
desc: "transaction: valid regId and domain",
|
|
id: "12345:example.com",
|
|
},
|
|
{
|
|
limit: FailedAuthorizationsPerDomainPerAccount,
|
|
desc: "transaction: invalid regId",
|
|
id: "12ea5:example.com",
|
|
err: "invalid regId",
|
|
},
|
|
{
|
|
limit: FailedAuthorizationsPerDomainPerAccount,
|
|
desc: "transaction: invalid domain",
|
|
id: "12345:examplecom",
|
|
err: "name needs at least one dot",
|
|
},
|
|
{
|
|
limit: FailedAuthorizationsPerDomainPerAccount,
|
|
desc: "override: valid regId",
|
|
id: "12345",
|
|
},
|
|
{
|
|
limit: FailedAuthorizationsPerDomainPerAccount,
|
|
desc: "override: invalid regId",
|
|
id: "12ea5",
|
|
err: "invalid regId",
|
|
},
|
|
{
|
|
limit: FailedAuthorizationsForPausingPerDomainPerAccount,
|
|
desc: "transaction: valid regId and domain",
|
|
id: "12345:example.com",
|
|
},
|
|
{
|
|
limit: FailedAuthorizationsForPausingPerDomainPerAccount,
|
|
desc: "transaction: invalid regId",
|
|
id: "12ea5:example.com",
|
|
err: "invalid regId",
|
|
},
|
|
{
|
|
limit: FailedAuthorizationsForPausingPerDomainPerAccount,
|
|
desc: "transaction: invalid domain",
|
|
id: "12345:examplecom",
|
|
err: "name needs at least one dot",
|
|
},
|
|
{
|
|
limit: FailedAuthorizationsForPausingPerDomainPerAccount,
|
|
desc: "override: valid regId",
|
|
id: "12345",
|
|
},
|
|
{
|
|
limit: FailedAuthorizationsForPausingPerDomainPerAccount,
|
|
desc: "override: invalid regId",
|
|
id: "12ea5",
|
|
err: "invalid regId",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomainPerAccount,
|
|
desc: "transaction: valid regId and domain",
|
|
id: "12345:example.com",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomainPerAccount,
|
|
desc: "transaction: invalid regId",
|
|
id: "12ea5:example.com",
|
|
err: "invalid regId",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomainPerAccount,
|
|
desc: "transaction: invalid domain",
|
|
id: "12345:examplecom",
|
|
err: "name needs at least one dot",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomainPerAccount,
|
|
desc: "override: valid regId",
|
|
id: "12345",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomainPerAccount,
|
|
desc: "override: invalid regId",
|
|
id: "12ea5",
|
|
err: "invalid regId",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomain,
|
|
desc: "valid domain",
|
|
id: "example.com",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomain,
|
|
desc: "valid IPv4 address",
|
|
id: "64.112.117.1",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomain,
|
|
desc: "valid IPv6 address",
|
|
id: "2602:80a:6000::",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomain,
|
|
desc: "IPv6 address with subnet",
|
|
id: "2602:80a:6000::/64",
|
|
err: "nor an IP address",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomain,
|
|
desc: "malformed domain",
|
|
id: "example:.com",
|
|
err: "name contains an invalid character",
|
|
},
|
|
{
|
|
limit: CertificatesPerDomain,
|
|
desc: "empty domain",
|
|
id: "",
|
|
err: "Identifier value (name) is empty",
|
|
},
|
|
{
|
|
limit: CertificatesPerFQDNSet,
|
|
desc: "valid fqdnSet containing a single domain",
|
|
id: "example.com",
|
|
},
|
|
{
|
|
limit: CertificatesPerFQDNSet,
|
|
desc: "valid fqdnSet containing a single IPv4 address",
|
|
id: "64.112.117.1",
|
|
},
|
|
{
|
|
limit: CertificatesPerFQDNSet,
|
|
desc: "valid fqdnSet containing a single IPv6 address",
|
|
id: "2602:80a:6000::1",
|
|
},
|
|
{
|
|
limit: CertificatesPerFQDNSet,
|
|
desc: "valid fqdnSet containing multiple domains",
|
|
id: "example.com,example.org",
|
|
},
|
|
{
|
|
limit: CertificatesPerFQDNSet,
|
|
desc: "valid fqdnSet containing multiple domains and IPs",
|
|
id: "2602:80a:6000::1,64.112.117.1,example.com,example.org",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(fmt.Sprintf("%s/%s", tc.limit, tc.desc), func(t *testing.T) {
|
|
t.Parallel()
|
|
err := validateIdForName(tc.limit, tc.id)
|
|
if tc.err != "" {
|
|
test.AssertError(t, err, "should have failed")
|
|
test.AssertContains(t, err.Error(), tc.err)
|
|
} else {
|
|
test.AssertNotError(t, err, "should have succeeded")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildBucketKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name Name
|
|
desc string
|
|
regId int64
|
|
singleIdent identifier.ACMEIdentifier
|
|
setOfIdents identifier.ACMEIdentifiers
|
|
subscriberIP netip.Addr
|
|
expectErrContains string
|
|
outputTest func(t *testing.T, key string)
|
|
}{
|
|
// NewRegistrationsPerIPAddress
|
|
{
|
|
name: NewRegistrationsPerIPAddress,
|
|
desc: "valid subscriber IPv4 address",
|
|
subscriberIP: netip.MustParseAddr("1.2.3.4"),
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:1.2.3.4", NewRegistrationsPerIPAddress), key)
|
|
},
|
|
},
|
|
{
|
|
name: NewRegistrationsPerIPAddress,
|
|
desc: "valid subscriber IPv6 address",
|
|
subscriberIP: netip.MustParseAddr("2001:db8::1"),
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:2001:db8::1", NewRegistrationsPerIPAddress), key)
|
|
},
|
|
},
|
|
// NewRegistrationsPerIPv6Range
|
|
{
|
|
name: NewRegistrationsPerIPv6Range,
|
|
desc: "valid subscriber IPv6 address",
|
|
subscriberIP: netip.MustParseAddr("2001:db8:abcd:12::1"),
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:2001:db8:abcd::/48", NewRegistrationsPerIPv6Range), key)
|
|
},
|
|
},
|
|
{
|
|
name: NewRegistrationsPerIPv6Range,
|
|
desc: "subscriber IPv4 given for subscriber IPv6 range limit",
|
|
subscriberIP: netip.MustParseAddr("1.2.3.4"),
|
|
expectErrContains: "requires an IPv6 address",
|
|
},
|
|
|
|
// NewOrdersPerAccount
|
|
{
|
|
name: NewOrdersPerAccount,
|
|
desc: "valid registration ID",
|
|
regId: 1337,
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:1337", NewOrdersPerAccount), key)
|
|
},
|
|
},
|
|
{
|
|
name: NewOrdersPerAccount,
|
|
desc: "registration ID missing",
|
|
expectErrContains: "regId is required",
|
|
},
|
|
|
|
// CertificatesPerDomain
|
|
{
|
|
name: CertificatesPerDomain,
|
|
desc: "DNS identifier → eTLD+1",
|
|
singleIdent: identifier.FromString("www.example.com"),
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:example.com", CertificatesPerDomain), key)
|
|
},
|
|
},
|
|
{
|
|
name: CertificatesPerDomain,
|
|
desc: "valid IPv4 address used as identifier",
|
|
singleIdent: identifier.FromString("5.6.7.8"),
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:5.6.7.8/32", CertificatesPerDomain), key)
|
|
},
|
|
},
|
|
{
|
|
name: CertificatesPerDomain,
|
|
desc: "valid IPv6 address used as identifier",
|
|
singleIdent: identifier.FromString("2001:db8::1"),
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:2001:db8::/64", CertificatesPerDomain), key)
|
|
},
|
|
},
|
|
{
|
|
name: CertificatesPerDomain,
|
|
desc: "identifier missing",
|
|
expectErrContains: "singleIdent is required",
|
|
},
|
|
|
|
// CertificatesPerFQDNSet
|
|
{
|
|
name: CertificatesPerFQDNSet,
|
|
desc: "multiple valid DNS identifiers",
|
|
setOfIdents: identifier.FromStringSlice([]string{"example.com", "example.org"}),
|
|
outputTest: func(t *testing.T, key string) {
|
|
if !strings.HasPrefix(key, fmt.Sprintf("%d:", CertificatesPerFQDNSet)) {
|
|
t.Errorf("expected key to start with %d: got %s", CertificatesPerFQDNSet, key)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: CertificatesPerFQDNSet,
|
|
desc: "multiple valid DNS and IP identifiers",
|
|
setOfIdents: identifier.FromStringSlice([]string{"example.net", "192.0.2.1", "2001:db8::1"}),
|
|
outputTest: func(t *testing.T, key string) {
|
|
if !strings.HasPrefix(key, fmt.Sprintf("%d:", CertificatesPerFQDNSet)) {
|
|
t.Errorf("expected key to start with %d: got %s", CertificatesPerFQDNSet, key)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: CertificatesPerFQDNSet,
|
|
desc: "identifiers missing",
|
|
expectErrContains: "setOfIdents is required",
|
|
},
|
|
|
|
// CertificatesPerDomainPerAccount
|
|
{
|
|
name: CertificatesPerDomainPerAccount,
|
|
desc: "only registration ID",
|
|
regId: 1337,
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:1337", CertificatesPerDomainPerAccount), key)
|
|
},
|
|
},
|
|
{
|
|
name: CertificatesPerDomainPerAccount,
|
|
desc: "registration ID and single DNS identifier provided",
|
|
regId: 1337,
|
|
singleIdent: identifier.FromString("example.com"),
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:1337:example.com", CertificatesPerDomainPerAccount), key)
|
|
},
|
|
},
|
|
{
|
|
name: CertificatesPerDomainPerAccount,
|
|
desc: "single DNS identifier provided without registration ID",
|
|
singleIdent: identifier.FromString("example.com"),
|
|
expectErrContains: "regId is required",
|
|
},
|
|
|
|
// FailedAuthorizationsPerDomainPerAccount
|
|
{
|
|
name: FailedAuthorizationsPerDomainPerAccount,
|
|
desc: "registration ID and single DNS identifier",
|
|
regId: 1337,
|
|
singleIdent: identifier.FromString("example.com"),
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:1337:example.com", FailedAuthorizationsPerDomainPerAccount), key)
|
|
},
|
|
},
|
|
{
|
|
name: FailedAuthorizationsPerDomainPerAccount,
|
|
desc: "only registration ID",
|
|
regId: 1337,
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:1337", FailedAuthorizationsPerDomainPerAccount), key)
|
|
},
|
|
},
|
|
|
|
// FailedAuthorizationsForPausingPerDomainPerAccount
|
|
{
|
|
name: FailedAuthorizationsForPausingPerDomainPerAccount,
|
|
desc: "registration ID and single DNS identifier",
|
|
regId: 1337,
|
|
singleIdent: identifier.FromString("example.com"),
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:1337:example.com", FailedAuthorizationsForPausingPerDomainPerAccount), key)
|
|
},
|
|
},
|
|
{
|
|
name: FailedAuthorizationsForPausingPerDomainPerAccount,
|
|
desc: "only registration ID",
|
|
regId: 1337,
|
|
outputTest: func(t *testing.T, key string) {
|
|
test.AssertEquals(t, fmt.Sprintf("%d:1337", FailedAuthorizationsForPausingPerDomainPerAccount), key)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(fmt.Sprintf("%s/%s", tc.name, tc.desc), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
key, err := BuildBucketKey(tc.name, tc.regId, tc.singleIdent, tc.setOfIdents, tc.subscriberIP)
|
|
if tc.expectErrContains != "" {
|
|
test.AssertError(t, err, "expected error")
|
|
test.AssertContains(t, err.Error(), tc.expectErrContains)
|
|
return
|
|
}
|
|
test.AssertNotError(t, err, "unexpected error")
|
|
tc.outputTest(t, key)
|
|
})
|
|
}
|
|
}
|