Fix HostRegexp and Query muxers

This commit is contained in:
Julien Salleyron 2022-06-27 15:16:08 +02:00 committed by GitHub
parent 77e1ce2877
commit a887794313
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 246 additions and 6 deletions

3
go.mod
View file

@ -176,7 +176,6 @@ require (
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/gophercloud/gophercloud v0.16.0 // indirect
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
@ -334,7 +333,7 @@ require (
replace (
github.com/abbot/go-http-auth => github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e
github.com/go-check/check => github.com/containous/check v0.0.0-20170915194414-ca0bf163426a
github.com/gorilla/mux => github.com/containous/mux v0.0.0-20220113180107-8ffa4f6d063c
github.com/gorilla/mux => github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f
github.com/mailgun/minheap => github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595
)

5
go.sum
View file

@ -456,8 +456,8 @@ github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e h1:D+uTE
github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e/go.mod h1:s8kLgBQolDbsJOPVIGCEEv9zGAKUUf/685Gi0Qqg8z8=
github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595 h1:aPspFRO6b94To3gl4yTDOEtpjFwXI7V2W+z0JcNljQ4=
github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595/go.mod h1:+lHFbEasIiQVGzhVDVw/cn0ZaOzde2OwNncp1NhXV4c=
github.com/containous/mux v0.0.0-20220113180107-8ffa4f6d063c h1:g6JvgTtfpS6AfhRjY87NZ0g39CrNDbdm8R+1CD85Cfo=
github.com/containous/mux v0.0.0-20220113180107-8ffa4f6d063c/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg=
github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f h1:1uEtynq2C0ljy3630jt7EAxg8jZY2gy6YHdGwdqEpWw=
github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg=
github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -930,7 +930,6 @@ github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=

View file

@ -0,0 +1,44 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.webHost]
address = ":8000"
[entryPoints.webHostRegexp]
address = ":8001"
[entryPoints.webQuery]
address = ":8002"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers]
[http.routers.router1]
entryPoints = ["webHost"]
service = "service1"
rule = "!Host(`test.localhost`)"
[http.routers.router2]
entryPoints = ["webHostRegexp"]
service = "service1"
rule = "!HostRegexp(`test.localhost`)"
[http.routers.router3]
entryPoints = ["webQuery"]
service = "service1"
rule = "!Query(`foo=`)"
[http.services]
[http.services.service1.loadBalancer]
[[http.services.service1.loadBalancer.servers]]
url = "{{ .Server1 }}"

View file

