diff --git a/api/http/docker_handler.go b/api/http/docker_handler.go index bb67a3943..f15b4386e 100644 --- a/api/http/docker_handler.go +++ b/api/http/docker_handler.go @@ -11,6 +11,7 @@ import ( "net/http/httputil" "net/url" "os" + "strings" ) // DockerHandler represents an HTTP API handler for proxying requests to the Docker API. @@ -61,14 +62,54 @@ func (handler *DockerHandler) setupProxy(config *portainer.EndpointConfiguration return nil } +// singleJoiningSlash from golang.org/src/net/http/httputil/reverseproxy.go +// included here for use in NewSingleHostReverseProxyWithHostHeader +// because its used in NewSingleHostReverseProxy from golang.org/src/net/http/httputil/reverseproxy.go + +func singleJoiningSlash(a, b string) string { + aslash := strings.HasSuffix(a, "/") + bslash := strings.HasPrefix(b, "/") + switch { + case aslash && bslash: + return a + b[1:] + case !aslash && !bslash: + return a + "/" + b + } + return a + b +} + +// NewSingleHostReverseProxyWithHostHeader is based on NewSingleHostReverseProxy +// from golang.org/src/net/http/httputil/reverseproxy.go and merely sets the Host +// HTTP header, which NewSingleHostReverseProxy deliberately preserves + +func NewSingleHostReverseProxyWithHostHeader(target *url.URL) *httputil.ReverseProxy { + targetQuery := target.RawQuery + director := func(req *http.Request) { + req.URL.Scheme = target.Scheme + req.URL.Host = target.Host + req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) + req.Host = req.URL.Host + if targetQuery == "" || req.URL.RawQuery == "" { + req.URL.RawQuery = targetQuery + req.URL.RawQuery + } else { + req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery + } + if _, ok := req.Header["User-Agent"]; !ok { + // explicitly disable User-Agent so it's not set to default value + req.Header.Set("User-Agent", "") + } + } + return &httputil.ReverseProxy{Director: director} +} + func newHTTPProxy(u *url.URL) http.Handler { u.Scheme = "http" - return httputil.NewSingleHostReverseProxy(u) + return NewSingleHostReverseProxyWithHostHeader(u) } func newHTTPSProxy(u *url.URL, endpointConfig *portainer.EndpointConfiguration) (http.Handler, error) { u.Scheme = "https" - proxy := httputil.NewSingleHostReverseProxy(u) + proxy := NewSingleHostReverseProxyWithHostHeader(u) config, err := createTLSConfiguration(endpointConfig.TLSCACertPath, endpointConfig.TLSCertPath, endpointConfig.TLSKeyPath) if err != nil { return nil, err