334 lines
15 KiB
Go
334 lines
15 KiB
Go
package ratelimits
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/letsencrypt/boulder/config"
|
|
"github.com/letsencrypt/boulder/core"
|
|
"github.com/letsencrypt/boulder/test"
|
|
)
|
|
|
|
func Test_parseOverrideNameId(t *testing.T) {
|
|
newRegistrationsPerIPAddressStr := nameToString[NewRegistrationsPerIPAddress]
|
|
newRegistrationsPerIPv6RangeStr := nameToString[NewRegistrationsPerIPv6Range]
|
|
|
|
// 'enum:ipv4'
|
|
// Valid IPv4 address.
|
|
name, id, err := parseOverrideNameId(newRegistrationsPerIPAddressStr + ":10.0.0.1")
|
|
test.AssertNotError(t, err, "should not error")
|
|
test.AssertEquals(t, name, NewRegistrationsPerIPAddress)
|
|
test.AssertEquals(t, id, "10.0.0.1")
|
|
|
|
// 'enum:ipv6range'
|
|
// Valid IPv6 address range.
|
|
name, id, err = parseOverrideNameId(newRegistrationsPerIPv6RangeStr + ":2001:0db8:0000::/48")
|
|
test.AssertNotError(t, err, "should not error")
|
|
test.AssertEquals(t, name, NewRegistrationsPerIPv6Range)
|
|
test.AssertEquals(t, id, "2001:0db8:0000::/48")
|
|
|
|
// Missing colon (this should never happen but we should avoid panicking).
|
|
_, _, err = parseOverrideNameId(newRegistrationsPerIPAddressStr + "10.0.0.1")
|
|
test.AssertError(t, err, "missing colon")
|
|
|
|
// Empty string.
|
|
_, _, err = parseOverrideNameId("")
|
|
test.AssertError(t, err, "empty string")
|
|
|
|
// Only a colon.
|
|
_, _, err = parseOverrideNameId(newRegistrationsPerIPAddressStr + ":")
|
|
test.AssertError(t, err, "only a colon")
|
|
|
|
// Invalid enum.
|
|
_, _, err = parseOverrideNameId("lol:noexist")
|
|
test.AssertError(t, err, "invalid enum")
|
|
}
|
|
|
|
func Test_validateLimit(t *testing.T) {
|
|
err := validateLimit(limit{Burst: 1, Count: 1, Period: config.Duration{Duration: time.Second}})
|
|
test.AssertNotError(t, err, "valid limit")
|
|
|
|
// All of the following are invalid.
|
|
for _, l := range []limit{
|
|
{Burst: 0, Count: 1, Period: config.Duration{Duration: time.Second}},
|
|
{Burst: 1, Count: 0, Period: config.Duration{Duration: time.Second}},
|
|
{Burst: 1, Count: 1, Period: config.Duration{Duration: 0}},
|
|
} {
|
|
err = validateLimit(l)
|
|
test.AssertError(t, err, "limit should be invalid")
|
|
}
|
|
}
|
|
|
|
func Test_validateIdForName(t *testing.T) {
|
|
// 'enum:ipAddress'
|
|
// Valid IPv4 address.
|
|
err := validateIdForName(NewRegistrationsPerIPAddress, "10.0.0.1")
|
|
test.AssertNotError(t, err, "valid ipv4 address")
|
|
|
|
// 'enum:ipAddress'
|
|
// Valid IPv6 address.
|
|
err = validateIdForName(NewRegistrationsPerIPAddress, "2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
|
test.AssertNotError(t, err, "valid ipv6 address")
|
|
|
|
// 'enum:ipv6rangeCIDR'
|
|
// Valid IPv6 address range.
|
|
err = validateIdForName(NewRegistrationsPerIPv6Range, "2001:0db8:0000::/48")
|
|
test.AssertNotError(t, err, "should not error")
|
|
|
|
// 'enum:regId'
|
|
// Valid regId.
|
|
err = validateIdForName(NewOrdersPerAccount, "1234567890")
|
|
test.AssertNotError(t, err, "valid regId")
|
|
|
|
// 'enum:regId:domain'
|
|
// Valid regId and domain.
|
|
err = validateIdForName(CertificatesPerDomainPerAccount, "1234567890:example.com")
|
|
test.AssertNotError(t, err, "valid regId and domain")
|
|
|
|
// 'enum:regId:fqdnSet'
|
|
// Valid regId and FQDN set containing a single domain.
|
|
err = validateIdForName(CertificatesPerFQDNSetPerAccount, "1234567890:example.com")
|
|
test.AssertNotError(t, err, "valid regId and FQDN set containing a single domain")
|
|
|
|
// 'enum:regId:fqdnSet'
|
|
// Valid regId and FQDN set containing multiple domains.
|
|
err = validateIdForName(CertificatesPerFQDNSetPerAccount, "1234567890:example.com,example.org")
|
|
test.AssertNotError(t, err, "valid regId and FQDN set containing multiple domains")
|
|
|
|
// Empty string.
|
|
err = validateIdForName(NewRegistrationsPerIPAddress, "")
|
|
test.AssertError(t, err, "Id is an empty string")
|
|
|
|
// One space.
|
|
err = validateIdForName(NewRegistrationsPerIPAddress, " ")
|
|
test.AssertError(t, err, "Id is a single space")
|
|
|
|
// Invalid IPv4 address.
|
|
err = validateIdForName(NewRegistrationsPerIPAddress, "10.0.0.9000")
|
|
test.AssertError(t, err, "invalid IPv4 address")
|
|
|
|
// Invalid IPv6 address.
|
|
err = validateIdForName(NewRegistrationsPerIPAddress, "2001:0db8:85a3:0000:0000:8a2e:0370:7334:9000")
|
|
test.AssertError(t, err, "invalid IPv6 address")
|
|
|
|
// Invalid IPv6 CIDR range.
|
|
err = validateIdForName(NewRegistrationsPerIPv6Range, "2001:0db8:0000::/128")
|
|
test.AssertError(t, err, "invalid IPv6 CIDR range")
|
|
|
|
// Invalid IPv6 CIDR.
|
|
err = validateIdForName(NewRegistrationsPerIPv6Range, "2001:0db8:0000::/48/48")
|
|
test.AssertError(t, err, "invalid IPv6 CIDR")
|
|
|
|
// IPv4 CIDR when we expect IPv6 CIDR range.
|
|
err = validateIdForName(NewRegistrationsPerIPv6Range, "10.0.0.0/16")
|
|
test.AssertError(t, err, "ipv4 cidr when we expect ipv6 cidr range")
|
|
|
|
// Invalid regId.
|
|
err = validateIdForName(NewOrdersPerAccount, "lol")
|
|
test.AssertError(t, err, "invalid regId")
|
|
|
|
// Invalid regId with good domain.
|
|
err = validateIdForName(CertificatesPerDomainPerAccount, "lol:example.com")
|
|
test.AssertError(t, err, "invalid regId with good domain")
|
|
|
|
// Valid regId with bad domain.
|
|
err = validateIdForName(CertificatesPerDomainPerAccount, "1234567890:lol")
|
|
test.AssertError(t, err, "valid regId with bad domain")
|
|
|
|
// Empty regId with good domain.
|
|
err = validateIdForName(CertificatesPerDomainPerAccount, ":lol")
|
|
test.AssertError(t, err, "valid regId with bad domain")
|
|
|
|
// Valid regId with empty domain.
|
|
err = validateIdForName(CertificatesPerDomainPerAccount, "1234567890:")
|
|
test.AssertError(t, err, "valid regId with empty domain")
|
|
|
|
// Empty regId with empty domain, no separator.
|
|
err = validateIdForName(CertificatesPerDomainPerAccount, "")
|
|
test.AssertError(t, err, "empty regId with empty domain, no separator")
|
|
|
|
// Instead of anything we would expect, we get lol.
|
|
err = validateIdForName(CertificatesPerDomainPerAccount, "lol")
|
|
test.AssertError(t, err, "instead of anything we would expect, just lol")
|
|
|
|
// Valid regId with good domain and a secret third separator.
|
|
err = validateIdForName(CertificatesPerDomainPerAccount, "1234567890:example.com:lol")
|
|
test.AssertError(t, err, "valid regId with good domain and a secret third separator")
|
|
|
|
// Valid regId with bad FQDN set.
|
|
err = validateIdForName(CertificatesPerFQDNSetPerAccount, "1234567890:lol..99")
|
|
test.AssertError(t, err, "valid regId with bad FQDN set")
|
|
|
|
// Bad regId with good FQDN set.
|
|
err = validateIdForName(CertificatesPerFQDNSetPerAccount, "lol:example.com,example.org")
|
|
test.AssertError(t, err, "bad regId with good FQDN set")
|
|
|
|
// Empty regId with good FQDN set.
|
|
err = validateIdForName(CertificatesPerFQDNSetPerAccount, ":example.com,example.org")
|
|
test.AssertError(t, err, "empty regId with good FQDN set")
|
|
|
|
// Good regId with empty FQDN set.
|
|
err = validateIdForName(CertificatesPerFQDNSetPerAccount, "1234567890:")
|
|
test.AssertError(t, err, "good regId with empty FQDN set")
|
|
|
|
// Empty regId with empty FQDN set, no separator.
|
|
err = validateIdForName(CertificatesPerFQDNSetPerAccount, "")
|
|
test.AssertError(t, err, "empty regId with empty FQDN set, no separator")
|
|
|
|
// Instead of anything we would expect, just lol.
|
|
err = validateIdForName(CertificatesPerFQDNSetPerAccount, "lol")
|
|
test.AssertError(t, err, "instead of anything we would expect, just lol")
|
|
|
|
// Valid regId with good FQDN set and a secret third separator.
|
|
err = validateIdForName(CertificatesPerFQDNSetPerAccount, "1234567890:example.com,example.org:lol")
|
|
test.AssertError(t, err, "valid regId with good FQDN set and a secret third separator")
|
|
}
|
|
|
|
func Test_loadAndParseOverrideLimits(t *testing.T) {
|
|
newRegistrationsPerIPAddressEnumStr := nameToEnumString(NewRegistrationsPerIPAddress)
|
|
newRegistrationsPerIPv6RangeEnumStr := nameToEnumString(NewRegistrationsPerIPv6Range)
|
|
|
|
// Load a single valid override limit with Id formatted as 'enum:RegId'.
|
|
l, err := loadAndParseOverrideLimits("testdata/working_override.yml")
|
|
test.AssertNotError(t, err, "valid single override limit")
|
|
expectKey := newRegistrationsPerIPAddressEnumStr + ":" + "10.0.0.2"
|
|
test.AssertEquals(t, l[expectKey].Burst, int64(40))
|
|
test.AssertEquals(t, l[expectKey].Count, int64(40))
|
|
test.AssertEquals(t, l[expectKey].Period.Duration, time.Second)
|
|
|
|
// Load single valid override limit with Id formatted as 'regId:domain'.
|
|
l, err = loadAndParseOverrideLimits("testdata/working_override_regid_domain.yml")
|
|
test.AssertNotError(t, err, "valid single override limit with Id of regId:domain")
|
|
expectKey = nameToEnumString(CertificatesPerDomainPerAccount) + ":" + "12345678:example.com"
|
|
test.AssertEquals(t, l[expectKey].Burst, int64(40))
|
|
test.AssertEquals(t, l[expectKey].Count, int64(40))
|
|
test.AssertEquals(t, l[expectKey].Period.Duration, time.Second)
|
|
|
|
// Load multiple valid override limits with 'enum:RegId' Ids.
|
|
l, err = loadAndParseOverrideLimits("testdata/working_overrides.yml")
|
|
expectKey1 := newRegistrationsPerIPAddressEnumStr + ":" + "10.0.0.2"
|
|
test.AssertNotError(t, err, "multiple valid override limits")
|
|
test.AssertEquals(t, l[expectKey1].Burst, int64(40))
|
|
test.AssertEquals(t, l[expectKey1].Count, int64(40))
|
|
test.AssertEquals(t, l[expectKey1].Period.Duration, time.Second)
|
|
expectKey2 := newRegistrationsPerIPv6RangeEnumStr + ":" + "2001:0db8:0000::/48"
|
|
test.AssertEquals(t, l[expectKey2].Burst, int64(50))
|
|
test.AssertEquals(t, l[expectKey2].Count, int64(50))
|
|
test.AssertEquals(t, l[expectKey2].Period.Duration, time.Second*2)
|
|
|
|
// Load multiple valid override limits with 'regID:fqdnSet' Ids as follows:
|
|
// - CertificatesPerFQDNSetPerAccount:12345678:example.com
|
|
// - CertificatesPerFQDNSetPerAccount:12345678:example.com,example.net
|
|
// - CertificatesPerFQDNSetPerAccount:12345678:example.com,example.net,example.org
|
|
firstEntryFQDNSetHash := string(core.HashNames([]string{"example.com"}))
|
|
secondEntryFQDNSetHash := string(core.HashNames([]string{"example.com", "example.net"}))
|
|
thirdEntryFQDNSetHash := string(core.HashNames([]string{"example.com", "example.net", "example.org"}))
|
|
firstEntryKey := nameToEnumString(CertificatesPerFQDNSetPerAccount) + ":" + "12345678:" + firstEntryFQDNSetHash
|
|
secondEntryKey := nameToEnumString(CertificatesPerFQDNSetPerAccount) + ":" + "12345678:" + secondEntryFQDNSetHash
|
|
thirdEntryKey := nameToEnumString(CertificatesPerFQDNSetPerAccount) + ":" + "12345678:" + thirdEntryFQDNSetHash
|
|
l, err = loadAndParseOverrideLimits("testdata/working_overrides_regid_fqdnset.yml")
|
|
test.AssertNotError(t, err, "multiple valid override limits with Id of regId:fqdnSets")
|
|
test.AssertEquals(t, l[firstEntryKey].Burst, int64(40))
|
|
test.AssertEquals(t, l[firstEntryKey].Count, int64(40))
|
|
test.AssertEquals(t, l[firstEntryKey].Period.Duration, time.Second)
|
|
test.AssertEquals(t, l[secondEntryKey].Burst, int64(50))
|
|
test.AssertEquals(t, l[secondEntryKey].Count, int64(50))
|
|
test.AssertEquals(t, l[secondEntryKey].Period.Duration, time.Second*2)
|
|
test.AssertEquals(t, l[thirdEntryKey].Burst, int64(60))
|
|
test.AssertEquals(t, l[thirdEntryKey].Count, int64(60))
|
|
test.AssertEquals(t, l[thirdEntryKey].Period.Duration, time.Second*3)
|
|
|
|
// Path is empty string.
|
|
_, err = loadAndParseOverrideLimits("")
|
|
test.AssertError(t, err, "path is empty string")
|
|
test.Assert(t, os.IsNotExist(err), "path is empty string")
|
|
|
|
// Path to file which does not exist.
|
|
_, err = loadAndParseOverrideLimits("testdata/file_does_not_exist.yml")
|
|
test.AssertError(t, err, "a file that does not exist ")
|
|
test.Assert(t, os.IsNotExist(err), "test file should not exist")
|
|
|
|
// Burst cannot be 0.
|
|
_, err = loadAndParseOverrideLimits("testdata/busted_override_burst_0.yml")
|
|
test.AssertError(t, err, "single override limit with burst=0")
|
|
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
|
|
|
// Id cannot be empty.
|
|
_, err = loadAndParseOverrideLimits("testdata/busted_override_empty_id.yml")
|
|
test.AssertError(t, err, "single override limit with empty id")
|
|
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
|
|
|
// Name cannot be empty.
|
|
_, err = loadAndParseOverrideLimits("testdata/busted_override_empty_name.yml")
|
|
test.AssertError(t, err, "single override limit with empty name")
|
|
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
|
|
|
// Name must be a string representation of a valid Name enumeration.
|
|
_, err = loadAndParseOverrideLimits("testdata/busted_override_invalid_name.yml")
|
|
test.AssertError(t, err, "single override limit with invalid name")
|
|
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
|
|
|
// Multiple entries, second entry has a bad name.
|
|
_, err = loadAndParseOverrideLimits("testdata/busted_overrides_second_entry_bad_name.yml")
|
|
test.AssertError(t, err, "multiple override limits, second entry is bad")
|
|
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
|
|
|
// Multiple entries, third entry has id of "lol", instead of an IPv4 address.
|
|
_, err = loadAndParseOverrideLimits("testdata/busted_overrides_third_entry_bad_id.yml")
|
|
test.AssertError(t, err, "multiple override limits, third entry has bad Id value")
|
|
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
|
}
|
|
|
|
func Test_loadAndParseDefaultLimits(t *testing.T) {
|
|
newRestistrationsPerIPv4AddressEnumStr := nameToEnumString(NewRegistrationsPerIPAddress)
|
|
newRegistrationsPerIPv6RangeEnumStr := nameToEnumString(NewRegistrationsPerIPv6Range)
|
|
|
|
// Load a single valid default limit.
|
|
l, err := loadAndParseDefaultLimits("testdata/working_default.yml")
|
|
test.AssertNotError(t, err, "valid single default limit")
|
|
test.AssertEquals(t, l[newRestistrationsPerIPv4AddressEnumStr].Burst, int64(20))
|
|
test.AssertEquals(t, l[newRestistrationsPerIPv4AddressEnumStr].Count, int64(20))
|
|
test.AssertEquals(t, l[newRestistrationsPerIPv4AddressEnumStr].Period.Duration, time.Second)
|
|
|
|
// Load multiple valid default limits.
|
|
l, err = loadAndParseDefaultLimits("testdata/working_defaults.yml")
|
|
test.AssertNotError(t, err, "multiple valid default limits")
|
|
test.AssertEquals(t, l[newRestistrationsPerIPv4AddressEnumStr].Burst, int64(20))
|
|
test.AssertEquals(t, l[newRestistrationsPerIPv4AddressEnumStr].Count, int64(20))
|
|
test.AssertEquals(t, l[newRestistrationsPerIPv4AddressEnumStr].Period.Duration, time.Second)
|
|
test.AssertEquals(t, l[newRegistrationsPerIPv6RangeEnumStr].Burst, int64(30))
|
|
test.AssertEquals(t, l[newRegistrationsPerIPv6RangeEnumStr].Count, int64(30))
|
|
test.AssertEquals(t, l[newRegistrationsPerIPv6RangeEnumStr].Period.Duration, time.Second*2)
|
|
|
|
// Path is empty string.
|
|
_, err = loadAndParseDefaultLimits("")
|
|
test.AssertError(t, err, "path is empty string")
|
|
test.Assert(t, os.IsNotExist(err), "path is empty string")
|
|
|
|
// Path to file which does not exist.
|
|
_, err = loadAndParseDefaultLimits("testdata/file_does_not_exist.yml")
|
|
test.AssertError(t, err, "a file that does not exist")
|
|
test.Assert(t, os.IsNotExist(err), "test file should not exist")
|
|
|
|
// Burst cannot be 0.
|
|
_, err = loadAndParseDefaultLimits("testdata/busted_default_burst_0.yml")
|
|
test.AssertError(t, err, "single default limit with burst=0")
|
|
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
|
|
|
// Name cannot be empty.
|
|
_, err = loadAndParseDefaultLimits("testdata/busted_default_empty_name.yml")
|
|
test.AssertError(t, err, "single default limit with empty name")
|
|
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
|
|
|
// Name must be a string representation of a valid Name enumeration.
|
|
_, err = loadAndParseDefaultLimits("testdata/busted_default_invalid_name.yml")
|
|
test.AssertError(t, err, "single default limit with invalid name")
|
|
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
|
|
|
// Multiple entries, second entry has a bad name.
|
|
_, err = loadAndParseDefaultLimits("testdata/busted_defaults_second_entry_bad_name.yml")
|
|
test.AssertError(t, err, "multiple default limits, one is bad")
|
|
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
|
}
|