Do not follow redirects for the health check URLs

This commit is contained in:
Robin Müller 2020-02-26 17:28:04 +01:00 committed by GitHub
parent 8c271cf40c
commit 18d90ecd96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 145 additions and 27 deletions

View file

@ -142,6 +142,7 @@
- "traefik.http.services.service01.loadbalancer.healthcheck.port=42" - "traefik.http.services.service01.loadbalancer.healthcheck.port=42"
- "traefik.http.services.service01.loadbalancer.healthcheck.scheme=foobar" - "traefik.http.services.service01.loadbalancer.healthcheck.scheme=foobar"
- "traefik.http.services.service01.loadbalancer.healthcheck.timeout=foobar" - "traefik.http.services.service01.loadbalancer.healthcheck.timeout=foobar"
- "traefik.http.services.service01.loadbalancer.healthcheck.followredirects=true"
- "traefik.http.services.service01.loadbalancer.passhostheader=true" - "traefik.http.services.service01.loadbalancer.passhostheader=true"
- "traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval=foobar" - "traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval=foobar"
- "traefik.http.services.service01.loadbalancer.sticky=true" - "traefik.http.services.service01.loadbalancer.sticky=true"

View file

@ -56,6 +56,7 @@
interval = "foobar" interval = "foobar"
timeout = "foobar" timeout = "foobar"
hostname = "foobar" hostname = "foobar"
followRedirects = true
[http.services.Service01.loadBalancer.healthCheck.headers] [http.services.Service01.loadBalancer.healthCheck.headers]
name0 = "foobar" name0 = "foobar"
name1 = "foobar" name1 = "foobar"

View file

@ -62,6 +62,7 @@ http:
interval: foobar interval: foobar
timeout: foobar timeout: foobar
hostname: foobar hostname: foobar
followRedirects: true
headers: headers:
name0: foobar name0: foobar
name1: foobar name1: foobar

View file

@ -156,6 +156,7 @@
| `traefik/http/routers/Router1/tls/domains/1/sans/0` | `foobar` | | `traefik/http/routers/Router1/tls/domains/1/sans/0` | `foobar` |
| `traefik/http/routers/Router1/tls/domains/1/sans/1` | `foobar` | | `traefik/http/routers/Router1/tls/domains/1/sans/1` | `foobar` |
| `traefik/http/routers/Router1/tls/options` | `foobar` | | `traefik/http/routers/Router1/tls/options` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/followRedirects` | `true` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0` | `foobar` | | `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name1` | `foobar` | | `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name1` | `foobar` |
| `traefik/http/services/Service01/loadBalancer/healthCheck/hostname` | `foobar` | | `traefik/http/services/Service01/loadBalancer/healthCheck/hostname` | `foobar` |

View file

@ -140,6 +140,7 @@
"traefik.http.services.service01.loadbalancer.healthcheck.port": "42", "traefik.http.services.service01.loadbalancer.healthcheck.port": "42",
"traefik.http.services.service01.loadbalancer.healthcheck.scheme": "foobar", "traefik.http.services.service01.loadbalancer.healthcheck.scheme": "foobar",
"traefik.http.services.service01.loadbalancer.healthcheck.timeout": "foobar", "traefik.http.services.service01.loadbalancer.healthcheck.timeout": "foobar",
"traefik.http.services.service01.loadbalancer.healthcheck.followredirects": "true",
"traefik.http.services.service01.loadbalancer.passhostheader": "true", "traefik.http.services.service01.loadbalancer.passhostheader": "true",
"traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval": "foobar", "traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval": "foobar",
"traefik.http.services.service01.loadbalancer.sticky.cookie.httponly": "true", "traefik.http.services.service01.loadbalancer.sticky.cookie.httponly": "true",

View file

@ -193,6 +193,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass
traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10 traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.followredirects`"
See [health check](../services/index.md#health-check) for more information.
```yaml
traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true
```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`" ??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information. See [sticky sessions](../services/index.md#sticky-sessions) for more information.

View file

@ -326,6 +326,14 @@ you'd add the label `traefik.http.services.<name-of-your-choice>.loadbalancer.pa
- "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10" - "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10"
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.followredirects`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`" ??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information. See [sticky sessions](../services/index.md#sticky-sessions) for more information.

View file

