Registry fails to start when a rate limiter leaves out warn_action, documented as optional
Summary
ApplyDefaults in configuration/configuration.go sets the rate limiter defaults (limit.period: second, action.warn_action: none and action.hard_action: none) while ranging over config.RateLimiter.Limiters by value, so each assignment goes to a copy of the limiter and is lost. No parsed configuration carries those defaults.
The rate limiter middleware validates every limiter and rejects the empty values, and NewApp returns that error. So the registry does not start when a limiter leaves out action.warn_action, which docs/configuration.md describes as optional with a default of none. The same happens when limit.period or action.hard_action is left out (the docs list those two as required).
The value loop came with 32b3f2e3 (docs: rate-limiting configuration settings, 2025-04-25), and the warn_action and hard_action defaults were added to it in d4e92b36 (feat(handlers): add rate-limiter middleware, 2025-05-08).
Steps to reproduce
-
Start Redis:
docker run -d --name registry-redis -p 127.0.0.1:16379:6379 redis:7.2-alpine -
Save this configuration as
/tmp/config.yml. Its limiter is the one in thedocs/configuration.mdexample, withoutwarn_actionand withoutlog_only: false(the default):version: 0.1 storage: filesystem: rootdirectory: /tmp/registry http: addr: 127.0.0.1:5000 secret: registry-http-secret database: enabled: false redis: ratelimiter: enabled: true addr: 127.0.0.1:16379 rate_limiter: enabled: true limiters: - name: global_rate_limit description: "Global IP rate limit" match: type: IP precedence: 10 limit: rate: 5000 period: "minute" burst: 8000 action: warn_threshold: 0.7 hard_action: "block" -
From a checkout of
master, start the registry:go run ./cmd/registry serve /tmp/config.yml
What is the current bug behavior?
On master at 672bd753 (Go 1.27.3, Redis 7.2.16) the registry exits with status 1:
creating new registry instance: configuring application: failed to configure rate limiting: 1 error occurred:
* action.warn_action must be one of: [none log]A limiter that also leaves out period and hard_action fails with period must be one of: [second minute hour] and action.hard_action must be one of: [none log block] as well.
configuration.Parse alone shows the cause: after it, the limiter's Action.WarnAction is "", and so are Limit.Period and Action.HardAction when they are left out. The default cases of TestParseRateLimiter_Limiters_Limit_Period, TestParseRateLimiter_Limiters_Action_WarnAction and TestParseRateLimiter_Limiters_Action_HardAction assert those empty values.
What is the expected correct behavior?
The registry starts, and the limiter gets warn_action: none, as documented. A limiter that leaves out period or hard_action gets second or none, the values ApplyDefaults sets for them.
Relevant code
At 672bd753:
configuration/configuration.go#L1311-1326: the value loop inApplyDefaults.registry/middleware/ratelimiter/ratelimiter.go#L159-184: the validation that rejects the empty values.registry/handlers/app.go#L278-284:NewAppreturns the error.docs/configuration.md#L1911-1915:limit.periodandaction.hard_actionlisted as required,action.warn_actionas optional with a default ofnone.
Possible fixes
Range by index and take a pointer to each element (limiter := &config.RateLimiter.Limiters[i]), and make the three tests expect the defaults. The Match.Type lowercasing in the same loop has never had an effect, and the middleware trims and lowercases the match type itself (ratelimiter.go#L148), so it can be dropped rather than made effective, which would change the parsed configuration that TestConfigSuite expects.