Skip to content

Commit 1513e57

Browse files
committed
net/http/httputil: add X-Forwarded-{Host,Proto} headers in ReverseProxy
X-Forwarded-Host contains the original request's host. X-Forwarded-Proto contains "http" or "https", depending on whether the original request was made on a TLS-secured connection. Setting either header to nil in Director disables adding the header, same as for X-Forwarded-For. Fixes #50465. Change-Id: If8ed1f48d83f8ea0389c53519bc7994cb53891db Reviewed-on: https://go-review.googlesource.com/c/go/+/407414 Reviewed-by: Michael Knyszek <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 2cf49a7 commit 1513e57

File tree

2 files changed

+72
-15
lines changed

2 files changed

+72
-15
lines changed

src/net/http/httputil/reverseproxy.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,18 @@ import (
2828
// sends it to another server, proxying the response back to the
2929
// client.
3030
//
31-
// ReverseProxy by default sets the client IP as the value of the
32-
// X-Forwarded-For header.
31+
// ReverseProxy by default sets
32+
// - the X-Forwarded-For header to the client IP address;
33+
// - the X-Forwarded-Host header to the host of the original client
34+
// request; and
35+
// - the X-Forwarded-Proto header to "https" if the client request
36+
// was made on a TLS-enabled connection or "http" otherwise.
3337
//
3438
// If an X-Forwarded-For header already exists, the client IP is
35-
// appended to the existing values. As a special case, if the header
36-
// exists in the Request.Header map but has a nil value (such as when
37-
// set by the Director func), the X-Forwarded-For header is
38-
// not modified.
39+
// appended to the existing values.
40+
//
41+
// If a header exists in the Request.Header map but has a nil value
42+
// (such as when set by the Director func), it is not modified.
3943
//
4044
// To prevent IP spoofing, be sure to delete any pre-existing
4145
// X-Forwarded-For header coming from the client or
@@ -306,6 +310,16 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
306310
outreq.Header.Set("X-Forwarded-For", clientIP)
307311
}
308312
}
313+
if prior, ok := outreq.Header["X-Forwarded-Host"]; !(ok && prior == nil) {
314+
outreq.Header.Set("X-Forwarded-Host", req.Host)
315+
}
316+
if prior, ok := outreq.Header["X-Forwarded-Proto"]; !(ok && prior == nil) {
317+
if req.TLS == nil {
318+
outreq.Header.Set("X-Forwarded-Proto", "http")
319+
} else {
320+
outreq.Header.Set("X-Forwarded-Proto", "https")
321+
}
322+
}
309323

310324
res, err := transport.RoundTrip(outreq)
311325
if err != nil {

src/net/http/httputil/reverseproxy_test.go

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ func TestReverseProxy(t *testing.T) {
5050
if r.Header.Get("X-Forwarded-For") == "" {
5151
t.Errorf("didn't get X-Forwarded-For header")
5252
}
53+
if r.Header.Get("X-Forwarded-Host") == "" {
54+
t.Errorf("didn't get X-Forwarded-Host header")
55+
}
56+
if r.Header.Get("X-Forwarded-Proto") == "" {
57+
t.Errorf("didn't get X-Forwarded-Proto header")
58+
}
5359
if c := r.Header.Get("Connection"); c != "" {
5460
t.Errorf("handler got Connection header value %q", c)
5561
}
@@ -299,13 +305,20 @@ func TestXForwardedFor(t *testing.T) {
299305
const prevForwardedFor = "client ip"
300306
const backendResponse = "I am the backend"
301307
const backendStatus = 404
308+
const host = "some-name"
302309
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
303310
if r.Header.Get("X-Forwarded-For") == "" {
304311
t.Errorf("didn't get X-Forwarded-For header")
305312
}
306313
if !strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) {
307314
t.Errorf("X-Forwarded-For didn't contain prior data")
308315
}
316+
if got, want := r.Header.Get("X-Forwarded-Host"), host; got != want {
317+
t.Errorf("X-Forwarded-Host = %q, want %q", got, want)
318+
}
319+
if got, want := r.Header.Get("X-Forwarded-Proto"), "http"; got != want {
320+
t.Errorf("X-Forwarded-Proto = %q, want %q", got, want)
321+
}
309322
w.WriteHeader(backendStatus)
310323
w.Write([]byte(backendResponse))
311324
}))
@@ -319,7 +332,7 @@ func TestXForwardedFor(t *testing.T) {
319332
defer frontend.Close()
320333

321334
getReq, _ := http.NewRequest("GET", frontend.URL, nil)
322-
getReq.Host = "some-name"
335+
getReq.Host = host
323336
getReq.Header.Set("Connection", "close")
324337
getReq.Header.Set("X-Forwarded-For", prevForwardedFor)
325338
getReq.Close = true
@@ -336,11 +349,36 @@ func TestXForwardedFor(t *testing.T) {
336349
}
337350
}
338351

