Support forwarded websocket protocol in RedirectScheme
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
28da781194
commit
8f6463ba7a
4 changed files with 54 additions and 5 deletions
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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" {
|
||||||
|
|
|
@ -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{
|
||||||
|
|
Loading…
Reference in a new issue