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. diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index 8fd567356..6dc69cf30 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 5720889d8..45e01904a 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, 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 a60f7393d..9ff846f74 100644 --- a/integration/fixtures/error_pages/simple.toml +++ b/integration/fixtures/error_pages/simple.toml @@ -31,7 +31,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 55684f82e..eda8c4811 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -209,6 +209,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() } diff --git a/pkg/middlewares/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go index 303b20e8e..9b95ab176 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter.go +++ b/pkg/middlewares/ratelimiter/rate_limiter.go @@ -81,10 +81,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, @@ -155,10 +157,6 @@ func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *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(rw, "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) + } }) } }