如何使用httputil.ReverseProxy设置X-Forwarded-For头部信息

huangapple go评论85阅读模式
英文:

How to set X-Forwarded-For with httputil.ReverseProxy

问题

为什么我想要这样做?

我遇到了这个问题两次。

第一次是在一个仅支持IPv6的服务器网络中使用反向代理。客户端请求通过NAT46进入。请求的源IP变成了[固定的96位前缀] + [32位客户端IPv4地址]。这意味着反向代理始终可以识别真实的客户端IP。然而,我找不到一种设置X-Forwarded-For头部为该地址的方法。不过,我通过修改后端服务器来解决了这个问题。

这一次,我有一个将在Google App Engine上运行的反向代理。请求首先到达Google的负载均衡器,它会添加X-Forwarded-For头部并将请求转发给我的应用程序。我想稍微修改请求,然后将其传递给一个我无法修改的后端服务器。后端服务器需要原始的客户端IP,并且可以通过X-Forwarded-For接受它(它是经过身份验证的,请放心)。在这种情况下,我希望将X-Forwarded-For头部从Google的负载均衡器中不经修改地传递过来。

问题

使用httputil.ReverseProxy时,似乎没有办法将X-Forwarded-For设置为我选择的值。如果我设置它(选项1),TCP连接的客户端地址将被追加。如果我将其设置为nil(选项2),它将被省略,就像文档中所建议的那样,但这也不是我想要的。

package main

import (
	"log"
	"net/http"
	"net/http/httputil"
)

func director(req *http.Request) {
	// 当前运行一个只记录头部的PHP脚本
	host := "bsweb02.bentonvillek12.org"

	// 要拨号的主机
	req.URL.Scheme = "https"
	req.URL.Host = host

	// 主机头部
	req.Host = host

	// 证明大多数头部可以正常传递
	req.Header["Foo"] = []string{"Bar"}

	req.Header["X-Forwarded-For"] = []string{"1.2.3.4"} // 选项1
	//req.Header["X-Forwarded-For"] = nil               // 选项2
}

func main() {
	http.ListenAndServe(":80", &httputil.ReverseProxy{
		Director: director,
	})
}

选项1

array(9) {
  ["Content-Type"]=>
  string(0) ""
  ["Content-Length"]=>
  string(1) "0"
  ["Foo"]=>
  string(3) "Bar"
  ["X-Forwarded-For"]=>
  string(18) "1.2.3.4, 127.0.0.1"
  ["User-Agent"]=>
  string(11) "curl/7.81.0"
  ["Host"]=>
  string(26) "bsweb02.bentonvillek12.org"
  ["Accept-Encoding"]=>
  string(4) "gzip"
  ["Accept"]=>
  string(3) "*/*"
  ["Connection"]=>
  string(5) "close"
}

选项2

array(8) {
  ["Content-Type"]=>
  string(0) ""
  ["Content-Length"]=>
  string(1) "0"
  ["Foo"]=>
  string(3) "Bar"
  ["User-Agent"]=>
  string(11) "curl/7.81.0"
  ["Host"]=>
  string(26) "bsweb02.bentonvillek12.org"
  ["Accept-Encoding"]=>
  string(4) "gzip"
  ["Accept"]=>
  string(3) "*/*"
  ["Connection"]=>
  string(5) "close"
}
英文:

Why would I want to do this

I have run in to this issue twice.

The first time was with a reverse proxy that lived inside an IPv6 only server network. Client requests come in through NAT46. The source-IP of the request becomes [fixed 96-bit prefix] + [32-bit client IPv4 address]. This means that the reverse proxy could always identify the real client IP. I couldn't find a way to set the X-Forwarded-For header to that address though. I got around it by modifying the backend server.

This time I have a reverse proxy which will run on Google App Engine. Requests hit Google's load balancer first, which adds the X-Forwarded-For header and forwards the request to my app. I want to modify the request a bit and then pass it to a backend server, which I cannot modify. The back-end needs the original client IP, and can accept it via X-Forwarded-For (it's authenticated, don't worry). In this case I want to pass the X-Forwarded-For header from Google's load balencer through unmodified.

The Problem

It seems like there is no way to set X-Forwarded-For to a value that I chose when using httputil.ReverseProxy. If I set it (option 1 below) the client address from the TCP connection will be appended. If if I set it to nil (option 2 below), it is omitted like the documentation suggests, but that's not what I want either.

package main

