-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Correct Entrypoint Redirect with Stripped or Added Path #3631
Conversation
Can't wait to see this fix landing a release! 😍 #1957 is in top 10 issues by the number of 👍, so this PR is waited by many! |
integration/https_test.go
Outdated
expectedURL: "https://example.com:8443/api/bacon/", | ||
}, | ||
} | ||
for _, test := range testCases { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add new line before please
integration/https_test.go
Outdated
@@ -804,3 +804,116 @@ func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName, en | |||
c.Assert(err, checker.IsNil) | |||
} | |||
} | |||
|
|||
func (s *HTTPSSuite) TestEntrypointHttpsRedirectAndPathPrefixStrip(c *check.C) { | |||
cmd, display := s.traefikCmd(withConfigFile("fixtures/https/https_redirect.toml")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should maybe simplify this 2 tests in 1.
The only things that change is the host
You could maybe do something like that
func (s *HTTPSSuite) TestEntrypointRedirectAndPath(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", 1*time.Second, 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 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: "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",
},
}
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)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great suggestion
middlewares/redirect/redirect.go
Outdated
@@ -85,6 +86,28 @@ func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http | |||
return | |||
} | |||
|
|||
if prefix, prefixOk := req.Context().Value(middlewares.StripPrefixKey).(string); prefixOk { | |||
if prefix != "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if len(prefix) > 0 {
middlewares/redirect/redirect.go
Outdated
} | ||
if addPrefix, addPrefixOk := req.Context().Value(middlewares.AddPrefixKey).(string); addPrefixOk { | ||
if addPrefix != "" { | ||
parsedURL, err = url.Parse(strings.Replace(parsedURL.String(), addPrefix, "", 1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err
not checked
middlewares/redirect/redirect.go
Outdated
splitURL := strings.SplitN(parsedURL.String(), "/", 4) | ||
parsedURL, err = url.Parse(strings.Join(splitURL[:3], "/") + prefix) | ||
if len(splitURL) > 3 && splitURL[3] != "" { | ||
parsedURL, err = url.Parse(strings.Join(splitURL[:3], "/") + prefix + "/" + splitURL[3]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err
not checked
middlewares/stripPrefix.go
Outdated
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) { | ||
if trailingSlash { | ||
r = r.WithContext(context.WithValue(r.Context(), StripPrefixSlashKey, trailingSlash)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that we can always set this boolean value WDYT?
middlewares/redirect/redirect.go
Outdated
@@ -85,6 +86,28 @@ func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http | |||
return | |||
} | |||
|
|||
if prefix, prefixOk := req.Context().Value(middlewares.StripPrefixKey).(string); prefixOk { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should maybe simplify this part like that
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)
}
}
You update parsedURL
instead of re parsing the URL multiple times
WDYT?
Could be nice to have this fix in v1.7 WDYT ? |
middlewares/stripPrefix.go
Outdated
@@ -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+"/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant parentheses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch :)
I can rebase and squeeze this into 1.7! |
Found that I didn't handle/test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job @dtomcej !! 👏
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this fix @dtomcej
I still have one comment ;)
sourceURL: "http://127.0.0.1:8888/api/bacon/", | ||
expectedURL: "https://foo.com:8443/api/bacon/", | ||
}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add few edge cases like double trailing redirect http://127.0.0.1:8888/api/bacon//
, no path (with/without) redirect http://127.0.0.1:8888
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added no path + double trailing slash tests.
There is an outstanding "issue" where an entrypoint redirect doesn't work when a frontend isn't matched (I think due to the middleware being applied to frontend chains). This would be out of the scope of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job @dtomcej
LGTM
Is this in 1.7.0 rc3? I have a problem that seems similar where PathPrefixStrip: /container1 works when hitting /container1/ but not /container1 (where it doesn't prefix strip the subsequent queries leading to http://192.168.7.229:40080/bower_components/slimScroll/jquery.slimscroll.min.js instead of http://192.168.7.229:40080/organizr/bower_components/bootstrap/dist/css/bootstrap.min.css?v=1.80 |
I just tried 1.7.0 rc3 and PathPrefixStrip works (or doesn't work to be honest)as mentioned by edasque. I tired even to place redirection to config file -
But same results as in 1.6.5 - it doesn't work. |
@marianschmotzer , do you know if it was supposed to? |
@edasque there is this commit 1950de2 |
@edasque I've creates short blog about this problem with our "fix" you can find it here maybe will be helpful https://medium.com/@smoco/fighting-trailing-slash-problem-c0416023d20e |
Cool trick @marianschmotzer but I’d rather not add nginx or anything to make something so simple work. |
@marianschmotzer @edasque If you are still having problems, can you please open a new issue instead of bumping a closed PR? Thanks! |
@dtomcej please find it here: #3852 @marianschmotzer can you confirm this illustrates the issue correctly. |
Hi
seems quite clear to me
|
As outlined in this (traefik/traefik#1957) issue SSL redirection with PathPrefixStrip does not work really well. A solution was provided in this (traefik/traefik#3631) PR, released in 1.7, but it didn't really solve the issue. In fact, there were several subsequent issues opened (traefik/traefik#3999, traefik/traefik#3876) but they got closed. Another issue was opened in the Traefik repo: traefik/traefik#4085 Until then this workaround provides the same functionality.
As outlined in this (traefik/traefik#1957) issue SSL redirection with PathPrefixStrip does not work really well. A solution was provided in this (traefik/traefik#3631) PR, released in 1.7, but it didn't really solve the issue. In fact, there were several subsequent issues opened (traefik/traefik#3999, traefik/traefik#3876) but they got closed. Another issue was opened in the Traefik repo: traefik/traefik#4085 Until then this workaround provides the same functionality.
What does this PR do?
Rebuilds entrypoint redirects when used with
PathPrefixStrip
orAddPrefix
Motivation
Fixes #1957
and issues linked to #2024
More