Skip to content

Commit a4f0f3e

Browse files
committed
net/http/httputil: add an option to overwrite forwarded headers in ReverseProxy
1 parent fb4f8a9 commit a4f0f3e

File tree

2 files changed

+30
-42
lines changed

2 files changed

+30
-42
lines changed

src/net/http/httputil/reverseproxy.go

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@ import (
2525
// sends it to another server, proxying the response back to the
2626
// client.
2727
//
28-
// ReverseProxy automatically sets the X-Forwarded-For,
29-
// X-Forwarded-Host and X-Forwarded-Proto headers.
30-
// Previous values of these headers can be preversed by
31-
// setting TrustForwardedHeaders to true.
28+
// ReverseProxy automatically sets the client IP as the value of the
29+
// X-Forwarded-For header.
30+
// If an X-Forwarded-For header already exists, the client IP is
31+
// appended to the existing values.
32+
// To prevent IP spoofing, be sure to delete any pre-existing
33+
// X-Forwarded-For header coming from the client or
34+
// an untrusted proxy, for instance, by setting
35+
// OverwriteForwardedHeaders to true.
3236
type ReverseProxy struct {
3337
// Director must be a function which modifies
3438
// the request into a new request to be sent
@@ -82,19 +86,19 @@ type ReverseProxy struct {
8286
// a 502 Status Bad Gateway response.
8387
ErrorHandler func(http.ResponseWriter, *http.Request, error)
8488

85-
// TrustForwardedHeaders specifies if X-Forwarded-For,
89+
// OverwriteForwardedHeaders specifies if X-Forwarded-For,
8690
// X-Forwarded-Proto and X-Forwarded-Host headers coming from
87-
// the previous proxy must be trusted or not.
91+
// the previous proxy must be replaced or not.
8892
//
89-
// If true, existing values of X-Forwarded-Proto and
90-
// X-Forwarded-Host will be preserved, and the current client IP
91-
// will be appended to the list in X-Forwarded-For. In this case
92-
// be sure that these 3 headers are removed from the request if
93-
// sent by the client to prevent spoofing attacks.
93+
// If true, these headers 3 headers will be set regardless of any
94+
// existing value.
9495
//
95-
// If false, values of these headers will be set regardless of
96-
// any existing value.
97-
TrustForwardedHeaders bool
96+
// If false, X-Forwarded-Proto and X-Forwarded-Host will not be
97+
// touched (not even created if they don't exist), and the
98+
// current client IP will be appended to the list in
99+
// X-Forwarded-For. If X-Forwarded-For doesn't exist, it will be
100+
// created.
101+
OverwriteForwardedHeaders bool
98102
}
99103

100104
// A BufferPool is an interface for getting and returning temporary
@@ -255,20 +259,14 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
255259
outreq.Header.Set("Upgrade", reqUpType)
256260
}
257261

258-
if _, ok := outreq.Header["X-Forwarded-Proto"]; !ok || !p.TrustForwardedHeaders {
262+
if p.OverwriteForwardedHeaders {
259263
proto := "https"
260264
if req.TLS == nil {
261265
proto = "http"
262266
}
263267

264268
outreq.Header.Set("X-Forwarded-Proto", proto)
265-
}
266-
267-
if _, ok := outreq.Header["X-Forwarded-Host"]; !ok || !p.TrustForwardedHeaders {
268269
outreq.Header.Set("X-Forwarded-Host", outreq.Host)
269-
}
270-
271-
if !p.TrustForwardedHeaders {
272270
outreq.Header.Del("X-Forwarded-For")
273271
}
274272

src/net/http/httputil/reverseproxy_test.go

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -236,24 +236,16 @@ func TestReverseProxyStripHeadersPresentInConnection(t *testing.T) {
236236
}
237237
}
238238

239-
func TestDontTrustForwardedHeaders(t *testing.T) {
239+
func TestXForwardedFor(t *testing.T) {
240240
const prevForwardedFor = "client ip"
241-
const prevForwardedProto = "https"
242-
const prevForwardedHost = "example.com"
243241
const backendResponse = "I am the backend"
244242
const backendStatus = 404
245243
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
246244
if r.Header.Get("X-Forwarded-For") == "" {
247245
t.Errorf("didn't get X-Forwarded-For header")
248246
}
249-
if strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) {
250-
t.Errorf("X-Forwarded-For contains prior data")
251-
}
252-
if strings.Contains(r.Header.Get("X-Forwarded-Proto"), prevForwardedProto) {
253-
t.Errorf("X-Forwarded-Proto contains prior data")
254-
}
255-
if strings.Contains(r.Header.Get("X-Forwarded-Host"), prevForwardedHost) {
256-
t.Errorf("X-Forwarded-Host contains prior data")
247+
if !strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) {
248+
t.Errorf("X-Forwarded-For didn't contain prior data")
257249
}
258250
w.WriteHeader(backendStatus)
259251
w.Write([]byte(backendResponse))
@@ -271,8 +263,6 @@ func TestDontTrustForwardedHeaders(t *testing.T) {
271263
getReq.Host = "some-name"
272264
getReq.Header.Set("Connection", "close")
273265
getReq.Header.Set("X-Forwarded-For", prevForwardedFor)
274-
getReq.Header.Set("X-Forwarded-Proto", prevForwardedProto)
275-
getReq.Header.Set("X-Forwarded-Host", prevForwardedHost)
276266
getReq.Close = true
277267
res, err := frontend.Client().Do(getReq)
278268
if err != nil {
@@ -287,7 +277,7 @@ func TestDontTrustForwardedHeaders(t *testing.T) {
287277
}
288278
}
289279

290-
func TestXForwardedFor(t *testing.T) {
280+
func TestOverwriteForwardedHeaders(t *testing.T) {
291281
const prevForwardedFor = "client ip"
292282
const prevForwardedProto = "https"
293283
const prevForwardedHost = "example.com"
@@ -297,14 +287,14 @@ func TestXForwardedFor(t *testing.T) {
297287
if r.Header.Get("X-Forwarded-For") == "" {
298288
t.Errorf("didn't get X-Forwarded-For header")
299289
}
300-
if !strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) {
301-
t.Errorf("X-Forwarded-For didn't contain prior data")
290+
if strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) {
291+
t.Errorf("X-Forwarded-For contains prior data")
302292
}
303-
if !strings.Contains(r.Header.Get("X-Forwarded-Proto"), prevForwardedProto) {
304-
t.Errorf("X-Forwarded-Proto didn't contain prior data")
293+
if strings.Contains(r.Header.Get("X-Forwarded-Proto"), prevForwardedProto) {
294+
t.Errorf("X-Forwarded-Proto contains prior data")
305295
}
306-
if !strings.Contains(r.Header.Get("X-Forwarded-Host"), prevForwardedHost) {
307-
t.Errorf("X-Forwarded-Host didn't contain prior data")
296+
if strings.Contains(r.Header.Get("X-Forwarded-Host"), prevForwardedHost) {
297+
t.Errorf("X-Forwarded-Host contains prior data")
308298
}
309299
w.WriteHeader(backendStatus)
310300
w.Write([]byte(backendResponse))
@@ -315,7 +305,7 @@ func TestXForwardedFor(t *testing.T) {
315305
t.Fatal(err)
316306
}
317307
proxyHandler := NewSingleHostReverseProxy(backendURL)
318-
proxyHandler.TrustForwardedHeaders = true
308+
proxyHandler.OverwriteForwardedHeaders = true
319309
frontend := httptest.NewServer(proxyHandler)
320310
defer frontend.Close()
321311

0 commit comments

Comments
 (0)