ratelimits: More compact overrides format (#7199)
Support a more compact format for supplying overrides to default rate limits. Fixes #7197
This commit is contained in:
parent
c21b376623
commit
8cd1e60abf
|
|
@ -70,8 +70,8 @@ func validateLimit(l limit) error {
|
|||
|
||||
type limits map[string]limit
|
||||
|
||||
// loadLimits marshals the YAML file at path into a map of limis.
|
||||
func loadLimits(path string) (limits, error) {
|
||||
// loadDefaults marshals the defaults YAML file at path into a map of limits.
|
||||
func loadDefaults(path string) (limits, error) {
|
||||
lm := make(limits)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
|
|
@ -84,6 +84,28 @@ func loadLimits(path string) (limits, error) {
|
|||
return lm, nil
|
||||
}
|
||||
|
||||
type overrideYAML struct {
|
||||
limit `yaml:",inline"`
|
||||
// Ids is a list of ids that this override applies to.
|
||||
Ids []string
|
||||
}
|
||||
|
||||
type overridesYAML []map[string]overrideYAML
|
||||
|
||||
// loadOverrides marshals the YAML file at path into a map of overrides.
|
||||
func loadOverrides(path string) (overridesYAML, error) {
|
||||
ov := overridesYAML{}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = strictyaml.Unmarshal(data, &ov)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ov, nil
|
||||
}
|
||||
|
||||
// parseOverrideNameId is broken out for ease of testing.
|
||||
func parseOverrideNameId(key string) (Name, string, error) {
|
||||
if !strings.Contains(key, ":") {
|
||||
|
|
@ -107,10 +129,12 @@ func parseOverrideNameId(key string) (Name, string, error) {
|
|||
return name, id, nil
|
||||
}
|
||||
|
||||
// loadAndParseOverrideLimits loads override limits from YAML, validates them,
|
||||
// and parses them into a map of limits keyed by 'Name:id'.
|
||||
func loadAndParseOverrideLimits(path string) (limits, error) {
|
||||
fromFile, err := loadLimits(path)
|
||||
// loadAndParseOverrideLimitsDeprecated loads override limits from YAML,
|
||||
// validates them, and parses them into a map of limits keyed by 'Name:id'.
|
||||
//
|
||||
// TODO(#7198): Remove this.
|
||||
func loadAndParseOverrideLimitsDeprecated(path string) (limits, error) {
|
||||
fromFile, err := loadDefaults(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -143,10 +167,53 @@ func loadAndParseOverrideLimits(path string) (limits, error) {
|
|||
return parsed, nil
|
||||
}
|
||||
|
||||
// loadAndParseOverrideLimits loads override limits from YAML. The YAML file
|
||||
// must be formatted as a list of maps, where each map has a single key
|
||||
// representing the limit name and a value that is a map containing the limit
|
||||
// fields and an additional 'ids' field that is a list of ids that this override
|
||||
// applies to.
|
||||
func loadAndParseOverrideLimits(path string) (limits, error) {
|
||||
fromFile, err := loadOverrides(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed := make(limits)
|
||||
|
||||
for _, ov := range fromFile {
|
||||
for k, v := range ov {
|
||||
err = validateLimit(v.limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating override limit %q: %w", k, err)
|
||||
}
|
||||
name, ok := stringToName[k]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unrecognized name %q in override limit, must be one of %v", k, limitNames)
|
||||
}
|
||||
v.limit.name = name
|
||||
v.limit.isOverride = true
|
||||
for _, id := range v.Ids {
|
||||
err = validateIdForName(name, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"validating name %s and id %q for override limit %q: %w", name, id, k, err)
|
||||
}
|
||||
if name == CertificatesPerFQDNSet {
|
||||
// FQDNSet hashes are not a nice thing to ask for in a
|
||||
// config file, so we allow the user to specify a
|
||||
// comma-separated list of FQDNs and compute the hash here.
|
||||
id = string(core.HashNames(strings.Split(id, ",")))
|
||||
}
|
||||
parsed[joinWithColon(name.EnumString(), id)] = precomputeLimit(v.limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// loadAndParseDefaultLimits loads default limits from YAML, validates them, and
|
||||
// parses them into a map of limits keyed by 'Name'.
|
||||
func loadAndParseDefaultLimits(path string) (limits, error) {
|
||||
fromFile, err := loadLimits(path)
|
||||
fromFile, err := loadDefaults(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -189,9 +256,13 @@ func newLimitRegistry(defaults, overrides string) (*limitRegistry, error) {
|
|||
return registry, nil
|
||||
}
|
||||
|
||||
registry.overrides, err = loadAndParseOverrideLimits(overrides)
|
||||
registry.overrides, err = loadAndParseOverrideLimitsDeprecated(overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// TODO(#7198): Leave this, remove the call above.
|
||||
registry.overrides, err = loadAndParseOverrideLimits(overrides)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return registry, nil
|
||||
|
|
|
|||
|
|
@ -134,6 +134,99 @@ func TestValidateIdForName(t *testing.T) {
|
|||
test.AssertError(t, err, "valid regId with empty domain")
|
||||
}
|
||||
|
||||
// TODO(#7198): Remove this.
|
||||
func TestLoadAndParseOverrideLimitsDeprecated(t *testing.T) {
|
||||
// Load a single valid override limit with Id formatted as 'enum:RegId'.
|
||||
l, err := loadAndParseOverrideLimitsDeprecated("testdata/working_override_deprecated.yml")
|
||||
test.AssertNotError(t, err, "valid single override limit")
|
||||
expectKey := joinWithColon(NewRegistrationsPerIPAddress.EnumString(), "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 = loadAndParseOverrideLimitsDeprecated("testdata/working_override_regid_domain_deprecated.yml")
|
||||
test.AssertNotError(t, err, "valid single override limit with Id of regId:domain")
|
||||
expectKey = joinWithColon(CertificatesPerDomain.EnumString(), "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 = loadAndParseOverrideLimitsDeprecated("testdata/working_overrides_deprecated.yml")
|
||||
expectKey1 := joinWithColon(NewRegistrationsPerIPAddress.EnumString(), "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 := joinWithColon(NewRegistrationsPerIPv6Range.EnumString(), "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 'fqdnSet' Ids, as follows:
|
||||
// - CertificatesPerFQDNSet:example.com
|
||||
// - CertificatesPerFQDNSet:example.com,example.net
|
||||
// - CertificatesPerFQDNSet: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 := joinWithColon(CertificatesPerFQDNSet.EnumString(), firstEntryFQDNSetHash)
|
||||
secondEntryKey := joinWithColon(CertificatesPerFQDNSet.EnumString(), secondEntryFQDNSetHash)
|
||||
thirdEntryKey := joinWithColon(CertificatesPerFQDNSet.EnumString(), thirdEntryFQDNSetHash)
|
||||
l, err = loadAndParseOverrideLimitsDeprecated("testdata/working_overrides_regid_fqdnset_deprecated.yml")
|
||||
test.AssertNotError(t, err, "multiple valid override limits with 'fqdnSet' Ids")
|
||||
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 = loadAndParseOverrideLimitsDeprecated("")
|
||||
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 = loadAndParseOverrideLimitsDeprecated("testdata/file_does_not_exist_deprecated.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 = loadAndParseOverrideLimitsDeprecated("testdata/busted_override_burst_0_deprecated.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 = loadAndParseOverrideLimitsDeprecated("testdata/busted_override_empty_id_deprecated.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 = loadAndParseOverrideLimitsDeprecated("testdata/busted_override_empty_name_deprecated.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 = loadAndParseOverrideLimitsDeprecated("testdata/busted_override_invalid_name_deprecated.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 = loadAndParseOverrideLimitsDeprecated("testdata/busted_overrides_second_entry_bad_name_deprecated.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 = loadAndParseOverrideLimitsDeprecated("testdata/busted_overrides_third_entry_bad_id_deprecated.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 TestLoadAndParseOverrideLimits(t *testing.T) {
|
||||
// Load a single valid override limit with Id formatted as 'enum:RegId'.
|
||||
l, err := loadAndParseOverrideLimits("testdata/working_override.yml")
|
||||
|
|
@ -143,7 +236,7 @@ func TestLoadAndParseOverrideLimits(t *testing.T) {
|
|||
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'.
|
||||
// Load single valid override limit with a 'domain' Id.
|
||||
l, err = loadAndParseOverrideLimits("testdata/working_override_regid_domain.yml")
|
||||
test.AssertNotError(t, err, "valid single override limit with Id of regId:domain")
|
||||
expectKey = joinWithColon(CertificatesPerDomain.EnumString(), "example.com")
|
||||
|
|
@ -151,10 +244,10 @@ func TestLoadAndParseOverrideLimits(t *testing.T) {
|
|||
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.
|
||||
// Load multiple valid override limits with 'regId' Ids.
|
||||
l, err = loadAndParseOverrideLimits("testdata/working_overrides.yml")
|
||||
expectKey1 := joinWithColon(NewRegistrationsPerIPAddress.EnumString(), "10.0.0.2")
|
||||
test.AssertNotError(t, err, "multiple valid override limits")
|
||||
expectKey1 := joinWithColon(NewRegistrationsPerIPAddress.EnumString(), "10.0.0.2")
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
NewRegistrationsPerIPAddress:10.0.0.2:
|
||||
burst: 0
|
||||
count: 40
|
||||
period: 1s
|
||||
- NewRegistrationsPerIPAddress:
|
||||
burst: 0
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: [10.0.0.2]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
NewRegistrationsPerIPAddress:10.0.0.2:
|
||||
burst: 0
|
||||
count: 40
|
||||
period: 1s
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
"UsageRequestsPerIPv10Address:":
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
- UsageRequestsPerIPv10Address:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: []
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
"UsageRequestsPerIPv10Address:":
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
":10.0.0.2":
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
- "":
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: [10.0.0.2]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
":10.0.0.2":
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
UsageRequestsPerIPv10Address:10.0.0.2:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
- UsageRequestsPerIPv10Address:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: [10.0.0.2]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
UsageRequestsPerIPv10Address:10.0.0.2:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
NewRegistrationsPerIPAddress:10.0.0.2:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
UsageRequestsPerIPv10Address:10.0.0.5:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
- NewRegistrationsPerIPAddress:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: [10.0.0.2]
|
||||
- UsageRequestsPerIPv10Address:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: [10.0.0.5]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
NewRegistrationsPerIPAddress:10.0.0.2:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
UsageRequestsPerIPv10Address:10.0.0.5:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
|
|
@ -1,12 +1,5 @@
|
|||
NewRegistrationsPerIPAddress:10.0.0.2:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
NewRegistrationsPerIPAddress:10.0.0.5:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
NewRegistrationsPerIPAddress:lol:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
- NewRegistrationsPerIPAddress:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: [10.0.0.5, 10.0.0.2, lol]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
NewRegistrationsPerIPAddress:10.0.0.2:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
NewRegistrationsPerIPAddress:10.0.0.5:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
NewRegistrationsPerIPAddress:lol:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
NewRegistrationsPerIPAddress:10.0.0.2:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
- NewRegistrationsPerIPAddress:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: [10.0.0.2]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
NewRegistrationsPerIPAddress:10.0.0.2:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
CertificatesPerDomain:example.com:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
- CertificatesPerDomain:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: [example.com]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
CertificatesPerDomain:example.com:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
NewRegistrationsPerIPAddress:10.0.0.2:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
NewRegistrationsPerIPv6Range:2001:0db8:0000::/48:
|
||||
burst: 50
|
||||
count: 50
|
||||
period: 2s
|
||||
- NewRegistrationsPerIPAddress:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: [10.0.0.2]
|
||||
- NewRegistrationsPerIPv6Range:
|
||||
burst: 50
|
||||
count: 50
|
||||
period: 2s
|
||||
ids: [2001:0db8:0000::/48]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
NewRegistrationsPerIPAddress:10.0.0.2:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
NewRegistrationsPerIPv6Range:2001:0db8:0000::/48:
|
||||
burst: 50
|
||||
count: 50
|
||||
period: 2s
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
CertificatesPerFQDNSet:example.com:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
CertificatesPerFQDNSet:example.com,example.net:
|
||||
burst: 50
|
||||
count: 50
|
||||
period: 2s
|
||||
CertificatesPerFQDNSet:example.com,example.net,example.org:
|
||||
burst: 60
|
||||
count: 60
|
||||
period: 3s
|
||||
- CertificatesPerFQDNSet:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
ids: [example.com]
|
||||
- CertificatesPerFQDNSet:
|
||||
burst: 50
|
||||
count: 50
|
||||
period: 2s
|
||||
ids: ["example.com,example.net"]
|
||||
- CertificatesPerFQDNSet:
|
||||
burst: 60
|
||||
count: 60
|
||||
period: 3s
|
||||
ids: ["example.com,example.net,example.org"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
CertificatesPerFQDNSet:example.com:
|
||||
burst: 40
|
||||
count: 40
|
||||
period: 1s
|
||||
CertificatesPerFQDNSet:example.com,example.net:
|
||||
burst: 50
|
||||
count: 50
|
||||
period: 2s
|
||||
CertificatesPerFQDNSet:example.com,example.net,example.org:
|
||||
burst: 60
|
||||
count: 60
|
||||
period: 3s
|
||||
|
|
@ -1 +1,5 @@
|
|||
NewRegistrationsPerIPAddress:127.0.0.1: { burst: 1000000, count: 1000000, period: 168h }
|
||||
- NewRegistrationsPerIPAddress:
|
||||
burst: 1000000
|
||||
count: 1000000
|
||||
period: 168h
|
||||
ids: [127.0.0.1]
|
||||
|
|
|
|||
Loading…
Reference in New Issue