summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2025-01-27 19:21:13 +0100
committerLibravatar GitHub <noreply@github.com>2025-01-27 19:21:13 +0100
commit904829094816fb38d8f1e1d2c19c4c9c014baa88 (patch)
treeef481d04b884011b838a03c8b3dd58b955c7eaec /internal
parent[chore] some tidy ups (#3677) (diff)
downloadgotosocial-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.go35
-rw-r--r--internal/config/config.go18
-rw-r--r--internal/config/gen/gen.go1
-rw-r--r--internal/config/helpers.gen.go30
-rw-r--r--internal/config/validate.go18
-rw-r--r--internal/middleware/ratelimit.go10
-rw-r--r--internal/middleware/ratelimit_test.go34
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,