From 1ee725650658ad1269b2d80a0c1e46fe50b5ce57 Mon Sep 17 00:00:00 2001 From: Daniel Carrillo Date: Mon, 2 May 2022 18:00:36 +0200 Subject: [PATCH] Remove headers set by a trusted proxy from outputs --- internal/httputils/http.go | 13 +++++++++++++ internal/setting/app.go | 10 ++++++---- router/generic.go | 6 +++--- router/generic_test.go | 5 ----- router/headers.go | 8 +++++--- router/headers_test.go | 10 +++++++++- router/setup_test.go | 4 ++-- 7 files changed, 38 insertions(+), 18 deletions(-) diff --git a/internal/httputils/http.go b/internal/httputils/http.go index 6bfda70..e3ccc08 100644 --- a/internal/httputils/http.go +++ b/internal/httputils/http.go @@ -3,9 +3,11 @@ package httputils import ( "fmt" "net/http" + "net/textproto" "sort" "strings" + "github.com/dcarrillo/whatismyip/internal/setting" "github.com/gin-gonic/gin" ) @@ -32,6 +34,17 @@ func HeadersToSortedString(headers http.Header) string { return output } +// GetHeadersWithoutTrustedHeaders return a http.Heade object with the original headers except trusted headers +func GetHeadersWithoutTrustedHeaders(ctx *gin.Context) http.Header { + h := ctx.Request.Header + + for _, k := range []string{setting.App.TrustedHeader, setting.App.TrustedPortHeader} { + delete(h, textproto.CanonicalMIMEHeaderKey(k)) + } + + return h +} + // GetLogFormatter returns our custom log format func GetLogFormatter(param gin.LogFormatterParams) string { return fmt.Sprintf("%s - [%s] \"%s %s %s\" %d %d %d %s \"%s\" \"%s\" \"%s\"\n", diff --git a/internal/setting/app.go b/internal/setting/app.go index 1af46c1..d303ead 100644 --- a/internal/setting/app.go +++ b/internal/setting/app.go @@ -70,15 +70,17 @@ func Setup(args []string) (output string, err error) { ) flags.StringVar(&App.TLSCrtPath, "tls-crt", "", "When using TLS, path to certificate file") flags.StringVar(&App.TLSKeyPath, "tls-key", "", "When using TLS, path to private key file") - flags.StringVar(&App.TrustedHeader, + flags.StringVar( + &App.TrustedHeader, "trusted-header", "", - "Trusted request header for remote IP (e.g. X-Real-IP)", + "Trusted request header for remote IP (e.g. X-Real-IP). When using this feature if -trusted-port-header is not set the client port is shown as 'unknown'", ) - flags.StringVar(&App.TrustedPortHeader, + flags.StringVar( + &App.TrustedPortHeader, "trusted-port-header", "", - "Trusted request header for remote client port (e.g. X-Real-Port)", + "Trusted request header for remote client port (e.g. X-Real-Port). When this parameter is set -trusted-header becomes mandatory", ) flags.BoolVar(&App.version, "version", false, "Output version information and exit") flags.BoolVar( diff --git a/router/generic.go b/router/generic.go index 7e404fd..68cdac1 100644 --- a/router/generic.go +++ b/router/generic.go @@ -79,8 +79,8 @@ func getAllAsString(ctx *gin.Context) { output += geoASNRecordToString(record) + "\n" } - h := ctx.Request.Header - h["Host"] = []string{ctx.Request.Host} + h := httputils.GetHeadersWithoutTrustedHeaders(ctx) + h.Set("Host", ctx.Request.Host) output += httputils.HeadersToSortedString(h) ctx.String(http.StatusOK, output) @@ -113,6 +113,6 @@ func jsonOutput(ctx *gin.Context) JSONResponse { ASN: asnRecord.AutonomousSystemNumber, ASNOrganization: asnRecord.AutonomousSystemOrganization, Host: ctx.Request.Host, - Headers: ctx.Request.Header, + Headers: httputils.GetHeadersWithoutTrustedHeaders(ctx), } } diff --git a/router/generic_test.go b/router/generic_test.go index 9382000..6ac258f 100644 --- a/router/generic_test.go +++ b/router/generic_test.go @@ -102,9 +102,6 @@ func TestClientPort(t *testing.T) { params []string headers map[string][]string } - type expected struct { - body string - } tests := []struct { name string args args @@ -249,8 +246,6 @@ ASN Organization: Header1: one Host: test -X-Real-Ip: 81.2.69.192 -X-Real-Port: 1001 ` _, _ = setting.Setup( []string{ diff --git a/router/headers.go b/router/headers.go index 04b914f..da4b863 100644 --- a/router/headers.go +++ b/router/headers.go @@ -10,15 +10,17 @@ import ( ) func getHeadersAsSortedString(ctx *gin.Context) { - h := ctx.Request.Header - h["Host"] = []string{ctx.Request.Host} + h := httputils.GetHeadersWithoutTrustedHeaders(ctx) + h.Set("Host", ctx.Request.Host) ctx.String(http.StatusOK, httputils.HeadersToSortedString(h)) } func getHeaderAsString(ctx *gin.Context) { + headers := httputils.GetHeadersWithoutTrustedHeaders(ctx) + h := ctx.Params.ByName("header") - if v := ctx.GetHeader(h); v != "" { + if v := headers.Get(ctx.Params.ByName("header")); v != "" { ctx.String(http.StatusOK, template.HTMLEscapeString(v)) } else if strings.ToLower(h) == "host" { ctx.String(http.StatusOK, template.HTMLEscapeString(ctx.Request.Host)) diff --git a/router/headers_test.go b/router/headers_test.go index 5c3c4e7..889a507 100644 --- a/router/headers_test.go +++ b/router/headers_test.go @@ -5,6 +5,7 @@ import ( "net/http/httptest" "testing" + "github.com/dcarrillo/whatismyip/internal/setting" "github.com/stretchr/testify/assert" ) @@ -26,13 +27,20 @@ Header2: value22 Header3: value3 Host: ` - + _, _ = setting.Setup([]string{ + "-geoip2-city", "city", + "-geoip2-asn", "asn", + "-trusted-header", trustedHeader, + "-trusted-port-header", trustedPortHeader, + }) req, _ := http.NewRequest("GET", "/headers", nil) req.Header = map[string][]string{ "Header1": {"value1"}, "Header2": {"value21", "value22"}, "Header3": {"value3"}, } + req.Header.Set(trustedHeader, "1.1.1.1") + req.Header.Set(trustedPortHeader, "1025") w := httptest.NewRecorder() app.ServeHTTP(w, req) diff --git a/router/setup_test.go b/router/setup_test.go index 712e46f..ac188b0 100644 --- a/router/setup_test.go +++ b/router/setup_test.go @@ -34,8 +34,8 @@ var ( text: "text/plain; charset=utf-8", json: "application/json; charset=utf-8", } - jsonIPv4 = `{"client_port":"1001","ip":"81.2.69.192","ip_version":4,"country":"United Kingdom","country_code":"GB","city":"London","latitude":51.5142,"longitude":-0.0931,"postal_code":"","time_zone":"Europe/London","asn":0,"asn_organization":"","host":"test","headers":{"X-Real-Ip":["81.2.69.192"], "X-Real-Port":["1001"]}}` - jsonIPv6 = `{"asn":3352, "asn_organization":"TELEFONICA DE ESPANA", "city":"", "client_port":"1001", "country":"", "country_code":"", "headers":{"X-Real-Ip":["2a02:9000::1"], "X-Real-Port":["1001"]}, "host":"test", "ip":"2a02:9000::1", "ip_version":6, "latitude":0, "longitude":0, "postal_code":"", "time_zone":""}` + jsonIPv4 = `{"client_port":"1001","ip":"81.2.69.192","ip_version":4,"country":"United Kingdom","country_code":"GB","city":"London","latitude":51.5142,"longitude":-0.0931,"postal_code":"","time_zone":"Europe/London","asn":0,"asn_organization":"","host":"test", "headers": {}}` + jsonIPv6 = `{"asn":3352, "asn_organization":"TELEFONICA DE ESPANA", "city":"", "client_port":"1001", "country":"", "country_code":"", "host":"test", "ip":"2a02:9000::1", "ip_version":6, "latitude":0, "longitude":0, "postal_code":"", "time_zone":"", "headers": {}}` ) const trustedHeader = "X-Real-IP"