英文:
Golang ReverseProxy with Apache2 SNI/Hostname error
问题
我正在使用Go编写自己的ReverseProxy。ReverseProxy应该连接我的Go Web服务器和Apache2 Web服务器。但是当我在另一个IP地址上运行我的反向代理时,当反向代理将请求发送到Apache时,我的Apache日志文件中出现以下错误:
"通过SNI提供的主机名xxxx与通过HTTP提供的主机名xxxx2不同"
我的反向代理和Apache Web服务器都在使用HTTPS。
以下是一些代码:
func (p *Proxy) directorApache(req *http.Request) {
mainServer := fmt.Sprintf("%s:%d", Config.HostMain, Config.PortMain)
req.URL.Scheme = "https"
req.URL.Host = mainServer
}
func (p *Proxy) directorGo(req *http.Request) {
goServer := fmt.Sprintf("%s:%d", Config.GoHost, Config.GoPort)
req.URL.Scheme = "http"
req.URL.Host = goServer
}
func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
fmt.Println(req.URL.Path)
if p.isGoRequest(req) {
fmt.Println("GO")
p.goProxy.ServeHTTP(rw, req)
return
}
p.httpProxy.ServeHTTP(rw, req)
}
func main() {
var configPath = flag.String("conf", "./configReverse.json", "Path to the Json config file.")
flag.Parse()
proxy := New(*configPath)
cert, err := tls.LoadX509KeyPair(Config.PathCert, Config.PathPrivateKey)
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{InsecureSkipVerify: true, Certificates: []tls.Certificate{cert}}
listener, err := net.Listen("tcp",
net.JoinHostPort(proxy.Host, strconv.Itoa(proxy.Port)))
if err != nil {
log.Fatalf("server: listen: %s", err)
}
log.Printf("server: listening on %s")
proxy.listener = tls.NewListener(listener, &config)
serverHTTPS := &http.Server{
Handler: proxy.mux,
TLSConfig: &config,
}
if err := serverHTTPS.Serve(proxy.listener); err != nil {
log.Fatal("SERVER ERROR:", err)
}
}
也许有人对这个问题有想法。
英文:
i am writing my own ReverseProxy in Go.The ReverseProxy should connect my go-webserver and my apache2 webserver. But when I run my reverseproxy on another IP-Adress then my Apache2 webserver I got following error in my apache-logfile, when the reverseproxy sends the request to apache.
"Hosname xxxx provided via sni and hostname xxxx2 provided via http are different"
My Reverse Proxy and apache-webserver running on https.
Here some code:
func (p *Proxy) directorApache(req *http.Request) {
mainServer := fmt.Sprintf("%s:%d", Config.HostMain, Config.PortMain)
req.URL.Scheme = "https"
req.URL.Host = mainServer
}
func (p *Proxy) directorGo(req *http.Request) {
goServer := fmt.Sprintf("%s:%d", Config.GoHost, Config.GoPort)
req.URL.Scheme = "http"
req.URL.Host = goServer
}
func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
fmt.Println(req.URL.Path)
if p.isGoRequest(req) {
fmt.Println("GO")
p.goProxy.ServeHTTP(rw, req)
return
}
p.httpProxy.ServeHTTP(rw, req)
}
func main() {
var configPath = flag.String("conf", "./configReverse.json", "Path to the Json config file.")
flag.Parse()
proxy := New(*configPath)
cert, err := tls.LoadX509KeyPair(Config.PathCert, Config.PathPrivateKey)
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{InsecureSkipVerify: true, Certificates: []tls.Certificate{cert}}
listener, err := net.Listen("tcp",
net.JoinHostPort(proxy.Host, strconv.Itoa(proxy.Port)))
if err != nil {
log.Fatalf("server: listen: %s", err)
}
log.Printf("server: listening on %s")
proxy.listener = tls.NewListener(listener, &config)
serverHTTPS := &http.Server{
Handler: proxy.mux,
TLSConfig: &config,
}
if err := serverHTTPS.Serve(proxy.listener); err != nil {
log.Fatal("SERVER ERROR:", err)
}
}
Perhaps someone has a idea about that issue.
答案1
得分: 4
简短示例
假设你正在发起一个 HTTP 请求到 https://your-proxy.local
。你的请求处理程序接收到 http.Request
结构体,并将其 URL
字段重写为 https://your-apache-backend.local
。
但是你没有考虑到,原始的 HTTP 请求还包含一个 Host
头部(Host: your-proxy.local
)。当将同样的请求传递给 http://your-apache-backend.local
时,该请求中的 Host
头部仍然是 Host: your-proxy.local
。这就是 Apache 抱怨的原因。
解释
由于你正在使用带有服务器名称指示(SNI)的 TLS,请求的主机名不仅用于 DNS 解析,还用于选择应该用于建立 TLS 连接的 SSL 证书。另一方面,HTTP 1.1 的 Host
头部用于由 Apache 区分多个虚拟主机。这两个名称必须匹配。这个问题也在 Apache HTTPD wiki 中提到:
> SNI/Request hostname mismatch, or SNI provides hostname and request doesn't.
>
> 这是一个浏览器的错误。Apache 将拒绝带有 400 类型错误的请求。
解决方案
同时重写 Host
头部。如果你想保留原始的 Host
头部,你可以将其存储在一个 X-Forwarded-Host
头部中(这是一个非标准的头部,但在反向代理中被广泛使用):
func (p *Proxy) directorApache(req *http.Request) {
mainServer := fmt.Sprintf("%s:%d", Config.HostMain, Config.PortMain)
req.URL.Scheme = "https"
req.URL.Host = mainServer
req.Header.Set("X-Forwarded-Host", req.Header.Get("Host"))
req.Host = mainServer
}
英文:
Short example
Say you're starting an HTTP request to https://your-proxy.local
. Your request handler takes the http.Request
struct and rewrites its URL
field to https://your-apache-backend.local
.
What you have not considered, is that the original HTTP request also contained a Host
header (Host: your-proxy.local
). When passing that same request to http://your-apache-backend.local
, the Host
header in that request still says Host: your-proxy.local
. And that's what Apache is complaining about.
Explanation
As you're using TLS with Server Name Indication (SNI), the request hostname will not only be used for DNS resolution, but also to select the SSL certificate that should be used to establish the TLS connection. The HTTP 1.1 Host
header on the other hand is used to distinguish several virtual hosts by Apache. Both names must match. This issue is also mentioned in the Apache HTTPD wiki:
>SNI/Request hostname mismatch, or SNI provides hostname and request doesn't.
>
>This is a browser bug. Apache will reject the request with a 400-type error.
Solution
Also rewrite the Host
header. If you want to preserve the original Host
header, you can store it in an X-Forwarded-Host
header (that's a non-standard header, but it's widely used in reverse proxies):
func (p *Proxy) directorApache(req *http.Request) {
mainServer := fmt.Sprintf("%s:%d", Config.HostMain, Config.PortMain)
req.URL.Scheme = "https"
req.URL.Host = mainServer
req.Header.Set("X-Forwarded-Host", req.Header().Get("Host"))
req.Host = mainServer
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论