Skip to content

Commit ef82f3e

Browse files
orangainvishr
authored andcommitted
Avoid redirect loop in HTTPSRedirect middleware (#1058)
In HTTPSRedirect and similar middlewares, determining if redirection is needed using `c.IsTLS()` causes redirect loop when an application is running behind a TLS termination proxy, e.g. AWS ELB. Instead, I believe, redirection should be determined by `c.Scheme() != "https"`. This works well even behind a TLS termination proxy.
1 parent ec048ea commit ef82f3e

File tree

2 files changed

+44
-17
lines changed

2 files changed

+44
-17
lines changed

middleware/redirect.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ type RedirectConfig struct {
1616
Code int `yaml:"code"`
1717
}
1818

19-
// redirectLogic represents a function that given a tls flag, host and uri
19+
// redirectLogic represents a function that given a scheme, host and uri
2020
// can both: 1) determine if redirect is needed (will set ok accordingly) and
2121
// 2) return the appropriate redirect url.
22-
type redirectLogic func(tls bool, scheme, host, uri string) (ok bool, url string)
22+
type redirectLogic func(scheme, host, uri string) (ok bool, url string)
2323

2424
const www = "www"
2525

@@ -40,8 +40,8 @@ func HTTPSRedirect() echo.MiddlewareFunc {
4040
// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config.
4141
// See `HTTPSRedirect()`.
4242
func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
43-
return redirect(config, func(tls bool, _, host, uri string) (ok bool, url string) {
44-
if ok = !tls; ok {
43+
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
44+
if ok = scheme != "https"; ok {
4545
url = "https://" + host + uri
4646
}
4747
return
@@ -59,8 +59,8 @@ func HTTPSWWWRedirect() echo.MiddlewareFunc {
5959
// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
6060
// See `HTTPSWWWRedirect()`.
6161
func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
62-
return redirect(config, func(tls bool, _, host, uri string) (ok bool, url string) {
63-
if ok = !tls && host[:3] != www; ok {
62+
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
63+
if ok = scheme != "https" && host[:3] != www; ok {
6464
url = "https://www." + host + uri
6565
}
6666
return
@@ -78,8 +78,8 @@ func HTTPSNonWWWRedirect() echo.MiddlewareFunc {
7878
// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
7979
// See `HTTPSNonWWWRedirect()`.
8080
func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
81-
return redirect(config, func(tls bool, _, host, uri string) (ok bool, url string) {
82-
if ok = !tls; ok {
81+
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
82+
if ok = scheme != "https"; ok {
8383
if host[:3] == www {
8484
host = host[4:]
8585
}
@@ -100,7 +100,7 @@ func WWWRedirect() echo.MiddlewareFunc {
100100
// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
101101
// See `WWWRedirect()`.
102102
func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
103-
return redirect(config, func(_ bool, scheme, host, uri string) (ok bool, url string) {
103+
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
104104
if ok = host[:3] != www; ok {
105105
url = scheme + "://www." + host + uri
106106
}
@@ -119,7 +119,7 @@ func NonWWWRedirect() echo.MiddlewareFunc {
119119
// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
120120
// See `NonWWWRedirect()`.
121121
func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
122-
return redirect(config, func(tls bool, scheme, host, uri string) (ok bool, url string) {
122+
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
123123
if ok = host[:3] == www; ok {
124124
url = scheme + "://" + host[4:] + uri
125125
}
@@ -143,7 +143,7 @@ func redirect(config RedirectConfig, cb redirectLogic) echo.MiddlewareFunc {
143143

144144
req, scheme := c.Request(), c.Scheme()
145145
host := req.Host
146-
if ok, url := cb(c.IsTLS(), scheme, host, req.RequestURI); ok {
146+
if ok, url := cb(scheme, host, req.RequestURI); ok {
147147
return c.Redirect(config.Code, url)
148148
}
149149

middleware/redirect_test.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,74 @@ import (
1212
type middlewareGenerator func() echo.MiddlewareFunc
1313

1414
func TestRedirectHTTPSRedirect(t *testing.T) {
15-
res := redirectTest(HTTPSRedirect, "labstack.com")
15+
res := redirectTest(HTTPSRedirect, "labstack.com", nil)
1616

1717
assert.Equal(t, http.StatusMovedPermanently, res.Code)
1818
assert.Equal(t, "https://labstack.com/", res.Header().Get(echo.HeaderLocation))
1919
}
2020

21+
func TestHTTPSRedirectBehindTLSTerminationProxy(t *testing.T) {
22+
header := http.Header{}
23+
header.Set(echo.HeaderXForwardedProto, "https")
24+
res := redirectTest(HTTPSRedirect, "labstack.com", header)
25+
26+
assert.Equal(t, http.StatusOK, res.Code)
27+
}
28+
2129
func TestRedirectHTTPSWWWRedirect(t *testing.T) {
22-
res := redirectTest(HTTPSWWWRedirect, "labstack.com")
30+
res := redirectTest(HTTPSWWWRedirect, "labstack.com", nil)
2331

2432
assert.Equal(t, http.StatusMovedPermanently, res.Code)
2533
assert.Equal(t, "https://www.labstack.com/", res.Header().Get(echo.HeaderLocation))
2634
}
2735

36+
func TestRedirectHTTPSWWWRedirectBehindTLSTerminationProxy(t *testing.T) {
37+
header := http.Header{}
38+
header.Set(echo.HeaderXForwardedProto, "https")
39+
res := redirectTest(HTTPSWWWRedirect, "labstack.com", header)
40+
41+
assert.Equal(t, http.StatusOK, res.Code)
42+
}
43+
2844
func TestRedirectHTTPSNonWWWRedirect(t *testing.T) {
29-
res := redirectTest(HTTPSNonWWWRedirect, "www.labstack.com")
45+
res := redirectTest(HTTPSNonWWWRedirect, "www.labstack.com", nil)
3046

3147
assert.Equal(t, http.StatusMovedPermanently, res.Code)
3248
assert.Equal(t, "https://labstack.com/", res.Header().Get(echo.HeaderLocation))
3349
}
3450

51+
func TestRedirectHTTPSNonWWWRedirectBehindTLSTerminationProxy(t *testing.T) {
52+
header := http.Header{}
53+
header.Set(echo.HeaderXForwardedProto, "https")
54+
res := redirectTest(HTTPSNonWWWRedirect, "www.labstack.com", header)
55+
56+
assert.Equal(t, http.StatusOK, res.Code)
57+
}
58+
3559
func TestRedirectWWWRedirect(t *testing.T) {
36-
res := redirectTest(WWWRedirect, "labstack.com")
60+
res := redirectTest(WWWRedirect, "labstack.com", nil)
3761

3862
assert.Equal(t, http.StatusMovedPermanently, res.Code)
3963
assert.Equal(t, "http://www.labstack.com/", res.Header().Get(echo.HeaderLocation))
4064
}
4165

4266
func TestRedirectNonWWWRedirect(t *testing.T) {
43-
res := redirectTest(NonWWWRedirect, "www.labstack.com")
67+
res := redirectTest(NonWWWRedirect, "www.labstack.com", nil)
4468

4569
assert.Equal(t, http.StatusMovedPermanently, res.Code)
4670
assert.Equal(t, "http://labstack.com/", res.Header().Get(echo.HeaderLocation))
4771
}
4872

49-
func redirectTest(fn middlewareGenerator, host string) *httptest.ResponseRecorder {
73+
func redirectTest(fn middlewareGenerator, host string, header http.Header) *httptest.ResponseRecorder {
5074
e := echo.New()
5175
next := func(c echo.Context) (err error) {
5276
return c.NoContent(http.StatusOK)
5377
}
5478
req := httptest.NewRequest(echo.GET, "/", nil)
5579
req.Host = host
80+
if header != nil {
81+
req.Header = header
82+
}
5683
res := httptest.NewRecorder()
5784
c := e.NewContext(req, res)
5885

0 commit comments

Comments
 (0)