aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNeil Alexander <neilalexander@users.noreply.github.com>2022-06-07 14:24:04 +0100
committerGitHub <noreply@github.com>2022-06-07 14:24:04 +0100
commit6d4bd5d890eeab47bddfad5a48d37766f954171f (patch)
tree994aa7168cc614894b632ef70f1779baec90a920
parent27948fb30468315ce613402dc8cc1fa7dba01679 (diff)
Rate limiting changes (#2519)
* Rate limiting changes This makes the following changes: * For logged in users, the rate limiting now applies to the device session rather than the remote IP address; * For non-logged in users, the rate limiting continues to apply to remote address as it does today; * It is now possible to add user IDs to the `exempt_user_ids` option under `rate_limiting` to exclude bots from rate limiting; * Admin and appservice users are now exempt from rate limiting by default. * Fix build with media API
-rw-r--r--clientapi/routing/routing.go50
-rw-r--r--dendrite-sample.monolith.yaml5
-rw-r--r--dendrite-sample.polylith.yaml5
-rw-r--r--internal/httputil/rate_limiting.go31
-rw-r--r--mediaapi/routing/routing.go6
-rw-r--r--setup/config/config_clientapi.go4
6 files changed, 67 insertions, 34 deletions
diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go
index fab45fdf..aa4b5a23 100644
--- a/clientapi/routing/routing.go
+++ b/clientapi/routing/routing.go
@@ -140,7 +140,7 @@ func Setup(
synapseAdminRouter.Handle("/admin/v1/send_server_notice/{txnID}",
httputil.MakeAuthAPI("send_server_notice", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
// not specced, but ensure we're rate limiting requests to this endpoint
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -160,7 +160,7 @@ func Setup(
synapseAdminRouter.Handle("/admin/v1/send_server_notice",
httputil.MakeAuthAPI("send_server_notice", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
// not specced, but ensure we're rate limiting requests to this endpoint
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
return SendServerNotice(
@@ -190,7 +190,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/join/{roomIDOrAlias}",
httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -206,7 +206,7 @@ func Setup(
if mscCfg.Enabled("msc2753") {
v3mux.Handle("/peek/{roomIDOrAlias}",
httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -226,7 +226,7 @@ func Setup(
).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/join",
httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -240,7 +240,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/leave",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -274,7 +274,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/invite",
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -392,14 +392,14 @@ func Setup(
).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/register", httputil.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, nil); r != nil {
return *r
}
return Register(req, userAPI, cfg)
})).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/register/available", httputil.MakeExternalAPI("registerAvailable", func(req *http.Request) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, nil); r != nil {
return *r
}
return RegisterAvailable(req, cfg, userAPI)
@@ -473,7 +473,7 @@ func Setup(
v3mux.Handle("/rooms/{roomID}/typing/{userID}",
httputil.MakeAuthAPI("rooms_typing", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -530,7 +530,7 @@ func Setup(
v3mux.Handle("/account/whoami",
httputil.MakeAuthAPI("whoami", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
return Whoami(req, device)
@@ -539,7 +539,7 @@ func Setup(
v3mux.Handle("/account/password",
httputil.MakeAuthAPI("password", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
return Password(req, userAPI, device, cfg)
@@ -548,7 +548,7 @@ func Setup(
v3mux.Handle("/account/deactivate",
httputil.MakeAuthAPI("deactivate", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
return Deactivate(req, userInteractiveAuth, userAPI, device)
@@ -559,7 +559,7 @@ func Setup(
v3mux.Handle("/login",
httputil.MakeExternalAPI("login", func(req *http.Request) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, nil); r != nil {
return *r
}
return Login(req, userAPI, cfg)
@@ -667,7 +667,7 @@ func Setup(
v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}",
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -733,7 +733,7 @@ func Setup(
v3mux.Handle("/profile/{userID}/avatar_url",
httputil.MakeAuthAPI("profile_avatar_url", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -758,7 +758,7 @@ func Setup(
v3mux.Handle("/profile/{userID}/displayname",
httputil.MakeAuthAPI("profile_displayname", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -797,7 +797,7 @@ func Setup(
v3mux.Handle("/voip/turnServer",
httputil.MakeAuthAPI("turn_server", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
return RequestTurnServer(req, device, cfg)
@@ -876,7 +876,7 @@ func Setup(
v3mux.Handle("/user/{userID}/openid/request_token",
httputil.MakeAuthAPI("openid_request_token", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -889,7 +889,7 @@ func Setup(
v3mux.Handle("/user_directory/search",
httputil.MakeAuthAPI("userdirectory_search", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
postContent := struct {
@@ -935,7 +935,7 @@ func Setup(
v3mux.Handle("/rooms/{roomID}/read_markers",
httputil.MakeAuthAPI("rooms_read_markers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -948,7 +948,7 @@ func Setup(
v3mux.Handle("/rooms/{roomID}/forget",
httputil.MakeAuthAPI("rooms_forget", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
@@ -1025,7 +1025,7 @@ func Setup(
v3mux.Handle("/pushers/set",
httputil.MakeAuthAPI("set_pushers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
return SetPusher(req, device, userAPI)
@@ -1083,7 +1083,7 @@ func Setup(
v3mux.Handle("/capabilities",
httputil.MakeAuthAPI("capabilities", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
return GetCapabilities(req, rsAPI)
@@ -1299,7 +1299,7 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}",
httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
diff --git a/dendrite-sample.monolith.yaml b/dendrite-sample.monolith.yaml
index e974dbcb..ce5da278 100644
--- a/dendrite-sample.monolith.yaml
+++ b/dendrite-sample.monolith.yaml
@@ -160,11 +160,14 @@ client_api:
# Settings for rate-limited endpoints. Rate limiting kicks in after the threshold
# number of "slots" have been taken by requests from a specific host. Each "slot"
- # will be released after the cooloff time in milliseconds.
+ # will be released after the cooloff time in milliseconds. Server administrators
+ # and appservice users are exempt from rate limiting by default.
rate_limiting:
enabled: true
threshold: 5
cooloff_ms: 500
+ exempt_user_ids:
+ # - @user:domain.com
# Configuration for the Federation API.
federation_api:
diff --git a/dendrite-sample.polylith.yaml b/dendrite-sample.polylith.yaml
index 4b67aaa9..439f09b0 100644
--- a/dendrite-sample.polylith.yaml
+++ b/dendrite-sample.polylith.yaml
@@ -163,11 +163,14 @@ client_api:
# Settings for rate-limited endpoints. Rate limiting kicks in after the threshold
# number of "slots" have been taken by requests from a specific host. Each "slot"
- # will be released after the cooloff time in milliseconds.
+ # will be released after the cooloff time in milliseconds. Server administrators
+ # and appservice users are exempt from rate limiting by default.
rate_limiting:
enabled: true
threshold: 5
cooloff_ms: 500
+ exempt_user_ids:
+ # - @user:domain.com
# Configuration for the Federation API.
federation_api:
diff --git a/internal/httputil/rate_limiting.go b/internal/httputil/rate_limiting.go
index c4f47c7b..dab36481 100644
--- a/internal/httputil/rate_limiting.go
+++ b/internal/httputil/rate_limiting.go
@@ -7,6 +7,7 @@ import (
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/setup/config"
+ userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/util"
)
@@ -17,6 +18,7 @@ type RateLimits struct {
enabled bool
requestThreshold int64
cooloffDuration time.Duration
+ exemptUserIDs map[string]struct{}
}
func NewRateLimits(cfg *config.RateLimiting) *RateLimits {
@@ -25,6 +27,10 @@ func NewRateLimits(cfg *config.RateLimiting) *RateLimits {
enabled: cfg.Enabled,
requestThreshold: cfg.Threshold,
cooloffDuration: time.Duration(cfg.CooloffMS) * time.Millisecond,
+ exemptUserIDs: map[string]struct{}{},
+ }
+ for _, userID := range cfg.ExemptUserIDs {
+ l.exemptUserIDs[userID] = struct{}{}
}
if l.enabled {
go l.clean()
@@ -52,7 +58,7 @@ func (l *RateLimits) clean() {
}
}
-func (l *RateLimits) Limit(req *http.Request) *util.JSONResponse {
+func (l *RateLimits) Limit(req *http.Request, device *userapi.Device) *util.JSONResponse {
// If rate limiting is disabled then do nothing.
if !l.enabled {
return nil
@@ -67,9 +73,26 @@ func (l *RateLimits) Limit(req *http.Request) *util.JSONResponse {
// First of all, work out if X-Forwarded-For was sent to us. If not
// then we'll just use the IP address of the caller.
- caller := req.RemoteAddr
- if forwardedFor := req.Header.Get("X-Forwarded-For"); forwardedFor != "" {
- caller = forwardedFor
+ var caller string
+ if device != nil {
+ switch device.AccountType {
+ case userapi.AccountTypeAdmin:
+ return nil // don't rate-limit server administrators
+ case userapi.AccountTypeAppService:
+ return nil // don't rate-limit appservice users
+ default:
+ if _, ok := l.exemptUserIDs[device.UserID]; ok {
+ // If the user is exempt from rate limiting then do nothing.
+ return nil
+ }
+ caller = device.UserID + device.ID
+ }
+ } else {
+ if forwardedFor := req.Header.Get("X-Forwarded-For"); forwardedFor != "" {
+ caller = forwardedFor
+ } else {
+ caller = req.RemoteAddr
+ }
}
// Look up the caller's channel, if they have one.
diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go
index 76f07415..19690818 100644
--- a/mediaapi/routing/routing.go
+++ b/mediaapi/routing/routing.go
@@ -62,7 +62,7 @@ func Setup(
uploadHandler := httputil.MakeAuthAPI(
"upload", userAPI,
func(req *http.Request, dev *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, dev); r != nil {
return *r
}
return Upload(req, cfg, dev, db, activeThumbnailGeneration)
@@ -70,7 +70,7 @@ func Setup(
)
configHandler := httputil.MakeAuthAPI("config", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, device); r != nil {
return *r
}
respondSize := &cfg.MaxFileSizeBytes
@@ -126,7 +126,7 @@ func makeDownloadAPI(
// Ratelimit requests
// NOTSPEC: The spec says everything at /media/ should be rate limited, but this causes issues with thumbnails (#2243)
if name != "thumbnail" {
- if r := rateLimits.Limit(req); r != nil {
+ if r := rateLimits.Limit(req, nil); r != nil {
if err := json.NewEncoder(w).Encode(r); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
diff --git a/setup/config/config_clientapi.go b/setup/config/config_clientapi.go
index bb786a14..ecf8f6bd 100644
--- a/setup/config/config_clientapi.go
+++ b/setup/config/config_clientapi.go
@@ -134,6 +134,10 @@ type RateLimiting struct {
// The cooloff period in milliseconds after a request before the "slot"
// is freed again
CooloffMS int64 `yaml:"cooloff_ms"`
+
+ // A list of users that are exempt from rate limiting, i.e. if you want
+ // to run Mjolnir or other bots.
+ ExemptUserIDs []string `yaml:"exempt_user_ids"`
}
func (r *RateLimiting) Verify(configErrs *ConfigErrors) {