Skip to content

Commit 472cf6e

Browse files
committed
coderd: tighten /login rate limit
1 parent c01910f commit 472cf6e

File tree

3 files changed

+16
-9
lines changed

3 files changed

+16
-9
lines changed

coderd/coderd.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func New(options *Options) *API {
187187
// app URL. If it is, it will serve that application.
188188
api.handleSubdomainApplications(
189189
// Middleware to impose on the served application.
190-
httpmw.RateLimitPerMinute(options.APIRateLimit),
190+
httpmw.RateLimit(options.APIRateLimit, time.Minute),
191191
httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
192192
DB: options.Database,
193193
OAuth2Configs: oauthConfigs,
@@ -212,7 +212,7 @@ func New(options *Options) *API {
212212
apps := func(r chi.Router) {
213213
r.Use(
214214
tracing.Middleware(api.TracerProvider),
215-
httpmw.RateLimitPerMinute(options.APIRateLimit),
215+
httpmw.RateLimit(options.APIRateLimit, time.Minute),
216216
apiKeyMiddlewareRedirect,
217217
httpmw.ExtractUserParam(api.Database),
218218
// Extracts the <workspace.agent> from the url
@@ -240,7 +240,7 @@ func New(options *Options) *API {
240240
r.Use(
241241
tracing.Middleware(api.TracerProvider),
242242
// Specific routes can specify smaller limits.
243-
httpmw.RateLimitPerMinute(options.APIRateLimit),
243+
httpmw.RateLimit(options.APIRateLimit, time.Minute),
244244
)
245245
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
246246
httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Response{
@@ -273,7 +273,7 @@ func New(options *Options) *API {
273273
apiKeyMiddleware,
274274
// This number is arbitrary, but reading/writing
275275
// file content is expensive so it should be small.
276-
httpmw.RateLimitPerMinute(12),
276+
httpmw.RateLimit(12, time.Minute),
277277
)
278278
r.Get("/{hash}", api.fileByHash)
279279
r.Post("/", api.postFile)
@@ -359,7 +359,13 @@ func New(options *Options) *API {
359359
r.Route("/users", func(r chi.Router) {
360360
r.Get("/first", api.firstUser)
361361
r.Post("/first", api.postFirstUser)
362-
r.Post("/login", api.postLogin)
362+
r.Group(func(r chi.Router) {
363+
// We use a tight limit for password login to protect
364+
// against audit-log write DoS, pbkdf2 DoS, and simple
365+
// brute-force attacks.
366+
r.Use(httpmw.RateLimit(10, time.Minute))
367+
r.Post("/login", api.postLogin)
368+
})
363369
r.Get("/authmethods", api.userAuthMethods)
364370
r.Route("/oauth2", func(r chi.Router) {
365371
r.Route("/github", func(r chi.Router) {

coderd/httpmw/ratelimit.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import (
1111
"github.com/coder/coder/codersdk"
1212
)
1313

14-
// RateLimitPerMinute returns a handler that limits requests per-minute based
14+
// RateLimit returns a handler that limits requests per-minute based
1515
// on IP, endpoint, and user ID (if available).
16-
func RateLimitPerMinute(count int) func(http.Handler) http.Handler {
16+
func RateLimit(count int, window time.Duration) func(http.Handler) http.Handler {
1717
// -1 is no rate limit
1818
if count <= 0 {
1919
return func(handler http.Handler) http.Handler {
@@ -22,7 +22,7 @@ func RateLimitPerMinute(count int) func(http.Handler) http.Handler {
2222
}
2323
return httprate.Limit(
2424
count,
25-
1*time.Minute,
25+
window,
2626
httprate.WithKeyFuncs(func(r *http.Request) (string, error) {
2727
// Prioritize by user, but fallback to IP.
2828
apiKey, ok := r.Context().Value(apiKeyContextKey{}).(database.APIKey)

coderd/httpmw/ratelimit_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/http"
55
"net/http/httptest"
66
"testing"
7+
"time"
78

89
"github.com/go-chi/chi/v5"
910
"github.com/stretchr/testify/require"
@@ -17,7 +18,7 @@ func TestRateLimit(t *testing.T) {
1718
t.Run("NoUser", func(t *testing.T) {
1819
t.Parallel()
1920
rtr := chi.NewRouter()
20-
rtr.Use(httpmw.RateLimitPerMinute(5))
21+
rtr.Use(httpmw.RateLimit(5, time.Second))
2122
rtr.Get("/", func(rw http.ResponseWriter, r *http.Request) {
2223
rw.WriteHeader(http.StatusOK)
2324
})

0 commit comments

Comments
 (0)