@ -224,6 +224,14 @@ For example, to change the passHostHeader behavior, you'd add the label `"traefi
"traefik.http.services.myservice.loadbalancer.healthcheck.timeout": "10" "traefik.http.services.myservice.loadbalancer.healthcheck.timeout": "10"
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.followredirects`"
See [health check](../services/index.md#health-check) for more information.
```json
"traefik.http.services.myservice.loadbalancer.healthcheck.followredirects": "true"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`" ??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information. See [sticky sessions](../services/index.md#sticky-sessions) for more information.

View file

@ -230,6 +230,14 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa
- "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10" - "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10"
``` ```
??? info "`traefik.http.services.<service_name>.loadbalancer.healthcheck.followredirects`"
See [health check](../services/index.md#health-check) for more information.
```yaml
- "traefik.http.services.myservice.loadbalancer.healthcheck.followredirects=true"
```
??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`" ??? info "`traefik.http.services.<service_name>.loadbalancer.sticky`"
See [sticky sessions](../services/index.md#sticky-sessions) for more information. See [sticky sessions](../services/index.md#sticky-sessions) for more information.

View file

@ -240,6 +240,7 @@ Below are the available options for the health check mechanism:
- `interval` defines the frequency of the health check calls. - `interval` defines the frequency of the health check calls.
- `timeout` defines the maximum duration Traefik will wait for a health check request before considering the server failed (unhealthy). - `timeout` defines the maximum duration Traefik will wait for a health check request before considering the server failed (unhealthy).
- `headers` defines custom headers to be sent to the health check endpoint. - `headers` defines custom headers to be sent to the health check endpoint.
- `followRedirects` defines whether redirects should be followed during the health check calls (default: true).
!!! info "Interval & Timeout Format" !!! info "Interval & Timeout Format"

View file

@ -164,7 +164,14 @@ type HealthCheck struct {
// FIXME change string to types.Duration // FIXME change string to types.Duration
Interval string `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty"` Interval string `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty"`
// FIXME change string to types.Duration // FIXME change string to types.Duration
Timeout string `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty"` Timeout string `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty"`
Hostname string `json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty"` Hostname string `json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty"`
Headers map[string]string `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` FollowRedirects *bool `json:"followRedirects" toml:"followRedirects" yaml:"followRedirects"`
Headers map[string]string `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"`
}
// SetDefaults Default values for a HealthCheck.
func (h *HealthCheck) SetDefaults() {
fr := true
h.FollowRedirects = &fr
} }

View file

@ -143,6 +143,7 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.http.services.Service0.loadbalancer.healthcheck.port": "42", "traefik.http.services.Service0.loadbalancer.healthcheck.port": "42",
"traefik.http.services.Service0.loadbalancer.healthcheck.scheme": "foobar", "traefik.http.services.Service0.loadbalancer.healthcheck.scheme": "foobar",
"traefik.http.services.Service0.loadbalancer.healthcheck.timeout": "foobar", "traefik.http.services.Service0.loadbalancer.healthcheck.timeout": "foobar",
"traefik.http.services.Service0.loadbalancer.healthcheck.followredirects": "true",
"traefik.http.services.Service0.loadbalancer.passhostheader": "true", "traefik.http.services.Service0.loadbalancer.passhostheader": "true",
"traefik.http.services.Service0.loadbalancer.responseforwarding.flushinterval": "foobar", "traefik.http.services.Service0.loadbalancer.responseforwarding.flushinterval": "foobar",
"traefik.http.services.Service0.loadbalancer.server.scheme": "foobar", "traefik.http.services.Service0.loadbalancer.server.scheme": "foobar",
@ -157,6 +158,7 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.http.services.Service1.loadbalancer.healthcheck.port": "42", "traefik.http.services.Service1.loadbalancer.healthcheck.port": "42",
"traefik.http.services.Service1.loadbalancer.healthcheck.scheme": "foobar", "traefik.http.services.Service1.loadbalancer.healthcheck.scheme": "foobar",
"traefik.http.services.Service1.loadbalancer.healthcheck.timeout": "foobar", "traefik.http.services.Service1.loadbalancer.healthcheck.timeout": "foobar",
"traefik.http.services.Service1.loadbalancer.healthcheck.followredirects": "true",
"traefik.http.services.Service1.loadbalancer.passhostheader": "true", "traefik.http.services.Service1.loadbalancer.passhostheader": "true",
"traefik.http.services.Service1.loadbalancer.responseforwarding.flushinterval": "foobar", "traefik.http.services.Service1.loadbalancer.responseforwarding.flushinterval": "foobar",
"traefik.http.services.Service1.loadbalancer.server.scheme": "foobar", "traefik.http.services.Service1.loadbalancer.server.scheme": "foobar",
@ -595,6 +597,7 @@ func TestDecodeConfiguration(t *testing.T) {
"name0": "foobar", "name0": "foobar",
"name1": "foobar", "name1": "foobar",
}, },
FollowRedirects: func(v bool) *bool { return &v }(true),
}, },
PassHostHeader: func(v bool) *bool { return &v }(true), PassHostHeader: func(v bool) *bool { return &v }(true),
ResponseForwarding: &dynamic.ResponseForwarding{ ResponseForwarding: &dynamic.ResponseForwarding{
@ -621,6 +624,7 @@ func TestDecodeConfiguration(t *testing.T) {
"name0": "foobar", "name0": "foobar",
"name1": "foobar", "name1": "foobar",
}, },
FollowRedirects: func(v bool) *bool { return &v }(true),
}, },
PassHostHeader: func(v bool) *bool { return &v }(true), PassHostHeader: func(v bool) *bool { return &v }(true),
ResponseForwarding: &dynamic.ResponseForwarding{ ResponseForwarding: &dynamic.ResponseForwarding{

View file

@ -47,19 +47,20 @@ type metricsRegistry interface {
// Options are the public health check options. // Options are the public health check options.
type Options struct { type Options struct {
Headers map[string]string Headers map[string]string
Hostname string Hostname string
Scheme string Scheme string
Path string Path string
Port int Port int
Transport http.RoundTripper FollowRedirects bool
Interval time.Duration Transport http.RoundTripper
Timeout time.Duration Interval time.Duration
LB Balancer Timeout time.Duration
LB Balancer
} }
func (opt Options) String() string { func (opt Options) String() string {
return fmt.Sprintf("[Hostname: %s Headers: %v Path: %s Port: %d Interval: %s Timeout: %s]", opt.Hostname, opt.Headers, opt.Path, opt.Port, opt.Interval, opt.Timeout) return fmt.Sprintf("[Hostname: %s Headers: %v Path: %s Port: %d Interval: %s Timeout: %s FollowRedirects: %v]", opt.Hostname, opt.Headers, opt.Path, opt.Port, opt.Interval, opt.Timeout, opt.FollowRedirects)
} }
type backendURL struct { type backendURL struct {
@ -239,6 +240,12 @@ func checkHealth(serverURL *url.URL, backend *BackendConfig) error {
Transport: backend.Options.Transport, Transport: backend.Options.Transport,
} }
if !backend.FollowRedirects {
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("HTTP request failed: %s", err) return fmt.Errorf("HTTP request failed: %s", err)

View file

@ -469,3 +469,57 @@ func TestLBStatusUpdater(t *testing.T) {
break break
} }
} }
func TestNotFollowingRedirects(t *testing.T) {
redirectServerCalled := false
redirectTestServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
redirectServerCalled = true
}))
defer redirectTestServer.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add("location", redirectTestServer.URL)
rw.WriteHeader(http.StatusSeeOther)
cancel()
}))
defer server.Close()
lb := &testLoadBalancer{
RWMutex: &sync.RWMutex{},
servers: []*url.URL{testhelpers.MustParseURL(server.URL)},
}
backend := NewBackendConfig(Options{
Path: "/path",
Interval: healthCheckInterval,
Timeout: healthCheckTimeout,
LB: lb,
FollowRedirects: false,
}, "backendName")
check := HealthCheck{
Backends: make(map[string]*BackendConfig),
metrics: testhelpers.NewCollectingHealthCheckMetrics(),
}
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
check.execute(ctx, backend)
wg.Done()
}()
timeout := time.Duration(int(healthCheckInterval) + 500)
select {
case <-time.After(timeout):
t.Fatal("test did not complete in time")
case <-ctx.Done():
wg.Wait()
}
assert.False(t, redirectServerCalled, "HTTP redirect must not be followed")
}

