From 73b4df4e18e22859fd06a0fabb2eabb029c768e7 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 26 Jul 2018 12:42:03 +0200 Subject: [PATCH 01/18] Remove TLS in API --- integration/consul_test.go | 21 +++-------- integration/etcd3_test.go | 42 +++++----------------- integration/etcd_test.go | 21 +++-------- integration/fixtures/file/dir/simple2.toml | 2 +- integration/try/condition.go | 25 +++++++++++++ integration/try/try.go | 35 +++++++++++++----- types/types.go | 2 +- 7 files changed, 69 insertions(+), 79 deletions(-) diff --git a/integration/consul_test.go b/integration/consul_test.go index a6c1c192f..52cc4f13e 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -585,21 +585,14 @@ func (s *ConsulSuite) TestSNIDynamicTlsConfig(c *check.C) { }) c.Assert(err, checker.IsNil) - // wait for traefik - err = try.GetRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, try.BodyContains("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7hG")) - c.Assert(err, checker.IsNil) - req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - client := &http.Client{Transport: tr1} req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - var resp *http.Response - resp, err = client.Do(req) + + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com")) c.Assert(err, checker.IsNil) - cn := resp.TLS.PeerCertificates[0].Subject.CommonName - c.Assert(cn, checker.Equals, "snitest.com") // now we configure the second keypair in consul and the request for host "snitest.org" will use the second keypair for key, value := range tlsconfigure2 { @@ -614,18 +607,12 @@ func (s *ConsulSuite) TestSNIDynamicTlsConfig(c *check.C) { }) c.Assert(err, checker.IsNil) - // waiting for traefik to pull configuration - err = try.GetRequest("http://127.0.0.1:8081/api/providers", 30*time.Second, try.BodyContains("MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r")) - c.Assert(err, checker.IsNil) - req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - client = &http.Client{Transport: tr2} req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - resp, err = client.Do(req) + + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("snitest.org")) c.Assert(err, checker.IsNil) - cn = resp.TLS.PeerCertificates[0].Subject.CommonName - c.Assert(cn, checker.Equals, "snitest.org") } diff --git a/integration/etcd3_test.go b/integration/etcd3_test.go index 00dc755ed..d769e937d 100644 --- a/integration/etcd3_test.go +++ b/integration/etcd3_test.go @@ -532,21 +532,14 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - // wait for Træfik - err = try.GetRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h"))) - c.Assert(err, checker.IsNil) - req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - client := &http.Client{Transport: tr1} req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - var resp *http.Response - resp, err = client.Do(req) + + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com")) c.Assert(err, checker.IsNil) - cn := resp.TLS.PeerCertificates[0].Subject.CommonName - c.Assert(cn, checker.Equals, "snitest.com") // now we configure the second keypair in etcd and the request for host "snitest.org" will use the second keypair @@ -562,20 +555,14 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) { }) c.Assert(err, checker.IsNil) - // waiting for Træfik to pull configuration - err = try.GetRequest("http://127.0.0.1:8081/api/providers", 30*time.Second, try.BodyContains("MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r")) - c.Assert(err, checker.IsNil) - req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - client = &http.Client{Transport: tr2} req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - resp, err = client.Do(req) + + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("snitest.org")) c.Assert(err, checker.IsNil) - cn = resp.TLS.PeerCertificates[0].Subject.CommonName - c.Assert(cn, checker.Equals, "snitest.org") } func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) { @@ -646,21 +633,14 @@ func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - // wait for Træfik - err = try.GetRequest(traefikWebEtcdURL+"api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h"))) - c.Assert(err, checker.IsNil) - req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - client := &http.Client{Transport: tr1} req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - var resp *http.Response - resp, err = client.Do(req) + + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com")) c.Assert(err, checker.IsNil) - cn := resp.TLS.PeerCertificates[0].Subject.CommonName - c.Assert(cn, checker.Equals, "snitest.com") // now we delete the tls cert/key pairs,so the endpoint show use default cert/key pair for key := range tlsconfigure1 { @@ -668,18 +648,12 @@ func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) { c.Assert(err, checker.IsNil) } - // waiting for Træfik to pull configuration - err = try.GetRequest(traefikWebEtcdURL+"api/providers", 30*time.Second, try.BodyNotContains("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h")) - c.Assert(err, checker.IsNil) - req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - client = &http.Client{Transport: tr1} req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - resp, err = client.Do(req) + + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("TRAEFIK DEFAULT CERT")) c.Assert(err, checker.IsNil) - cn = resp.TLS.PeerCertificates[0].Subject.CommonName - c.Assert(cn, checker.Equals, "TRAEFIK DEFAULT CERT") } diff --git a/integration/etcd_test.go b/integration/etcd_test.go index 6e6133f86..9ae0e303f 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -548,21 +548,14 @@ func (s *EtcdSuite) TestSNIDynamicTlsConfig(c *check.C) { c.Assert(err, checker.IsNil) defer cmd.Process.Kill() - // wait for Træfik - err = try.GetRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, try.BodyContains(string("MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7h"))) - c.Assert(err, checker.IsNil) - req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - client := &http.Client{Transport: tr1} req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - var resp *http.Response - resp, err = client.Do(req) + + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com")) c.Assert(err, checker.IsNil) - cn := resp.TLS.PeerCertificates[0].Subject.CommonName - c.Assert(cn, checker.Equals, "snitest.com") // now we configure the second keypair in etcd and the request for host "snitest.org" will use the second keypair @@ -578,18 +571,12 @@ func (s *EtcdSuite) TestSNIDynamicTlsConfig(c *check.C) { }) c.Assert(err, checker.IsNil) - // waiting for Træfik to pull configuration - err = try.GetRequest("http://127.0.0.1:8081/api/providers", 30*time.Second, try.BodyContains("MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r")) - c.Assert(err, checker.IsNil) - req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - client = &http.Client{Transport: tr2} req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - resp, err = client.Do(req) + + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("snitest.org")) c.Assert(err, checker.IsNil) - cn = resp.TLS.PeerCertificates[0].Subject.CommonName - c.Assert(cn, checker.Equals, "snitest.org") } diff --git a/integration/fixtures/file/dir/simple2.toml b/integration/fixtures/file/dir/simple2.toml index e02f63550..dcbcffc57 100644 --- a/integration/fixtures/file/dir/simple2.toml +++ b/integration/fixtures/file/dir/simple2.toml @@ -2,7 +2,7 @@ [backends] [backends.backend2] [backends.backend2.servers.server1] - url = "http://172.17.0.2:80" + url = "http://172.17.0.123:80" weight = 1 [frontends] diff --git a/integration/try/condition.go b/integration/try/condition.go index 999b6256e..537859d53 100644 --- a/integration/try/condition.go +++ b/integration/try/condition.go @@ -88,6 +88,31 @@ func HasBody() ResponseCondition { } } +// HasCn returns a retry condition function. +// The condition returns an error if the cn is not correct. +func HasCn(cn string) ResponseCondition { + return func(res *http.Response) error { + if res.TLS == nil { + return errors.New("response doesn't have TLS") + } + + if len(res.TLS.PeerCertificates) == 0 { + return errors.New("response TLS doesn't have peer certificates") + } + + if res.TLS.PeerCertificates[0] == nil { + return errors.New("first peer certificate is nil") + } + + commonName := res.TLS.PeerCertificates[0].Subject.CommonName + if cn != commonName { + return fmt.Errorf("common name don't match: %s != %s", cn, commonName) + } + + return nil + } +} + // StatusCodeIs returns a retry condition function. // The condition returns an error if the given response's status code is not the // given HTTP status code. diff --git a/integration/try/try.go b/integration/try/try.go index f201cd0d8..0f75cbdaf 100644 --- a/integration/try/try.go +++ b/integration/try/try.go @@ -31,7 +31,7 @@ func Sleep(d time.Duration) { // response body needs to be closed or not. Callers are expected to close on // their own if the function returns a nil error. func Response(req *http.Request, timeout time.Duration) (*http.Response, error) { - return doTryRequest(req, timeout) + return doTryRequest(req, timeout, nil) } // ResponseUntilStatusCode is like Request, but returns the response for further @@ -40,7 +40,7 @@ func Response(req *http.Request, timeout time.Duration) (*http.Response, error) // response body needs to be closed or not. Callers are expected to close on // their own if the function returns a nil error. func ResponseUntilStatusCode(req *http.Request, timeout time.Duration, statusCode int) (*http.Response, error) { - return doTryRequest(req, timeout, StatusCodeIs(statusCode)) + return doTryRequest(req, timeout, nil, StatusCodeIs(statusCode)) } // GetRequest is like Do, but runs a request against the given URL and applies @@ -48,7 +48,7 @@ func ResponseUntilStatusCode(req *http.Request, timeout time.Duration, statusCod // ResponseCondition may be nil, in which case only the request against the URL must // succeed. func GetRequest(url string, timeout time.Duration, conditions ...ResponseCondition) error { - resp, err := doTryGet(url, timeout, conditions...) + resp, err := doTryGet(url, timeout, nil, conditions...) if resp != nil && resp.Body != nil { defer resp.Body.Close() @@ -62,7 +62,21 @@ func GetRequest(url string, timeout time.Duration, conditions ...ResponseConditi // ResponseCondition may be nil, in which case only the request against the URL must // succeed. func Request(req *http.Request, timeout time.Duration, conditions ...ResponseCondition) error { - resp, err := doTryRequest(req, timeout, conditions...) + resp, err := doTryRequest(req, timeout, nil, conditions...) + + if resp != nil && resp.Body != nil { + defer resp.Body.Close() + } + + return err +} + +// RequestWithTransport is like Do, but runs a request against the given URL and applies +// the condition on the response. +// ResponseCondition may be nil, in which case only the request against the URL must +// succeed. +func RequestWithTransport(req *http.Request, timeout time.Duration, transport *http.Transport, conditions ...ResponseCondition) error { + resp, err := doTryRequest(req, timeout, transport, conditions...) if resp != nil && resp.Body != nil { defer resp.Body.Close() @@ -112,24 +126,27 @@ func Do(timeout time.Duration, operation DoCondition) error { } } -func doTryGet(url string, timeout time.Duration, conditions ...ResponseCondition) (*http.Response, error) { +func doTryGet(url string, timeout time.Duration, transport *http.Transport, conditions ...ResponseCondition) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } - return doTryRequest(req, timeout, conditions...) + return doTryRequest(req, timeout, transport, conditions...) } -func doTryRequest(request *http.Request, timeout time.Duration, conditions ...ResponseCondition) (*http.Response, error) { - return doRequest(Do, timeout, request, conditions...) +func doTryRequest(request *http.Request, timeout time.Duration, transport *http.Transport, conditions ...ResponseCondition) (*http.Response, error) { + return doRequest(Do, timeout, request, transport, conditions...) } -func doRequest(action timedAction, timeout time.Duration, request *http.Request, conditions ...ResponseCondition) (*http.Response, error) { +func doRequest(action timedAction, timeout time.Duration, request *http.Request, transport *http.Transport, conditions ...ResponseCondition) (*http.Response, error) { var resp *http.Response return resp, action(timeout, func() error { var err error client := http.DefaultClient + if transport != nil { + client.Transport = transport + } resp, err = client.Do(request) if err != nil { diff --git a/types/types.go b/types/types.go index 1ab95ac86..b92557afb 100644 --- a/types/types.go +++ b/types/types.go @@ -254,7 +254,7 @@ type Configurations map[string]*Configuration type Configuration struct { Backends map[string]*Backend `json:"backends,omitempty"` Frontends map[string]*Frontend `json:"frontends,omitempty"` - TLS []*traefiktls.Configuration `json:"tls,omitempty"` + TLS []*traefiktls.Configuration `json:"-"` } // ConfigMessage hold configuration information exchanged between parts of traefik. From 7d2b7cd7f1801b0b5290e6b6f1056e8ede7b425e Mon Sep 17 00:00:00 2001 From: Alex Antonov Date: Thu, 26 Jul 2018 10:44:03 -0500 Subject: [PATCH 02/18] Added default configuration for DataDog APM Tracer --- configuration/configuration.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/configuration/configuration.go b/configuration/configuration.go index 0a347e334..f21850c25 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -11,6 +11,7 @@ import ( "github.com/containous/traefik/api" "github.com/containous/traefik/log" "github.com/containous/traefik/middlewares/tracing" + "github.com/containous/traefik/middlewares/tracing/datadog" "github.com/containous/traefik/middlewares/tracing/jaeger" "github.com/containous/traefik/middlewares/tracing/zipkin" "github.com/containous/traefik/ping" @@ -332,6 +333,10 @@ func (gc *GlobalConfiguration) initTracing() { log.Warn("Zipkin configuration will be ignored") gc.Tracing.Zipkin = nil } + if gc.Tracing.DataDog != nil { + log.Warn("DataDog configuration will be ignored") + gc.Tracing.DataDog = nil + } case zipkin.Name: if gc.Tracing.Zipkin == nil { gc.Tracing.Zipkin = &zipkin.Config{ @@ -345,6 +350,26 @@ func (gc *GlobalConfiguration) initTracing() { log.Warn("Jaeger configuration will be ignored") gc.Tracing.Jaeger = nil } + if gc.Tracing.DataDog != nil { + log.Warn("DataDog configuration will be ignored") + gc.Tracing.DataDog = nil + } + case datadog.Name: + if gc.Tracing.DataDog == nil { + gc.Tracing.DataDog = &datadog.Config{ + LocalAgentHostPort: "localhost:8126", + GlobalTag: "", + Debug: false, + } + } + if gc.Tracing.Zipkin != nil { + log.Warn("Zipkin configuration will be ignored") + gc.Tracing.Zipkin = nil + } + if gc.Tracing.Jaeger != nil { + log.Warn("Jaeger configuration will be ignored") + gc.Tracing.Jaeger = nil + } default: log.Warnf("Unknown tracer %q", gc.Tracing.Backend) return From ba3a579d077ac064f4100433b0bec42e207650b0 Mon Sep 17 00:00:00 2001 From: Daniel Tomcej Date: Tue, 31 Jul 2018 01:08:03 -0600 Subject: [PATCH 03/18] Fix Rewrite-target regex --- provider/kubernetes/kubernetes.go | 2 +- provider/kubernetes/kubernetes_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index d29e52309..628e4ac44 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -535,7 +535,7 @@ func getRuleForPath(pa extensionsv1beta1.HTTPIngressPath, i *extensionsv1beta1.I if ruleType == ruleTypeReplacePath { return "", fmt.Errorf("rewrite-target must not be used together with annotation %q", annotationKubernetesRuleType) } - rewriteTargetRule := fmt.Sprintf("ReplacePathRegex: ^%s/(.*) %s/$1", pa.Path, strings.TrimRight(rewriteTarget, "/")) + rewriteTargetRule := fmt.Sprintf("ReplacePathRegex: ^%s(.*) %s$1", pa.Path, strings.TrimRight(rewriteTarget, "/")) rules = append(rules, rewriteTargetRule) } diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index df1cc5953..83d2d3e18 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -1410,7 +1410,7 @@ rateset: frontend("rewrite/api", passHostHeader(), routes( - route("/api", "PathPrefix:/api;ReplacePathRegex: ^/api/(.*) /$1"), + route("/api", "PathPrefix:/api;ReplacePathRegex: ^/api(.*) $1"), route("rewrite", "Host:rewrite")), ), frontend("error-pages/errorpages", From 967e4208da1d828e980e7262561ac0b3bdc25ac4 Mon Sep 17 00:00:00 2001 From: Rasmus Holm Date: Tue, 31 Jul 2018 10:28:02 +0200 Subject: [PATCH 04/18] Updating oxy dependency --- Gopkg.lock | 2 +- vendor/github.com/vulcand/oxy/forward/fwd.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Gopkg.lock b/Gopkg.lock index 598f11735..275659522 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1272,7 +1272,7 @@ "roundrobin", "utils" ] - revision = "a3ed5f65204f4ffccbb56d58cec466cdb7ab730b" + revision = "fb889e801a26e7e18ef36322ac72a07157f8cc1f" [[projects]] name = "github.com/vulcand/predicate" diff --git a/vendor/github.com/vulcand/oxy/forward/fwd.go b/vendor/github.com/vulcand/oxy/forward/fwd.go index 337d5eff5..cd057f59c 100644 --- a/vendor/github.com/vulcand/oxy/forward/fwd.go +++ b/vendor/github.com/vulcand/oxy/forward/fwd.go @@ -395,6 +395,15 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request, errClient := make(chan error, 1) errBackend := make(chan error, 1) replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error) { + + src.SetPingHandler(func(data string) error { + return dst.WriteMessage(websocket.PingMessage, []byte(data)) + }) + + src.SetPongHandler(func(data string) error { + return dst.WriteMessage(websocket.PongMessage, []byte(data)) + }) + for { msgType, msg, err := src.ReadMessage() From baf8d63cb4c5e59055f71102a0effb3547d49b97 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 31 Jul 2018 10:48:03 +0200 Subject: [PATCH 05/18] Improve TLS integration tests --- integration/consul_test.go | 4 +- integration/etcd3_test.go | 6 +- integration/etcd_test.go | 4 +- integration/https_test.go | 141 +++++++++---------------------------- 4 files changed, 41 insertions(+), 114 deletions(-) diff --git a/integration/consul_test.go b/integration/consul_test.go index 52cc4f13e..39a321db6 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -591,7 +591,7 @@ func (s *ConsulSuite) TestSNIDynamicTlsConfig(c *check.C) { req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com")) + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName)) c.Assert(err, checker.IsNil) // now we configure the second keypair in consul and the request for host "snitest.org" will use the second keypair @@ -613,6 +613,6 @@ func (s *ConsulSuite) TestSNIDynamicTlsConfig(c *check.C) { req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("snitest.org")) + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName)) c.Assert(err, checker.IsNil) } diff --git a/integration/etcd3_test.go b/integration/etcd3_test.go index d769e937d..ad877469b 100644 --- a/integration/etcd3_test.go +++ b/integration/etcd3_test.go @@ -538,7 +538,7 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) { req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com")) + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName)) c.Assert(err, checker.IsNil) // now we configure the second keypair in etcd and the request for host "snitest.org" will use the second keypair @@ -561,7 +561,7 @@ func (s *Etcd3Suite) TestSNIDynamicTlsConfig(c *check.C) { req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("snitest.org")) + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName)) c.Assert(err, checker.IsNil) } @@ -639,7 +639,7 @@ func (s *Etcd3Suite) TestDeleteSNIDynamicTlsConfig(c *check.C) { req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com")) + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName)) c.Assert(err, checker.IsNil) // now we delete the tls cert/key pairs,so the endpoint show use default cert/key pair diff --git a/integration/etcd_test.go b/integration/etcd_test.go index 9ae0e303f..80dcf0b40 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -554,7 +554,7 @@ func (s *EtcdSuite) TestSNIDynamicTlsConfig(c *check.C) { req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn("snitest.com")) + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName)) c.Assert(err, checker.IsNil) // now we configure the second keypair in etcd and the request for host "snitest.org" will use the second keypair @@ -577,6 +577,6 @@ func (s *EtcdSuite) TestSNIDynamicTlsConfig(c *check.C) { req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("snitest.org")) + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName)) c.Assert(err, checker.IsNil) } diff --git a/integration/https_test.go b/integration/https_test.go index 8729549af..32deef404 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -3,7 +3,6 @@ package integration import ( "bytes" "crypto/tls" - "fmt" "net" "net/http" "net/http/httptest" @@ -66,7 +65,7 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/providers", 500*time.Millisecond, try.BodyContains("Host:snitest.org")) + err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:snitest.org")) c.Assert(err, checker.IsNil) backend1 := startTestServer("9010", http.StatusNoContent) @@ -92,27 +91,23 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) { }, } - client := &http.Client{Transport: tr1} req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - req.Host = "snitest.com" - req.Header.Set("Host", "snitest.com") + req.Host = tr1.TLSClientConfig.ServerName + req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - resp, err := client.Do(req) - c.Assert(err, checker.IsNil) - // Expected a 204 (from backend1) - c.Assert(resp.StatusCode, checker.Equals, http.StatusNoContent) - client = &http.Client{Transport: tr2} + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNoContent)) + c.Assert(err, checker.IsNil) + req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - req.Host = "snitest.org" - req.Header.Set("Host", "snitest.org") + req.Host = tr2.TLSClientConfig.ServerName + req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - resp, err = client.Do(req) + + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent)) c.Assert(err, checker.IsNil) - // Expected a 205 (from backend2) - c.Assert(resp.StatusCode, checker.Equals, http.StatusResetContent) } // TestWithSNIStrictNotMatchedRequest involves a client sending a SNI hostname of @@ -561,28 +556,25 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) { err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent)) c.Assert(err, checker.IsNil) - client := &http.Client{Transport: tr1} req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - resp, err := client.Do(req) - c.Assert(err, checker.IsNil) - // snitest.org certificate must be used yet - c.Assert(resp.TLS.PeerCertificates[0].Subject.CommonName, check.Equals, tr1.TLSClientConfig.ServerName) - // Expected a 204 (from backend1) - c.Assert(resp.StatusCode, checker.Equals, http.StatusResetContent) - client = &http.Client{Transport: tr2} + // snitest.org certificate must be used yet && Expected a 204 (from backend1) + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent)) + c.Assert(err, checker.IsNil) + + req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) + c.Assert(err, checker.IsNil) req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) - resp, err = client.Do(req) + req.Header.Set("Accept", "*/*") + + // snitest.com certificate does not exist, default certificate has to be used && Expected a 205 (from backend2) + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("TRAEFIK DEFAULT CERT"), try.StatusCodeIs(http.StatusNoContent)) c.Assert(err, checker.IsNil) - // snitest.com certificate does not exist, default certificate has to be used - c.Assert(resp.TLS.PeerCertificates[0].Subject.CommonName, checker.Not(check.Equals), tr2.TLSClientConfig.ServerName) - // Expected a 205 (from backend2) - c.Assert(resp.StatusCode, checker.Equals, http.StatusNoContent) } // TestWithSNIDynamicConfigRouteWithChange involves a client sending HTTPS requests with @@ -633,57 +625,26 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) { err = try.GetRequest(backend2.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusResetContent)) c.Assert(err, checker.IsNil) + // Change certificates configuration file content + modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName, "https") + req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - client := &http.Client{Transport: tr1} + c.Assert(err, checker.IsNil) req.Host = tr1.TLSClientConfig.ServerName req.Header.Set("Host", tr1.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - // Change certificates configuration file content - modifyCertificateConfFileContent(c, tr1.TLSClientConfig.ServerName, dynamicConfFileName, "https") - var resp *http.Response - err = try.Do(30*time.Second, func() error { - resp, err = client.Do(req) - - // /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\ - req.Close = true - - if err != nil { - return err - } - - cn := resp.TLS.PeerCertificates[0].Subject.CommonName - if cn != tr1.TLSClientConfig.ServerName { - return fmt.Errorf("domain %s found in place of %s", cn, tr1.TLSClientConfig.ServerName) - } - - return nil - }) + err = try.RequestWithTransport(req, 30*time.Second, tr1, try.HasCn(tr1.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNotFound)) + c.Assert(err, checker.IsNil) + + req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) c.Assert(err, checker.IsNil) - c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound) - client = &http.Client{Transport: tr2} req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) + req.Header.Set("Accept", "*/*") - err = try.Do(60*time.Second, func() error { - resp, err = client.Do(req) - - // /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\ - req.Close = true - - if err != nil { - return err - } - - cn := resp.TLS.PeerCertificates[0].Subject.CommonName - if cn == tr2.TLSClientConfig.ServerName { - return fmt.Errorf("domain %s found in place of default one", tr2.TLSClientConfig.ServerName) - } - - return nil - }) + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("TRAEFIK DEFAULT CERT"), try.StatusCodeIs(http.StatusNotFound)) c.Assert(err, checker.IsNil) - c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound) } // TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion involves a client sending HTTPS requests with @@ -725,53 +686,19 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c c.Assert(err, checker.IsNil) req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil) - client := &http.Client{Transport: tr2} + c.Assert(err, checker.IsNil) req.Host = tr2.TLSClientConfig.ServerName req.Header.Set("Host", tr2.TLSClientConfig.ServerName) req.Header.Set("Accept", "*/*") - var resp *http.Response - err = try.Do(30*time.Second, func() error { - resp, err = client.Do(req) - - // /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\ - req.Close = true - - if err != nil { - return err - } - - cn := resp.TLS.PeerCertificates[0].Subject.CommonName - if cn != tr2.TLSClientConfig.ServerName { - return fmt.Errorf("domain %s found in place of %s", cn, tr2.TLSClientConfig.ServerName) - } - - return nil - }) + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn(tr2.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusResetContent)) c.Assert(err, checker.IsNil) - c.Assert(resp.StatusCode, checker.Equals, http.StatusResetContent) + // Change certificates configuration file content modifyCertificateConfFileContent(c, "", dynamicConfFileName, "https02") - err = try.Do(60*time.Second, func() error { - resp, err = client.Do(req) - - // /!\ If connection is not closed, SSLHandshake will only be done during the first trial /!\ - req.Close = true - - if err != nil { - return err - } - - cn := resp.TLS.PeerCertificates[0].Subject.CommonName - if cn == tr2.TLSClientConfig.ServerName { - return fmt.Errorf("domain %s found instead of the default one", tr2.TLSClientConfig.ServerName) - } - - return nil - }) + err = try.RequestWithTransport(req, 30*time.Second, tr2, try.HasCn("TRAEFIK DEFAULT CERT"), try.StatusCodeIs(http.StatusNotFound)) c.Assert(err, checker.IsNil) - c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound) } // modifyCertificateConfFileContent replaces the content of a HTTPS configuration file. From eea60b6baab4aa52313a9368c495c67ff6f3b931 Mon Sep 17 00:00:00 2001 From: Andrei Korigodski Date: Tue, 31 Jul 2018 11:58:03 +0300 Subject: [PATCH 06/18] Replace unrendered emoji --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index e466c3f6d..833c85f33 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,7 +44,7 @@ _(But if you'd rather configure some of your routes manually, Træfik supports t - Keeps access logs (JSON, CLF) - Fast - Exposes a Rest API -- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image +- Packaged as a single binary file (made with ❤️ with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image ## Supported Providers From 91cafd1752044e4edc0be170c29a485208bb4f99 Mon Sep 17 00:00:00 2001 From: Daniel Tomcej Date: Tue, 31 Jul 2018 03:28:03 -0600 Subject: [PATCH 07/18] Correct Entrypoint Redirect with Stripped or Added Path --- .../fixtures/https/https_redirect.toml | 36 +++++ integration/https_test.go | 131 ++++++++++++++++++ middlewares/addPrefix.go | 10 ++ middlewares/redirect/redirect.go | 25 ++++ middlewares/stripPrefix.go | 20 ++- middlewares/stripPrefixRegex.go | 4 + 6 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 integration/fixtures/https/https_redirect.toml 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) From 838dd8c19f005b9b854b6da4243e819d4bd351cd Mon Sep 17 00:00:00 2001 From: Lukas Prettenthaler Date: Tue, 31 Jul 2018 11:50:03 +0200 Subject: [PATCH 08/18] Fix acme account deletion without provider change --- acme/acme.go | 18 +++++++- provider/acme/provider.go | 18 +++++++- provider/acme/provider_test.go | 75 ++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/acme/acme.go b/acme/acme.go index 85599bc3e..492439d83 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -9,6 +9,7 @@ import ( fmtlog "log" "net" "net/http" + "net/url" "reflect" "strings" "time" @@ -183,7 +184,8 @@ func (a *ACME) leadershipListener(elected bool) error { account := object.(*Account) account.Init() // Reset Account values if caServer changed, thus registration URI can be updated - if account != nil && account.Registration != nil && !strings.HasPrefix(account.Registration.URI, a.CAServer) { + if account != nil && account.Registration != nil && !isAccountMatchingCaServer(account.Registration.URI, a.CAServer) { + log.Info("Account URI does not match the current CAServer. The account will be reset") account.reset() } @@ -230,6 +232,20 @@ func (a *ACME) leadershipListener(elected bool) error { return nil } +func isAccountMatchingCaServer(accountURI string, serverURI string) bool { + aru, err := url.Parse(accountURI) + if err != nil { + log.Infof("Unable to parse account.Registration URL : %v", err) + return false + } + cau, err := url.Parse(serverURI) + if err != nil { + log.Infof("Unable to parse CAServer URL : %v", err) + return false + } + return cau.Hostname() == aru.Hostname() +} + func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { domain := types.CanonicalDomain(clientHello.ServerName) account := a.store.Get().(*Account) diff --git a/provider/acme/provider.go b/provider/acme/provider.go index 7c6beb97a..57377cfd2 100644 --- a/provider/acme/provider.go +++ b/provider/acme/provider.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" fmtlog "log" + "net/url" "reflect" "strings" "sync" @@ -130,7 +131,8 @@ func (p *Provider) Init(_ types.Constraints) error { } // Reset Account if caServer changed, thus registration URI can be updated - if p.account != nil && p.account.Registration != nil && !strings.HasPrefix(p.account.Registration.URI, p.CAServer) { + if p.account != nil && p.account.Registration != nil && !isAccountMatchingCaServer(p.account.Registration.URI, p.CAServer) { + log.Info("Account URI does not match the current CAServer. The account will be reset") p.account = nil } @@ -142,6 +144,20 @@ func (p *Provider) Init(_ types.Constraints) error { return nil } +func isAccountMatchingCaServer(accountURI string, serverURI string) bool { + aru, err := url.Parse(accountURI) + if err != nil { + log.Infof("Unable to parse account.Registration URL : %v", err) + return false + } + cau, err := url.Parse(serverURI) + if err != nil { + log.Infof("Unable to parse CAServer URL : %v", err) + return false + } + return cau.Hostname() == aru.Hostname() +} + // Provide allows the file provider to provide configurations to traefik // using the given Configuration channel. func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { diff --git a/provider/acme/provider_test.go b/provider/acme/provider_test.go index 047f4058f..869b916fb 100644 --- a/provider/acme/provider_test.go +++ b/provider/acme/provider_test.go @@ -429,3 +429,78 @@ func TestDeleteUnnecessaryDomains(t *testing.T) { }) } } + +func TestIsAccountMatchingCaServer(t *testing.T) { + testCases := []struct { + desc string + accountURI string + serverURI string + expected bool + }{ + { + desc: "acme staging with matching account", + accountURI: "https://acme-staging-v02.api.letsencrypt.org/acme/acct/1234567", + serverURI: "https://acme-staging-v02.api.letsencrypt.org/acme/directory", + expected: true, + }, + { + desc: "acme production with matching account", + accountURI: "https://acme-v02.api.letsencrypt.org/acme/acct/1234567", + serverURI: "https://acme-v02.api.letsencrypt.org/acme/directory", + expected: true, + }, + { + desc: "http only acme with matching account", + accountURI: "http://acme.api.letsencrypt.org/acme/acct/1234567", + serverURI: "http://acme.api.letsencrypt.org/acme/directory", + expected: true, + }, + { + desc: "different subdomains for account and server", + accountURI: "https://test1.example.org/acme/acct/1234567", + serverURI: "https://test2.example.org/acme/directory", + expected: false, + }, + { + desc: "different domains for account and server", + accountURI: "https://test.example1.org/acme/acct/1234567", + serverURI: "https://test.example2.org/acme/directory", + expected: false, + }, + { + desc: "different tld for account and server", + accountURI: "https://test.example.com/acme/acct/1234567", + serverURI: "https://test.example.org/acme/directory", + expected: false, + }, + { + desc: "malformed account url", + accountURI: "//|\\/test.example.com/acme/acct/1234567", + serverURI: "https://test.example.com/acme/directory", + expected: false, + }, + { + desc: "malformed server url", + accountURI: "https://test.example.com/acme/acct/1234567", + serverURI: "//|\\/test.example.com/acme/directory", + expected: false, + }, + { + desc: "malformed server and account url", + accountURI: "//|\\/test.example.com/acme/acct/1234567", + serverURI: "//|\\/test.example.com/acme/directory", + expected: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + result := isAccountMatchingCaServer(test.accountURI, test.serverURI) + + assert.Equal(t, test.expected, result) + }) + } +} From 402f7011d4fa1aceace7680cbffa38e2f32e22d8 Mon Sep 17 00:00:00 2001 From: NicoMen Date: Tue, 31 Jul 2018 12:32:04 +0200 Subject: [PATCH 09/18] Fix ACME certificate for wildcard and root domains --- provider/acme/provider.go | 82 ++++++++++++++++++++++++++++++++-- provider/acme/provider_test.go | 58 ++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 4 deletions(-) diff --git a/provider/acme/provider.go b/provider/acme/provider.go index 57377cfd2..4a3ccdec2 100644 --- a/provider/acme/provider.go +++ b/provider/acme/provider.go @@ -13,6 +13,7 @@ import ( "time" "github.com/BurntSushi/ty/fun" + "github.com/cenk/backoff" "github.com/containous/flaeg" "github.com/containous/traefik/log" "github.com/containous/traefik/rules" @@ -74,6 +75,8 @@ type Certificate struct { type DNSChallenge struct { Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."` DelayBeforeCheck flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` + preCheckTimeout time.Duration + preCheckInterval time.Duration } // HTTPChallenge contains HTTP challenge Configuration @@ -262,6 +265,16 @@ func (p *Provider) getClient() (*acme.Client, error) { if err != nil { return nil, err } + + // Same default values than LEGO + p.DNSChallenge.preCheckTimeout = 60 * time.Second + p.DNSChallenge.preCheckInterval = 2 * time.Second + + // Set the precheck timeout into the DNSChallenge provider + if challengeProviderTimeout, ok := provider.(acme.ChallengeProviderTimeout); ok { + p.DNSChallenge.preCheckTimeout, p.DNSChallenge.preCheckInterval = challengeProviderTimeout.Timeout() + } + } else if p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0 { log.Debug("Using HTTP Challenge provider.") @@ -361,13 +374,20 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati return nil, fmt.Errorf("cannot get ACME client %v", err) } + var certificate *acme.CertificateResource bundle := true - - certificate, err := client.ObtainCertificate(uncheckedDomains, bundle, nil, OSCPMustStaple) - if err != nil { - return nil, fmt.Errorf("cannot obtain certificates: %+v", err) + if p.useCertificateWithRetry(uncheckedDomains) { + certificate, err = obtainCertificateWithRetry(domains, client, p.DNSChallenge.preCheckTimeout, p.DNSChallenge.preCheckInterval, bundle) + } else { + certificate, err = client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple) } + if err != nil { + return nil, fmt.Errorf("unable to generate a certificate for the domains %v: %v", uncheckedDomains, err) + } + if certificate == nil { + return nil, fmt.Errorf("domains %v do not generate a certificate", uncheckedDomains) + } if len(certificate.Certificate) == 0 || len(certificate.PrivateKey) == 0 { return nil, fmt.Errorf("domains %v generate certificate with no value: %v", uncheckedDomains, certificate) } @@ -384,6 +404,60 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati return certificate, nil } +func (p *Provider) useCertificateWithRetry(domains []string) bool { + // Check if we can use the retry mechanism only if we use the DNS Challenge and if is there are at least 2 domains to check + if p.DNSChallenge != nil && len(domains) > 1 { + rootDomain := "" + for _, searchWildcardDomain := range domains { + // Search a wildcard domain if not already found + if len(rootDomain) == 0 && strings.HasPrefix(searchWildcardDomain, "*.") { + rootDomain = strings.TrimPrefix(searchWildcardDomain, "*.") + if len(rootDomain) > 0 { + // Look for a root domain which matches the wildcard domain + for _, searchRootDomain := range domains { + if rootDomain == searchRootDomain { + // If the domains list contains a wildcard domain and its root domain, we can use the retry mechanism to obtain the certificate + return true + } + } + } + // There is only one wildcard domain in the slice, if its root domain has not been found, the retry mechanism does not have to be used + return false + } + } + } + + return false +} + +func obtainCertificateWithRetry(domains []string, client *acme.Client, timeout, interval time.Duration, bundle bool) (*acme.CertificateResource, error) { + var certificate *acme.CertificateResource + var err error + + operation := func() error { + certificate, err = client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple) + return err + } + + notify := func(err error, time time.Duration) { + log.Errorf("Error obtaining certificate retrying in %s", time) + } + + // Define a retry backOff to let LEGO tries twice to obtain a certificate for both wildcard and root domain + ebo := backoff.NewExponentialBackOff() + ebo.MaxElapsedTime = 2 * timeout + ebo.MaxInterval = interval + rbo := backoff.WithMaxRetries(ebo, 2) + + err = backoff.RetryNotify(safe.OperationWithRecover(operation), rbo, notify) + if err != nil { + log.Errorf("Error obtaining certificate: %v", err) + return nil, err + } + + return certificate, nil +} + func dnsOverrideDelay(delay flaeg.Duration) error { if delay == 0 { return nil diff --git a/provider/acme/provider_test.go b/provider/acme/provider_test.go index 869b916fb..abbe34a0f 100644 --- a/provider/acme/provider_test.go +++ b/provider/acme/provider_test.go @@ -504,3 +504,61 @@ func TestIsAccountMatchingCaServer(t *testing.T) { }) } } + +func TestUseBackOffToObtainCertificate(t *testing.T) { + testCases := []struct { + desc string + domains []string + dnsChallenge *DNSChallenge + expectedResponse bool + }{ + { + desc: "only one single domain", + domains: []string{"acme.wtf"}, + dnsChallenge: &DNSChallenge{}, + expectedResponse: false, + }, + { + desc: "only one wildcard domain", + domains: []string{"*.acme.wtf"}, + dnsChallenge: &DNSChallenge{}, + expectedResponse: false, + }, + { + desc: "wildcard domain with no root domain", + domains: []string{"*.acme.wtf", "foo.acme.wtf", "bar.acme.wtf", "foo.bar"}, + dnsChallenge: &DNSChallenge{}, + expectedResponse: false, + }, + { + desc: "wildcard and root domain", + domains: []string{"*.acme.wtf", "foo.acme.wtf", "bar.acme.wtf", "acme.wtf"}, + dnsChallenge: &DNSChallenge{}, + expectedResponse: true, + }, + { + desc: "wildcard and root domain but no DNS challenge", + domains: []string{"*.acme.wtf", "acme.wtf"}, + dnsChallenge: nil, + expectedResponse: false, + }, + { + desc: "two wildcard domains (must never happen)", + domains: []string{"*.acme.wtf", "*.bar.foo"}, + dnsChallenge: nil, + expectedResponse: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + acmeProvider := Provider{Configuration: &Configuration{DNSChallenge: test.dnsChallenge}} + + actualResponse := acmeProvider.useCertificateWithRetry(test.domains) + assert.Equal(t, test.expectedResponse, actualResponse, "unexpected response to use backOff") + }) + } +} From 09b489a614920678a510a955f17d5c5cd8ec04bd Mon Sep 17 00:00:00 2001 From: Manuel Zapf Date: Tue, 31 Jul 2018 17:12:03 +0200 Subject: [PATCH 10/18] Add possibility to set a protocol --- docs/configuration/backends/kubernetes.md | 3 +- provider/kubernetes/annotations.go | 1 + provider/kubernetes/kubernetes.go | 12 +++ provider/kubernetes/kubernetes_test.go | 95 +++++++++++++++++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index b574942d1..32fa18971 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -159,7 +159,8 @@ The following general annotations are applicable on the Ingress object: | `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access (6). | | `ingress.kubernetes.io/whitelist-x-forwarded-for: "true"` | Use `X-Forwarded-For` header as valid source of IP for the white list. | | `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for `/` to the defined path. (4) | -| `traefik.ingress.kubernetes.io/service-weights: ` | Set ingress backend weights specified as percentage or decimal numbers in YAML. (5) | +| `traefik.ingress.kubernetes.io/service-weights: ` | Set ingress backend weights specified as percentage or decimal numbers in YAML. (5) +| `ingress.kubernetes.io/protocol: ` | Set the protocol Traefik will use to communicate with pods. <1> `traefik.ingress.kubernetes.io/error-pages` example: diff --git a/provider/kubernetes/annotations.go b/provider/kubernetes/annotations.go index 4b716e395..e85211b2f 100644 --- a/provider/kubernetes/annotations.go +++ b/provider/kubernetes/annotations.go @@ -63,6 +63,7 @@ const ( annotationKubernetesPublicKey = "ingress.kubernetes.io/public-key" annotationKubernetesReferrerPolicy = "ingress.kubernetes.io/referrer-policy" annotationKubernetesIsDevelopment = "ingress.kubernetes.io/is-development" + annotationKubernetesProtocol = "ingress.kubernetes.io/protocol" ) // TODO [breaking] remove label support diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 628e4ac44..d7a4a3109 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -43,6 +43,8 @@ const ( traefikDefaultIngressClass = "traefik" defaultBackendName = "global-default-backend" defaultFrontendName = "global-default-frontend" + allowedProtocolHTTPS = "https" + allowedProtocolH2C = "h2c" ) // IngressEndpoint holds the endpoint information for the Kubernetes provider @@ -312,6 +314,16 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) protocol = "https" } + protocol = getStringValue(i.Annotations, annotationKubernetesProtocol, protocol) + switch protocol { + case allowedProtocolHTTPS: + case allowedProtocolH2C: + case label.DefaultProtocol: + default: + log.Errorf("Invalid protocol %s/%s specified for Ingress %s - skipping", annotationKubernetesProtocol, i.Namespace, i.Name) + continue + } + if service.Spec.Type == "ExternalName" { url := protocol + "://" + service.Spec.ExternalName if port.Port != 443 && port.Port != 80 { diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index 83d2d3e18..52d8c0e15 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -1224,6 +1224,41 @@ rateset: iPaths(onePath(iPath("/customheaders"), iBackend("service1", intstr.FromInt(80))))), ), ), + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesProtocol, "h2c"), + iRules( + iRule( + iHost("protocol"), + iPaths(onePath(iPath("/valid"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesProtocol, "foobar"), + iRules( + iRule( + iHost("protocol"), + iPaths(onePath(iPath("/notvalid"), iBackend("service1", intstr.FromInt(80))))), + ), + ), + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesProtocol, "http"), + iRules( + iRule( + iHost("protocol"), + iPaths(onePath(iPath("/missmatch"), iBackend("serviceHTTPS", intstr.FromInt(443))))), + ), + ), + buildIngress( + iNamespace("testing"), + iRules( + iRule( + iHost("protocol"), + iPaths(onePath(iPath("/noAnnotation"), iBackend("serviceHTTPS", intstr.FromInt(443))))), + ), + ), } services := []*corev1.Service{ @@ -1245,6 +1280,16 @@ rateset: clusterIP("10.0.0.2"), sPorts(sPort(802, ""))), ), + buildService( + sName("serviceHTTPS"), + sNamespace("testing"), + sUID("2"), + sSpec( + clusterIP("10.0.0.3"), + sType("ExternalName"), + sExternalName("example.com"), + sPorts(sPort(443, "https"))), + ), } secrets := []*corev1.Secret{ @@ -1352,6 +1397,28 @@ rateset: servers(), lbMethod("wrr"), ), + backend("protocol/valid", + servers( + server("h2c://example.com", weight(1)), + server("h2c://example.com", weight(1))), + lbMethod("wrr"), + ), + backend("protocol/notvalid", + servers(), + lbMethod("wrr"), + ), + backend("protocol/missmatch", + servers( + server("http://example.com", weight(1)), + server("http://example.com", weight(1))), + lbMethod("wrr"), + ), + backend("protocol/noAnnotation", + servers( + server("https://example.com", weight(1)), + server("https://example.com", weight(1))), + lbMethod("wrr"), + ), ), frontends( frontend("foo/bar", @@ -1483,6 +1550,34 @@ rateset: route("root", "Host:root"), ), ), + frontend("protocol/valid", + passHostHeader(), + routes( + route("/valid", "PathPrefix:/valid"), + route("protocol", "Host:protocol"), + ), + ), + frontend("protocol/notvalid", + passHostHeader(), + routes( + route("/notvalid", "PathPrefix:/notvalid"), + route("protocol", "Host:protocol"), + ), + ), + frontend("protocol/missmatch", + passHostHeader(), + routes( + route("/missmatch", "PathPrefix:/missmatch"), + route("protocol", "Host:protocol"), + ), + ), + frontend("protocol/noAnnotation", + passHostHeader(), + routes( + route("/noAnnotation", "PathPrefix:/noAnnotation"), + route("protocol", "Host:protocol"), + ), + ), ), ) From fb4717d5f380b0a8ea043553ba266641237c6b3c Mon Sep 17 00:00:00 2001 From: Teo Stocco Date: Tue, 31 Jul 2018 17:58:04 +0200 Subject: [PATCH 11/18] Add traefik prefix to k8s annotations --- docs/user-guide/kubernetes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user-guide/kubernetes.md b/docs/user-guide/kubernetes.md index d6f0eb42f..c194af91e 100644 --- a/docs/user-guide/kubernetes.md +++ b/docs/user-guide/kubernetes.md @@ -453,8 +453,8 @@ kubectl create secret generic mysecret --from-file auth --namespace=monitoring C. Attach the following annotations to the Ingress object: -- `ingress.kubernetes.io/auth-type: "basic"` -- `ingress.kubernetes.io/auth-secret: "mysecret"` +- `traefik.ingress.kubernetes.io/auth-type: "basic"` +- `traefik.ingress.kubernetes.io/auth-secret: "mysecret"` They specify basic authentication and reference the Secret `mysecret` containing the credentials. @@ -468,8 +468,8 @@ metadata: namespace: monitoring annotations: kubernetes.io/ingress.class: traefik - ingress.kubernetes.io/auth-type: "basic" - ingress.kubernetes.io/auth-secret: "mysecret" + traefik.ingress.kubernetes.io/auth-type: "basic" + traefik.ingress.kubernetes.io/auth-secret: "mysecret" spec: rules: - host: dashboard.prometheus.example.com From ed0c7d9c49604619e43edcbfa76957262694e949 Mon Sep 17 00:00:00 2001 From: Damien Duportal Date: Tue, 31 Jul 2018 18:56:03 +0200 Subject: [PATCH 12/18] H2C: Remove buggy line in init to make verbose switch working --- h2c/h2c.go | 1 - 1 file changed, 1 deletion(-) diff --git a/h2c/h2c.go b/h2c/h2c.go index 70e144d2d..c5961620a 100644 --- a/h2c/h2c.go +++ b/h2c/h2c.go @@ -38,7 +38,6 @@ func init() { if strings.Contains(e, "http2debug=1") || strings.Contains(e, "http2debug=2") { http2VerboseLogs = true } - http2VerboseLogs = true } // Server implements net.Handler and enables h2c. Users who want h2c just need From 4d79c2a6d2c1f2d5ff5600c657a25402cb68e8a4 Mon Sep 17 00:00:00 2001 From: Alex Antonov Date: Tue, 31 Jul 2018 17:16:03 -0500 Subject: [PATCH 13/18] Added support for Trace name truncation for traces --- cmd/configuration.go | 11 +- docs/configuration/tracing.md | 21 ++++ middlewares/tracing/entrypoint.go | 23 ++++- middlewares/tracing/entrypoint_test.go | 69 +++++++++++++ middlewares/tracing/forwarder.go | 19 +++- middlewares/tracing/forwarder_test.go | 93 +++++++++++++++++ middlewares/tracing/tracing.go | 49 +++++++-- middlewares/tracing/tracing_test.go | 133 +++++++++++++++++++++++++ 8 files changed, 404 insertions(+), 14 deletions(-) create mode 100644 middlewares/tracing/entrypoint_test.go create mode 100644 middlewares/tracing/forwarder_test.go create mode 100644 middlewares/tracing/tracing_test.go diff --git a/cmd/configuration.go b/cmd/configuration.go index c2fd0675a..acd2083b5 100644 --- a/cmd/configuration.go +++ b/cmd/configuration.go @@ -9,6 +9,7 @@ import ( "github.com/containous/traefik/configuration" "github.com/containous/traefik/middlewares/accesslog" "github.com/containous/traefik/middlewares/tracing" + "github.com/containous/traefik/middlewares/tracing/datadog" "github.com/containous/traefik/middlewares/tracing/jaeger" "github.com/containous/traefik/middlewares/tracing/zipkin" "github.com/containous/traefik/ping" @@ -218,8 +219,9 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { // default Tracing defaultTracing := tracing.Tracing{ - Backend: "jaeger", - ServiceName: "traefik", + Backend: "jaeger", + ServiceName: "traefik", + SpanNameLimit: 0, Jaeger: &jaeger.Config{ SamplingServerURL: "http://localhost:5778/sampling", SamplingType: "const", @@ -232,6 +234,11 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { ID128Bit: true, Debug: false, }, + DataDog: &datadog.Config{ + LocalAgentHostPort: "localhost:8126", + GlobalTag: "", + Debug: false, + }, } // default LifeCycle diff --git a/docs/configuration/tracing.md b/docs/configuration/tracing.md index f4ce62a4f..abfdadd99 100644 --- a/docs/configuration/tracing.md +++ b/docs/configuration/tracing.md @@ -22,6 +22,13 @@ Træfik supports two backends: Jaeger and Zipkin. # Default: "traefik" # serviceName = "traefik" + + # Span name limit allows for name truncation in case of very long Frontend/Backend names + # This can prevent certain tracing providers to drop traces that exceed their length limits + # + # Default: 0 - no truncation will occur + # + spanNameLimit = 0 [tracing.jaeger] # Sampling Server URL is the address of jaeger-agent's HTTP sampling server @@ -72,6 +79,13 @@ Træfik supports two backends: Jaeger and Zipkin. # Default: "traefik" # serviceName = "traefik" + + # Span name limit allows for name truncation in case of very long Frontend/Backend names + # This can prevent certain tracing providers to drop traces that exceed their length limits + # + # Default: 0 - no truncation will occur + # + spanNameLimit = 150 [tracing.zipkin] # Zipking HTTP endpoint used to send data @@ -115,6 +129,13 @@ Træfik supports two backends: Jaeger and Zipkin. # Default: "traefik" # serviceName = "traefik" + + # Span name limit allows for name truncation in case of very long Frontend/Backend names + # This can prevent certain tracing providers to drop traces that exceed their length limits + # + # Default: 0 - no truncation will occur + # + spanNameLimit = 100 [tracing.datadog] # Local Agent Host Port instructs reporter to send spans to datadog-tracing-agent at this address diff --git a/middlewares/tracing/entrypoint.go b/middlewares/tracing/entrypoint.go index eb2f6aca6..a35bcea92 100644 --- a/middlewares/tracing/entrypoint.go +++ b/middlewares/tracing/entrypoint.go @@ -22,12 +22,10 @@ func (t *Tracing) NewEntryPoint(name string) negroni.Handler { } func (e *entryPointMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - opNameFunc := func(r *http.Request) string { - return fmt.Sprintf("Entrypoint %s %s", e.entryPoint, r.Host) - } + opNameFunc := generateEntryPointSpanName ctx, _ := e.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header)) - span := e.StartSpan(opNameFunc(r), ext.RPCServerOption(ctx)) + span := e.StartSpan(opNameFunc(r, e.entryPoint, e.SpanNameLimit), ext.RPCServerOption(ctx)) ext.Component.Set(span, e.ServiceName) LogRequest(span, r) ext.SpanKindRPCServer.Set(span) @@ -40,3 +38,20 @@ func (e *entryPointMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, LogResponseCode(span, recorder.Status()) span.Finish() } + +// generateEntryPointSpanName will return a Span name of an appropriate lenth based on the 'spanLimit' argument. If needed, it will be truncated, but will not be less than 24 characters. +func generateEntryPointSpanName(r *http.Request, entryPoint string, spanLimit int) string { + name := fmt.Sprintf("Entrypoint %s %s", entryPoint, r.Host) + + if spanLimit > 0 && len(name) > spanLimit { + if spanLimit < EntryPointMaxLengthNumber { + log.Warnf("SpanNameLimit is set to be less than required static number of characters, defaulting to %d + 3", EntryPointMaxLengthNumber) + spanLimit = EntryPointMaxLengthNumber + 3 + } + hash := computeHash(name) + limit := (spanLimit - EntryPointMaxLengthNumber) / 2 + name = fmt.Sprintf("Entrypoint %s %s %s", truncateString(entryPoint, limit), truncateString(r.Host, limit), hash) + } + + return name +} diff --git a/middlewares/tracing/entrypoint_test.go b/middlewares/tracing/entrypoint_test.go new file mode 100644 index 000000000..f00b74ce1 --- /dev/null +++ b/middlewares/tracing/entrypoint_test.go @@ -0,0 +1,69 @@ +package tracing + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/opentracing/opentracing-go/ext" + "github.com/stretchr/testify/assert" +) + +func TestEntryPointMiddlewareServeHTTP(t *testing.T) { + expectedTags := map[string]interface{}{ + "span.kind": ext.SpanKindRPCServerEnum, + "http.method": "GET", + "component": "", + "http.url": "http://www.test.com", + "http.host": "www.test.com", + } + testCases := []struct { + desc string + entryPoint string + tracing *Tracing + expectedTags map[string]interface{} + expectedName string + }{ + { + desc: "no truncation test", + entryPoint: "test", + tracing: &Tracing{ + SpanNameLimit: 0, + tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, + }, + expectedTags: expectedTags, + expectedName: "Entrypoint test www.test.com", + }, { + desc: "basic test", + entryPoint: "test", + tracing: &Tracing{ + SpanNameLimit: 25, + tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}}, + }, + expectedTags: expectedTags, + expectedName: "Entrypoint te... ww... 39b97e58", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + e := &entryPointMiddleware{ + entryPoint: test.entryPoint, + Tracing: test.tracing, + } + + next := func(http.ResponseWriter, *http.Request) { + span := test.tracing.tracer.(*MockTracer).Span + + actual := span.Tags + assert.Equal(t, test.expectedTags, actual) + assert.Equal(t, test.expectedName, span.OpName) + } + + e.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "http://www.test.com", nil), next) + }) + } +} diff --git a/middlewares/tracing/forwarder.go b/middlewares/tracing/forwarder.go index aa80486c5..fd4f243bf 100644 --- a/middlewares/tracing/forwarder.go +++ b/middlewares/tracing/forwarder.go @@ -23,7 +23,7 @@ func (t *Tracing) NewForwarderMiddleware(frontend, backend string) negroni.Handl Tracing: t, frontend: frontend, backend: backend, - opName: fmt.Sprintf("forward %s/%s", frontend, backend), + opName: generateForwardSpanName(frontend, backend, t.SpanNameLimit), } } @@ -44,3 +44,20 @@ func (f *forwarderMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, LogResponseCode(span, recorder.Status()) } + +// generateForwardSpanName will return a Span name of an appropriate lenth based on the 'spanLimit' argument. If needed, it will be truncated, but will not be less than 21 characters +func generateForwardSpanName(frontend, backend string, spanLimit int) string { + name := fmt.Sprintf("forward %s/%s", frontend, backend) + + if spanLimit > 0 && len(name) > spanLimit { + if spanLimit < ForwardMaxLengthNumber { + log.Warnf("SpanNameLimit is set to be less than required static number of characters, defaulting to %d + 3", ForwardMaxLengthNumber) + spanLimit = ForwardMaxLengthNumber + 3 + } + hash := computeHash(name) + limit := (spanLimit - ForwardMaxLengthNumber) / 2 + name = fmt.Sprintf("forward %s/%s/%s", truncateString(frontend, limit), truncateString(backend, limit), hash) + } + + return name +} diff --git a/middlewares/tracing/forwarder_test.go b/middlewares/tracing/forwarder_test.go new file mode 100644 index 000000000..00c90c293 --- /dev/null +++ b/middlewares/tracing/forwarder_test.go @@ -0,0 +1,93 @@ +package tracing + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTracingNewForwarderMiddleware(t *testing.T) { + testCases := []struct { + desc string + tracer *Tracing + frontend string + backend string + expected *forwarderMiddleware + }{ + { + desc: "Simple Forward Tracer without truncation and hashing", + tracer: &Tracing{ + SpanNameLimit: 101, + }, + frontend: "some-service.domain.tld", + backend: "some-service.domain.tld", + expected: &forwarderMiddleware{ + Tracing: &Tracing{ + SpanNameLimit: 101, + }, + frontend: "some-service.domain.tld", + backend: "some-service.domain.tld", + opName: "forward some-service.domain.tld/some-service.domain.tld", + }, + }, { + desc: "Simple Forward Tracer with truncation and hashing", + tracer: &Tracing{ + SpanNameLimit: 101, + }, + frontend: "some-service-100.slug.namespace.environment.domain.tld", + backend: "some-service-100.slug.namespace.environment.domain.tld", + expected: &forwarderMiddleware{ + Tracing: &Tracing{ + SpanNameLimit: 101, + }, + frontend: "some-service-100.slug.namespace.environment.domain.tld", + backend: "some-service-100.slug.namespace.environment.domain.tld", + opName: "forward some-service-100.slug.namespace.enviro.../some-service-100.slug.namespace.enviro.../bc4a0d48", + }, + }, + { + desc: "Exactly 101 chars", + tracer: &Tracing{ + SpanNameLimit: 101, + }, + frontend: "some-service1.namespace.environment.domain.tld", + backend: "some-service1.namespace.environment.domain.tld", + expected: &forwarderMiddleware{ + Tracing: &Tracing{ + SpanNameLimit: 101, + }, + frontend: "some-service1.namespace.environment.domain.tld", + backend: "some-service1.namespace.environment.domain.tld", + opName: "forward some-service1.namespace.environment.domain.tld/some-service1.namespace.environment.domain.tld", + }, + }, + { + desc: "More than 101 chars", + tracer: &Tracing{ + SpanNameLimit: 101, + }, + frontend: "some-service1.frontend.namespace.environment.domain.tld", + backend: "some-service1.backend.namespace.environment.domain.tld", + expected: &forwarderMiddleware{ + Tracing: &Tracing{ + SpanNameLimit: 101, + }, + frontend: "some-service1.frontend.namespace.environment.domain.tld", + backend: "some-service1.backend.namespace.environment.domain.tld", + opName: "forward some-service1.frontend.namespace.envir.../some-service1.backend.namespace.enviro.../fa49dd23", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := test.tracer.NewForwarderMiddleware(test.frontend, test.backend) + + assert.Equal(t, test.expected, actual) + assert.True(t, len(test.expected.opName) <= test.tracer.SpanNameLimit) + }) + } +} diff --git a/middlewares/tracing/tracing.go b/middlewares/tracing/tracing.go index ac7f5b7a9..a18c81806 100644 --- a/middlewares/tracing/tracing.go +++ b/middlewares/tracing/tracing.go @@ -1,6 +1,7 @@ package tracing import ( + "crypto/sha256" "fmt" "io" "net/http" @@ -13,13 +14,23 @@ import ( "github.com/opentracing/opentracing-go/ext" ) +// ForwardMaxLengthNumber defines the number of static characters in the Forwarding Span Trace name : 8 chars for 'forward ' + 8 chars for hash + 2 chars for '_'. +const ForwardMaxLengthNumber = 18 + +// EntryPointMaxLengthNumber defines the number of static characters in the Entrypoint Span Trace name : 11 chars for 'Entrypoint ' + 8 chars for hash + 2 chars for '_'. +const EntryPointMaxLengthNumber = 21 + +// TraceNameHashLength defines the number of characters to use from the head of the generated hash. +const TraceNameHashLength = 8 + // Tracing middleware type Tracing struct { - Backend string `description:"Selects the tracking backend ('jaeger','zipkin', 'datadog')." export:"true"` - ServiceName string `description:"Set the name for this service" export:"true"` - Jaeger *jaeger.Config `description:"Settings for jaeger"` - Zipkin *zipkin.Config `description:"Settings for zipkin"` - DataDog *datadog.Config `description:"Settings for DataDog"` + Backend string `description:"Selects the tracking backend ('jaeger','zipkin', 'datadog')." export:"true"` + ServiceName string `description:"Set the name for this service" export:"true"` + SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)" export:"true"` + Jaeger *jaeger.Config `description:"Settings for jaeger"` + Zipkin *zipkin.Config `description:"Settings for zipkin"` + DataDog *datadog.Config `description:"Settings for DataDog"` tracer opentracing.Tracer closer io.Closer @@ -147,16 +158,40 @@ func SetError(r *http.Request) { } } -// SetErrorAndDebugLog flags the span associated with this request as in error and create a debug log +// SetErrorAndDebugLog flags the span associated with this request as in error and create a debug log. func SetErrorAndDebugLog(r *http.Request, format string, args ...interface{}) { SetError(r) log.Debugf(format, args...) LogEventf(r, format, args...) } -// SetErrorAndWarnLog flags the span associated with this request as in error and create a debug log +// SetErrorAndWarnLog flags the span associated with this request as in error and create a debug log. func SetErrorAndWarnLog(r *http.Request, format string, args ...interface{}) { SetError(r) log.Warnf(format, args...) LogEventf(r, format, args...) } + +// truncateString reduces the length of the 'str' argument to 'num' - 3 and adds a '...' suffix to the tail. +func truncateString(str string, num int) string { + text := str + if len(str) > num { + if num > 3 { + num -= 3 + } + text = str[0:num] + "..." + } + return text +} + +// computeHash returns the first TraceNameHashLength character of the sha256 hash for 'name' argument. +func computeHash(name string) string { + data := []byte(name) + hash := sha256.New() + if _, err := hash.Write(data); err != nil { + // Impossible case + log.Errorf("Fail to create Span name hash for %s: %v", name, err) + } + + return fmt.Sprintf("%x", hash.Sum(nil))[:TraceNameHashLength] +} diff --git a/middlewares/tracing/tracing_test.go b/middlewares/tracing/tracing_test.go new file mode 100644 index 000000000..d4a631312 --- /dev/null +++ b/middlewares/tracing/tracing_test.go @@ -0,0 +1,133 @@ +package tracing + +import ( + "testing" + + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/log" + "github.com/stretchr/testify/assert" +) + +type MockTracer struct { + Span *MockSpan +} + +type MockSpan struct { + OpName string + Tags map[string]interface{} +} + +type MockSpanContext struct { +} + +// MockSpanContext: +func (n MockSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} + +// MockSpan: +func (n MockSpan) Context() opentracing.SpanContext { return MockSpanContext{} } +func (n MockSpan) SetBaggageItem(key, val string) opentracing.Span { + return MockSpan{Tags: make(map[string]interface{})} +} +func (n MockSpan) BaggageItem(key string) string { return "" } +func (n MockSpan) SetTag(key string, value interface{}) opentracing.Span { + n.Tags[key] = value + return n +} +func (n MockSpan) LogFields(fields ...log.Field) {} +func (n MockSpan) LogKV(keyVals ...interface{}) {} +func (n MockSpan) Finish() {} +func (n MockSpan) FinishWithOptions(opts opentracing.FinishOptions) {} +func (n MockSpan) SetOperationName(operationName string) opentracing.Span { return n } +func (n MockSpan) Tracer() opentracing.Tracer { return MockTracer{} } +func (n MockSpan) LogEvent(event string) {} +func (n MockSpan) LogEventWithPayload(event string, payload interface{}) {} +func (n MockSpan) Log(data opentracing.LogData) {} +func (n MockSpan) Reset() { + n.Tags = make(map[string]interface{}) +} + +// StartSpan belongs to the Tracer interface. +func (n MockTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span { + n.Span.OpName = operationName + return n.Span +} + +// Inject belongs to the Tracer interface. +func (n MockTracer) Inject(sp opentracing.SpanContext, format interface{}, carrier interface{}) error { + return nil +} + +// Extract belongs to the Tracer interface. +func (n MockTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) { + return nil, opentracing.ErrSpanContextNotFound +} + +func TestTruncateString(t *testing.T) { + testCases := []struct { + desc string + text string + limit int + expected string + }{ + { + desc: "short text less than limit 10", + text: "short", + limit: 10, + expected: "short", + }, + { + desc: "basic truncate with limit 10", + text: "some very long pice of text", + limit: 10, + expected: "some ve...", + }, + { + desc: "truncate long FQDN to 39 chars", + text: "some-service-100.slug.namespace.environment.domain.tld", + limit: 39, + expected: "some-service-100.slug.namespace.envi...", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := truncateString(test.text, test.limit) + + assert.Equal(t, test.expected, actual) + assert.True(t, len(actual) <= test.limit) + }) + } +} + +func TestComputeHash(t *testing.T) { + testCases := []struct { + desc string + text string + expected string + }{ + { + desc: "hashing", + text: "some very long pice of text", + expected: "0258ea1c", + }, + { + desc: "short text less than limit 10", + text: "short", + expected: "f9b0078b", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := computeHash(test.text) + + assert.Equal(t, test.expected, actual) + }) + } +} From 8c733abef38735d802e19cc998ce6eee2fc0bd2b Mon Sep 17 00:00:00 2001 From: Andrei Korigodski Date: Wed, 1 Aug 2018 12:22:03 +0300 Subject: [PATCH 14/18] Fix style in examples/quickstart --- examples/quickstart/docker-compose.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/quickstart/docker-compose.yml b/examples/quickstart/docker-compose.yml index f31f5d408..bd1c8a202 100644 --- a/examples/quickstart/docker-compose.yml +++ b/examples/quickstart/docker-compose.yml @@ -1,18 +1,18 @@ version: '3' services: - #The reverse proxy service (Træfik) + # The reverse proxy service (Træfik) reverse-proxy: - image: traefik #The official Traefik docker image - command: --api --docker #Enables the web UI and tells Træfik to listen to docker + image: traefik # The official Traefik docker image + command: --api --docker # Enables the web UI and tells Træfik to listen to docker ports: - - "80:80" #The HTTP port - - "8080:8080" #The Web UI (enabled by --api) + - "80:80" # The HTTP port + - "8080:8080" # The Web UI (enabled by --api) volumes: - - /var/run/docker.sock:/var/run/docker.sock #So that Traefik can listen to the Docker events + - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events - #A container that exposes a simple API + # A container that exposes a simple API whoami: - image: emilevauge/whoami #A container that exposes an API to show it's IP address + image: emilevauge/whoami # A container that exposes an API to show it's IP address labels: - - "traefik.frontend.rule=Host:whoami.docker.localhost" \ No newline at end of file + - "traefik.frontend.rule=Host:whoami.docker.localhost" From 7732e2307ef22961b127dcffafcc04d50e5cc3b0 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 1 Aug 2018 13:36:03 +0200 Subject: [PATCH 15/18] Fix missing tracing backend in documentation --- docs/configuration/tracing.md | 2 +- middlewares/tracing/entrypoint_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/configuration/tracing.md b/docs/configuration/tracing.md index abfdadd99..0c2b62e99 100644 --- a/docs/configuration/tracing.md +++ b/docs/configuration/tracing.md @@ -4,7 +4,7 @@ Tracing system allows developers to visualize call flows in there infrastructure We use [OpenTracing](http://opentracing.io). It is an open standard designed for distributed tracing. -Træfik supports two backends: Jaeger and Zipkin. +Træfik supports three tracing backends: Jaeger, Zipkin and DataDog. ## Jaeger diff --git a/middlewares/tracing/entrypoint_test.go b/middlewares/tracing/entrypoint_test.go index f00b74ce1..865bcfc09 100644 --- a/middlewares/tracing/entrypoint_test.go +++ b/middlewares/tracing/entrypoint_test.go @@ -17,6 +17,7 @@ func TestEntryPointMiddlewareServeHTTP(t *testing.T) { "http.url": "http://www.test.com", "http.host": "www.test.com", } + testCases := []struct { desc string entryPoint string From cfe2f1a1e6c17408fc28d213ce20465575f655ff Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 1 Aug 2018 15:28:03 +0200 Subject: [PATCH 16/18] Prepare release 1.7.0-rc3 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c62480e59..0dcc6c8c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Change Log +## [v1.7.0-rc3](https://github.com/containous/traefik/tree/v1.7.0-rc3) (2018-08-01) +[All Commits](https://github.com/containous/traefik/compare/v1.7.0-rc2...v1.7.0-rc3) + +**Enhancements:** +- **[consul,etcd,tls]** Improve TLS integration tests ([#3679](https://github.com/containous/traefik/pull/3679) by [mmatur](https://github.com/mmatur)) +- **[k8s]** Add possibility to set a protocol ([#3648](https://github.com/containous/traefik/pull/3648) by [SantoDE](https://github.com/SantoDE)) + +**Bug fixes:** +- **[acme]** Fix acme account deletion without provider change ([#3664](https://github.com/containous/traefik/pull/3664) by [zyclonite](https://github.com/zyclonite)) +- **[acme]** Update lego ([#3659](https://github.com/containous/traefik/pull/3659) by [mmatur](https://github.com/mmatur)) +- **[acme]** Fix ACME certificate for wildcard and root domains ([#3675](https://github.com/containous/traefik/pull/3675) by [nmengin](https://github.com/nmengin)) +- **[api]** Remove TLS in API ([#3665](https://github.com/containous/traefik/pull/3665) by [mmatur](https://github.com/mmatur)) +- **[docker]** Uses both binded HostIP and HostPort when useBindPortIP=true ([#3638](https://github.com/containous/traefik/pull/3638) by [geraldcroes](https://github.com/geraldcroes)) +- **[k8s]** Fix Rewrite-target regex ([#3699](https://github.com/containous/traefik/pull/3699) by [dtomcej](https://github.com/dtomcej)) +- **[middleware]** Correct Entrypoint Redirect with Stripped or Added Path ([#3631](https://github.com/containous/traefik/pull/3631) by [dtomcej](https://github.com/dtomcej)) +- **[tracing]** Added default configuration for DataDog APM Tracer ([#3655](https://github.com/containous/traefik/pull/3655) by [aantono](https://github.com/aantono)) +- **[tracing]** Added support for Trace name truncation for traces ([#3689](https://github.com/containous/traefik/pull/3689) by [aantono](https://github.com/aantono)) +- **[websocket]** Handle shutdown of Hijacked connections ([#3636](https://github.com/containous/traefik/pull/3636) by [Juliens](https://github.com/Juliens)) +- H2C: Remove buggy line in init to make verbose switch working ([#3701](https://github.com/containous/traefik/pull/3701) by [dduportal](https://github.com/dduportal)) +- Updating oxy dependency ([#3700](https://github.com/containous/traefik/pull/3700) by [crholm](https://github.com/crholm)) + +**Documentation:** +- **[acme]** Update Namecheap status ([#3604](https://github.com/containous/traefik/pull/3604) by [stoinov](https://github.com/stoinov)) +- **[acme]** Fix some DNS provider link ([#3639](https://github.com/containous/traefik/pull/3639) by [ldez](https://github.com/ldez)) +- **[docker]** Fix style in examples/quickstart ([#3705](https://github.com/containous/traefik/pull/3705) by [korigod](https://github.com/korigod)) +- **[k8s]** Add traefik prefix to k8s annotations ([#3682](https://github.com/containous/traefik/pull/3682) by [zifeo](https://github.com/zifeo)) +- **[middleware,tracing]** Fix missing tracing backend in documentation ([#3706](https://github.com/containous/traefik/pull/3706) by [mmatur](https://github.com/mmatur)) +- Replace unrendered emoji ([#3690](https://github.com/containous/traefik/pull/3690) by [korigod](https://github.com/korigod)) + ## [v1.7.0-rc2](https://github.com/containous/traefik/tree/v1.7.0-rc2) (2018-07-17) [All Commits](https://github.com/containous/traefik/compare/v1.7.0-rc1...v1.7.0-rc2) From d62f7e20827ff9340a1224fe69dae64843e540b4 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 1 Aug 2018 16:56:04 +0200 Subject: [PATCH 17/18] Use official Pebble Image. --- integration/acme_test.go | 4 ++-- integration/resources/compose/{peddle.yml => pebble.yml} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename integration/resources/compose/{peddle.yml => pebble.yml} (73%) diff --git a/integration/acme_test.go b/integration/acme_test.go index 6f10f81e8..906f48cf2 100644 --- a/integration/acme_test.go +++ b/integration/acme_test.go @@ -79,7 +79,7 @@ func setupPebbleRootCA() (*http.Transport, error) { } func (s *AcmeSuite) SetUpSuite(c *check.C) { - s.createComposeProject(c, "peddle") + s.createComposeProject(c, "pebble") s.composeProject.Start(c) s.fakeDNSServer = startFakeDNSServer() @@ -91,7 +91,7 @@ func (s *AcmeSuite) SetUpSuite(c *check.C) { c.Fatal(err) } - // wait for peddle + // wait for pebble req := testhelpers.MustNewRequest(http.MethodGet, s.getAcmeURL(), nil) client := &http.Client{ diff --git a/integration/resources/compose/peddle.yml b/integration/resources/compose/pebble.yml similarity index 73% rename from integration/resources/compose/peddle.yml rename to integration/resources/compose/pebble.yml index f1b053f34..2828c66ef 100644 --- a/integration/resources/compose/peddle.yml +++ b/integration/resources/compose/pebble.yml @@ -1,6 +1,6 @@ pebble: - image: ldez/pebble - command: --dnsserver ${DOCKER_HOST_IP}:5053 + image: letsencrypt/pebble:2018-07-27 + command: pebble --dnsserver ${DOCKER_HOST_IP}:5053 ports: - 14000:14000 environment: From b4ac3d4470c3a98b007a1109013b29206aae8631 Mon Sep 17 00:00:00 2001 From: Daniel Tomcej Date: Thu, 2 Aug 2018 00:14:02 -0600 Subject: [PATCH 18/18] Improve Connection Limit Kubernetes Documentation --- docs/configuration/backends/kubernetes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index 32fa18971..e8896c50d 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -255,7 +255,7 @@ The following annotations are applicable on the Service object associated with a | `traefik.ingress.kubernetes.io/affinity: "true"` | Enable backend sticky sessions. | | `traefik.ingress.kubernetes.io/circuit-breaker-expression: ` | Set the circuit breaker expression for the backend. | | `traefik.ingress.kubernetes.io/load-balancer-method: drr` | Override the default `wrr` load balancer algorithm. | -| `traefik.ingress.kubernetes.io/max-conn-amount: 10` | Set a maximum number of connections to the backend.
Must be used in conjunction with the below label to take effect. | +| `traefik.ingress.kubernetes.io/max-conn-amount: "10"` | Set a maximum number of connections to the backend.
Must be used in conjunction with the below label to take effect. | | `traefik.ingress.kubernetes.io/max-conn-extractor-func: client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by.
Must be used in conjunction with the above label to take effect. | | `traefik.ingress.kubernetes.io/session-cookie-name: ` | Manually set the cookie name for sticky sessions. |