352+
func TestXForwardedProtoTLS(t *testing.T) {
353+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
354+
if got, want := r.Header.Get("X-Forwarded-Proto"), "https"; got != want {
355+
t.Errorf("X-Forwarded-Proto = %q, want %q", got, want)
356+
}
357+
}))
358+
defer backend.Close()
359+
backendURL, err := url.Parse(backend.URL)
360+
if err != nil {
361+
t.Fatal(err)
362+
}
363+
proxyHandler := NewSingleHostReverseProxy(backendURL)
364+
frontend := httptest.NewTLSServer(proxyHandler)
365+
defer frontend.Close()
366+
367+
getReq, _ := http.NewRequest("GET", frontend.URL, nil)
368+
getReq.Host = "some-host"
369+
_, err = frontend.Client().Do(getReq)
370+
if err != nil {
371+
t.Fatalf("Get: %v", err)
372+
}
373+
}
374+
339375
// Issue 38079: don't append to X-Forwarded-For if it's present but nil
340376
func TestXForwardedFor_Omit(t *testing.T) {
341377
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
342-
if v := r.Header.Get("X-Forwarded-For"); v != "" {
343-
t.Errorf("got X-Forwarded-For header: %q", v)
378+
for _, h := range []string{"X-Forwarded-For", "X-Forwarded-Host", "X-Forwarded-Proto"} {
379+
if v := r.Header.Get(h); v != "" {
380+
t.Errorf("got %v header: %q", h, v)
381+
}
344382
}
345383
w.Write([]byte("hi"))
346384
}))
@@ -356,6 +394,8 @@ func TestXForwardedFor_Omit(t *testing.T) {
356394
oldDirector := proxyHandler.Director
357395
proxyHandler.Director = func(r *http.Request) {
358396
r.Header["X-Forwarded-For"] = nil
397+
r.Header["X-Forwarded-Host"] = nil
398+
r.Header["X-Forwarded-Proto"] = nil
359399
oldDirector(r)
360400
}
361401

@@ -1029,13 +1069,16 @@ func TestClonesRequestHeaders(t *testing.T) {
10291069
}
10301070
rp.ServeHTTP(httptest.NewRecorder(), req)
10311071

1032-
if req.Header.Get("From-Director") == "1" {
1033-
t.Error("Director header mutation modified caller's request")
1034-
}
1035-
if req.Header.Get("X-Forwarded-For") != "" {
1036-
t.Error("X-Forward-For header mutation modified caller's request")
1072+
for _, h := range []string{
1073+
"From-Director",
1074+
"X-Forwarded-For",
1075+
"X-Forwarded-Host",
1076+
"X-Forwarded-Proto",
1077+
} {
1078+
if req.Header.Get(h) != "" {
1079+
t.Errorf("%v header mutation modified caller's request", h)
1080+
}
10371081
}
1038-
10391082
}
10401083

10411084
type roundTripperFunc func(req *http.Request) (*http.Response, error)

0 commit comments

Comments
 (0)