Skip to content

Commit 5384500

Browse files
committed
net/http/httputil: deprecate ReverseProxy.Director
The Director function has been superseded by Rewrite. Rewrite avoids fundamental security issues with hop-by-hop header handling in the Director API and has better default handling of X-Forwarded-* headers. Fixes #73161 Change-Id: Iadaf3070e0082458f79fb892ade51cb7ce832802 Reviewed-on: https://go-review.googlesource.com/c/go/+/708615 Reviewed-by: Nicholas Husin <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Nicholas Husin <[email protected]>
1 parent bbdff9e commit 5384500

File tree

3 files changed

+98
-30
lines changed

3 files changed

+98
-30
lines changed

api/next/73161.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg net/http/httputil, type ReverseProxy struct, Director //deprecated #73161
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
The [ReverseProxy.Director] configuration field is deprecated
2+
in favor of [ReverseProxy.Rewrite].
3+
4+
A malicious client can remove headers added by a `Director` function
5+
by designating those headers as hop-by-hop. Since there is no way to address
6+
this problem within the scope of the `Director` API, we added a new
7+
`Rewrite` hook in Go 1.20. `Rewrite` hooks are provided with both the
8+
unmodified inbound request received by the proxy and the outbound request
9+
which will be sent by the proxy.
10+
11+
Since the `Director` hook is fundamentally unsafe, we are now deprecating it.

src/net/http/httputil/reverseproxy.go

Lines changed: 86 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -133,36 +133,6 @@ type ReverseProxy struct {
133133
// At most one of Rewrite or Director may be set.
134134
Rewrite func(*ProxyRequest)
135135

136-
// Director is a function which modifies
137-
// the request into a new request to be sent
138-
// using Transport. Its response is then copied
139-
// back to the original client unmodified.
140-
// Director must not access the provided Request
141-
// after returning.
142-
//
143-
// By default, the X-Forwarded-For header is set to the
144-
// value of the client IP address. If an X-Forwarded-For
145-
// header already exists, the client IP is appended to the
146-
// existing values. As a special case, if the header
147-
// exists in the Request.Header map but has a nil value
148-
// (such as when set by the Director func), the X-Forwarded-For
149-
// header is not modified.
150-
//
151-
// To prevent IP spoofing, be sure to delete any pre-existing
152-
// X-Forwarded-For header coming from the client or
153-
// an untrusted proxy.
154-
//
155-
// Hop-by-hop headers are removed from the request after
156-
// Director returns, which can remove headers added by
157-
// Director. Use a Rewrite function instead to ensure
158-
// modifications to the request are preserved.
159-
//
160-
// Unparsable query parameters are removed from the outbound
161-
// request if Request.Form is set after Director returns.
162-
//
163-
// At most one of Rewrite or Director may be set.
164-
Director func(*http.Request)
165-
166136
// The transport used to perform proxy requests.
167137
// If nil, http.DefaultTransport is used.
168138
Transport http.RoundTripper
@@ -210,6 +180,88 @@ type ReverseProxy struct {
210180
// If nil, the default is to log the provided error and return
211181
// a 502 Status Bad Gateway response.
212182
ErrorHandler func(http.ResponseWriter, *http.Request, error)
183+
184+
// Director is deprecated. Use Rewrite instead.
185+
//
186+
// This function is insecure:
187+
//
188+
// - Hop-by-hop headers are removed from the request after Director
189+
// returns, which can remove headers added by Director.
190+
// A client can designate headers as hop-by-hop by listing them
191+
// in the Connection header, so this permits a malicious client
192+
// to remove any headers that may be added by Director.
193+
//
194+
// - X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto
195+
// headers in inbound requests are preserved by default,
196+
// which can permit IP spoofing if the Director function is
197+
// not careful to remove these headers.
198+
//
199+
// Rewrite addresses these issues.
200+
//
201+
// As an example of converting a Director function to Rewrite:
202+
//
203+
// // ReverseProxy with a Director function.
204+
// proxy := &httputil.ReverseProxy{
205+
// Director: func(req *http.Request) {
206+
// req.URL.Scheme = "https"
207+
// req.URL.Host = proxyHost
208+
//
209+
// // A malicious client can remove this header.
210+
// req.Header.Set("Some-Header", "some-header-value")
211+
//
212+
// // X-Forwarded-* headers sent by the client are preserved,
213+
// // since Director did not remove them.
214+
// },
215+
// }
216+
//
217+
// // ReverseProxy with a Rewrite function.
218+
// proxy := &httputil.ReverseProxy{
219+
// Rewrite: func(preq *httputil.ProxyRequest) {
220+
// // See also ProxyRequest.SetURL.
221+
// preq.Out.URL.Scheme = "https"
222+
// preq.Out.URL.Host = proxyHost
223+
//
224+
// // This header cannot be affected by a malicious client.
225+
// preq.Out.Header.Set("Some-Header", "some-header-value")
226+
//
227+
// // X-Forwarded- headers sent by the client have been
228+
// // removed from preq.Out.
229+
// // ProxyRequest.SetXForwarded optionally adds new ones.
230+
// preq.SetXForwarded()
231+
// },
232+
// }
233+
//
234+
// Director is a function which modifies
235+
// the request into a new request to be sent
236+
// using Transport. Its response is then copied
237+
// back to the original client unmodified.
238+
// Director must not access the provided Request
239+
// after returning.
240+
//
241+
// By default, the X-Forwarded-For header is set to the
242+
// value of the client IP address. If an X-Forwarded-For
243+
// header already exists, the client IP is appended to the
244+
// existing values. As a special case, if the header
245+
// exists in the Request.Header map but has a nil value
246+
// (such as when set by the Director func), the X-Forwarded-For
247+
// header is not modified.
248+
//
249+
// To prevent IP spoofing, be sure to delete any pre-existing
250+
// X-Forwarded-For header coming from the client or
251+
// an untrusted proxy.
252+
//
253+
// Hop-by-hop headers are removed from the request after
254+
// Director returns, which can remove headers added by
255+
// Director. Use a Rewrite function instead to ensure
256+
// modifications to the request are preserved.
257+
//
258+
// Unparsable query parameters are removed from the outbound
259+
// request if Request.Form is set after Director returns.
260+
//
261+
// At most one of Rewrite or Director may be set.
262+
//
263+
// Deprecated: Use Rewrite instead.
264+
Director func(*http.Request)
213265
}
214266

215267
// A BufferPool is an interface for getting and returning temporary
@@ -259,6 +311,10 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
259311
//
260312
// NewSingleHostReverseProxy does not rewrite the Host header.
261313
//
314+
// For backwards compatibility reasons, NewSingleHostReverseProxy
315+
// returns a ReverseProxy using the deprecated Director function.
316+
// This proxy preserves X-Forwarded-* headers sent by the client.
317+
//
262318
// To customize the ReverseProxy behavior beyond what
263319
// NewSingleHostReverseProxy provides, use ReverseProxy directly
264320
// with a Rewrite function. The ProxyRequest SetURL method

0 commit comments

Comments
 (0)