Skip to content

Commit

Permalink
Rate limit all unauthenticated endpoints
Browse files Browse the repository at this point in the history
This commit is an extension to what was done in #172. And is designed to fix #4330 and https://github.com/gravitational/teleport-private/issues/403.

Rather than audit endpoints and choose what endpoints should be rate limited, this commit proposes that for safety and reduced cognitive load, all unauthenticated endpoints become rate limited.

The primary concern in this type of change would be if our rate limit becomes too aggressive for general use.  There are two considered strategies to make sure this does not become impacting:
1. Adjust the rate limiter so the rate limit becomes endpoint specific.  This would avoid the need to consider how activity on one endpoint effects another.
2. Accept that rate limit interactions are possible and instead ensure rate limits are high enough to avoid this concern.

This commit chooses option #2.  While #1 has advantages, particularly as endpoints and new use cases are added.  #2 provides the strictest and safest rate limits.

Our rate limits were configured to:
period: 1 min
avg rate: 10
burst rate: 20

In order to build a safety buffer with option #2 those allowed rates were doubled.
  • Loading branch information
jentfoo committed Apr 18, 2023
1 parent 093dab9 commit 150c3c3
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 25 deletions.
4 changes: 2 additions & 2 deletions lib/defaults/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,9 @@ const (
LimiterPasswordlessPeriod = 1 * time.Minute
// LimiterPasswordlessAverage is the default average for passwordless
// limiters.
LimiterPasswordlessAverage = 10
LimiterPasswordlessAverage = 20
// LimiterPasswordlessBurst is the default burst for passwordless limiters.
LimiterPasswordlessBurst = 20
LimiterPasswordlessBurst = 40
)

const (
Expand Down
46 changes: 23 additions & 23 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) {
return nil, trace.BadParameter("failed parsing index.html template: %v", err)
}

h.Handle("GET", "/web/config.js", httplib.MakeHandler(h.getWebConfig))
h.Handle("GET", "/web/config.js", h.WithLimiter(h.getWebConfig))
}

routingHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -508,9 +508,9 @@ func (h *Handler) bindMinimalEndpoints() {
// find is like ping, but is faster because it is optimized for servers
// and does not fetch the data that servers don't need, e.g.
// OIDC connectors and auth preferences
h.GET("/webapi/find", httplib.MakeHandler(h.find))
h.GET("/webapi/find", h.WithLimiter(h.find))
// Issue host credentials.
h.POST("/webapi/host/credentials", httplib.MakeHandler(h.hostCredentials))
h.POST("/webapi/host/credentials", h.WithLimiter(h.hostCredentials))
}

