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

@ -166,5 +166,12 @@ type HealthCheck struct {
// 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"`
FollowRedirects *bool `json:"followRedirects" toml:"followRedirects" yaml:"followRedirects"`
Headers map[string]string `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` 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

@ -52,6 +52,7 @@ type Options struct {
Scheme string Scheme string
Path string Path string
Port int Port int
FollowRedirects bool
Transport http.RoundTripper Transport http.RoundTripper
Interval time.Duration Interval time.Duration
Timeout time.Duration Timeout time.Duration
@ -59,7 +60,7 @@ type Options struct {
} }
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",
@ -614,6 +615,7 @@ func Test_buildConfiguration(t *testing.T) {
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,6 +262,11 @@ 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,
@ -271,6 +276,7 @@ func buildHealthCheckOptions(ctx context.Context, lb healthcheck.Balancer, backe
LB: lb, LB: lb,
Hostname: hc.Hostname, Hostname: hc.Hostname,
Headers: hc.Headers, Headers: hc.Headers,
FollowRedirects: followRedirects,
} }
} }