View file

@ -47,6 +47,7 @@ func Test_buildConfiguration(t *testing.T) {
"traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0": "foobar", "traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/headers/name1": "foobar", "traefik/http/services/Service01/loadBalancer/healthCheck/headers/name1": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/scheme": "foobar", "traefik/http/services/Service01/loadBalancer/healthCheck/scheme": "foobar",
"traefik/http/services/Service01/loadBalancer/healthCheck/followredirects": "true",
"traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval": "foobar", "traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval": "foobar",
"traefik/http/services/Service01/loadBalancer/passHostHeader": "true", "traefik/http/services/Service01/loadBalancer/passHostHeader": "true",
"traefik/http/services/Service01/loadBalancer/sticky/cookie/name": "foobar", "traefik/http/services/Service01/loadBalancer/sticky/cookie/name": "foobar",
@ -608,12 +609,13 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
HealthCheck: &dynamic.HealthCheck{ HealthCheck: &dynamic.HealthCheck{
Scheme: "foobar", Scheme: "foobar",
Path: "foobar", Path: "foobar",
Port: 42, Port: 42,
Interval: "foobar", Interval: "foobar",
Timeout: "foobar", Timeout: "foobar",
Hostname: "foobar", Hostname: "foobar",
FollowRedirects: func(v bool) *bool { return &v }(true),
Headers: map[string]string{ Headers: map[string]string{
"name0": "foobar", "name0": "foobar",
"name1": "foobar", "name1": "foobar",

View file

@ -262,15 +262,21 @@ func buildHealthCheckOptions(ctx context.Context, lb healthcheck.Balancer, backe
logger.Warnf("Health check timeout for backend '%s' should be lower than the health check interval. Interval set to timeout + 1 second (%s).", backend, interval) logger.Warnf("Health check timeout for backend '%s' should be lower than the health check interval. Interval set to timeout + 1 second (%s).", backend, interval)
} }
followRedirects := true
if hc.FollowRedirects != nil {
followRedirects = *hc.FollowRedirects
}
return &healthcheck.Options{ return &healthcheck.Options{
Scheme: hc.Scheme, Scheme: hc.Scheme,
Path: hc.Path, Path: hc.Path,
Port: hc.Port, Port: hc.Port,
Interval: interval, Interval: interval,
Timeout: timeout, Timeout: timeout,
LB: lb, LB: lb,
Hostname: hc.Hostname, Hostname: hc.Hostname,
Headers: hc.Headers, Headers: hc.Headers,
FollowRedirects: followRedirects,
} }
} }