Commit 33257de2 authored by Francis Lavoie's avatar Francis Lavoie Committed by Matt Holt

proxy: Fix #1574; health check now respects hostname when upstream Host header...

proxy: Fix #1574; health check now respects hostname when upstream Host header is configured (#1577)

* Implement adding Host header to health check

* Fix type problems

* Fix duplicate function, Replace args

* Add debugging

* Add debugging

* Add debugging

* Add debugging

* Attempt to set req.Host instead of the header

* Clean up debugging

* Fix missing newline

* Fix spelling

* Add test, refactoring

* Fix with gofmt

* Add error check on NewRequest
parent 8d1da68b
...@@ -31,7 +31,7 @@ var paths = map[string]map[string]string{ ...@@ -31,7 +31,7 @@ var paths = map[string]map[string]string{
func assertEqual(t *testing.T, expected, received string) { func assertEqual(t *testing.T, expected, received string) {
if expected != received { if expected != received {
t.Errorf("\tExpected: %s\n\t\t\tRecieved: %s", expected, received) t.Errorf("\tExpected: %s\n\t\t\tReceived: %s", expected, received)
} }
} }
......
...@@ -163,7 +163,7 @@ func TestReverseProxyMaxConnLimit(t *testing.T) { ...@@ -163,7 +163,7 @@ func TestReverseProxyMaxConnLimit(t *testing.T) {
proxy / `+backend.URL+` { proxy / `+backend.URL+` {
max_conns `+fmt.Sprint(MaxTestConns)+` max_conns `+fmt.Sprint(MaxTestConns)+`
} }
`))) `)), "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -1008,7 +1008,7 @@ func TestReverseProxyRetry(t *testing.T) { ...@@ -1008,7 +1008,7 @@ func TestReverseProxyRetry(t *testing.T) {
try_duration 5s try_duration 5s
try_interval 250ms try_interval 250ms
} }
`))) `)), "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -1057,7 +1057,7 @@ func TestReverseProxyLargeBody(t *testing.T) { ...@@ -1057,7 +1057,7 @@ func TestReverseProxyLargeBody(t *testing.T) {
})) }))
defer backend.Close() defer backend.Close()
su, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(`proxy / `+backend.URL))) su, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(`proxy / `+backend.URL)), "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
...@@ -14,7 +14,7 @@ func init() { ...@@ -14,7 +14,7 @@ func init() {
// setup configures a new Proxy middleware instance. // setup configures a new Proxy middleware instance.
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
upstreams, err := NewStaticUpstreams(c.Dispenser) upstreams, err := NewStaticUpstreams(c.Dispenser, httpserver.GetConfig(c).Host())
if err != nil { if err != nil {
return err return err
} }
......
...@@ -41,6 +41,7 @@ type staticUpstream struct { ...@@ -41,6 +41,7 @@ type staticUpstream struct {
Path string Path string
Interval time.Duration Interval time.Duration
Timeout time.Duration Timeout time.Duration
Host string
} }
WithoutPathPrefix string WithoutPathPrefix string
IgnoredSubPaths []string IgnoredSubPaths []string
...@@ -49,8 +50,10 @@ type staticUpstream struct { ...@@ -49,8 +50,10 @@ type staticUpstream struct {
} }
// NewStaticUpstreams parses the configuration input and sets up // NewStaticUpstreams parses the configuration input and sets up
// static upstreams for the proxy middleware. // static upstreams for the proxy middleware. The host string parameter,
func NewStaticUpstreams(c caddyfile.Dispenser) ([]Upstream, error) { // if not empty, is used for setting the upstream Host header for the
// health checks if the upstream header config requires it.
func NewStaticUpstreams(c caddyfile.Dispenser, host string) ([]Upstream, error) {
var upstreams []Upstream var upstreams []Upstream
for c.Next() { for c.Next() {
...@@ -118,6 +121,14 @@ func NewStaticUpstreams(c caddyfile.Dispenser) ([]Upstream, error) { ...@@ -118,6 +121,14 @@ func NewStaticUpstreams(c caddyfile.Dispenser) ([]Upstream, error) {
TLSClientConfig: &tls.Config{InsecureSkipVerify: upstream.insecureSkipVerify}, TLSClientConfig: &tls.Config{InsecureSkipVerify: upstream.insecureSkipVerify},
}, },
} }
// set up health check upstream host if we have one
if host != "" {
hostHeader := upstream.upstreamHeaders.Get("Host")
if strings.Contains(hostHeader, "{host}") {
upstream.HealthCheck.Host = strings.Replace(hostHeader, "{host}", host, -1)
}
}
upstream.wg.Add(1) upstream.wg.Add(1)
go func() { go func() {
defer upstream.wg.Done() defer upstream.wg.Done()
...@@ -371,12 +382,25 @@ func (u *staticUpstream) healthCheck() { ...@@ -371,12 +382,25 @@ func (u *staticUpstream) healthCheck() {
for _, host := range u.Hosts { for _, host := range u.Hosts {
hostURL := host.Name + u.HealthCheck.Path hostURL := host.Name + u.HealthCheck.Path
var unhealthy bool var unhealthy bool
if r, err := u.HealthCheck.Client.Get(hostURL); err == nil {
io.Copy(ioutil.Discard, r.Body) // set up request, needed to be able to modify headers
r.Body.Close() // possible errors are bad HTTP methods or un-parsable urls
unhealthy = r.StatusCode < 200 || r.StatusCode >= 400 req, err := http.NewRequest("GET", hostURL, nil)
} else { if err != nil {
unhealthy = true unhealthy = true
} else {
// set host for request going upstream
if u.HealthCheck.Host != "" {
req.Host = u.HealthCheck.Host
}
if r, err := u.HealthCheck.Client.Do(req); err == nil {
io.Copy(ioutil.Discard, r.Body)
r.Body.Close()
unhealthy = r.StatusCode < 200 || r.StatusCode >= 400
} else {
unhealthy = true
}
} }
if unhealthy { if unhealthy {
atomic.StoreInt32(&host.Unhealthy, 1) atomic.StoreInt32(&host.Unhealthy, 1)
......
...@@ -228,7 +228,7 @@ func TestStop(t *testing.T) { ...@@ -228,7 +228,7 @@ func TestStop(t *testing.T) {
defer backend.Close() defer backend.Close()
upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(fmt.Sprintf(config, backend.URL, test.intervalInMilliseconds)))) upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(fmt.Sprintf(config, backend.URL, test.intervalInMilliseconds))), "")
if err != nil { if err != nil {
t.Error("Expected no error. Got:", err.Error()) t.Error("Expected no error. Got:", err.Error())
} }
...@@ -277,7 +277,7 @@ func TestParseBlock(t *testing.T) { ...@@ -277,7 +277,7 @@ func TestParseBlock(t *testing.T) {
} }
for i, test := range tests { for i, test := range tests {
upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config))) upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), "")
if err != nil { if err != nil {
t.Errorf("Expected no error. Got: %s", err.Error()) t.Errorf("Expected no error. Got: %s", err.Error())
} }
...@@ -301,7 +301,7 @@ func TestParseBlock(t *testing.T) { ...@@ -301,7 +301,7 @@ func TestParseBlock(t *testing.T) {
func TestHealthSetUp(t *testing.T) { func TestHealthSetUp(t *testing.T) {
// tests for insecure skip verify // tests for insecure skip verify
isv_tests := []struct { tests := []struct {
config string config string
flag bool flag bool
}{ }{
...@@ -312,24 +312,65 @@ func TestHealthSetUp(t *testing.T) { ...@@ -312,24 +312,65 @@ func TestHealthSetUp(t *testing.T) {
{"proxy / localhost:8080 {\n health_check / \n insecure_skip_verify \n}", true}, {"proxy / localhost:8080 {\n health_check / \n insecure_skip_verify \n}", true},
} }
for i, test := range isv_tests { for i, test := range tests {
upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config))) upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), "")
if err != nil { if err != nil {
t.Errorf("Expected no error. Got: %s", err.Error()) t.Errorf("Expected no error. Got: %s", err.Error())
} }
for _, upstream := range upstreams { for _, upstream := range upstreams {
staticUpstream, ok := upstream.(*staticUpstream) staticUpstream, ok := upstream.(*staticUpstream)
if !ok { if !ok {
t.Errorf("type mismatch: %#v", upstream) t.Errorf("Type mismatch: %#v", upstream)
continue continue
} }
transport, ok := staticUpstream.HealthCheck.Client.Transport.(*http.Transport) transport, ok := staticUpstream.HealthCheck.Client.Transport.(*http.Transport)
if !ok { if !ok {
t.Errorf("type mismatch: %#v", staticUpstream.HealthCheck.Client.Transport) t.Errorf("Type mismatch: %#v", staticUpstream.HealthCheck.Client.Transport)
continue continue
} }
if test.flag != transport.TLSClientConfig.InsecureSkipVerify { if test.flag != transport.TLSClientConfig.InsecureSkipVerify {
t.Errorf("test %d: expected transport.TLSClientCnfig.InsecureSkipVerify=%v, got %v", i, test.flag, transport.TLSClientConfig.InsecureSkipVerify) t.Errorf("Test %d: expected transport.TLSClientCnfig.InsecureSkipVerify=%v, got %v", i, test.flag, transport.TLSClientConfig.InsecureSkipVerify)
}
}
}
}
func TestHealthCheckHost(t *testing.T) {
// tests for upstream host on health checks
tests := []struct {
config string
flag bool
host string
}{
// Test #1: without upstream header
{"proxy / localhost:8080 {\n health_check / \n}", false, "example.com"},
// Test #2: without upstream header, missing host
{"proxy / localhost:8080 {\n health_check / \n}", true, ""},
// Test #3: with upstream header (via transparent preset)
{"proxy / localhost:8080 {\n health_check / \n transparent \n}", true, "foo.example.com"},
// Test #4: with upstream header (explicit header)
{"proxy / localhost:8080 {\n health_check / \n header_upstream Host {host} \n}", true, "example.com"},
// Test #5: with upstream header, missing host
{"proxy / localhost:8080 {\n health_check / \n transparent \n}", true, ""},
}
for i, test := range tests {
upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), test.host)
if err != nil {
t.Errorf("Expected no error. Got: %s", err.Error())
}
for _, upstream := range upstreams {
staticUpstream, ok := upstream.(*staticUpstream)
if !ok {
t.Errorf("Type mismatch: %#v", upstream)
continue
}
if test.flag != (staticUpstream.HealthCheck.Host == test.host) {
t.Errorf("Test %d: expected staticUpstream.HealthCheck.Host=%v, got %v", i, test.host, staticUpstream.HealthCheck.Host)
} }
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment