From e1e86763e3353cf6d3df59d7d5c39ad24eeb5ebe Mon Sep 17 00:00:00 2001 From: Tom Moulard Date: Mon, 2 Jan 2023 17:00:05 +0100 Subject: [PATCH 1/4] Prevents superfluous WriteHeader call in the error middleware Co-authored-by: LandryBe --- integration/error_pages_test.go | 33 ++++++++++++++++++- integration/fixtures/error_pages/simple.toml | 2 +- pkg/middlewares/customerrors/custom_errors.go | 9 +++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/integration/error_pages_test.go b/integration/error_pages_test.go index 436b2cc96..a9bd1c7a1 100644 --- a/integration/error_pages_test.go +++ b/integration/error_pages_test.go @@ -2,6 +2,7 @@ package integration import ( "net/http" + "net/http/httptest" "os" "time" @@ -29,7 +30,7 @@ func (s *ErrorPagesSuite) TestSimpleConfiguration(c *check.C) { file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct { Server1 string Server2 string - }{s.BackendIP, s.ErrorPageIP}) + }{"http://" + s.BackendIP + ":80", s.ErrorPageIP}) defer os.Remove(file) cmd, display := s.traefikCmd(withConfigFile(file)) @@ -67,3 +68,33 @@ func (s *ErrorPagesSuite) TestErrorPage(c *check.C) { err = try.Request(frontendReq, 2*time.Second, try.BodyContains("An error occurred.")) c.Assert(err, checker.IsNil) } + +func (s *ErrorPagesSuite) TestErrorPageFlush(c *check.C) { + srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.Header().Add("Transfer-Encoding", "chunked") + rw.WriteHeader(http.StatusInternalServerError) + _, _ = rw.Write([]byte("KO")) + })) + + file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct { + Server1 string + Server2 string + }{srv.URL, s.ErrorPageIP}) + defer os.Remove(file) + + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer s.killCmd(cmd) + + frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil) + c.Assert(err, checker.IsNil) + frontendReq.Host = "test.local" + + err = try.Request(frontendReq, 2*time.Second, + try.BodyContains("An error occurred."), + try.HasHeaderValue("Content-Type", "text/html", true), + ) + c.Assert(err, checker.IsNil) +} diff --git a/integration/fixtures/error_pages/simple.toml b/integration/fixtures/error_pages/simple.toml index a734098b9..298b66a42 100644 --- a/integration/fixtures/error_pages/simple.toml +++ b/integration/fixtures/error_pages/simple.toml @@ -30,7 +30,7 @@ [http.services.service1.loadBalancer] passHostHeader = true [[http.services.service1.loadBalancer.servers]] - url = "http://{{.Server1}}:80" + url = "{{.Server1}}" [http.services.error.loadBalancer] [[http.services.error.loadBalancer.servers]] diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go index 58b36d115..b24a6b04c 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -233,6 +233,15 @@ func (cc *codeCatcher) Flush() { // Otherwise, cc.code is actually a 200 here. cc.WriteHeader(cc.code) + // We don't care about the contents of the response, + // since we want to serve the ones from the error page, + // so we just don't flush. + // (e.g., To prevent superfluous WriteHeader on request with a + // `Transfert-Encoding: chunked` header). + if cc.caughtFilteredCode { + return + } + if flusher, ok := cc.responseWriter.(http.Flusher); ok { flusher.Flush() } From 0861c47e54f3386e0734f9ab0f61376ff4acf2d3 Mon Sep 17 00:00:00 2001 From: Witold Duranek Date: Tue, 3 Jan 2023 16:16:05 +0100 Subject: [PATCH 2/4] fix no rate limiting if average is 0 --- pkg/middlewares/ratelimiter/rate_limiter.go | 12 +++++------- pkg/middlewares/ratelimiter/rate_limiter_test.go | 13 +++++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/pkg/middlewares/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go index 312595bfc..e06679787 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter.go +++ b/pkg/middlewares/ratelimiter/rate_limiter.go @@ -79,10 +79,12 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name period = time.Second } - // if config.Average == 0, in that case, - // the value of maxDelay does not matter since the reservation will (buggily) give us a delay of 0 anyway. + // Initialized at rate.Inf to enforce no rate limiting when config.Average == 0 + rtl := float64(rate.Inf) + // No need to set any particular value for maxDelay as the reservation's delay + // will be <= 0 in the Inf case (i.e. the average == 0 case). var maxDelay time.Duration - var rtl float64 + if config.Average > 0 { rtl = float64(config.Average*int64(time.Second)) / float64(period) // maxDelay does not scale well for rates below 1, @@ -153,10 +155,6 @@ func (rl *rateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - // time/rate is bugged, since a rate.Limiter with a 0 Limit not only allows a Reservation to take place, - // but also gives a 0 delay below (because of a division by zero, followed by a multiplication that flips into the negatives), - // regardless of the current load. - // However, for now we take advantage of this behavior to provide the no-limit ratelimiter when config.Average is 0. res := bucket.Reserve() if !res.OK() { http.Error(w, "No bursty traffic allowed", http.StatusTooManyRequests) diff --git a/pkg/middlewares/ratelimiter/rate_limiter_test.go b/pkg/middlewares/ratelimiter/rate_limiter_test.go index ac73fc2ec..670561774 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter_test.go +++ b/pkg/middlewares/ratelimiter/rate_limiter_test.go @@ -15,6 +15,7 @@ import ( "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/testhelpers" "github.com/vulcand/oxy/v2/utils" + "golang.org/x/time/rate" ) func TestNewRateLimiter(t *testing.T) { @@ -25,7 +26,16 @@ func TestNewRateLimiter(t *testing.T) { expectedSourceIP string requestHeader string expectedError string + expectedRTL rate.Limit }{ + { + desc: "no ratelimit on Average == 0", + config: dynamic.RateLimit{ + Average: 0, + Burst: 10, + }, + expectedRTL: rate.Inf, + }, { desc: "maxDelay computation", config: dynamic.RateLimit{ @@ -120,6 +130,9 @@ func TestNewRateLimiter(t *testing.T) { assert.NoError(t, err) assert.Equal(t, test.requestHeader, hd) } + if test.expectedRTL != 0 { + assert.Equal(t, test.expectedRTL, rtl.rate) + } }) } } From c9e9e8dee21fd9c8f84929667cab3e6376739ea9 Mon Sep 17 00:00:00 2001 From: hcooper Date: Wed, 4 Jan 2023 03:10:05 -0800 Subject: [PATCH 3/4] Further Let's Encrypt ratelimit warnings --- docs/content/https/acme.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index d3fc20997..7d942b1a2 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -11,7 +11,11 @@ Automatic HTTPS You can configure Traefik to use an ACME provider (like Let's Encrypt) for automatic certificate generation. !!! warning "Let's Encrypt and Rate Limiting" - Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits). + Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits). These last up to __one week__, and can not be overridden. + + When running Traefik in a container this file should be persisted across restarts. + If Traefik requests new certificates each time it starts up, a crash-looping container can quickly reach Let's Encrypt's ratelimits. + To configure where certificates are stored, please take a look at the [storage](#storage) configuration. Use Let's Encrypt staging server with the [`caServer`](#caserver) configuration option when experimenting to avoid hitting this limit too fast. From e82976e001448f3304b4c6f3608a44c7617283f4 Mon Sep 17 00:00:00 2001 From: sven Date: Mon, 9 Jan 2023 16:07:09 +0100 Subject: [PATCH 4/4] Add info admonition about routing to k8 services --- docs/content/providers/docker.md | 2 +- docs/content/routing/providers/kubernetes-ingress.md | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index b34752eec..155803608 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -95,7 +95,7 @@ and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/). ## Routing Configuration When using Docker as a [provider](./overview.md), -Traefik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file) to retrieve its routing configuration. +Traefik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#-set-metadata-on-container--l---label---label-file) to retrieve its routing configuration. See the list of labels in the dedicated [routing](../routing/providers/docker.md) section. diff --git a/docs/content/routing/providers/kubernetes-ingress.md b/docs/content/routing/providers/kubernetes-ingress.md index c7c8ab914..a82f602eb 100644 --- a/docs/content/routing/providers/kubernetes-ingress.md +++ b/docs/content/routing/providers/kubernetes-ingress.md @@ -888,14 +888,20 @@ TLS certificates can be managed in Secrets objects. ### Communication Between Traefik and Pods +!!! info "It is not possible to route requests directly to [Kubernetes services](https://kubernetes.io/docs/concepts/services-networking/service/ "Link to Kubernetes service docs")" + + You can use an `ExternalName` service to forward requests to the Kubernetes service through DNS. + + For doing so, you have to [allow external name services](https://doc.traefik.io/traefik/providers/kubernetes-ingress/#allowexternalnameservices "Link to docs about allowing external name services"). + Traefik automatically requests endpoint information based on the service provided in the ingress spec. Although Traefik will connect directly to the endpoints (pods), it still checks the service port to see if TLS communication is required. -There are 3 ways to configure Traefik to use https to communicate with pods: +There are 3 ways to configure Traefik to use HTTPS to communicate with pods: 1. If the service port defined in the ingress spec is `443` (note that you can still use `targetPort` to use a different port on your pod). -1. If the service port defined in the ingress spec has a name that starts with https (such as `https-api`, `https-web` or just `https`). +1. If the service port defined in the ingress spec has a name that starts with `https` (such as `https-api`, `https-web` or just `https`). 1. If the service spec includes the annotation `traefik.ingress.kubernetes.io/service.serversscheme: https`. If either of those configuration options exist, then the backend communication protocol is assumed to be TLS,