diff --git a/cmd/boulder-ra/main.go b/cmd/boulder-ra/main.go index dd30f78cdcc..3b71ac07390 100644 --- a/cmd/boulder-ra/main.go +++ b/cmd/boulder-ra/main.go @@ -69,9 +69,10 @@ type Config struct { // Overrides is a path to a YAML file containing overrides for the // default rate limits. See: ratelimits/README.md for details. If - // this field is not set, all requesters will be subject to the - // default rate limits. Overrides passed in this file must be - // identical to those in the WFE. + // neither this field nor the OverridesFromDB feature flag is set, + // all requesters will be subject to the default rate limits. + // Overrides passed in this file must be identical to those in the + // WFE. // // Note: At this time, only the Failed Authorizations overrides are // necessary in the RA. @@ -271,8 +272,15 @@ func main() { source := ratelimits.NewRedisSource(limiterRedis.Ring, clk, scope) limiter, err = ratelimits.NewLimiter(clk, source, scope) cmd.FailOnError(err, "Failed to create rate limiter") - txnBuilder, err = ratelimits.NewTransactionBuilderFromFiles(c.RA.Limiter.Defaults, c.RA.Limiter.Overrides) + if c.RA.Features.OverridesFromDB { + saroc := sapb.NewStorageAuthorityReadOnlyClient(saConn) + txnBuilder, err = ratelimits.NewTransactionBuilderFromDatabase(c.RA.Limiter.Defaults, saroc.GetEnabledRateLimitOverrides, scope, logger) + } else { + txnBuilder, err = ratelimits.NewTransactionBuilderFromFiles(c.RA.Limiter.Defaults, c.RA.Limiter.Overrides, scope, logger) + } cmd.FailOnError(err, "Failed to create rate limits transaction builder") + overrideRefresherShutdown := txnBuilder.NewRefresher() + defer overrideRefresherShutdown() } rai := ra.NewRegistrationAuthorityImpl( diff --git a/cmd/boulder-wfe2/main.go b/cmd/boulder-wfe2/main.go index e82da4b2589..d3854d67524 100644 --- a/cmd/boulder-wfe2/main.go +++ b/cmd/boulder-wfe2/main.go @@ -151,10 +151,10 @@ type Config struct { // Overrides is a path to a YAML file containing overrides for the // default rate limits. See: ratelimits/README.md for details. If - // this field is not set, all requesters will be subject to the - // default rate limits. Overrides for the Failed Authorizations - // overrides passed in this file must be identical to those in the - // RA. + // neither this field nor the OverridesFromDB feature flag is set, + // all requesters will be subject to the default rate limits. + // Overrides for the Failed Authorizations overrides passed in this + // file must be identical to those in the RA. Overrides string } @@ -326,6 +326,7 @@ func main() { var limiter *ratelimits.Limiter var txnBuilder *ratelimits.TransactionBuilder var limiterRedis *bredis.Ring + overridesRefresherShutdown := func() {} if c.WFE.Limiter.Defaults != "" { // Setup rate limiting. limiterRedis, err = bredis.NewRingFromConfig(*c.WFE.Limiter.Redis, stats, logger) @@ -334,8 +335,13 @@ func main() { source := ratelimits.NewRedisSource(limiterRedis.Ring, clk, stats) limiter, err = ratelimits.NewLimiter(clk, source, stats) cmd.FailOnError(err, "Failed to create rate limiter") - txnBuilder, err = ratelimits.NewTransactionBuilderFromFiles(c.WFE.Limiter.Defaults, c.WFE.Limiter.Overrides) + if c.WFE.Features.OverridesFromDB { + txnBuilder, err = ratelimits.NewTransactionBuilderFromDatabase(c.WFE.Limiter.Defaults, sac.GetEnabledRateLimitOverrides, stats, logger) + } else { + txnBuilder, err = ratelimits.NewTransactionBuilderFromFiles(c.WFE.Limiter.Defaults, c.WFE.Limiter.Overrides, stats, logger) + } cmd.FailOnError(err, "Failed to create rate limits transaction builder") + overridesRefresherShutdown = txnBuilder.NewRefresher() } var accountGetter wfe2.AccountGetter @@ -413,6 +419,7 @@ func main() { defer func() { ctx, cancel := context.WithTimeout(context.Background(), c.WFE.ShutdownStopTimeout.Duration) defer cancel() + overridesRefresherShutdown() _ = srv.Shutdown(ctx) _ = tlsSrv.Shutdown(ctx) limiterRedis.StopLookups() diff --git a/cmd/sfe/main.go b/cmd/sfe/main.go index 3c7b91109df..365dee6aba3 100644 --- a/cmd/sfe/main.go +++ b/cmd/sfe/main.go @@ -216,7 +216,7 @@ func main() { source := ratelimits.NewRedisSource(limiterRedis.Ring, clk, stats) limiter, err = ratelimits.NewLimiter(clk, source, stats) cmd.FailOnError(err, "Failed to create rate limiter") - txnBuilder, err = ratelimits.NewTransactionBuilderFromFiles(c.SFE.Limiter.Defaults, "") + txnBuilder, err = ratelimits.NewTransactionBuilderFromFiles(c.SFE.Limiter.Defaults, "", stats, logger) cmd.FailOnError(err, "Failed to create rate limits transaction builder") } diff --git a/features/features.go b/features/features.go index 0c922f3eb7b..0034e0d629e 100644 --- a/features/features.go +++ b/features/features.go @@ -86,6 +86,10 @@ type Config struct { // during certificate issuance. This flag must be set to true in the // RA, VA, and WFE2 services for full functionality. DNSAccount01Enabled bool + + // OverridesFromDB causes the WFE and RA to retrieve rate limit overrides + // from the database, instead of from a file. + OverridesFromDB bool } var fMu = new(sync.RWMutex) diff --git a/ra/ra_test.go b/ra/ra_test.go index 88fa3f01537..57d9e41e165 100644 --- a/ra/ra_test.go +++ b/ra/ra_test.go @@ -363,7 +363,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, sapb.StorageAutho rlSource := ratelimits.NewInmemSource() limiter, err := ratelimits.NewLimiter(fc, rlSource, stats) test.AssertNotError(t, err, "making limiter") - txnBuilder, err := ratelimits.NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "") + txnBuilder, err := ratelimits.NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "", metrics.NoopRegisterer, log) test.AssertNotError(t, err, "making transaction composer") testKeyPolicy, err := goodkey.NewPolicy(nil, nil) @@ -708,7 +708,7 @@ func TestPerformValidation_FailedValidationsTriggerPauseIdentifiersRatelimit(t * Burst: 1, Count: 1, Period: config.Duration{Duration: time.Hour * 24}}, - }) + }, nil, metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "making transaction composer") ra.txnBuilder = txnBuilder @@ -967,7 +967,7 @@ func TestDeactivateAuthorization_Pausing(t *testing.T) { Burst: 1, Count: 1, Period: config.Duration{Duration: time.Hour * 24}}, - }) + }, nil, metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "making transaction composer") ra.txnBuilder = txnBuilder diff --git a/ratelimits/limit.go b/ratelimits/limit.go index f87e6e3ee97..6f4cdb3a1da 100644 --- a/ratelimits/limit.go +++ b/ratelimits/limit.go @@ -1,6 +1,7 @@ package ratelimits import ( + "context" "encoding/csv" "errors" "fmt" @@ -9,10 +10,14 @@ import ( "sort" "strconv" "strings" + "time" + + "github.com/prometheus/client_golang/prometheus" "github.com/letsencrypt/boulder/config" "github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/identifier" + blog "github.com/letsencrypt/boulder/log" "github.com/letsencrypt/boulder/strictyaml" ) @@ -105,8 +110,9 @@ func ValidateLimit(l *Limit) error { type Limits map[string]*Limit -// loadDefaults marshals the defaults YAML file at path into a map of limits. -func loadDefaults(path string) (LimitConfigs, error) { +// loadDefaultsFromFile marshals the defaults YAML file at path into a map of +// limits. +func loadDefaultsFromFile(path string) (LimitConfigs, error) { lm := make(LimitConfigs) data, err := os.ReadFile(path) if err != nil { @@ -132,8 +138,8 @@ type overrideYAML struct { type overridesYAML []map[string]overrideYAML -// loadOverrides marshals the YAML file at path into a map of overrides. -func loadOverrides(path string) (overridesYAML, error) { +// loadOverridesFromFile marshals the YAML file at path into a map of overrides. +func loadOverridesFromFile(path string) (overridesYAML, error) { ov := overridesYAML{} data, err := os.ReadFile(path) if err != nil { @@ -202,6 +208,9 @@ func parseOverrideNameEnumId(key string) (Name, string, error) { // 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. +// +// When the OverridesFromDB feature flag is on, hydrateOverrideLimit is used as +// this method's equivalent. func parseOverrideLimits(newOverridesYAML overridesYAML) (Limits, error) { parsed := make(Limits) @@ -263,6 +272,49 @@ func parseOverrideLimits(newOverridesYAML overridesYAML) (Limits, error) { return parsed, nil } +// hydrateOverrideLimit validates the limit Name, values, and override bucket +// key. It returns the correct bucket key to use in-memory. It should be called +// when loading overrides from the database. +// +// When the OverridesFromDB feature flag is off, parseOverrideLimits is used as +// this method's equivalent. +func hydrateOverrideLimit(bucketKey string, limit *Limit) (string, error) { + if !limit.Name.isValid() { + return "", fmt.Errorf("unrecognized limit name %d", limit.Name) + } + + err := ValidateLimit(limit) + if err != nil { + return "", err + } + + err = validateIdForName(limit.Name, bucketKey) + if err != nil { + return "", err + } + + // Interpret and compute a new in-memory bucket key for two rate limits, + // since their keys aren't nice to store in a config file or database entry. + switch limit.Name { + case CertificatesPerDomain: + // Convert IP addresses to their covering /32 (IPv4) or /64 + // (IPv6) prefixes in CIDR notation. + ip, err := netip.ParseAddr(bucketKey) + if err == nil { + prefix, err := coveringIPPrefix(limit.Name, ip) + if err != nil { + return "", fmt.Errorf("computing prefix for IP address %q: %w", bucketKey, err) + } + bucketKey = prefix.String() + } + case CertificatesPerFQDNSet: + // Compute the hash of a comma-separated list of identifier values. + bucketKey = fmt.Sprintf("%x", core.HashIdentifiers(identifier.FromStringSlice(strings.Split(bucketKey, ",")))) + } + + return bucketKey, nil +} + // parseDefaultLimits validates a map of default limits and rekeys it by 'Name'. func parseDefaultLimits(newDefaultLimits LimitConfigs) (Limits, error) { parsed := make(Limits) @@ -291,47 +343,24 @@ func parseDefaultLimits(newDefaultLimits LimitConfigs) (Limits, error) { return parsed, nil } +type OverridesRefresher func(context.Context, prometheus.Gauge, blog.Logger) (Limits, error) + type limitRegistry struct { // defaults stores default limits by 'name'. defaults Limits // overrides stores override limits by 'name:id'. overrides Limits -} - -func newLimitRegistryFromFiles(defaults, overrides string) (*limitRegistry, error) { - defaultsData, err := loadDefaults(defaults) - if err != nil { - return nil, err - } - if overrides == "" { - return newLimitRegistry(defaultsData, nil) - } + // refreshOverrides is a function to refresh override limits. + refreshOverrides OverridesRefresher - overridesData, err := loadOverrides(overrides) - if err != nil { - return nil, err - } + stats prometheus.Registerer + overridesTimestamp prometheus.Gauge + overridesErrors prometheus.Gauge + overridesPerLimit map[Name]prometheus.Gauge - return newLimitRegistry(defaultsData, overridesData) -} - -func newLimitRegistry(defaults LimitConfigs, overrides overridesYAML) (*limitRegistry, error) { - regDefaults, err := parseDefaultLimits(defaults) - if err != nil { - return nil, err - } - - regOverrides, err := parseOverrideLimits(overrides) - if err != nil { - return nil, err - } - - return &limitRegistry{ - defaults: regDefaults, - overrides: regOverrides, - }, nil + logger blog.Logger } // getLimit returns the limit for the specified by name and bucketKey, name is @@ -358,13 +387,69 @@ func (l *limitRegistry) getLimit(name Name, bucketKey string) (*Limit, error) { return nil, errLimitDisabled } +// loadOverrides replaces this registry's overrides with a new dataset. This is +// separate from the goroutine in NewRefresher(), for ease of testing. +func (l *limitRegistry) loadOverrides(ctx context.Context) error { + newOverrides, err := l.refreshOverrides(ctx, l.overridesErrors, l.logger) + if err != nil { + l.logger.Errf("loading overrides: %v", err) + return err + } + + // If it's an empty set, don't replace any current overrides. + if len(newOverrides) < 1 { + l.logger.Warning("loading overrides: no valid overrides") + return nil + } + + newOverridesPerLimit := make(map[Name]float64) + for _, override := range newOverrides { + newOverridesPerLimit[override.Name]++ + } + + l.overrides = newOverrides + for name := range l.overridesPerLimit { + count, ok := newOverridesPerLimit[name] + if ok { + l.overridesPerLimit[name].Set(count) + } else { + l.overridesPerLimit[name].Set(0) + } + } + l.overridesTimestamp.SetToCurrentTime() + + return nil +} + +// NewRefresher periodically loads refreshed overrides using this registry's +// refreshOverrides function. +func (l *limitRegistry) NewRefresher() context.CancelFunc { + ctx, cancel := context.WithCancel(context.Background()) + + ticker := time.NewTicker(30 * time.Minute) + + go func() { + defer ticker.Stop() + for { + select { + case <-ticker.C: + l.loadOverrides(ctx) //nolint:errcheck // Refreshing overrides is best-effort. + case <-ctx.Done(): + return + } + } + }() + + return cancel +} + // LoadOverridesByBucketKey loads the overrides YAML at the supplied path, // parses it with the existing helpers, and returns the resulting limits map // keyed by ":". This function is exported to support admin tooling // used during the migration from overrides.yaml to the overrides database // table. func LoadOverridesByBucketKey(path string) (Limits, error) { - ovs, err := loadOverrides(path) + ovs, err := loadOverridesFromFile(path) if err != nil { return nil, err } diff --git a/ratelimits/limit_test.go b/ratelimits/limit_test.go index fb20ff08f6a..86169a13c6f 100644 --- a/ratelimits/limit_test.go +++ b/ratelimits/limit_test.go @@ -18,7 +18,7 @@ import ( // // TODO(#7901): Update the tests to test these functions individually. func loadAndParseDefaultLimits(path string) (Limits, error) { - fromFile, err := loadDefaults(path) + fromFile, err := loadDefaultsFromFile(path) if err != nil { return nil, err } @@ -31,7 +31,7 @@ func loadAndParseDefaultLimits(path string) (Limits, error) { // // TODO(#7901): Update the tests to test these functions individually. func loadAndParseOverrideLimits(path string) (Limits, error) { - fromFile, err := loadOverrides(path) + fromFile, err := loadOverridesFromFile(path) if err != nil { return nil, err } diff --git a/ratelimits/limiter_test.go b/ratelimits/limiter_test.go index c111bca1cdf..daf6690d7ed 100644 --- a/ratelimits/limiter_test.go +++ b/ratelimits/limiter_test.go @@ -12,6 +12,7 @@ import ( "github.com/letsencrypt/boulder/config" berrors "github.com/letsencrypt/boulder/errors" + blog "github.com/letsencrypt/boulder/log" "github.com/letsencrypt/boulder/metrics" "github.com/letsencrypt/boulder/test" ) @@ -32,7 +33,7 @@ func newTestLimiter(t *testing.T, s Source, clk clock.FakeClock) *Limiter { // - 'NewRegistrationsPerIPAddress' burst: 20 count: 20 period: 1s // - 'NewRegistrationsPerIPAddress:64.112.117.1' burst: 40 count: 40 period: 1s func newTestTransactionBuilder(t *testing.T) *TransactionBuilder { - c, err := NewTransactionBuilderFromFiles("testdata/working_default.yml", "testdata/working_override.yml") + c, err := NewTransactionBuilderFromFiles("testdata/working_default.yml", "testdata/working_override.yml", metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "should not error") return c } diff --git a/ratelimits/transaction.go b/ratelimits/transaction.go index 033e4cfc3c1..f290663f706 100644 --- a/ratelimits/transaction.go +++ b/ratelimits/transaction.go @@ -1,13 +1,24 @@ package ratelimits import ( + "context" "errors" "fmt" + "io" "net/netip" "strconv" + "time" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/letsencrypt/boulder/config" "github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/identifier" + blog "github.com/letsencrypt/boulder/log" + sapb "github.com/letsencrypt/boulder/sa/proto" ) // ErrInvalidCost indicates that the cost specified was < 0. @@ -149,26 +160,164 @@ type TransactionBuilder struct { *limitRegistry } +// GetOverridesFunc is used to pass in the sa.GetEnabledRateLimitOverrides +// method to NewTransactionBuilderFromDatabase, rather than storing a full +// sa.SQLStorageAuthority. This makes testing significantly simpler. +type GetOverridesFunc func(context.Context, *emptypb.Empty, ...grpc.CallOption) (grpc.ServerStreamingClient[sapb.RateLimitOverrideResponse], error) + +// NewTransactionBuilderFromDatabase returns a new *TransactionBuilder. The +// provided defaults path is expected to be a path to a YAML file that contains +// the default limits. The provided overrides function is expected to be an SA's +// GetEnabledRateLimitOverrides. Both are required. +func NewTransactionBuilderFromDatabase(defaults string, overrides GetOverridesFunc, stats prometheus.Registerer, logger blog.Logger) (*TransactionBuilder, error) { + defaultsData, err := loadDefaultsFromFile(defaults) + if err != nil { + return nil, err + } + + refresher := func(ctx context.Context, errorGauge prometheus.Gauge, logger blog.Logger) (Limits, error) { + ctx, cancel := context.WithTimeout(ctx, 60*time.Second) + defer cancel() + + stream, err := overrides(ctx, &emptypb.Empty{}) + if err != nil { + return nil, fmt.Errorf("fetching enabled overrides: %w", err) + } + + overrides := make(Limits) + var errorCount float64 + for { + r, err := stream.Recv() + if err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("reading overrides stream: %w", err) + } + + newLimit := &Limit{ + Burst: r.Override.Burst, + Count: r.Override.Count, + Period: config.Duration{Duration: r.Override.Period.AsDuration()}, + Name: Name(r.Override.LimitEnum), + Comment: fmt.Sprintf("Last Updated: %s - %s", + r.UpdatedAt.AsTime().Format("2006-01-02"), + r.Override.Comment, + ), + } + + bucketKey, err := hydrateOverrideLimit(r.Override.BucketKey, newLimit) + if err != nil { + logger.Errf("hydrating %s override with key %q: %v", newLimit.Name.String(), r.Override.BucketKey, err) + errorCount++ + continue + } + + newLimit.precompute() + overrides[bucketKey] = newLimit + } + errorGauge.Set(errorCount) + return overrides, nil + } + + return NewTransactionBuilder(defaultsData, refresher, stats, logger) +} + // NewTransactionBuilderFromFiles returns a new *TransactionBuilder. The // provided defaults and overrides paths are expected to be paths to YAML files // that contain the default and override limits, respectively. Overrides is // optional, defaults is required. -func NewTransactionBuilderFromFiles(defaults, overrides string) (*TransactionBuilder, error) { - registry, err := newLimitRegistryFromFiles(defaults, overrides) +func NewTransactionBuilderFromFiles(defaults string, overrides string, stats prometheus.Registerer, logger blog.Logger) (*TransactionBuilder, error) { + defaultsData, err := loadDefaultsFromFile(defaults) if err != nil { return nil, err } - return &TransactionBuilder{registry}, nil + + if overrides == "" { + return NewTransactionBuilder(defaultsData, nil, stats, logger) + } + + refresher := func(ctx context.Context, _ prometheus.Gauge, _ blog.Logger) (Limits, error) { + overridesData, err := loadOverridesFromFile(overrides) + if err != nil { + return nil, err + } + return parseOverrideLimits(overridesData) + } + + return NewTransactionBuilder(defaultsData, refresher, stats, logger) } -// NewTransactionBuilder returns a new *TransactionBuilder. The provided -// defaults map is expected to contain default limit data. Overrides are not -// supported. Defaults is required. -func NewTransactionBuilder(defaults LimitConfigs) (*TransactionBuilder, error) { - registry, err := newLimitRegistry(defaults, nil) +// NewTransactionBuilder returns a new *TransactionBuilder. A defaults map is +// required. +func NewTransactionBuilder(defaults LimitConfigs, overrides OverridesRefresher, stats prometheus.Registerer, logger blog.Logger) (*TransactionBuilder, error) { + regDefaults, err := parseDefaultLimits(defaults) if err != nil { return nil, err } + + if overrides == nil { + overrides = func(context.Context, prometheus.Gauge, blog.Logger) (Limits, error) { + return nil, nil + } + } + + overridesTimestamp := prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "ratelimits", + Subsystem: "overrides", + Name: "timestamp_seconds", + Help: "A gauge with the last timestamp when overrides were successfully loaded", + }) + stats.MustRegister(overridesTimestamp) + + overridesErrors := prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "ratelimits", + Subsystem: "overrides", + Name: "errors", + Help: "A gauge with the number of errors while last trying to load overrides", + }) + stats.MustRegister(overridesErrors) + + overridesPerLimit := make(map[Name]prometheus.Gauge) + i := Unknown + 1 + for i < Name(len(nameToString)) { + overridesPerLimit[i] = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "ratelimits", + Subsystem: "overrides", + Name: "active", + ConstLabels: prometheus.Labels{"limit": nameToString[i]}, + Help: "A gauge with the number of overrides per rate limit", + }) + stats.MustRegister(overridesPerLimit[i]) + i++ + } + + registry := &limitRegistry{ + defaults: regDefaults, + refreshOverrides: overrides, + stats: stats, + logger: logger, + + overridesTimestamp: overridesTimestamp, + overridesErrors: overridesErrors, + overridesPerLimit: overridesPerLimit, + } + + // Load overrides, wrapped in a retry as a workaround to avoid depending on + // the SA being ready before other components start. + retries := 0 + for retries < 5 { + err = registry.loadOverrides(context.Background()) + if err == nil { + break + } + retries++ + time.Sleep(core.RetryBackoff(retries, time.Millisecond*250, time.Millisecond*2500, 2)) + } + if err != nil { + return nil, err + } + return &TransactionBuilder{registry}, nil } diff --git a/ratelimits/transaction_test.go b/ratelimits/transaction_test.go index a0fce990de6..12effb64211 100644 --- a/ratelimits/transaction_test.go +++ b/ratelimits/transaction_test.go @@ -10,15 +10,17 @@ import ( "github.com/letsencrypt/boulder/config" "github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/identifier" + blog "github.com/letsencrypt/boulder/log" + "github.com/letsencrypt/boulder/metrics" "github.com/letsencrypt/boulder/test" ) func TestNewTransactionBuilderFromFiles_WithBadLimitsPath(t *testing.T) { t.Parallel() - _, err := NewTransactionBuilderFromFiles("testdata/does-not-exist.yml", "") + _, err := NewTransactionBuilderFromFiles("testdata/does-not-exist.yml", "", metrics.NoopRegisterer, blog.NewMock()) test.AssertError(t, err, "should error") - _, err = NewTransactionBuilderFromFiles("testdata/defaults.yml", "testdata/does-not-exist.yml") + _, err = NewTransactionBuilderFromFiles("testdata/defaults.yml", "testdata/does-not-exist.yml", metrics.NoopRegisterer, blog.NewMock()) test.AssertError(t, err, "should error") } @@ -32,7 +34,7 @@ func sortTransactions(txns []Transaction) []Transaction { func TestNewRegistrationsPerIPAddressTransactions(t *testing.T) { t.Parallel() - tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "") + tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "", metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "creating TransactionBuilder") // A check-and-spend transaction for the global limit. @@ -45,7 +47,7 @@ func TestNewRegistrationsPerIPAddressTransactions(t *testing.T) { func TestNewRegistrationsPerIPv6AddressTransactions(t *testing.T) { t.Parallel() - tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "") + tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "", metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "creating TransactionBuilder") // A check-and-spend transaction for the global limit. @@ -58,7 +60,7 @@ func TestNewRegistrationsPerIPv6AddressTransactions(t *testing.T) { func TestNewOrdersPerAccountTransactions(t *testing.T) { t.Parallel() - tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "") + tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "", metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "creating TransactionBuilder") // A check-and-spend transaction for the global limit. @@ -71,7 +73,7 @@ func TestNewOrdersPerAccountTransactions(t *testing.T) { func TestFailedAuthorizationsPerDomainPerAccountTransactions(t *testing.T) { t.Parallel() - tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "testdata/working_override_13371338.yml") + tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "testdata/working_override_13371338.yml", metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "creating TransactionBuilder") // A check-only transaction for the default per-account limit. @@ -108,7 +110,7 @@ func TestFailedAuthorizationsPerDomainPerAccountTransactions(t *testing.T) { func TestFailedAuthorizationsForPausingPerDomainPerAccountTransactions(t *testing.T) { t.Parallel() - tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "testdata/working_override_13371338.yml") + tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "testdata/working_override_13371338.yml", metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "creating TransactionBuilder") // A transaction for the per-account limit override. @@ -122,7 +124,7 @@ func TestFailedAuthorizationsForPausingPerDomainPerAccountTransactions(t *testin func TestCertificatesPerDomainTransactions(t *testing.T) { t.Parallel() - tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "") + tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "", metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "creating TransactionBuilder") // One check-only transaction for the global limit. @@ -143,7 +145,7 @@ func TestCertificatesPerDomainTransactions(t *testing.T) { func TestCertificatesPerDomainPerAccountTransactions(t *testing.T) { t.Parallel() - tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "testdata/working_override_13371338.yml") + tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "testdata/working_override_13371338.yml", metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "creating TransactionBuilder") // We only expect a single check-only transaction for the per-account limit @@ -194,7 +196,7 @@ func TestCertificatesPerDomainPerAccountTransactions(t *testing.T) { func TestCertificatesPerFQDNSetTransactions(t *testing.T) { t.Parallel() - tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "") + tb, err := NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "", metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "creating TransactionBuilder") // A single check-only transaction for the global limit. @@ -218,7 +220,7 @@ func TestNewTransactionBuilder(t *testing.T) { Burst: expectedBurst, Count: expectedCount, Period: expectedPeriod}, - }) + }, nil, metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "creating TransactionBuilder") newRegDefault, ok := tb.limitRegistry.defaults[NewRegistrationsPerIPAddress.EnumString()] diff --git a/sfe/sfe_test.go b/sfe/sfe_test.go index 54eeeb243a7..23dab29f44d 100644 --- a/sfe/sfe_test.go +++ b/sfe/sfe_test.go @@ -45,6 +45,7 @@ func setupSFE(t *testing.T) (SelfServiceFrontEndImpl, clock.FakeClock) { fc.Set(time.Date(2020, 10, 10, 0, 0, 0, 0, time.UTC)) stats := metrics.NoopRegisterer + logger := blog.NewMock() mockSA := mocks.NewStorageAuthorityReadOnly(fc) @@ -54,13 +55,13 @@ func setupSFE(t *testing.T) (SelfServiceFrontEndImpl, clock.FakeClock) { limiter, err := ratelimits.NewLimiter(fc, ratelimits.NewInmemSource(), stats) test.AssertNotError(t, err, "making limiter") - txnBuilder, err := ratelimits.NewTransactionBuilderFromFiles("../test/config-next/sfe-ratelimit-defaults.yml", "") + txnBuilder, err := ratelimits.NewTransactionBuilderFromFiles("../test/config-next/sfe-ratelimit-defaults.yml", "", stats, logger) test.AssertNotError(t, err, "making transaction composer") sfe, err := NewSelfServiceFrontEndImpl( stats, fc, - blog.NewMock(), + logger, 10*time.Second, &MockRegistrationAuthority{}, mockSA, diff --git a/test/config-next/ra.json b/test/config-next/ra.json index 172fef2a5f5..ac915f2f6d0 100644 --- a/test/config-next/ra.json +++ b/test/config-next/ra.json @@ -21,8 +21,7 @@ "keyFile": "test/certs/ipki/wfe.boulder/key.pem" } }, - "Defaults": "test/config-next/wfe2-ratelimit-defaults.yml", - "Overrides": "test/config-next/wfe2-ratelimit-overrides.yml" + "Defaults": "test/config-next/wfe2-ratelimit-defaults.yml" }, "maxContactsPerRegistration": 3, "hostnamePolicyFile": "test/ident-policy.yaml", @@ -163,7 +162,8 @@ "AsyncFinalize": true, "AutomaticallyPauseZombieClients": true, "DNSAccount01Enabled": true, - "NoPendingAuthzReuse": true + "NoPendingAuthzReuse": true, + "OverridesFromDB": true } }, "pa": { diff --git a/test/config-next/sa.json b/test/config-next/sa.json index 1af58f20647..6a6d780c5a2 100644 --- a/test/config-next/sa.json +++ b/test/config-next/sa.json @@ -34,7 +34,8 @@ "clientNames": [ "admin.boulder", "wfe.boulder", - "sfe.boulder" + "sfe.boulder", + "ra.boulder" ] }, "grpc.health.v1.Health": { diff --git a/test/config-next/wfe2.json b/test/config-next/wfe2.json index e68249aa94f..3ac125f4937 100644 --- a/test/config-next/wfe2.json +++ b/test/config-next/wfe2.json @@ -130,13 +130,13 @@ "keyFile": "test/certs/ipki/wfe.boulder/key.pem" } }, - "Defaults": "test/config-next/wfe2-ratelimit-defaults.yml", - "Overrides": "test/config-next/wfe2-ratelimit-overrides.yml" + "Defaults": "test/config-next/wfe2-ratelimit-defaults.yml" }, "features": { "PropagateCancels": true, "ServeRenewalInfo": true, - "CheckIdentifiersPaused": true + "CheckIdentifiersPaused": true, + "OverridesFromDB": true }, "certProfiles": { "legacy": "The normal profile you know and love", diff --git a/test/startservers.py b/test/startservers.py index c045ff280d5..cdfcad084b5 100644 --- a/test/startservers.py +++ b/test/startservers.py @@ -87,11 +87,11 @@ Service('boulder-ra-sct-provider-1', 8118, 9594, 'ra.boulder', ('./bin/boulder', 'boulder-ra', '--config', os.path.join(config_dir, 'ra.json'), '--addr', ':9594', '--debug-addr', ':8118'), - ('boulder-publisher-1', 'boulder-publisher-2')), + ('boulder-sa-1', 'boulder-sa-2', 'boulder-publisher-1', 'boulder-publisher-2')), Service('boulder-ra-sct-provider-2', 8119, 9694, 'ra.boulder', ('./bin/boulder', 'boulder-ra', '--config', os.path.join(config_dir, 'ra.json'), '--addr', ':9694', '--debug-addr', ':8119'), - ('boulder-publisher-1', 'boulder-publisher-2')), + ('boulder-sa-1', 'boulder-sa-2', 'boulder-publisher-1', 'boulder-publisher-2')), Service('bad-key-revoker', 8020, None, None, ('./bin/boulder', 'bad-key-revoker', '--config', os.path.join(config_dir, 'bad-key-revoker.json'), '--debug-addr', ':8020'), diff --git a/wfe2/wfe_test.go b/wfe2/wfe_test.go index affa9e85419..8915db1bb3e 100644 --- a/wfe2/wfe_test.go +++ b/wfe2/wfe_test.go @@ -358,6 +358,7 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) { fc := clock.NewFake() stats := metrics.NoopRegisterer + logger := blog.NewMock() testKeyPolicy, err := goodkey.NewPolicy(nil, nil) test.AssertNotError(t, err, "creating test keypolicy") @@ -411,7 +412,7 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) { // Setup rate limiting. limiter, err := ratelimits.NewLimiter(fc, ratelimits.NewInmemSource(), stats) test.AssertNotError(t, err, "making limiter") - txnBuilder, err := ratelimits.NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "") + txnBuilder, err := ratelimits.NewTransactionBuilderFromFiles("../test/config-next/wfe2-ratelimit-defaults.yml", "", stats, logger) test.AssertNotError(t, err, "making transaction composer") unpauseSigner, err := unpause.NewJWTSigner(cmd.HMACKeyConfig{KeyFile: "../test/secrets/sfe_unpause_key"}) @@ -424,7 +425,7 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) { testKeyPolicy, certChains, issuerCertificates, - blog.NewMock(), + logger, 10*time.Second, 10*time.Second, 2, @@ -4240,7 +4241,7 @@ func TestNewOrderRateLimits(t *testing.T) { Burst: 1, Count: 1, Period: config.Duration{Duration: time.Hour * 24}}, - }) + }, nil, metrics.NoopRegisterer, blog.NewMock()) test.AssertNotError(t, err, "making transaction composer") wfe.txnBuilder = txnBuilder