Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[chore] reshuffle rate limit middleware, use single gzip middleware instance #1290

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions cmd/gotosocial/action/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,21 @@ var Start action.GTSAction = func(ctx context.Context) error {
webModule = web.New(processor) // web pages + user profiles + settings panels etc
)

// create required middleware
limit := config.GetAdvancedRateLimitRequests()
gzip := middleware.Gzip() // all except fileserver
clLimit := middleware.RateLimit(limit) // client api
s2sLimit := middleware.RateLimit(limit) // server-to-server (AP)
fsLimit := middleware.RateLimit(limit) // fileserver / web templates

// these should be routed in order
authModule.Route(router)
clientModule.Route(router)
fileserverModule.Route(router)
wellKnownModule.Route(router)
nodeInfoModule.Route(router)
activityPubModule.Route(router)
webModule.Route(router)
authModule.Route(router, clLimit, gzip)
clientModule.Route(router, clLimit, gzip)
fileserverModule.Route(router, fsLimit)
wellKnownModule.Route(router, gzip, s2sLimit)
nodeInfoModule.Route(router, s2sLimit, gzip)
activityPubModule.Route(router, s2sLimit, gzip)
webModule.Route(router, fsLimit, gzip)

gts, err := gotosocial.NewServer(dbService, router, federator, mediaManager)
if err != nil {
Expand All @@ -208,8 +215,8 @@ var Start action.GTSAction = func(ctx context.Context) error {

// catch shutdown signals from the operating system
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
sig := <-sigs
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs // block until signal received
log.Infof("received signal %s, shutting down", sig)

// close down all running services in order
Expand Down
11 changes: 6 additions & 5 deletions internal/api/activitypub.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"context"
"net/url"

"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/emoji"
"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users"
"github.com/superseriousbusiness/gotosocial/internal/db"
Expand All @@ -37,20 +38,20 @@ type ActivityPub struct {
isURIBlocked func(context.Context, *url.URL) (bool, db.Error)
}

func (a *ActivityPub) Route(r router.Router) {
func (a *ActivityPub) Route(r router.Router, m ...gin.HandlerFunc) {
// create groupings for the 'emoji' and 'users' prefixes
emojiGroup := r.AttachGroup("emoji")
usersGroup := r.AttachGroup("users")

// instantiate + attach shared, non-global middlewares to both of these groups
var (
rateLimitMiddleware = middleware.RateLimit() // nolint:contextcheck
signatureCheckMiddleware = middleware.SignatureCheck(a.isURIBlocked)
gzipMiddleware = middleware.Gzip()
cacheControlMiddleware = middleware.CacheControl("no-store")
)
emojiGroup.Use(rateLimitMiddleware, signatureCheckMiddleware, gzipMiddleware, cacheControlMiddleware)
usersGroup.Use(rateLimitMiddleware, signatureCheckMiddleware, gzipMiddleware, cacheControlMiddleware)
emojiGroup.Use(m...)
usersGroup.Use(m...)
emojiGroup.Use(signatureCheckMiddleware, cacheControlMiddleware)
usersGroup.Use(signatureCheckMiddleware, cacheControlMiddleware)

a.emoji.Route(emojiGroup.Handle)
a.users.Route(usersGroup.Handle)
Expand Down
11 changes: 6 additions & 5 deletions internal/api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package api

import (
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api/auth"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
Expand All @@ -36,20 +37,20 @@ type Auth struct {
}

// Route attaches 'auth' and 'oauth' groups to the given router.
func (a *Auth) Route(r router.Router) {
func (a *Auth) Route(r router.Router, m ...gin.HandlerFunc) {
// create groupings for the 'auth' and 'oauth' prefixes
authGroup := r.AttachGroup("auth")
oauthGroup := r.AttachGroup("oauth")

// instantiate + attach shared, non-global middlewares to both of these groups
var (
rateLimitMiddleware = middleware.RateLimit() // nolint:contextcheck
gzipMiddleware = middleware.Gzip()
cacheControlMiddleware = middleware.CacheControl("private", "max-age=120")
sessionMiddleware = middleware.Session(a.sessionName, a.routerSession.Auth, a.routerSession.Crypt)
)
authGroup.Use(rateLimitMiddleware, gzipMiddleware, cacheControlMiddleware, sessionMiddleware)
oauthGroup.Use(rateLimitMiddleware, gzipMiddleware, cacheControlMiddleware, sessionMiddleware)
authGroup.Use(m...)
oauthGroup.Use(m...)
authGroup.Use(cacheControlMiddleware, sessionMiddleware)
oauthGroup.Use(cacheControlMiddleware, sessionMiddleware)

a.auth.RouteAuth(authGroup.Handle)
a.auth.RouteOauth(oauthGroup.Handle)
Expand Down
6 changes: 3 additions & 3 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package api

import (
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"
"github.com/superseriousbusiness/gotosocial/internal/api/client/admin"
"github.com/superseriousbusiness/gotosocial/internal/api/client/apps"
Expand Down Expand Up @@ -67,15 +68,14 @@ type Client struct {
user *user.Module // api/v1/user
}

func (c *Client) Route(r router.Router) {
func (c *Client) Route(r router.Router, m ...gin.HandlerFunc) {
// create a new group on the top level client 'api' prefix
apiGroup := r.AttachGroup("api")

// attach non-global middlewares appropriate to the client api
apiGroup.Use(m...)
apiGroup.Use(
middleware.TokenCheck(c.db, c.processor.OAuthValidateBearerToken),
middleware.RateLimit(),
middleware.Gzip(),
middleware.CacheControl("no-store"), // never cache api responses
)

Expand Down
5 changes: 3 additions & 2 deletions internal/api/fileserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package api

import (
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api/fileserver"
"github.com/superseriousbusiness/gotosocial/internal/middleware"
"github.com/superseriousbusiness/gotosocial/internal/processing"
Expand All @@ -29,12 +30,12 @@ type Fileserver struct {
fileserver *fileserver.Module
}

func (f *Fileserver) Route(r router.Router) {
func (f *Fileserver) Route(r router.Router, m ...gin.HandlerFunc) {
fileserverGroup := r.AttachGroup("fileserver")

// attach middlewares appropriate for this group
fileserverGroup.Use(m...)
fileserverGroup.Use(
middleware.RateLimit(),
// Since we'll never host different files at the same
// URL (bc the ULIDs are generated per piece of media),
// it's sensible and safe to use a long cache here, so
Expand Down
9 changes: 5 additions & 4 deletions internal/api/nodeinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package api

import (
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api/nodeinfo"
"github.com/superseriousbusiness/gotosocial/internal/middleware"
"github.com/superseriousbusiness/gotosocial/internal/processing"
Expand All @@ -29,15 +30,15 @@ type NodeInfo struct {
nodeInfo *nodeinfo.Module
}

func (w *NodeInfo) Route(r router.Router) {
func (w *NodeInfo) Route(r router.Router, m ...gin.HandlerFunc) {
// group nodeinfo endpoints together
nodeInfoGroup := r.AttachGroup("nodeinfo")

// attach middlewares appropriate for this group
nodeInfoGroup.Use(m...)
nodeInfoGroup.Use(
middleware.Gzip(),
middleware.RateLimit(),
middleware.CacheControl("public", "max-age=120"), // allow cache for 2 minutes
// allow cache for 2 minutes
middleware.CacheControl("public", "max-age=120"),
)

w.nodeInfo.Route(nodeInfoGroup.Handle)
Expand Down
6 changes: 3 additions & 3 deletions internal/api/wellknown.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package api

import (
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/nodeinfo"
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger"
"github.com/superseriousbusiness/gotosocial/internal/middleware"
Expand All @@ -31,14 +32,13 @@ type WellKnown struct {
webfinger *webfinger.Module
}

func (w *WellKnown) Route(r router.Router) {
func (w *WellKnown) Route(r router.Router, m ...gin.HandlerFunc) {
// group .well-known endpoints together
wellKnownGroup := r.AttachGroup(".well-known")

// attach middlewares appropriate for this group
wellKnownGroup.Use(m...)
wellKnownGroup.Use(
middleware.Gzip(),
middleware.RateLimit(),
// allow .well-known responses to be cached for 2 minutes
middleware.CacheControl("public", "max-age=120"),
)
Expand Down
12 changes: 9 additions & 3 deletions internal/middleware/gzip.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@
package middleware

import (
ginGzip "github.com/gin-contrib/gzip"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
)

// Gzip returns a gzip gin middleware using default compression.
func Gzip() gin.HandlerFunc {
// todo: make this configurable
return ginGzip.Gzip(ginGzip.DefaultCompression)
const enabled = true

if !enabled {
// use noop middleware if gzip is disabled
return func(ctx *gin.Context) {}
}

return gzip.Gzip(gzip.DefaultCompression)
}
30 changes: 10 additions & 20 deletions internal/middleware/ratelimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"time"

"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/ulule/limiter/v3"
limitergin "github.com/ulule/limiter/v3/drivers/middleware/gin"
"github.com/ulule/limiter/v3/drivers/store/memory"
Expand All @@ -44,34 +43,25 @@ const rateLimitPeriod = 5 * time.Minute
//
// If the config AdvancedRateLimitRequests value is <= 0, then a noop handler will be returned,
// which performs no rate limiting.
func RateLimit() gin.HandlerFunc {
// only enable rate limit middleware if configured
// advanced-rate-limit-requests is greater than 0
rateLimitRequests := config.GetAdvancedRateLimitRequests()
if rateLimitRequests <= 0 {
func RateLimit(limit int) gin.HandlerFunc {
if limit <= 0 {
// use noop middleware if ratelimiting is disabled
return func(c *gin.Context) {}
return func(ctx *gin.Context) {}
}

rate := limiter.Rate{
Period: rateLimitPeriod,
Limit: int64(rateLimitRequests),
}

limiterInstance := limiter.New(
limiter := limiter.New(
memory.NewStore(),
rate,
limiter.Rate{Period: rateLimitPeriod, Limit: int64(limit)},
limiter.WithIPv6Mask(net.CIDRMask(64, 128)), // apply /64 mask to IPv6 addresses
)

limitReachedHandler := func(c *gin.Context) {
// use custom rate limit reached error
handler := func(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit reached"})
}

middleware := limitergin.NewMiddleware(
limiterInstance,
limitergin.WithLimitReachedHandler(limitReachedHandler), // use custom rate limit reached error
return limitergin.NewMiddleware(
limiter,
limitergin.WithLimitReachedHandler(handler),
)

return middleware
}
3 changes: 2 additions & 1 deletion internal/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func New(processor processing.Processor) *Module {
}
}

func (m *Module) Route(r router.Router) {
func (m *Module) Route(r router.Router, mi ...gin.HandlerFunc) {
// serve static files from assets dir at /assets
assetsGroup := r.AttachGroup(assetsPathPrefix)
webAssetsAbsFilePath, err := filepath.Abs(config.GetWebAssetBaseDir())
Expand All @@ -80,6 +80,7 @@ func (m *Module) Route(r router.Router) {

// use the cache middleware on all handlers in this group
assetsGroup.Use(m.assetsCacheControlMiddleware(fs))
assetsGroup.Use(mi...)

// serve static file system in the root of this group,
// will end up being something like "/assets/"
Expand Down