Skip to content
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

Merged
merged 3 commits into from
Jul 31, 2018

Conversation

dtomcej
Copy link
Contributor

@dtomcej dtomcej commented Jul 16, 2018

What does this PR do?

Rebuilds entrypoint redirects when used with PathPrefixStrip or AddPrefix

Motivation

Fixes #1957
and issues linked to #2024

More

  • Added/updated documentation - No documentation needed, as this is intended behavior
  • Rebuild trailing slash from prefix match
  • Added/updated tests
  • Rebuild correct redirect when prefix is added
  • Added/updated tests

@dtomcej dtomcej added kind/bug/confirmed a confirmed bug (reproducible). status/2-needs-review kind/bug/fix a bug fix and removed status/0-needs-triage kind/bug/confirmed a confirmed bug (reproducible). labels Jul 17, 2018
@dtomcej dtomcej changed the title Correct Entrypoint Redirect with Stripped Path Correct Entrypoint Redirect with Stripped or Added Path Jul 20, 2018
@kachkaev
Copy link
Contributor

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!

expectedURL: "https://example.com:8443/api/bacon/",
},
}
for _, test := range testCases {
Copy link
Member

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

@@ -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"))
Copy link
Member

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)
	}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion

@@ -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 != "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if len(prefix) > 0 {

}
if addPrefix, addPrefixOk := req.Context().Value(middlewares.AddPrefixKey).(string); addPrefixOk {
if addPrefix != "" {
parsedURL, err = url.Parse(strings.Replace(parsedURL.String(), addPrefix, "", 1))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

err not checked

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])
Copy link
Member

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 Show resolved Hide resolved
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))
Copy link
Member

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?

@@ -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 {
Copy link
Member

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?

@mmatur
Copy link
Member

mmatur commented Jul 27, 2018

Could be nice to have this fix in v1.7 WDYT ?

@@ -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+"/")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant parentheses

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch :)

@dtomcej
Copy link
Contributor Author

dtomcej commented Jul 27, 2018

I can rebase and squeeze this into 1.7!

@dtomcej
Copy link
Contributor Author

dtomcej commented Jul 28, 2018

Found that I didn't handle/test PathPrefixStripRegex properly. Didn't know that was a matcher 😝 Updated code coming.

Copy link
Member

@juliens juliens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@mmatur mmatur left a 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

Copy link
Member

@emilevauge emilevauge left a 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/",
},
}
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

@emilevauge emilevauge left a 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

@edasque
Copy link

edasque commented Aug 13, 2018

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

@marianschmotzer
Copy link

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 -

[entryPoints.https.redirect]                                                                                                                                                                      
     regex = "(http[a-z]{0,1}://[^/]+/([a-zA-Z0-9-]+)[^/])$"                                                                                                                                         
     replacement = "${1}/" 

But same results as in 1.6.5 - it doesn't work.

@edasque
Copy link

edasque commented Aug 15, 2018

@marianschmotzer , do you know if it was supposed to?

@marianschmotzer
Copy link

marianschmotzer commented Aug 15, 2018

@edasque there is this commit 1950de2
So i would say. In any case is quite stupid, but critical bug (at least for us).
My feeling (after looking into that) is that problem is in rewriting response - where if response is mybackslah/ and PathPrefixStrip is mybackslah response paths are not rewritten at all in html code.

@marianschmotzer
Copy link

@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

@edasque
Copy link

edasque commented Sep 3, 2018

Cool trick @marianschmotzer but I’d rather not add nginx or anything to make something so simple work.

@dtomcej
Copy link
Contributor Author

dtomcej commented Sep 3, 2018

@marianschmotzer @edasque If you are still having problems, can you please open a new issue instead of bumping a closed PR?

Thanks!

@edasque
Copy link

edasque commented Sep 4, 2018

@dtomcej please find it here: #3852

@marianschmotzer can you confirm this illustrates the issue correctly.

@marianschmotzer
Copy link

marianschmotzer commented Sep 5, 2018 via email

sagikazarmark added a commit to banzaicloud/banzai-charts that referenced this pull request Oct 19, 2018
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.
ahma pushed a commit to banzaicloud/banzai-charts that referenced this pull request Oct 21, 2018
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants