Support forwarded websocket protocol in RedirectScheme

Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
Maxence Moutoussamy 2022-07-06 11:54:08 +02:00 committed by GitHub
parent 28da781194
commit 8f6463ba7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 5 deletions

View file

@ -13,7 +13,6 @@ TODO: add schema
--> -->
The RedirectScheme middleware redirects the request if the request scheme is different from the configured scheme. 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" !!! warning "When behind another reverse-proxy"

View file

@ -142,6 +142,9 @@ func (x *XForwarded) rewrite(outreq *http.Request) {
xfProto := unsafeHeader(outreq.Header).Get(xForwardedProto) xfProto := unsafeHeader(outreq.Header).Get(xForwardedProto)
if xfProto == "" { 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 isWebsocketRequest(outreq) {
if outreq.TLS != nil { if outreq.TLS != nil {
unsafeHeader(outreq.Header).Set(xForwardedProto, "wss") unsafeHeader(outreq.Header).Set(xForwardedProto, "wss")

View file

@ -33,10 +33,10 @@ func NewRedirectScheme(ctx context.Context, next http.Handler, conf dynamic.Redi
port = ":" + conf.Port 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 scheme := schemeHTTP
host, port, err := net.SplitHostPort(req.Host) host, port, err := net.SplitHostPort(req.Host)
if err != nil { if err != nil {
@ -64,8 +64,20 @@ func rawURLScheme(req *http.Request) string {
scheme = schemeHTTPS scheme = schemeHTTPS
} }
if value := req.Header.Get(xForwardedProto); value != "" { if xProto := req.Header.Get(xForwardedProto); xProto != "" {
scheme = value // 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" { if scheme == schemeHTTP && port == ":80" || scheme == schemeHTTPS && port == ":443" {

View file

@ -63,6 +63,41 @@ func TestRedirectSchemeHandler(t *testing.T) {
}, },
expectedStatus: http.StatusOK, 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", desc: "HTTP with port to HTTPS without port",
config: dynamic.RedirectScheme{ config: dynamic.RedirectScheme{