mirror of
https://github.com/TecharoHQ/anubis.git
synced 2025-08-03 01:38:14 -04:00
feat(cmd/anubis): compute full XFF header (#328)
* feat(cmd/anubis): compute full XFF header this one is pretty important to not pass through blindly, as many applications and frameworks will trust them * feat(cmd/anubis): skip XFF compute if remote address is loopback * docs: update CHANGELOG
This commit is contained in:
parent
bec7199ab6
commit
4e2c9de708
@ -280,6 +280,7 @@ func main() {
|
||||
h = s
|
||||
h = internal.RemoteXRealIP(*useRemoteAddress, *bindNetwork, h)
|
||||
h = internal.XForwardedForToXRealIP(h)
|
||||
h = internal.XForwardedForUpdate(h)
|
||||
|
||||
srv := http.Server{Handler: h}
|
||||
listener, listenerUrl := setupListener(*bindNetwork, *bind)
|
||||
|
@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Added documentation on how to use Anubis with Traefik in Docker
|
||||
- Improved error handling in some edge cases
|
||||
- Disable `generic-bot-catchall` rule because of its high false positive rate in real-world scenarios
|
||||
- Set or append to `X-Forwarded-For` header unless the remote connects over a loopback address [#328](https://github.com/TecharoHQ/anubis/issues/328)
|
||||
|
||||
## v1.16.0
|
||||
|
||||
|
@ -65,6 +65,46 @@ func XForwardedForToXRealIP(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// XForwardedForUpdate sets or updates the X-Forwarded-For header, adding
|
||||
// the known remote address to an existing chain if present
|
||||
func XForwardedForUpdate(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer next.ServeHTTP(w, r)
|
||||
|
||||
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
|
||||
if parsedRemoteIP := net.ParseIP(remoteIP); parsedRemoteIP != nil && parsedRemoteIP.IsLoopback() {
|
||||
// anubis is likely deployed behind a local reverse proxy
|
||||
// pass header as-is to not break existing applications
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
slog.Warn("The default format of request.RemoteAddr should be IP:Port", "remoteAddr", r.RemoteAddr)
|
||||
return
|
||||
}
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
forwardedList := strings.Split(",", xff)
|
||||
forwardedList = append(forwardedList, remoteIP)
|
||||
// this behavior is equivalent to
|
||||
// ingress-nginx "compute-full-forwarded-for"
|
||||
// https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#compute-full-forwarded-for
|
||||
//
|
||||
// this would be the correct place to strip and/or flatten this list
|
||||
//
|
||||
// strip - iterate backwards and eliminate configured trusted IPs
|
||||
// flatten - only return the last element to avoid spoofing confusion
|
||||
//
|
||||
// many applications handle this in different ways, but
|
||||
// generally they'd be expected to do these two things on
|
||||
// their own end to find the first non-spoofed IP
|
||||
r.Header.Set("X-Forwarded-For", strings.Join(forwardedList, ","))
|
||||
} else {
|
||||
r.Header.Set("X-Forwarded-For", remoteIP)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// NoStoreCache sets the Cache-Control header to no-store for the response.
|
||||
func NoStoreCache(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user