From f8c3e565f3f7287e4982b4ba6b040718e5407c29 Mon Sep 17 00:00:00 2001 From: Bethuel Mmbaga Date: Thu, 17 Sep 2026 12:45:03 +0300 Subject: [PATCH] [management] Read X-Real-IP when extracting the peer connection IP (#7561) --- management/internals/server/boot.go | 7 +++---- management/internals/server/realip_test.go | 15 +++++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/management/internals/server/boot.go b/management/internals/server/boot.go index c28758ab8..cd3fb9a62 100644 --- a/management/internals/server/boot.go +++ b/management/internals/server/boot.go @@ -373,9 +373,8 @@ func streamInterceptor( // is used directly. Operators terminating connections at a reverse proxy should // configure TrustedPeers with that proxy's address or network. // -// Only X-Forwarded-For is trusted. X-Real-IP contains a single client-supplied -// address with no proxy chain to validate, and none of the reverse proxies we ship -// use it on the gRPC path. +// X-Forwarded-For is consulted first. X-Real-IP is read when X-Forwarded-For is +// absent or has no entries left after TrustedHTTPProxiesCount is applied. func realIPOptions(cfg nbconfig.ReverseProxy) []realip.Option { if idx := slices.IndexFunc(cfg.TrustedPeers, func(p netip.Prefix) bool { return p.Bits() == 0 }); idx >= 0 { log.WithContext(context.Background()).Warnf("TrustedPeers contains the default route %s, which trusts "+ @@ -393,6 +392,6 @@ func realIPOptions(cfg nbconfig.ReverseProxy) []realip.Option { realip.WithTrustedPeers(cfg.TrustedPeers), realip.WithTrustedProxies(cfg.TrustedHTTPProxies), realip.WithTrustedProxiesCount(cfg.TrustedHTTPProxiesCount), - realip.WithHeaders([]string{realip.XForwardedFor}), + realip.WithHeaders([]string{realip.XForwardedFor, realip.XRealIp}), } } diff --git a/management/internals/server/realip_test.go b/management/internals/server/realip_test.go index 89ac02730..661cbc94a 100644 --- a/management/internals/server/realip_test.go +++ b/management/internals/server/realip_test.go @@ -9,14 +9,13 @@ import ( "time" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip" + nbconfig "github.com/netbirdio/netbird/management/internals/server/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/types/known/emptypb" - - nbconfig "github.com/netbirdio/netbird/management/internals/server/config" ) const ( @@ -161,11 +160,19 @@ func TestRealIPTrustedPeerHonoursForwardedHeaders(t *testing.T) { ) } -func TestRealIPIgnoresXRealIPWhenProxyCountIsSet(t *testing.T) { +func TestRealIPReadsXRealIPWhenProxyCountSkipsForwardedFor(t *testing.T) { cfg := nbconfig.ReverseProxy{ TrustedPeers: []netip.Prefix{netip.MustParsePrefix("127.0.0.1/32")}, TrustedHTTPProxiesCount: 1, } - assertRealIP(t, cfg, "127.0.0.1", realip.XRealIp, "203.0.113.44") + t.Run("no X-Forwarded-For", func(t *testing.T) { + assertRealIP(t, cfg, "203.0.113.44", realip.XRealIp, "203.0.113.44") + }) + t.Run("single-entry X-Forwarded-For", func(t *testing.T) { + assertRealIP(t, cfg, "198.51.100.7", + realip.XForwardedFor, "203.0.113.44", + realip.XRealIp, "198.51.100.7", + ) + }) }