diff --git a/docs/content/middlewares/http/redirectscheme.md b/docs/content/middlewares/http/redirectscheme.md index 3e37e89fc..9c7a67ec1 100644 --- a/docs/content/middlewares/http/redirectscheme.md +++ b/docs/content/middlewares/http/redirectscheme.md @@ -13,7 +13,6 @@ TODO: add schema --> The RedirectScheme middleware redirects the request if the request scheme is different from the configured scheme. -The middleware does not work for websocket requests. !!! warning "When behind another reverse-proxy" diff --git a/pkg/middlewares/forwardedheaders/forwarded_header.go b/pkg/middlewares/forwardedheaders/forwarded_header.go index d38011aa8..19881ad74 100644 --- a/pkg/middlewares/forwardedheaders/forwarded_header.go +++ b/pkg/middlewares/forwardedheaders/forwarded_header.go @@ -142,6 +142,9 @@ func (x *XForwarded) rewrite(outreq *http.Request) { xfProto := unsafeHeader(outreq.Header).Get(xForwardedProto) if xfProto == "" { + // TODO: is this expected to set the X-Forwarded-Proto header value to + // ws(s) as the underlying request used to upgrade the connection is + // made over HTTP(S)? if isWebsocketRequest(outreq) { if outreq.TLS != nil { unsafeHeader(outreq.Header).Set(xForwardedProto, "wss") diff --git a/pkg/middlewares/redirect/redirect_scheme.go b/pkg/middlewares/redirect/redirect_scheme.go index 204e1a16c..063d6c4bc 100644 --- a/pkg/middlewares/redirect/redirect_scheme.go +++ b/pkg/middlewares/redirect/redirect_scheme.go @@ -33,10 +33,10 @@ func NewRedirectScheme(ctx context.Context, next http.Handler, conf dynamic.Redi port = ":" + conf.Port } - return newRedirect(next, uriPattern, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, rawURLScheme, name) + return newRedirect(next, uriPattern, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, clientRequestURL, name) } -func rawURLScheme(req *http.Request) string { +func clientRequestURL(req *http.Request) string { scheme := schemeHTTP host, port, err := net.SplitHostPort(req.Host) if err != nil { @@ -64,8 +64,20 @@ func rawURLScheme(req *http.Request) string { scheme = schemeHTTPS } - if value := req.Header.Get(xForwardedProto); value != "" { - scheme = value + if xProto := req.Header.Get(xForwardedProto); xProto != "" { + // When the initial request is a connection upgrade request, + // X-Forwarded-Proto header might have been set by a previous hop to ws(s), + // even though the actual protocol used so far is HTTP(s). + // Given that we're in a middleware that is only used in the context of HTTP(s) requests, + // the only possible valid schemes are one of "http" or "https", so we convert back to them. + switch { + case strings.EqualFold(xProto, "ws"): + scheme = schemeHTTP + case strings.EqualFold(xProto, "wss"): + scheme = schemeHTTPS + default: + scheme = xProto + } } if scheme == schemeHTTP && port == ":80" || scheme == schemeHTTPS && port == ":443" { diff --git a/pkg/middlewares/redirect/redirect_scheme_test.go b/pkg/middlewares/redirect/redirect_scheme_test.go index 3d94bfcda..dea1d6265 100644 --- a/pkg/middlewares/redirect/redirect_scheme_test.go +++ b/pkg/middlewares/redirect/redirect_scheme_test.go @@ -63,6 +63,41 @@ func TestRedirectSchemeHandler(t *testing.T) { }, expectedStatus: http.StatusOK, }, + { + desc: "HTTP to HTTPS, with X-Forwarded-Proto to unknown value", + config: dynamic.RedirectScheme{ + Scheme: "https", + }, + url: "http://foo", + headers: map[string]string{ + "X-Forwarded-Proto": "bar", + }, + expectedURL: "https://bar://foo", + expectedStatus: http.StatusFound, + }, + { + desc: "HTTP to HTTPS, with X-Forwarded-Proto to ws", + config: dynamic.RedirectScheme{ + Scheme: "https", + }, + url: "http://foo", + headers: map[string]string{ + "X-Forwarded-Proto": "ws", + }, + expectedURL: "https://foo", + expectedStatus: http.StatusFound, + }, + { + desc: "HTTP to HTTPS, with X-Forwarded-Proto to wss", + config: dynamic.RedirectScheme{ + Scheme: "https", + }, + url: "http://foo", + headers: map[string]string{ + "X-Forwarded-Proto": "wss", + }, + expectedStatus: http.StatusOK, + }, { desc: "HTTP with port to HTTPS without port", config: dynamic.RedirectScheme{