diff options
author | 2025-01-27 19:21:13 +0100 | |
---|---|---|
committer | 2025-01-27 19:21:13 +0100 | |
commit | 904829094816fb38d8f1e1d2c19c4c9c014baa88 (patch) | |
tree | ef481d04b884011b838a03c8b3dd58b955c7eaec /internal | |
parent | [chore] some tidy ups (#3677) (diff) | |
download | gotosocial-904829094816fb38d8f1e1d2c19c4c9c014baa88.tar.xz |
[chore] skip `trusted-proxies` warning if ip excepted from rate limiting (#3699)
* [chore] skip `trusted-proxies` warning if ip excepted from rate limiting
* weep
* typo
* fix env parsing test
Diffstat (limited to 'internal')
-rw-r--r-- | internal/api/util/template.go | 35 | ||||
-rw-r--r-- | internal/config/config.go | 18 | ||||
-rw-r--r-- | internal/config/gen/gen.go | 1 | ||||
-rw-r--r-- | internal/config/helpers.gen.go | 30 | ||||
-rw-r--r-- | internal/config/validate.go | 18 | ||||
-rw-r--r-- | internal/middleware/ratelimit.go | 10 | ||||
-rw-r--r-- | internal/middleware/ratelimit_test.go | 34 |
7 files changed, 91 insertions, 55 deletions
diff --git a/internal/api/util/template.go b/internal/api/util/template.go index f58563660..990874028 100644 --- a/internal/api/util/template.go +++ b/internal/api/util/template.go @@ -18,12 +18,13 @@ package util import ( - "net" "net/http" + "net/netip" "github.com/gin-gonic/gin" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/log" ) // WebPage encapsulates variables for @@ -137,7 +138,15 @@ func injectTrustedProxiesRec( return } - ip := net.ParseIP(clientIP) + ip, err := netip.ParseAddr(clientIP) + if err != nil { + log.Warnf( + c.Request.Context(), + "gin returned invalid clientIP %s: %v", + clientIP, err, + ) + } + if !ip.IsPrivate() { // Upstream set a remote IP // header but final clientIP @@ -147,8 +156,18 @@ func injectTrustedProxiesRec( return } + except := config.GetAdvancedRateLimitExceptionsParsed() + for _, prefix := range except { + if prefix.Contains(ip) { + // This ip is exempt from + // rate limiting, so don't + // inject the suggestion. + return + } + } + // Private IP, guess if Docker. - if dockerSubnet.Contains(ip) { + if DockerSubnet.Contains(ip) { // Suggest a CIDR that likely // covers this Docker subnet, // eg., 172.17.0.0 -> 172.17.255.255. @@ -163,16 +182,10 @@ func injectTrustedProxiesRec( obj["trustedProxiesRec"] = trustedProxiesRec } -// dockerSubnet is a CIDR that lets one make hazy guesses +// DockerSubnet is a CIDR that lets one make hazy guesses // as to whether an address is within the ranges Docker // uses for subnets, ie., 172.16.0.0 -> 172.31.255.255. -var dockerSubnet = func() *net.IPNet { - _, subnet, err := net.ParseCIDR("172.16.0.0/12") - if err != nil { - panic(err) - } - return subnet -}() +var DockerSubnet = netip.MustParsePrefix("172.16.0.0/12") // templateErrorPage renders the given // HTTP code, error, and request ID diff --git a/internal/config/config.go b/internal/config/config.go index f77c86c50..33b4553a8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -18,6 +18,7 @@ package config import ( + "net/netip" "reflect" "time" @@ -163,14 +164,15 @@ type Configuration struct { SyslogProtocol string `name:"syslog-protocol" usage:"Protocol to use when directing logs to syslog. Leave empty to connect to local syslog."` SyslogAddress string `name:"syslog-address" usage:"Address:port to send syslog logs to. Leave empty to connect to local syslog."` - AdvancedCookiesSamesite string `name:"advanced-cookies-samesite" usage:"'strict' or 'lax', see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite"` - AdvancedRateLimitRequests int `name:"advanced-rate-limit-requests" usage:"Amount of HTTP requests to permit within a 5 minute window. 0 or less turns rate limiting off."` - AdvancedRateLimitExceptions []string `name:"advanced-rate-limit-exceptions" usage:"Slice of CIDRs to exclude from rate limit restrictions."` - AdvancedThrottlingMultiplier int `name:"advanced-throttling-multiplier" usage:"Multiplier to use per cpu for http request throttling. 0 or less turns throttling off."` - AdvancedThrottlingRetryAfter time.Duration `name:"advanced-throttling-retry-after" usage:"Retry-After duration response to send for throttled requests."` - AdvancedSenderMultiplier int `name:"advanced-sender-multiplier" usage:"Multiplier to use per cpu for batching outgoing fedi messages. 0 or less turns batching off (not recommended)."` - AdvancedCSPExtraURIs []string `name:"advanced-csp-extra-uris" usage:"Additional URIs to allow when building content-security-policy for media + images."` - AdvancedHeaderFilterMode string `name:"advanced-header-filter-mode" usage:"Set incoming request header filtering mode."` + AdvancedCookiesSamesite string `name:"advanced-cookies-samesite" usage:"'strict' or 'lax', see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite"` + AdvancedRateLimitRequests int `name:"advanced-rate-limit-requests" usage:"Amount of HTTP requests to permit within a 5 minute window. 0 or less turns rate limiting off."` + AdvancedRateLimitExceptions []string `name:"advanced-rate-limit-exceptions" usage:"Slice of CIDRs to exclude from rate limit restrictions."` + AdvancedRateLimitExceptionsParsed []netip.Prefix `name:"advanced-rate-limit-exceptions-parsed"` + AdvancedThrottlingMultiplier int `name:"advanced-throttling-multiplier" usage:"Multiplier to use per cpu for http request throttling. 0 or less turns throttling off."` + AdvancedThrottlingRetryAfter time.Duration `name:"advanced-throttling-retry-after" usage:"Retry-After duration response to send for throttled requests."` + AdvancedSenderMultiplier int `name:"advanced-sender-multiplier" usage:"Multiplier to use per cpu for batching outgoing fedi messages. 0 or less turns batching off (not recommended)."` + AdvancedCSPExtraURIs []string `name:"advanced-csp-extra-uris" usage:"Additional URIs to allow when building content-security-policy for media + images."` + AdvancedHeaderFilterMode string `name:"advanced-header-filter-mode" usage:"Set incoming request header filtering mode."` // HTTPClient configuration vars. HTTPClient HTTPClientConfiguration `name:"http-client"` diff --git a/internal/config/gen/gen.go b/internal/config/gen/gen.go index 9130f8606..a3742ee15 100644 --- a/internal/config/gen/gen.go +++ b/internal/config/gen/gen.go @@ -65,6 +65,7 @@ func main() { fmt.Fprint(output, license) fmt.Fprint(output, "package config\n\n") fmt.Fprint(output, "import (\n") + fmt.Fprint(output, "\t\"net/netip\"\n") fmt.Fprint(output, "\t\"time\"\n\n") fmt.Fprint(output, "\t\"codeberg.org/gruf/go-bytesize\"\n") fmt.Fprint(output, "\t\"github.com/superseriousbusiness/gotosocial/internal/language\"\n") diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go index fd1b86898..0f8ec02ce 100644 --- a/internal/config/helpers.gen.go +++ b/internal/config/helpers.gen.go @@ -19,6 +19,7 @@ package config import ( + "net/netip" "time" "codeberg.org/gruf/go-bytesize" @@ -2681,6 +2682,35 @@ func GetAdvancedRateLimitExceptions() []string { return global.GetAdvancedRateLi // SetAdvancedRateLimitExceptions safely sets the value for global configuration 'AdvancedRateLimitExceptions' field func SetAdvancedRateLimitExceptions(v []string) { global.SetAdvancedRateLimitExceptions(v) } +// GetAdvancedRateLimitExceptionsParsed safely fetches the Configuration value for state's 'AdvancedRateLimitExceptionsParsed' field +func (st *ConfigState) GetAdvancedRateLimitExceptionsParsed() (v []netip.Prefix) { + st.mutex.RLock() + v = st.config.AdvancedRateLimitExceptionsParsed + st.mutex.RUnlock() + return +} + +// SetAdvancedRateLimitExceptionsParsed safely sets the Configuration value for state's 'AdvancedRateLimitExceptionsParsed' field +func (st *ConfigState) SetAdvancedRateLimitExceptionsParsed(v []netip.Prefix) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.AdvancedRateLimitExceptionsParsed = v + st.reloadToViper() +} + +// AdvancedRateLimitExceptionsParsedFlag returns the flag name for the 'AdvancedRateLimitExceptionsParsed' field +func AdvancedRateLimitExceptionsParsedFlag() string { return "" } + +// GetAdvancedRateLimitExceptionsParsed safely fetches the value for global configuration 'AdvancedRateLimitExceptionsParsed' field +func GetAdvancedRateLimitExceptionsParsed() []netip.Prefix { + return global.GetAdvancedRateLimitExceptionsParsed() +} + +// SetAdvancedRateLimitExceptionsParsed safely sets the value for global configuration 'AdvancedRateLimitExceptionsParsed' field +func SetAdvancedRateLimitExceptionsParsed(v []netip.Prefix) { + global.SetAdvancedRateLimitExceptionsParsed(v) +} + // GetAdvancedThrottlingMultiplier safely fetches the Configuration value for state's 'AdvancedThrottlingMultiplier' field func (st *ConfigState) GetAdvancedThrottlingMultiplier() (v int) { st.mutex.RLock() diff --git a/internal/config/validate.go b/internal/config/validate.go index 723d5c931..c8ebd4f2d 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -19,6 +19,7 @@ package config import ( "fmt" + "net/netip" "net/url" "strings" @@ -168,5 +169,22 @@ func Validate() error { ) } + // Parse `advanced-rate-limit-exceptions` and set + // parsed versions on config to avoid reparsing calls. + rles := GetAdvancedRateLimitExceptions() + rlesParsed := make([]netip.Prefix, 0, len(rles)) + for _, rle := range rles { + parsed, err := netip.ParsePrefix(rle) + if err != nil { + errf( + "invalid entry %s in %s: %w", + rle, AdvancedRateLimitExceptionsFlag(), err, + ) + continue + } + rlesParsed = append(rlesParsed, parsed) + } + SetAdvancedRateLimitExceptionsParsed(rlesParsed) + return errs.Combine() } diff --git a/internal/middleware/ratelimit.go b/internal/middleware/ratelimit.go index 352a30c22..a259cd575 100644 --- a/internal/middleware/ratelimit.go +++ b/internal/middleware/ratelimit.go @@ -48,7 +48,7 @@ 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(limit int, exceptions []string) gin.HandlerFunc { +func RateLimit(limit int, except []netip.Prefix) gin.HandlerFunc { if limit <= 0 { // Rate limiting is disabled. // Return noop middleware. @@ -63,12 +63,6 @@ func RateLimit(limit int, exceptions []string) gin.HandlerFunc { }, ) - // Convert exceptions IP ranges into prefixes. - exceptPrefs := make([]netip.Prefix, len(exceptions)) - for i, str := range exceptions { - exceptPrefs[i] = netip.MustParsePrefix(str) - } - // It's prettymuch impossible to effectively // rate limit the immense IPv6 address space // unless we mask some of the bytes. @@ -88,7 +82,7 @@ func RateLimit(limit int, exceptions []string) gin.HandlerFunc { // Check if this IP is exempt from rate // limits and skip further checks if so. - for _, prefix := range exceptPrefs { + for _, prefix := range except { if prefix.Contains(clientIP) { c.Next() return diff --git a/internal/middleware/ratelimit_test.go b/internal/middleware/ratelimit_test.go index ad9891d79..e5afd40a6 100644 --- a/internal/middleware/ratelimit_test.go +++ b/internal/middleware/ratelimit_test.go @@ -20,6 +20,7 @@ package middleware_test import ( "net/http" "net/http/httptest" + "net/netip" "strconv" "testing" "time" @@ -47,60 +48,37 @@ func (suite *RateLimitTestSuite) TestRateLimit() { type rlTest struct { limit int - exceptions []string + exceptions []netip.Prefix clientIP string - shouldPanic bool shouldExcept bool } for _, test := range []rlTest{ { limit: 10, - exceptions: []string{}, + exceptions: nil, clientIP: "192.0.2.0", - shouldPanic: false, shouldExcept: false, }, { limit: 10, - exceptions: []string{}, + exceptions: nil, clientIP: "192.0.2.0", - shouldPanic: false, shouldExcept: false, }, { limit: 10, - exceptions: []string{"192.0.2.0/24"}, + exceptions: []netip.Prefix{netip.MustParsePrefix("192.0.2.0/24")}, clientIP: "192.0.2.0", - shouldPanic: false, shouldExcept: true, }, { limit: 10, - exceptions: []string{"192.0.2.0/32"}, + exceptions: []netip.Prefix{netip.MustParsePrefix("192.0.2.0/32")}, clientIP: "192.0.2.1", - shouldPanic: false, - shouldExcept: false, - }, - { - limit: 10, - exceptions: []string{"Ceci n'est pas une CIDR"}, - clientIP: "192.0.2.0", - shouldPanic: true, shouldExcept: false, }, } { - if test.shouldPanic { - // Try to trigger panic. - suite.Panics(func() { - _ = middleware.RateLimit( - test.limit, - test.exceptions, - ) - }) - continue - } - rlMiddleware := middleware.RateLimit( test.limit, test.exceptions, |