// bindDefaultEndpoints binds the default endpoints for the web API.
Expand All @@ -521,23 +521,23 @@ func (h *Handler) bindDefaultEndpoints() {
// endpoint returns the default authentication method and configuration that
// the server supports. the /webapi/ping/:connector endpoint can be used to
// query the authentication configuration for a specific connector.
h.GET("/webapi/ping", httplib.MakeHandler(h.ping))
h.GET("/webapi/ping/:connector", httplib.MakeHandler(h.pingWithConnector))
h.GET("/webapi/ping", h.WithLimiter(h.ping))
h.GET("/webapi/ping/:connector", h.WithLimiter(h.pingWithConnector))

// Unauthenticated access to JWT public keys.
h.GET("/.well-known/jwks.json", httplib.MakeHandler(h.jwks))
h.GET("/.well-known/jwks.json", h.WithLimiter(h.jwks))

// Unauthenticated access to the message of the day
h.GET("/webapi/motd", httplib.MakeHandler(h.motd))
h.GET("/webapi/motd", h.WithLimiter(h.motd))

// Unauthenticated access to retrieving the script used to install
// Teleport
h.GET("/webapi/scripts/installer/:name", httplib.MakeHandler(h.installer))
h.GET("/webapi/scripts/installer/:name", h.WithLimiter(h.installer))

// desktop access configuration scripts
h.GET("/webapi/scripts/desktop-access/install-ad-ds.ps1", httplib.MakeHandler(h.desktopAccessScriptInstallADDSHandle))
h.GET("/webapi/scripts/desktop-access/install-ad-cs.ps1", httplib.MakeHandler(h.desktopAccessScriptInstallADCSHandle))
h.GET("/webapi/scripts/desktop-access/configure/:token/configure-ad.ps1", httplib.MakeHandler(h.desktopAccessScriptConfigureHandle))
h.GET("/webapi/scripts/desktop-access/install-ad-ds.ps1", h.WithLimiter(h.desktopAccessScriptInstallADDSHandle))
h.GET("/webapi/scripts/desktop-access/install-ad-cs.ps1", h.WithLimiter(h.desktopAccessScriptInstallADCSHandle))
h.GET("/webapi/scripts/desktop-access/configure/:token/configure-ad.ps1", h.WithLimiter(h.desktopAccessScriptConfigureHandle))

// Forwards traces to the configured upstream collector
h.POST("/webapi/traces", h.WithAuth(h.traces))
Expand All @@ -556,7 +556,7 @@ func (h *Handler) bindDefaultEndpoints() {

// We have an overlap route here, please see godoc of handleGetUserOrResetToken
// h.GET("/webapi/users/:username", h.WithAuth(h.getUserHandle))
// h.GET("/webapi/users/password/token/:token", httplib.MakeHandler(h.getResetPasswordTokenHandle))
// h.GET("/webapi/users/password/token/:token", h.WithLimiter(h.getResetPasswordTokenHandle))
h.GET("/webapi/users/*wildcard", h.handleGetUserOrResetToken)

h.PUT("/webapi/users/password/token", httplib.WithCSRFProtection(h.changeUserAuthentication))
Expand Down Expand Up @@ -622,9 +622,9 @@ func (h *Handler) bindDefaultEndpoints() {
h.POST("/webapi/token", h.WithAuth(h.createTokenHandle))

// join scripts
h.GET("/scripts/:token/install-node.sh", httplib.MakeHandler(h.getNodeJoinScriptHandle))
h.GET("/scripts/:token/install-app.sh", httplib.MakeHandler(h.getAppJoinScriptHandle))
h.GET("/scripts/:token/install-database.sh", httplib.MakeHandler(h.getDatabaseJoinScriptHandle))
h.GET("/scripts/:token/install-node.sh", h.WithLimiter(h.getNodeJoinScriptHandle))
h.GET("/scripts/:token/install-app.sh", h.WithLimiter(h.getAppJoinScriptHandle))
h.GET("/scripts/:token/install-database.sh", h.WithLimiter(h.getDatabaseJoinScriptHandle))
// web context
h.GET("/webapi/sites/:site/context", h.WithClusterAuth(h.getUserContext))
h.GET("/webapi/sites/:site/resources/check", h.WithClusterAuth(h.checkAccessToRegisteredResource))
Expand All @@ -651,12 +651,12 @@ func (h *Handler) bindDefaultEndpoints() {
// MFA public endpoints.
h.POST("/webapi/sites/:site/mfa/required", h.WithClusterAuth(h.isMFARequired))
h.POST("/webapi/mfa/login/begin", h.WithLimiter(h.mfaLoginBegin))
h.POST("/webapi/mfa/login/finish", httplib.MakeHandler(h.mfaLoginFinish))
h.POST("/webapi/mfa/login/finishsession", httplib.MakeHandler(h.mfaLoginFinishSession))
h.DELETE("/webapi/mfa/token/:token/devices/:devicename", httplib.MakeHandler(h.deleteMFADeviceWithTokenHandle))
h.GET("/webapi/mfa/token/:token/devices", httplib.MakeHandler(h.getMFADevicesWithTokenHandle))
h.POST("/webapi/mfa/token/:token/authenticatechallenge", httplib.MakeHandler(h.createAuthenticateChallengeWithTokenHandle))
h.POST("/webapi/mfa/token/:token/registerchallenge", httplib.MakeHandler(h.createRegisterChallengeWithTokenHandle))
h.POST("/webapi/mfa/login/finish", h.WithLimiter(h.mfaLoginFinish))
h.POST("/webapi/mfa/login/finishsession", h.WithLimiter(h.mfaLoginFinishSession))
h.DELETE("/webapi/mfa/token/:token/devices/:devicename", h.WithLimiter(h.deleteMFADeviceWithTokenHandle))
h.GET("/webapi/mfa/token/:token/devices", h.WithLimiter(h.getMFADevicesWithTokenHandle))
h.POST("/webapi/mfa/token/:token/authenticatechallenge", h.WithLimiter(h.createAuthenticateChallengeWithTokenHandle))
h.POST("/webapi/mfa/token/:token/registerchallenge", h.WithLimiter(h.createRegisterChallengeWithTokenHandle))

// MFA private endpoints.
h.GET("/webapi/mfa/devices", h.WithAuth(h.getMFADevicesHandle))
Expand All @@ -665,7 +665,7 @@ func (h *Handler) bindDefaultEndpoints() {
h.POST("/webapi/mfa/authenticatechallenge/password", h.WithAuth(h.createAuthenticateChallengeWithPassword))

// trusted clusters
h.POST("/webapi/trustedclusters/validate", httplib.MakeHandler(h.validateTrustedCluster))
h.POST("/webapi/trustedclusters/validate", h.WithLimiter(h.validateTrustedCluster))

// User Status (used by client to check if user session is valid)
h.GET("/webapi/user/status", h.WithAuth(h.getUserStatus))
Expand Down Expand Up @@ -711,7 +711,7 @@ func (h *Handler) bindDefaultEndpoints() {
h.DELETE("/webapi/sites/:site/integrations/:name", h.WithClusterAuth(h.integrationsDelete))

// Connection upgrades.
h.GET("/webapi/connectionupgrade", httplib.MakeHandler(h.connectionUpgrade))
h.GET("/webapi/connectionupgrade", h.WithLimiter(h.connectionUpgrade))

// create user events.
h.POST("/webapi/precapture", h.WithLimiter(h.createPreUserEventHandle))
Expand Down

0 comments on commit 150c3c3

Please sign in to comment.