@ -1,6 +1,7 @@
package integration
import (
"bufio"
"bytes"
"crypto/rand"
"encoding/json"
@ -1178,3 +1179,124 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) {
})
c.Assert(err, checker.IsNil)
}
func (s *SimpleSuite) TestMuxer(c *check.C) {
s.createComposeProject(c, "base")
s.composeUp(c)
defer s.composeDown(c)
whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP(c, "whoami1"), "80")
file := s.adaptFile(c, "fixtures/simple_muxer.toml", struct {
Server1 string
}{whoami1URL})
defer os.Remove(file)
cmd, output := s.traefikCmd(withConfigFile(file))
defer output(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("!Host"))
c.Assert(err, checker.IsNil)
testCases := []struct {
desc string
request string
target string
body string
expected int
}{
{
desc: "!Host with absolute-form URL with empty host and host header, no match",
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
target: "127.0.0.1:8000",
expected: http.StatusNotFound,
},
{
desc: "!Host with absolute-form URL with empty host and host header, match",
request: "GET http://@/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
target: "127.0.0.1:8000",
expected: http.StatusOK,
},
{
desc: "!Host with absolute-form URL and host header, no match",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
target: "127.0.0.1:8000",
expected: http.StatusNotFound,
},
{
desc: "!Host with absolute-form URL and host header, match",
request: "GET http://toto.localhost/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
target: "127.0.0.1:8000",
expected: http.StatusOK,
},
{
desc: "!HostRegexp with absolute-form URL with empty host and host header, no match",
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
target: "127.0.0.1:8001",
expected: http.StatusNotFound,
},
{
desc: "!HostRegexp with absolute-form URL with empty host and host header, match",
request: "GET http://@/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
target: "127.0.0.1:8001",
expected: http.StatusOK,
},
{
desc: "!HostRegexp with absolute-form URL and host header, no match",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
target: "127.0.0.1:8001",
expected: http.StatusNotFound,
},
{
desc: "!HostRegexp with absolute-form URL and host header, match",
request: "GET http://toto.localhost/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
target: "127.0.0.1:8001",
expected: http.StatusOK,
},
{
desc: "!Query with semicolon, no match",
request: "GET /?foo=; HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
target: "127.0.0.1:8002",
expected: http.StatusNotFound,
},
{
desc: "!Query with semicolon, no match",
request: "GET /?foo=titi;bar=toto HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
target: "127.0.0.1:8002",
expected: http.StatusNotFound,
},
{
desc: "!Query with semicolon, match",
request: "GET /?bar=toto;boo=titi HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
target: "127.0.0.1:8002",
expected: http.StatusOK,
body: "bar=toto&boo=titi",
},
}
for _, test := range testCases {
conn, err := net.Dial("tcp", test.target)
c.Assert(err, checker.IsNil)
_, err = conn.Write([]byte(test.request))
c.Assert(err, checker.IsNil)
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
c.Assert(err, checker.IsNil)
if resp.StatusCode != test.expected {
c.Errorf("%s failed with %d instead of %d", test.desc, resp.StatusCode, test.expected)
}
if test.body != "" {
body, err := io.ReadAll(resp.Body)
c.Assert(err, checker.IsNil)
c.Assert(string(body), checker.Contains, test.body)
}
}
}

View file

@ -1,6 +1,8 @@
package http
import (
"bufio"
"bytes"
"net/http"
"net/http/httptest"
"testing"
@ -956,3 +958,74 @@ func TestParseDomains(t *testing.T) {
})
}
}
func TestAbsoluteFormURL(t *testing.T) {
testCases := []struct {
desc string
request string
rule string
expected int
}{
{
desc: "!HostRegexp with absolute-form URL with empty host with non-matching host header",
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
rule: "!HostRegexp(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!Host with absolute-form URL with empty host with non-matching host header",
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
rule: "!Host(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!HostRegexp with absolute-form URL with matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!HostRegexp(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!Host with absolute-form URL with matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!Host(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!HostRegexp with absolute-form URL with non-matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!HostRegexp(`toto.localhost`)",
expected: http.StatusOK,
},
{
desc: "!Host with absolute-form URL with non-matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!Host(`toto.localhost`)",
expected: http.StatusOK,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
require.NoError(t, err)
// RequestDecorator is necessary for the host rule
reqHost := requestdecorator.New(nil)
w := httptest.NewRecorder()
req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader([]byte(test.request))))
require.NoError(t, err)
reqHost.ServeHTTP(w, req, muxer.ServeHTTP)
assert.Equal(t, test.expected, w.Code)
})
}
}

View file

@ -523,6 +523,8 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
return nil, err
}
handler = http.AllowQuerySemicolons(handler)
if withH2c {
handler = h2c.NewHandler(handler, &http2.Server{})
}

View file

@ -9,6 +9,7 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
ptypes "github.com/traefik/paerser/types"
@ -46,7 +47,7 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar
outReq.URL.Path = u.Path
outReq.URL.RawPath = u.RawPath
outReq.URL.RawQuery = u.RawQuery
outReq.URL.RawQuery = strings.ReplaceAll(u.RawQuery, ";", "&")
outReq.RequestURI = "" // Outgoing request should not have RequestURI
outReq.Proto = "HTTP/1.1"