From 288bb4f7cabe84b2eaa6a02f7271fc5c2466170d Mon Sep 17 00:00:00 2001 From: n33pm <12273891+n33pm@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:28:39 +0200 Subject: [PATCH 1/5] feat(): add CF-Connection-IP --- middleware/realip.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/middleware/realip.go b/middleware/realip.go index 55c95a89..11c0348a 100644 --- a/middleware/realip.go +++ b/middleware/realip.go @@ -9,12 +9,13 @@ import ( "strings" ) +var cfConnectionIP = http.CanonicalHeaderKey("CF-Connecting-IP") var trueClientIP = http.CanonicalHeaderKey("True-Client-IP") var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") var xRealIP = http.CanonicalHeaderKey("X-Real-IP") // RealIP is a middleware that sets a http.Request's RemoteAddr to the results -// of parsing either the True-Client-IP, X-Real-IP or the X-Forwarded-For headers +// of parsing either the CF-Connecting-IP, True-Client-IP, X-Real-IP or the X-Forwarded-For headers // (in that order). // // This middleware should be inserted fairly early in the middleware stack to @@ -42,7 +43,9 @@ func RealIP(h http.Handler) http.Handler { func realIP(r *http.Request) string { var ip string - if tcip := r.Header.Get(trueClientIP); tcip != "" { + if cfcip := r.Header.Get(cfConnectionIP); cfcip != "" { + ip = cfcip + } else if tcip := r.Header.Get(trueClientIP); tcip != "" { ip = tcip } else if xrip := r.Header.Get(xRealIP); xrip != "" { ip = xrip From 34335091b8597eb5745c0cb5adda562e7c4a4681 Mon Sep 17 00:00:00 2001 From: n33pm <12273891+n33pm@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:44:50 +0200 Subject: [PATCH 2/5] feat(): custom real ip header #908 --- middleware/realip.go | 56 ++++++++++++++++++++++++--------------- middleware/realip_test.go | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 22 deletions(-) diff --git a/middleware/realip.go b/middleware/realip.go index 11c0348a..48057c11 100644 --- a/middleware/realip.go +++ b/middleware/realip.go @@ -9,10 +9,12 @@ import ( "strings" ) -var cfConnectionIP = http.CanonicalHeaderKey("CF-Connecting-IP") -var trueClientIP = http.CanonicalHeaderKey("True-Client-IP") -var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") -var xRealIP = http.CanonicalHeaderKey("X-Real-IP") +var DefaultRealIPHeaders = []string{ + "CF-Connecting-IP", // Cloudflare free plan + "True-Client-IP", // Cloudflare Enterprise plan + "X-Real-IP", + "X-Forwarded-For", +} // RealIP is a middleware that sets a http.Request's RemoteAddr to the results // of parsing either the CF-Connecting-IP, True-Client-IP, X-Real-IP or the X-Forwarded-For headers @@ -31,7 +33,7 @@ var xRealIP = http.CanonicalHeaderKey("X-Real-IP") // how you're using RemoteAddr, vulnerable to an attack of some sort). func RealIP(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { - if rip := realIP(r); rip != "" { + if rip := getRealIP(r, DefaultRealIPHeaders); rip != "" { r.RemoteAddr = rip } h.ServeHTTP(w, r) @@ -40,24 +42,34 @@ func RealIP(h http.Handler) http.Handler { return http.HandlerFunc(fn) } -func realIP(r *http.Request) string { - var ip string - - if cfcip := r.Header.Get(cfConnectionIP); cfcip != "" { - ip = cfcip - } else if tcip := r.Header.Get(trueClientIP); tcip != "" { - ip = tcip - } else if xrip := r.Header.Get(xRealIP); xrip != "" { - ip = xrip - } else if xff := r.Header.Get(xForwardedFor); xff != "" { - i := strings.Index(xff, ",") - if i == -1 { - i = len(xff) +// RealIPCustomHeader is a middleware that sets a http.Request's RemoteAddr to the results +// of parsing the custom headers. +// +// usage: +// r.Use(RealIPCustomHeader([]string{"X-CUSTOM-IP"})) +// r.Use(RealIPCustomHeader(append(DefaultRealIPHeaders, "X-CUSTOM-IP"))) +func RealIPCustomHeader(realIPHeaders []string) func(http.Handler) http.Handler { + f := func(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if rip := getRealIP(r, realIPHeaders); rip != "" { + r.RemoteAddr = rip + } + h.ServeHTTP(w, r) } - ip = xff[:i] + return http.HandlerFunc(fn) } - if ip == "" || net.ParseIP(ip) == nil { - return "" + return f +} + +func getRealIP(r *http.Request, realIPHeaders []string) string { + for _, header := range realIPHeaders { + if ip := r.Header.Get(header); ip != "" { + ips := strings.Split(ip, ",") + if ips[0] == "" || net.ParseIP(ips[0]) == nil { + continue + } + return ips[0] + } } - return ip + return "" } diff --git a/middleware/realip_test.go b/middleware/realip_test.go index 1ab5e95e..cc75dfcf 100644 --- a/middleware/realip_test.go +++ b/middleware/realip_test.go @@ -113,3 +113,52 @@ func TestInvalidIP(t *testing.T) { t.Fatal("Invalid IP used.") } } + +func TestCustomIPHeader(t *testing.T) { + var customHeaderKey = "X-CUSTOM-IP" + req, _ := http.NewRequest("GET", "/", nil) + req.Header.Add(customHeaderKey, "100.100.100.100") + w := httptest.NewRecorder() + + r := chi.NewRouter() + r.Use(RealIPCustomHeader([]string{customHeaderKey})) + + realIP := "" + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + realIP = r.RemoteAddr + w.Write([]byte("Hello World")) + }) + r.ServeHTTP(w, req) + + if w.Code != 200 { + t.Fatal("Response Code should be 200") + } + + if realIP != "100.100.100.100" { + t.Fatal("Test get real IP precedence error.") + } +} + +func TestCustomIPHeaderWithoutDefault(t *testing.T) { + req, _ := http.NewRequest("GET", "/", nil) + req.Header.Add("X-REAL-IP", "100.100.100.100") + w := httptest.NewRecorder() + + r := chi.NewRouter() + r.Use(RealIPCustomHeader([]string{"X-CUSTOM-IP"})) + + realIP := "" + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + realIP = r.RemoteAddr + w.Write([]byte("Hello World")) + }) + r.ServeHTTP(w, req) + + if w.Code != 200 { + t.Fatal("Response Code should be 200") + } + + if realIP != "" { + t.Fatal("Invalid IP used.") + } +} From cb4392478ea57566b7e00b3ffeb9af775932fac9 Mon Sep 17 00:00:00 2001 From: n33pm <12273891+n33pm@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:15:41 +0200 Subject: [PATCH 3/5] fix(): typo --- middleware/realip.go | 8 ++++---- middleware/realip_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/middleware/realip.go b/middleware/realip.go index 48057c11..f460fd1a 100644 --- a/middleware/realip.go +++ b/middleware/realip.go @@ -42,13 +42,13 @@ func RealIP(h http.Handler) http.Handler { return http.HandlerFunc(fn) } -// RealIPCustomHeader is a middleware that sets a http.Request's RemoteAddr to the results +// RealIPFromHeaders is a middleware that sets a http.Request's RemoteAddr to the results // of parsing the custom headers. // // usage: -// r.Use(RealIPCustomHeader([]string{"X-CUSTOM-IP"})) -// r.Use(RealIPCustomHeader(append(DefaultRealIPHeaders, "X-CUSTOM-IP"))) -func RealIPCustomHeader(realIPHeaders []string) func(http.Handler) http.Handler { +// r.Use(RealIPFromHeaders([]string{"CF-Connecting-IP"})) +// r.Use(RealIPFromHeaders(append(DefaultRealIPHeaders, "CF-Connecting-IP"))) +func RealIPFromHeaders(realIPHeaders []string) func(http.Handler) http.Handler { f := func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { if rip := getRealIP(r, realIPHeaders); rip != "" { diff --git a/middleware/realip_test.go b/middleware/realip_test.go index cc75dfcf..b0eb97dc 100644 --- a/middleware/realip_test.go +++ b/middleware/realip_test.go @@ -121,7 +121,7 @@ func TestCustomIPHeader(t *testing.T) { w := httptest.NewRecorder() r := chi.NewRouter() - r.Use(RealIPCustomHeader([]string{customHeaderKey})) + r.Use(RealIPFromHeaders([]string{customHeaderKey})) realIP := "" r.Get("/", func(w http.ResponseWriter, r *http.Request) { @@ -145,7 +145,7 @@ func TestCustomIPHeaderWithoutDefault(t *testing.T) { w := httptest.NewRecorder() r := chi.NewRouter() - r.Use(RealIPCustomHeader([]string{"X-CUSTOM-IP"})) + r.Use(RealIPFromHeaders([]string{"X-CUSTOM-IP"})) realIP := "" r.Get("/", func(w http.ResponseWriter, r *http.Request) { From da495745556e4252269857c93e299a9cf5f25418 Mon Sep 17 00:00:00 2001 From: n33pm <12273891+n33pm@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:17:45 +0200 Subject: [PATCH 4/5] refactor(): remove cf-connecting-ip from default headers --- middleware/realip.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/middleware/realip.go b/middleware/realip.go index f460fd1a..3b305957 100644 --- a/middleware/realip.go +++ b/middleware/realip.go @@ -10,14 +10,13 @@ import ( ) var DefaultRealIPHeaders = []string{ - "CF-Connecting-IP", // Cloudflare free plan "True-Client-IP", // Cloudflare Enterprise plan "X-Real-IP", "X-Forwarded-For", } // RealIP is a middleware that sets a http.Request's RemoteAddr to the results -// of parsing either the CF-Connecting-IP, True-Client-IP, X-Real-IP or the X-Forwarded-For headers +// of parsing either the True-Client-IP, X-Real-IP or the X-Forwarded-For headers // (in that order). // // This middleware should be inserted fairly early in the middleware stack to From 277d4a5ae105ac2c3568b9e5e01da47ccbdce888 Mon Sep 17 00:00:00 2001 From: n33pm <12273891+n33pm@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:36:11 +0200 Subject: [PATCH 5/5] refactor(): back to unexported default headers --- middleware/realip.go | 17 ++++++++--------- middleware/realip_test.go | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/middleware/realip.go b/middleware/realip.go index 3b305957..d91f2d1c 100644 --- a/middleware/realip.go +++ b/middleware/realip.go @@ -9,8 +9,8 @@ import ( "strings" ) -var DefaultRealIPHeaders = []string{ - "True-Client-IP", // Cloudflare Enterprise plan +var defaultHeaders = []string{ + "True-Client-IP", // Cloudflare Enterprise plan "X-Real-IP", "X-Forwarded-For", } @@ -32,7 +32,7 @@ var DefaultRealIPHeaders = []string{ // how you're using RemoteAddr, vulnerable to an attack of some sort). func RealIP(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { - if rip := getRealIP(r, DefaultRealIPHeaders); rip != "" { + if rip := getRealIP(r, defaultHeaders); rip != "" { r.RemoteAddr = rip } h.ServeHTTP(w, r) @@ -45,12 +45,11 @@ func RealIP(h http.Handler) http.Handler { // of parsing the custom headers. // // usage: -// r.Use(RealIPFromHeaders([]string{"CF-Connecting-IP"})) -// r.Use(RealIPFromHeaders(append(DefaultRealIPHeaders, "CF-Connecting-IP"))) -func RealIPFromHeaders(realIPHeaders []string) func(http.Handler) http.Handler { +// r.Use(RealIPFromHeaders("CF-Connecting-IP")) +func RealIPFromHeaders(headers ...string) func(http.Handler) http.Handler { f := func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { - if rip := getRealIP(r, realIPHeaders); rip != "" { + if rip := getRealIP(r, headers); rip != "" { r.RemoteAddr = rip } h.ServeHTTP(w, r) @@ -60,8 +59,8 @@ func RealIPFromHeaders(realIPHeaders []string) func(http.Handler) http.Handler { return f } -func getRealIP(r *http.Request, realIPHeaders []string) string { - for _, header := range realIPHeaders { +func getRealIP(r *http.Request, headers []string) string { + for _, header := range headers { if ip := r.Header.Get(header); ip != "" { ips := strings.Split(ip, ",") if ips[0] == "" || net.ParseIP(ips[0]) == nil { diff --git a/middleware/realip_test.go b/middleware/realip_test.go index b0eb97dc..97370323 100644 --- a/middleware/realip_test.go +++ b/middleware/realip_test.go @@ -121,7 +121,7 @@ func TestCustomIPHeader(t *testing.T) { w := httptest.NewRecorder() r := chi.NewRouter() - r.Use(RealIPFromHeaders([]string{customHeaderKey})) + r.Use(RealIPFromHeaders(customHeaderKey)) realIP := "" r.Get("/", func(w http.ResponseWriter, r *http.Request) { @@ -145,7 +145,7 @@ func TestCustomIPHeaderWithoutDefault(t *testing.T) { w := httptest.NewRecorder() r := chi.NewRouter() - r.Use(RealIPFromHeaders([]string{"X-CUSTOM-IP"})) + r.Use(RealIPFromHeaders("CF-Connecting-IP")) realIP := "" r.Get("/", func(w http.ResponseWriter, r *http.Request) {