import (
	"log"
	"net/http"
	"net/http/httputil"
)

func director(req *http.Request) {
	// currently running a PHP script that just records headers
	host := "bsweb02.bentonvillek12.org"

	// who we dial
	req.URL.Scheme = "https"
	req.URL.Host = host

	// host header
	req.Host = host

	// proof that most headers can be passed through fine
	req.Header["Foo"] = []string{"Bar"}

	req.Header["X-Forwarded-For"] = []string{"1.2.3.4"} // option 1
	//req.Header["X-Forwarded-For"] = nil               // option 2
}

func main() {
	http.ListenAndServe(":80", &httputil.ReverseProxy{
		Director: director,
	})
}

Option 1

array(9) {
  ["Content-Type"]=>
  string(0) ""
  ["Content-Length"]=>
  string(1) "0"
  ["Foo"]=>
  string(3) "Bar"
  ["X-Forwarded-For"]=>
  string(18) "1.2.3.4, 127.0.0.1"
  ["User-Agent"]=>
  string(11) "curl/7.81.0"
  ["Host"]=>
  string(26) "bsweb02.bentonvillek12.org"
  ["Accept-Encoding"]=>
  string(4) "gzip"
  ["Accept"]=>
  string(3) "*/*"
  ["Connection"]=>
  string(5) "close"
}

Option 2

array(8) {
  ["Content-Type"]=>
  string(0) ""
  ["Content-Length"]=>
  string(1) "0"
  ["Foo"]=>
  string(3) "Bar"
  ["User-Agent"]=>
  string(11) "curl/7.81.0"
  ["Host"]=>
  string(26) "bsweb02.bentonvillek12.org"
  ["Accept-Encoding"]=>
  string(4) "gzip"
  ["Accept"]=>
  string(3) "*/*"
  ["Connection"]=>
  string(5) "close"
}

答案1

得分: 2

我相信你有两个选项。

1. 实现 http.RoundTripper

你可以实现自己的 RoundTripper 并在其中重新设置 X-Forwarded-For。(演示)

type MyRoundTripper struct{}

func (t *MyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	req.Header["X-Forwarded-For"] = []string{"1.2.3.4"}
	return http.DefaultTransport.RoundTrip(req)
}

func main() {
    http.ListenAndServe(":80", &httputil.ReverseProxy{
        Director: director,
        Transport: &MyRoundTripper{},
    })
}

httputil.ReverseProxyTransport 字段未设置时,它会回退到 http.DefaultTransport,所以你也可以在自定义代码之后回退到它。

2. 取消设置 req.RemoteAddr

在调用反向代理之前,你可以重置原始请求的 RemoteAddr 字段。该字段由 HTTP 服务器设置,当存在时,会触发反向代理实现中的 X-Forwarded-For 替换。(演示)

func main() {
    http.ListenAndServe(":80", func(w http.ResponseWriter, r *http.Request) {
        r.RemoteAddr = ""
        proxy := &httputil.ReverseProxy{ Director: director } 
        proxy.ServeHTTP(w, r)    
    })
}

然而,这种行为依赖于实现细节,可能会在将来改变。我建议使用选项 1。

英文:

I believe you have two options.

1. Implement http.RoundTripper

You implement your own RoundTripper and re-set X-Forwarded-For in there. (demonstration)

type MyRoundTripper struct{}

func (t *MyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	req.Header["X-Forwarded-For"] = []string{"1.2.3.4"}
	return http.DefaultTransport.RoundTrip(req)
}

func main() {
    http.ListenAndServe(":80", &httputil.ReverseProxy{
        Director: director,
        Transport: &MyRoundTripper{},
    })
}

When the Transport field isn't set on httputil.ReverseProxy it falls back to http.DefaultTransport, so you can fall back to it too after your custom code.

2. Unset req.RemoteAddr

You reset the original request's RemoteAddr field before invoking the reverse proxy. This field is set by the HTTP server and, when present, triggers the X-Forwarded-For replacement in the reverse proxy implementation. (demonstration)

func main() {
    http.ListenAndServe(":80", func(w http.ResponseWriter, r *http.Request) {
        r.RemoteAddr = ""
        proxy := &httputil.ReverseProxy{ Director: director } 
        proxy.ServeHTTP(w, r)    
    })
}

However this behavior relies on an implementation detail, which may or may not change in the future. I recommend using option 1 instead.

huangapple
  • 本文由 发表于 2022年4月2日 06:51:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/71713465.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定