mirror of
				https://github.com/dcarrillo/whatismyip.git
				synced 2025-10-31 16:59:09 +00:00 
			
		
		
		
	Remove headers set by a trusted proxy from outputs
This commit is contained in:
		| @@ -3,9 +3,11 @@ package httputils | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/textproto" | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/dcarrillo/whatismyip/internal/setting" | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -32,6 +34,17 @@ func HeadersToSortedString(headers http.Header) string { | |||||||
| 	return output | 	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 | // GetLogFormatter returns our custom log format | ||||||
| func GetLogFormatter(param gin.LogFormatterParams) string { | func GetLogFormatter(param gin.LogFormatterParams) string { | ||||||
| 	return fmt.Sprintf("%s - [%s] \"%s %s %s\" %d %d %d %s \"%s\" \"%s\" \"%s\"\n", | 	return fmt.Sprintf("%s - [%s] \"%s %s %s\" %d %d %d %s \"%s\" \"%s\" \"%s\"\n", | ||||||
|   | |||||||
| @@ -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.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.TLSKeyPath, "tls-key", "", "When using TLS, path to private key file") | ||||||
| 	flags.StringVar(&App.TrustedHeader, | 	flags.StringVar( | ||||||
|  | 		&App.TrustedHeader, | ||||||
| 		"trusted-header", | 		"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-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(&App.version, "version", false, "Output version information and exit") | ||||||
| 	flags.BoolVar( | 	flags.BoolVar( | ||||||
|   | |||||||
| @@ -79,8 +79,8 @@ func getAllAsString(ctx *gin.Context) { | |||||||
| 		output += geoASNRecordToString(record) + "\n" | 		output += geoASNRecordToString(record) + "\n" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	h := ctx.Request.Header | 	h := httputils.GetHeadersWithoutTrustedHeaders(ctx) | ||||||
| 	h["Host"] = []string{ctx.Request.Host} | 	h.Set("Host", ctx.Request.Host) | ||||||
| 	output += httputils.HeadersToSortedString(h) | 	output += httputils.HeadersToSortedString(h) | ||||||
|  |  | ||||||
| 	ctx.String(http.StatusOK, output) | 	ctx.String(http.StatusOK, output) | ||||||
| @@ -113,6 +113,6 @@ func jsonOutput(ctx *gin.Context) JSONResponse { | |||||||
| 		ASN:             asnRecord.AutonomousSystemNumber, | 		ASN:             asnRecord.AutonomousSystemNumber, | ||||||
| 		ASNOrganization: asnRecord.AutonomousSystemOrganization, | 		ASNOrganization: asnRecord.AutonomousSystemOrganization, | ||||||
| 		Host:            ctx.Request.Host, | 		Host:            ctx.Request.Host, | ||||||
| 		Headers:         ctx.Request.Header, | 		Headers:         httputils.GetHeadersWithoutTrustedHeaders(ctx), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -102,9 +102,6 @@ func TestClientPort(t *testing.T) { | |||||||
| 		params  []string | 		params  []string | ||||||
| 		headers map[string][]string | 		headers map[string][]string | ||||||
| 	} | 	} | ||||||
| 	type expected struct { |  | ||||||
| 		body string |  | ||||||
| 	} |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name     string | 		name     string | ||||||
| 		args     args | 		args     args | ||||||
| @@ -249,8 +246,6 @@ ASN Organization: | |||||||
|  |  | ||||||
| Header1: one | Header1: one | ||||||
| Host: test | Host: test | ||||||
| X-Real-Ip: 81.2.69.192 |  | ||||||
| X-Real-Port: 1001 |  | ||||||
| ` | ` | ||||||
| 	_, _ = setting.Setup( | 	_, _ = setting.Setup( | ||||||
| 		[]string{ | 		[]string{ | ||||||
|   | |||||||
| @@ -10,15 +10,17 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func getHeadersAsSortedString(ctx *gin.Context) { | func getHeadersAsSortedString(ctx *gin.Context) { | ||||||
| 	h := ctx.Request.Header | 	h := httputils.GetHeadersWithoutTrustedHeaders(ctx) | ||||||
| 	h["Host"] = []string{ctx.Request.Host} | 	h.Set("Host", ctx.Request.Host) | ||||||
|  |  | ||||||
| 	ctx.String(http.StatusOK, httputils.HeadersToSortedString(h)) | 	ctx.String(http.StatusOK, httputils.HeadersToSortedString(h)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func getHeaderAsString(ctx *gin.Context) { | func getHeaderAsString(ctx *gin.Context) { | ||||||
|  | 	headers := httputils.GetHeadersWithoutTrustedHeaders(ctx) | ||||||
|  |  | ||||||
| 	h := ctx.Params.ByName("header") | 	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)) | 		ctx.String(http.StatusOK, template.HTMLEscapeString(v)) | ||||||
| 	} else if strings.ToLower(h) == "host" { | 	} else if strings.ToLower(h) == "host" { | ||||||
| 		ctx.String(http.StatusOK, template.HTMLEscapeString(ctx.Request.Host)) | 		ctx.String(http.StatusOK, template.HTMLEscapeString(ctx.Request.Host)) | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/dcarrillo/whatismyip/internal/setting" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -26,13 +27,20 @@ Header2: value22 | |||||||
| Header3: value3 | Header3: value3 | ||||||
| Host:  | Host:  | ||||||
| ` | ` | ||||||
|  | 	_, _ = setting.Setup([]string{ | ||||||
|  | 		"-geoip2-city", "city", | ||||||
|  | 		"-geoip2-asn", "asn", | ||||||
|  | 		"-trusted-header", trustedHeader, | ||||||
|  | 		"-trusted-port-header", trustedPortHeader, | ||||||
|  | 	}) | ||||||
| 	req, _ := http.NewRequest("GET", "/headers", nil) | 	req, _ := http.NewRequest("GET", "/headers", nil) | ||||||
| 	req.Header = map[string][]string{ | 	req.Header = map[string][]string{ | ||||||
| 		"Header1": {"value1"}, | 		"Header1": {"value1"}, | ||||||
| 		"Header2": {"value21", "value22"}, | 		"Header2": {"value21", "value22"}, | ||||||
| 		"Header3": {"value3"}, | 		"Header3": {"value3"}, | ||||||
| 	} | 	} | ||||||
|  | 	req.Header.Set(trustedHeader, "1.1.1.1") | ||||||
|  | 	req.Header.Set(trustedPortHeader, "1025") | ||||||
|  |  | ||||||
| 	w := httptest.NewRecorder() | 	w := httptest.NewRecorder() | ||||||
| 	app.ServeHTTP(w, req) | 	app.ServeHTTP(w, req) | ||||||
|   | |||||||
| @@ -34,8 +34,8 @@ var ( | |||||||
| 		text: "text/plain; charset=utf-8", | 		text: "text/plain; charset=utf-8", | ||||||
| 		json: "application/json; 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"]}}` | 	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":"", "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":""}` | 	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" | const trustedHeader = "X-Real-IP" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user