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 middleware does not work for websocket requests.
!!! 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)
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")

View file

@ -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" {

View file

@ -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{