Golang ReverseProxy与Apache2 SNI/主机名错误

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

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
}

huangapple
  • 本文由 发表于 2016年1月12日 21:58:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/34745654.html
匿名

发表评论

匿名网友

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

确定