diff --git a/pkg/middlewares/forwardedheaders/forwarded_header.go b/pkg/middlewares/forwardedheaders/forwarded_header.go index 2a36859c4..d38011aa8 100644 --- a/pkg/middlewares/forwardedheaders/forwarded_header.go +++ b/pkg/middlewares/forwardedheaders/forwarded_header.go @@ -165,6 +165,12 @@ func (x *XForwarded) rewrite(outreq *http.Request) { unsafeHeader(outreq.Header).Set(xForwardedHost, outreq.Host) } + // Per https://www.rfc-editor.org/rfc/rfc2616#section-4.2, the Forwarded IPs list is in + // the same order as the values in the X-Forwarded-For header(s). + if xffs := unsafeHeader(outreq.Header).Values(xForwardedFor); len(xffs) > 0 { + unsafeHeader(outreq.Header).Set(xForwardedFor, strings.Join(xffs, ", ")) + } + if x.hostname != "" { unsafeHeader(outreq.Header).Set(xForwardedServer, x.hostname) } @@ -198,6 +204,10 @@ func (h unsafeHeader) Get(key string) string { return h[key][0] } +func (h unsafeHeader) Values(key string) []string { + return h[key] +} + func (h unsafeHeader) Del(key string) { delete(h, key) } diff --git a/pkg/middlewares/forwardedheaders/forwarded_header_test.go b/pkg/middlewares/forwardedheaders/forwarded_header_test.go index 40e2c323a..4d2dab2fc 100644 --- a/pkg/middlewares/forwardedheaders/forwarded_header_test.go +++ b/pkg/middlewares/forwardedheaders/forwarded_header_test.go @@ -15,7 +15,7 @@ func TestServeHTTP(t *testing.T) { desc string insecure bool trustedIps []string - incomingHeaders map[string]string + incomingHeaders map[string][]string remoteAddr string expectedHeaders map[string]string tls bool @@ -27,13 +27,13 @@ func TestServeHTTP(t *testing.T) { insecure: true, trustedIps: nil, remoteAddr: "", - incomingHeaders: map[string]string{}, + incomingHeaders: map[string][]string{}, expectedHeaders: map[string]string{ - "X-Forwarded-for": "", - "X-Forwarded-Uri": "", - "X-Forwarded-Method": "", - "X-Forwarded-Tls-Client-Cert": "", - "X-Forwarded-Tls-Client-Cert-Info": "", + xForwardedFor: "", + xForwardedURI: "", + xForwardedMethod: "", + xForwardedTLSClientCert: "", + xForwardedTLSClientCertInfo: "", }, }, { @@ -41,19 +41,19 @@ func TestServeHTTP(t *testing.T) { insecure: true, trustedIps: nil, remoteAddr: "", - incomingHeaders: map[string]string{ - "X-Forwarded-for": "10.0.1.0, 10.0.1.12", - "X-Forwarded-Uri": "/bar", - "X-Forwarded-Method": "GET", - "X-Forwarded-Tls-Client-Cert": "Cert", - "X-Forwarded-Tls-Client-Cert-Info": "CertInfo", + incomingHeaders: map[string][]string{ + xForwardedFor: {"10.0.1.0, 10.0.1.12"}, + xForwardedURI: {"/bar"}, + xForwardedMethod: {"GET"}, + xForwardedTLSClientCert: {"Cert"}, + xForwardedTLSClientCertInfo: {"CertInfo"}, }, expectedHeaders: map[string]string{ - "X-Forwarded-for": "10.0.1.0, 10.0.1.12", - "X-Forwarded-Uri": "/bar", - "X-Forwarded-Method": "GET", - "X-Forwarded-Tls-Client-Cert": "Cert", - "X-Forwarded-Tls-Client-Cert-Info": "CertInfo", + xForwardedFor: "10.0.1.0, 10.0.1.12", + xForwardedURI: "/bar", + xForwardedMethod: "GET", + xForwardedTLSClientCert: "Cert", + xForwardedTLSClientCertInfo: "CertInfo", }, }, { @@ -61,19 +61,19 @@ func TestServeHTTP(t *testing.T) { insecure: false, trustedIps: nil, remoteAddr: "", - incomingHeaders: map[string]string{ - "X-Forwarded-for": "10.0.1.0, 10.0.1.12", - "X-Forwarded-Uri": "/bar", - "X-Forwarded-Method": "GET", - "X-Forwarded-Tls-Client-Cert": "Cert", - "X-Forwarded-Tls-Client-Cert-Info": "CertInfo", + incomingHeaders: map[string][]string{ + xForwardedFor: {"10.0.1.0, 10.0.1.12"}, + xForwardedURI: {"/bar"}, + xForwardedMethod: {"GET"}, + xForwardedTLSClientCert: {"Cert"}, + xForwardedTLSClientCertInfo: {"CertInfo"}, }, expectedHeaders: map[string]string{ - "X-Forwarded-for": "", - "X-Forwarded-Uri": "", - "X-Forwarded-Method": "", - "X-Forwarded-Tls-Client-Cert": "", - "X-Forwarded-Tls-Client-Cert-Info": "", + xForwardedFor: "", + xForwardedURI: "", + xForwardedMethod: "", + xForwardedTLSClientCert: "", + xForwardedTLSClientCertInfo: "", }, }, { @@ -81,19 +81,19 @@ func TestServeHTTP(t *testing.T) { insecure: false, trustedIps: []string{"10.0.1.100"}, remoteAddr: "10.0.1.100:80", - incomingHeaders: map[string]string{ - "X-Forwarded-for": "10.0.1.0, 10.0.1.12", - "X-Forwarded-Uri": "/bar", - "X-Forwarded-Method": "GET", - "X-Forwarded-Tls-Client-Cert": "Cert", - "X-Forwarded-Tls-Client-Cert-Info": "CertInfo", + incomingHeaders: map[string][]string{ + xForwardedFor: {"10.0.1.0, 10.0.1.12"}, + xForwardedURI: {"/bar"}, + xForwardedMethod: {"GET"}, + xForwardedTLSClientCert: {"Cert"}, + xForwardedTLSClientCertInfo: {"CertInfo"}, }, expectedHeaders: map[string]string{ - "X-Forwarded-for": "10.0.1.0, 10.0.1.12", - "X-Forwarded-Uri": "/bar", - "X-Forwarded-Method": "GET", - "X-Forwarded-Tls-Client-Cert": "Cert", - "X-Forwarded-Tls-Client-Cert-Info": "CertInfo", + xForwardedFor: "10.0.1.0, 10.0.1.12", + xForwardedURI: "/bar", + xForwardedMethod: "GET", + xForwardedTLSClientCert: "Cert", + xForwardedTLSClientCertInfo: "CertInfo", }, }, { @@ -101,19 +101,19 @@ func TestServeHTTP(t *testing.T) { insecure: false, trustedIps: []string{"10.0.1.100"}, remoteAddr: "10.0.1.101:80", - incomingHeaders: map[string]string{ - "X-Forwarded-for": "10.0.1.0, 10.0.1.12", - "X-Forwarded-Uri": "/bar", - "X-Forwarded-Method": "GET", - "X-Forwarded-Tls-Client-Cert": "Cert", - "X-Forwarded-Tls-Client-Cert-Info": "CertInfo", + incomingHeaders: map[string][]string{ + xForwardedFor: {"10.0.1.0, 10.0.1.12"}, + xForwardedURI: {"/bar"}, + xForwardedMethod: {"GET"}, + xForwardedTLSClientCert: {"Cert"}, + xForwardedTLSClientCertInfo: {"CertInfo"}, }, expectedHeaders: map[string]string{ - "X-Forwarded-for": "", - "X-Forwarded-Uri": "", - "X-Forwarded-Method": "", - "X-Forwarded-Tls-Client-Cert": "", - "X-Forwarded-Tls-Client-Cert-Info": "", + xForwardedFor: "", + xForwardedURI: "", + xForwardedMethod: "", + xForwardedTLSClientCert: "", + xForwardedTLSClientCertInfo: "", }, }, { @@ -121,19 +121,19 @@ func TestServeHTTP(t *testing.T) { insecure: false, trustedIps: []string{"1.2.3.4/24"}, remoteAddr: "1.2.3.156:80", - incomingHeaders: map[string]string{ - "X-Forwarded-for": "10.0.1.0, 10.0.1.12", - "X-Forwarded-Uri": "/bar", - "X-Forwarded-Method": "GET", - "X-Forwarded-Tls-Client-Cert": "Cert", - "X-Forwarded-Tls-Client-Cert-Info": "CertInfo", + incomingHeaders: map[string][]string{ + xForwardedFor: {"10.0.1.0, 10.0.1.12"}, + xForwardedURI: {"/bar"}, + xForwardedMethod: {"GET"}, + xForwardedTLSClientCert: {"Cert"}, + xForwardedTLSClientCertInfo: {"CertInfo"}, }, expectedHeaders: map[string]string{ - "X-Forwarded-for": "10.0.1.0, 10.0.1.12", - "X-Forwarded-Uri": "/bar", - "X-Forwarded-Method": "GET", - "X-Forwarded-Tls-Client-Cert": "Cert", - "X-Forwarded-Tls-Client-Cert-Info": "CertInfo", + xForwardedFor: "10.0.1.0, 10.0.1.12", + xForwardedURI: "/bar", + xForwardedMethod: "GET", + xForwardedTLSClientCert: "Cert", + xForwardedTLSClientCertInfo: "CertInfo", }, }, { @@ -141,19 +141,33 @@ func TestServeHTTP(t *testing.T) { insecure: false, trustedIps: []string{"1.2.3.4/24"}, remoteAddr: "10.0.1.101:80", - incomingHeaders: map[string]string{ - "X-Forwarded-for": "10.0.1.0, 10.0.1.12", - "X-Forwarded-Uri": "/bar", - "X-Forwarded-Method": "GET", - "X-Forwarded-Tls-Client-Cert": "Cert", - "X-Forwarded-Tls-Client-Cert-Info": "CertInfo", + incomingHeaders: map[string][]string{ + xForwardedFor: {"10.0.1.0, 10.0.1.12"}, + xForwardedURI: {"/bar"}, + xForwardedMethod: {"GET"}, + xForwardedTLSClientCert: {"Cert"}, + xForwardedTLSClientCertInfo: {"CertInfo"}, }, expectedHeaders: map[string]string{ - "X-Forwarded-for": "", - "X-Forwarded-Uri": "", - "X-Forwarded-Method": "", - "X-Forwarded-Tls-Client-Cert": "", - "X-Forwarded-Tls-Client-Cert-Info": "", + xForwardedFor: "", + xForwardedURI: "", + xForwardedMethod: "", + xForwardedTLSClientCert: "", + xForwardedTLSClientCertInfo: "", + }, + }, + { + desc: "xForwardedFor with multiple header(s) values", + insecure: true, + incomingHeaders: map[string][]string{ + xForwardedFor: { + "10.0.0.4, 10.0.0.3", + "10.0.0.2, 10.0.0.1", + "10.0.0.0", + }, + }, + expectedHeaders: map[string]string{ + xForwardedFor: "10.0.0.4, 10.0.0.3, 10.0.0.2, 10.0.0.1, 10.0.0.0", }, }, { @@ -167,8 +181,8 @@ func TestServeHTTP(t *testing.T) { desc: "xRealIP was already populated from previous headers", insecure: true, remoteAddr: "10.0.1.101:80", - incomingHeaders: map[string]string{ - xRealIP: "10.0.1.12", + incomingHeaders: map[string][]string{ + xRealIP: {"10.0.1.12"}, }, expectedHeaders: map[string]string{ xRealIP: "10.0.1.12", @@ -208,8 +222,8 @@ func TestServeHTTP(t *testing.T) { desc: "xForwardedProto with websocket and tls and already x-forwarded-proto with wss", tls: true, websocket: true, - incomingHeaders: map[string]string{ - xForwardedProto: "wss", + incomingHeaders: map[string][]string{ + xForwardedProto: {"wss"}, }, expectedHeaders: map[string]string{ xForwardedProto: "wss", @@ -226,8 +240,8 @@ func TestServeHTTP(t *testing.T) { desc: "xForwardedPort with implicit tls port from proto header", // setting insecure just so our initial xForwardedProto does not get cleaned insecure: true, - incomingHeaders: map[string]string{ - xForwardedProto: "https", + incomingHeaders: map[string][]string{ + xForwardedProto: {"https"}, }, expectedHeaders: map[string]string{ xForwardedProto: "https", @@ -280,8 +294,10 @@ func TestServeHTTP(t *testing.T) { req.Host = test.host } - for k, v := range test.incomingHeaders { - req.Header.Set(k, v) + for k, values := range test.incomingHeaders { + for _, value := range values { + req.Header.Add(k, value) + } } m, err := NewXForwarded(test.insecure, test.trustedIps,