diff --git a/integration/fixtures/https/https_redirect.toml b/integration/fixtures/https/https_redirect.toml new file mode 100644 index 000000000..498d14f89 --- /dev/null +++ b/integration/fixtures/https/https_redirect.toml @@ -0,0 +1,36 @@ +logLevel = "DEBUG" + +defaultEntryPoints = ["http", "https"] + +[entryPoints] + [entryPoints.http] + address = ":8888" + [entryPoints.http.redirect] + entryPoint = "https" + [entryPoints.https] + address = ":8443" + [entryPoints.https.tls] + +[api] + +[file] + +[backends] + [backends.backend1] + [backends.backend1.servers.server1] + url = "http://127.0.0.1:80" + weight = 1 + +[frontends] + [frontends.frontend1] + backend = "backend1" + [frontends.frontend1.routes.test_1] + rule = "Host: example.com; PathPrefixStrip: /api" + [frontends.frontend2] + backend = "backend1" + [frontends.frontend2.routes.test_1] + rule = "Host: test.com; AddPrefix: /foo" + [frontends.frontend3] + backend = "backend1" + [frontends.frontend3.routes.test_1] + rule = "Host: foo.com; PathPrefixStripRegex: /{id:[a-z]+}" diff --git a/integration/https_test.go b/integration/https_test.go index 32deef404..5db8a54ab 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -731,3 +731,134 @@ func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName, en c.Assert(err, checker.IsNil) } } + +func (s *HTTPSSuite) TestEntrypointHttpsRedirectAndPathModification(c *check.C) { + cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_redirect.toml")) + defer display(c) + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + // wait for Traefik + err = try.GetRequest("http://127.0.0.1:8080/api/providers", 500*time.Millisecond, try.BodyContains("Host: example.com")) + c.Assert(err, checker.IsNil) + + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + testCases := []struct { + desc string + host string + sourceURL string + expectedURL string + }{ + { + desc: "Stripped URL redirect", + host: "example.com", + sourceURL: "http://127.0.0.1:8888/api", + expectedURL: "https://example.com:8443/api", + }, + { + desc: "Stripped URL with trailing slash redirect", + host: "example.com", + sourceURL: "http://127.0.0.1:8888/api/", + expectedURL: "https://example.com:8443/api/", + }, + { + desc: "Stripped URL with double trailing slash redirect", + host: "example.com", + sourceURL: "http://127.0.0.1:8888/api//", + expectedURL: "https://example.com:8443/api//", + }, + { + desc: "Stripped URL with path redirect", + host: "example.com", + sourceURL: "http://127.0.0.1:8888/api/bacon", + expectedURL: "https://example.com:8443/api/bacon", + }, + { + desc: "Stripped URL with path and trailing slash redirect", + host: "example.com", + sourceURL: "http://127.0.0.1:8888/api/bacon/", + expectedURL: "https://example.com:8443/api/bacon/", + }, + { + desc: "Stripped URL with path and double trailing slash redirect", + host: "example.com", + sourceURL: "http://127.0.0.1:8888/api/bacon//", + expectedURL: "https://example.com:8443/api/bacon//", + }, + { + desc: "Root Path with redirect", + host: "test.com", + sourceURL: "http://127.0.0.1:8888/", + expectedURL: "https://test.com:8443/", + }, + { + desc: "Root Path with double trailing slash redirect", + host: "test.com", + sourceURL: "http://127.0.0.1:8888//", + expectedURL: "https://test.com:8443//", + }, + { + desc: "AddPrefix with redirect", + host: "test.com", + sourceURL: "http://127.0.0.1:8888/wtf", + expectedURL: "https://test.com:8443/wtf", + }, + { + desc: "AddPrefix with trailing slash redirect", + host: "test.com", + sourceURL: "http://127.0.0.1:8888/wtf/", + expectedURL: "https://test.com:8443/wtf/", + }, + { + desc: "AddPrefix with matching path segment redirect", + host: "test.com", + sourceURL: "http://127.0.0.1:8888/wtf/foo", + expectedURL: "https://test.com:8443/wtf/foo", + }, + { + desc: "Stripped URL Regex redirect", + host: "foo.com", + sourceURL: "http://127.0.0.1:8888/api", + expectedURL: "https://foo.com:8443/api", + }, + { + desc: "Stripped URL Regex with trailing slash redirect", + host: "foo.com", + sourceURL: "http://127.0.0.1:8888/api/", + expectedURL: "https://foo.com:8443/api/", + }, + { + desc: "Stripped URL Regex with path redirect", + host: "foo.com", + sourceURL: "http://127.0.0.1:8888/api/bacon", + expectedURL: "https://foo.com:8443/api/bacon", + }, + { + desc: "Stripped URL Regex with path and trailing slash redirect", + host: "foo.com", + sourceURL: "http://127.0.0.1:8888/api/bacon/", + expectedURL: "https://foo.com:8443/api/bacon/", + }, + } + + for _, test := range testCases { + test := test + + req, err := http.NewRequest("GET", test.sourceURL, nil) + c.Assert(err, checker.IsNil) + req.Host = test.host + + resp, err := client.Do(req) + c.Assert(err, checker.IsNil) + defer resp.Body.Close() + + location := resp.Header.Get("Location") + c.Assert(location, checker.Equals, test.expectedURL) + } +} diff --git a/middlewares/addPrefix.go b/middlewares/addPrefix.go index 306903ae2..19f142fe3 100644 --- a/middlewares/addPrefix.go +++ b/middlewares/addPrefix.go @@ -1,6 +1,7 @@ package middlewares import ( + "context" "net/http" ) @@ -10,12 +11,21 @@ type AddPrefix struct { Prefix string } +type key string + +const ( + // AddPrefixKey is the key within the request context used to + // store the added prefix + AddPrefixKey key = "AddPrefix" +) + func (s *AddPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) { r.URL.Path = s.Prefix + r.URL.Path if r.URL.RawPath != "" { r.URL.RawPath = s.Prefix + r.URL.RawPath } r.RequestURI = r.URL.RequestURI() + r = r.WithContext(context.WithValue(r.Context(), AddPrefixKey, s.Prefix)) s.Handler.ServeHTTP(w, r) } diff --git a/middlewares/redirect/redirect.go b/middlewares/redirect/redirect.go index c1ec263b3..1b1dfe7dd 100644 --- a/middlewares/redirect/redirect.go +++ b/middlewares/redirect/redirect.go @@ -11,6 +11,7 @@ import ( "text/template" "github.com/containous/traefik/configuration" + "github.com/containous/traefik/middlewares" "github.com/urfave/negroni" "github.com/vulcand/oxy/utils" ) @@ -85,6 +86,30 @@ func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http return } + if stripPrefix, stripPrefixOk := req.Context().Value(middlewares.StripPrefixKey).(string); stripPrefixOk { + if len(stripPrefix) > 0 { + tempPath := parsedURL.Path + parsedURL.Path = stripPrefix + if len(tempPath) > 0 && tempPath != "/" { + parsedURL.Path = stripPrefix + tempPath + } + + if trailingSlash, trailingSlashOk := req.Context().Value(middlewares.StripPrefixSlashKey).(bool); trailingSlashOk { + if trailingSlash { + if !strings.HasSuffix(parsedURL.Path, "/") { + parsedURL.Path = fmt.Sprintf("%s/", parsedURL.Path) + } + } + } + } + } + + if addPrefix, addPrefixOk := req.Context().Value(middlewares.AddPrefixKey).(string); addPrefixOk { + if len(addPrefix) > 0 { + parsedURL.Path = strings.Replace(parsedURL.Path, addPrefix, "", 1) + } + } + if newURL != oldURL { handler := &moveHandler{location: parsedURL, permanent: h.permanent} handler.ServeHTTP(rw, req) diff --git a/middlewares/stripPrefix.go b/middlewares/stripPrefix.go index e156950a1..f5295d94c 100644 --- a/middlewares/stripPrefix.go +++ b/middlewares/stripPrefix.go @@ -1,12 +1,21 @@ package middlewares import ( + "context" "net/http" "strings" ) -// ForwardedPrefixHeader is the default header to set prefix -const ForwardedPrefixHeader = "X-Forwarded-Prefix" +const ( + // StripPrefixKey is the key within the request context used to + // store the stripped prefix + StripPrefixKey key = "StripPrefix" + // StripPrefixSlashKey is the key within the request context used to + // store the stripped slash + StripPrefixSlashKey key = "StripPrefixSlash" + // ForwardedPrefixHeader is the default header to set prefix + ForwardedPrefixHeader = "X-Forwarded-Prefix" +) // StripPrefix is a middleware used to strip prefix from an URL request type StripPrefix struct { @@ -17,18 +26,21 @@ type StripPrefix struct { func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) { for _, prefix := range s.Prefixes { if strings.HasPrefix(r.URL.Path, prefix) { + trailingSlash := r.URL.Path == prefix+"/" r.URL.Path = stripPrefix(r.URL.Path, prefix) if r.URL.RawPath != "" { r.URL.RawPath = stripPrefix(r.URL.RawPath, prefix) } - s.serveRequest(w, r, strings.TrimSpace(prefix)) + s.serveRequest(w, r, strings.TrimSpace(prefix), trailingSlash) return } } http.NotFound(w, r) } -func (s *StripPrefix) serveRequest(w http.ResponseWriter, r *http.Request, prefix string) { +func (s *StripPrefix) serveRequest(w http.ResponseWriter, r *http.Request, prefix string, trailingSlash bool) { + r = r.WithContext(context.WithValue(r.Context(), StripPrefixSlashKey, trailingSlash)) + r = r.WithContext(context.WithValue(r.Context(), StripPrefixKey, prefix)) r.Header.Add(ForwardedPrefixHeader, prefix) r.RequestURI = r.URL.RequestURI() s.Handler.ServeHTTP(w, r) diff --git a/middlewares/stripPrefixRegex.go b/middlewares/stripPrefixRegex.go index bcd66d912..d86733dd6 100644 --- a/middlewares/stripPrefixRegex.go +++ b/middlewares/stripPrefixRegex.go @@ -1,6 +1,7 @@ package middlewares import ( + "context" "net/http" "github.com/containous/mux" @@ -39,10 +40,13 @@ func (s *StripPrefixRegex) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + trailingSlash := r.URL.Path == prefix.Path+"/" r.URL.Path = r.URL.Path[len(prefix.Path):] if r.URL.RawPath != "" { r.URL.RawPath = r.URL.RawPath[len(prefix.Path):] } + r = r.WithContext(context.WithValue(r.Context(), StripPrefixSlashKey, trailingSlash)) + r = r.WithContext(context.WithValue(r.Context(), StripPrefixKey, prefix.Path)) r.Header.Add(ForwardedPrefixHeader, prefix.Path) r.RequestURI = r.URL.RequestURI() s.Handler.ServeHTTP(w, r)