Add setting to skip ssl certificate verification for HTTP checks (#1984)

* http check: add setting to skip ssl certificate verification

* update http check documentation

* fix typo in documentation

* Add TLSSkipVerify to agent api
This commit is contained in:
Kyle McCullough 2016-11-03 15:17:30 -05:00 committed by James Phillips
parent 5a4a2191a6
commit 73b281a27c
8 changed files with 207 additions and 16 deletions

View File

@ -73,6 +73,7 @@ type AgentServiceCheck struct {
HTTP string `json:",omitempty"` HTTP string `json:",omitempty"`
TCP string `json:",omitempty"` TCP string `json:",omitempty"`
Status string `json:",omitempty"` Status string `json:",omitempty"`
TLSSkipVerify string `json:",omitempty"`
// In Consul 0.7 and later, checks that are associated with a service // In Consul 0.7 and later, checks that are associated with a service
// may also contain this optional DeregisterCriticalServiceAfter field, // may also contain this optional DeregisterCriticalServiceAfter field,

View File

@ -982,12 +982,13 @@ func (a *Agent) AddCheck(check *structs.HealthCheck, chkType *CheckType, persist
} }
http := &CheckHTTP{ http := &CheckHTTP{
Notify: &a.state, Notify: &a.state,
CheckID: check.CheckID, CheckID: check.CheckID,
HTTP: chkType.HTTP, HTTP: chkType.HTTP,
Interval: chkType.Interval, Interval: chkType.Interval,
Timeout: chkType.Timeout, Timeout: chkType.Timeout,
Logger: a.logger, Logger: a.logger,
TLSSkipVerify: chkType.TLSSkipVerify,
} }
http.Start() http.Start()
a.checkHTTPs[check.CheckID] = http a.checkHTTPs[check.CheckID] = http

View File

@ -1,6 +1,7 @@
package agent package agent
import ( import (
"crypto/tls"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -47,6 +48,7 @@ type CheckType struct {
Interval time.Duration Interval time.Duration
DockerContainerID string DockerContainerID string
Shell string Shell string
TLSSkipVerify bool
Timeout time.Duration Timeout time.Duration
TTL time.Duration TTL time.Duration
@ -340,12 +342,13 @@ type persistedCheckState struct {
// The check is critical if the response code is anything else // The check is critical if the response code is anything else
// or if the request returns an error // or if the request returns an error
type CheckHTTP struct { type CheckHTTP struct {
Notify CheckNotifier Notify CheckNotifier
CheckID types.CheckID CheckID types.CheckID
HTTP string HTTP string
Interval time.Duration Interval time.Duration
Timeout time.Duration Timeout time.Duration
Logger *log.Logger Logger *log.Logger
TLSSkipVerify bool
httpClient *http.Client httpClient *http.Client
stop bool stop bool
@ -365,6 +368,15 @@ func (c *CheckHTTP) Start() {
trans := cleanhttp.DefaultTransport() trans := cleanhttp.DefaultTransport()
trans.DisableKeepAlives = true trans.DisableKeepAlives = true
// Skip SSL certificate verification if TLSSkipVerify is true
if trans.TLSClientConfig == nil {
trans.TLSClientConfig = &tls.Config{
InsecureSkipVerify: c.TLSSkipVerify,
}
} else {
trans.TLSClientConfig.InsecureSkipVerify = c.TLSSkipVerify
}
// Create the HTTP client. // Create the HTTP client.
c.httpClient = &http.Client{ c.httpClient = &http.Client{
Timeout: 10 * time.Second, Timeout: 10 * time.Second,

View File

@ -228,6 +228,19 @@ func mockHTTPServer(responseCode int) *httptest.Server {
return httptest.NewServer(mux) return httptest.NewServer(mux)
} }
func mockTLSHTTPServer(responseCode int) *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Body larger than 4k limit
body := bytes.Repeat([]byte{'a'}, 2*CheckBufSize)
w.WriteHeader(responseCode)
w.Write(body)
return
})
return httptest.NewTLSServer(mux)
}
func expectHTTPStatus(t *testing.T, url string, status string) { func expectHTTPStatus(t *testing.T, url string, status string) {
mock := &MockNotify{ mock := &MockNotify{
state: make(map[types.CheckID]string), state: make(map[types.CheckID]string),
@ -381,6 +394,120 @@ func TestCheckHTTP_disablesKeepAlives(t *testing.T) {
} }
} }
func TestCheckHTTP_TLSSkipVerify_defaultFalse(t *testing.T) {
check := &CheckHTTP{
CheckID: "foo",
HTTP: "https://foo.bar/baz",
Interval: 10 * time.Second,
Logger: log.New(os.Stderr, "", log.LstdFlags),
}
check.Start()
defer check.Stop()
if check.httpClient.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify {
t.Fatalf("should default to false")
}
}
func TestCheckHTTP_TLSSkipVerify_true_pass(t *testing.T) {
server := mockTLSHTTPServer(200)
defer server.Close()
mock := &MockNotify{
state: make(map[types.CheckID]string),
updates: make(map[types.CheckID]int),
output: make(map[types.CheckID]string),
}
check := &CheckHTTP{
Notify: mock,
CheckID: types.CheckID("skipverify_true"),
HTTP: server.URL,
Interval: 5 * time.Millisecond,
Logger: log.New(os.Stderr, "", log.LstdFlags),
TLSSkipVerify: true,
}
check.Start()
defer check.Stop()
time.Sleep(50 * time.Millisecond)
if !check.httpClient.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify {
t.Fatalf("should be true")
}
if mock.state["skipverify_true"] != structs.HealthPassing {
t.Fatalf("should be passing %v", mock.state)
}
}
func TestCheckHTTP_TLSSkipVerify_true_fail(t *testing.T) {
server := mockTLSHTTPServer(500)
defer server.Close()
mock := &MockNotify{
state: make(map[types.CheckID]string),
updates: make(map[types.CheckID]int),
output: make(map[types.CheckID]string),
}
check := &CheckHTTP{
Notify: mock,
CheckID: types.CheckID("skipverify_true"),
HTTP: server.URL,
Interval: 5 * time.Millisecond,
Logger: log.New(os.Stderr, "", log.LstdFlags),
TLSSkipVerify: true,
}
check.Start()
defer check.Stop()
time.Sleep(50 * time.Millisecond)
if !check.httpClient.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify {
t.Fatalf("should be true")
}
if mock.state["skipverify_true"] != structs.HealthCritical {
t.Fatalf("should be critical %v", mock.state)
}
}
func TestCheckHTTP_TLSSkipVerify_false(t *testing.T) {
server := mockTLSHTTPServer(200)
defer server.Close()
mock := &MockNotify{
state: make(map[types.CheckID]string),
updates: make(map[types.CheckID]int),
output: make(map[types.CheckID]string),
}
check := &CheckHTTP{
Notify: mock,
CheckID: types.CheckID("skipverify_false"),
HTTP: server.URL,
Interval: 100 * time.Millisecond,
Logger: log.New(os.Stderr, "", log.LstdFlags),
TLSSkipVerify: false,
}
check.Start()
defer check.Stop()
time.Sleep(150 * time.Millisecond)
if check.httpClient.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify {
t.Fatalf("should be false")
}
// This should fail due to an invalid SSL cert
if mock.state["skipverify_false"] != structs.HealthCritical {
t.Fatalf("should be critical %v", mock.state)
}
if !strings.Contains(mock.output["skipverify_false"], "certificate signed by unknown authority") {
t.Fatalf("should fail with certificate error %v", mock.output)
}
}
func mockTCPServer(network string) net.Listener { func mockTCPServer(network string) net.Listener {
var ( var (
addr string addr string

View File

@ -1066,6 +1066,9 @@ func FixupCheckType(raw interface{}) error {
case "docker_container_id": case "docker_container_id":
rawMap["DockerContainerID"] = v rawMap["DockerContainerID"] = v
delete(rawMap, k) delete(rawMap, k)
case "tls_skip_verify":
rawMap["TLSSkipVerify"] = v
delete(rawMap, k)
} }
} }

View File

@ -1175,6 +1175,23 @@ func TestDecodeConfig_Checks(t *testing.T) {
"interval": "10s", "interval": "10s",
"timeout": "100ms", "timeout": "100ms",
"service_id": "elasticsearch" "service_id": "elasticsearch"
},
{
"id": "chk5",
"name": "service:sslservice",
"HTTP": "https://sslservice/status",
"interval": "10s",
"timeout": "100ms",
"service_id": "sslservice"
},
{
"id": "chk6",
"name": "service:insecure-sslservice",
"HTTP": "https://insecure-sslservice/status",
"interval": "10s",
"timeout": "100ms",
"service_id": "insecure-sslservice",
"tls_skip_verify": true
} }
] ]
}` }`
@ -1221,6 +1238,28 @@ func TestDecodeConfig_Checks(t *testing.T) {
Timeout: 100 * time.Millisecond, Timeout: 100 * time.Millisecond,
}, },
}, },
&CheckDefinition{
ID: "chk5",
Name: "service:sslservice",
ServiceID: "sslservice",
CheckType: CheckType{
HTTP: "https://sslservice/status",
Interval: 10 * time.Second,
Timeout: 100 * time.Millisecond,
TLSSkipVerify: false,
},
},
&CheckDefinition{
ID: "chk6",
Name: "service:insecure-sslservice",
ServiceID: "insecure-sslservice",
CheckType: CheckType{
HTTP: "https://insecure-sslservice/status",
Interval: 10 * time.Second,
Timeout: 100 * time.Millisecond,
TLSSkipVerify: true,
},
},
}, },
} }

View File

@ -34,7 +34,10 @@ There are five different kinds of checks:
with a request timeout equal to the check interval, with a max of 10 seconds. with a request timeout equal to the check interval, with a max of 10 seconds.
It is possible to configure a custom HTTP check timeout value by specifying It is possible to configure a custom HTTP check timeout value by specifying
the `timeout` field in the check definition. The output of the check is the `timeout` field in the check definition. The output of the check is
limited to roughly 4K. Responses larger than this will be truncated. limited to roughly 4K. Responses larger than this will be truncated. HTTP checks
also support SSL. By default, a valid SSL certificate is expected. Certificate
verification can be turned off by setting the `tls_skip_verify` field to `true`
in the check definition.
* TCP + Interval - These checks make an TCP connection attempt every Interval * TCP + Interval - These checks make an TCP connection attempt every Interval
(e.g. every 30 seconds) to the specified IP/hostname and port. If no hostname (e.g. every 30 seconds) to the specified IP/hostname and port. If no hostname

View File

@ -250,7 +250,8 @@ body must look like:
"HTTP": "http://example.com", "HTTP": "http://example.com",
"TCP": "example.com:22", "TCP": "example.com:22",
"Interval": "10s", "Interval": "10s",
"TTL": "15s" "TTL": "15s",
"TLSSkipVerify": true
} }
``` ```
@ -282,9 +283,13 @@ evaluate the script every `Interval` in the given container using the specified
An `HTTP` check will perform an HTTP GET request against the value of `HTTP` (expected to An `HTTP` check will perform an HTTP GET request against the value of `HTTP` (expected to
be a URL) every `Interval`. If the response is any `2xx` code, the check is `passing`. be a URL) every `Interval`. If the response is any `2xx` code, the check is `passing`.
If the response is `429 Too Many Requests`, the check is `warning`. Otherwise, the check If the response is `429 Too Many Requests`, the check is `warning`. Otherwise, the check
is `critical`. is `critical`. HTTP checks also support SSL. By default, a valid SSL certificate is expected.
Certificate verification can be controlled using the `TLSSkipVerify`.
An `TCP` check will perform an TCP connection attempt against the value of `TCP` If `TLSSkipVerify` is set to `true`, certificate verification will be disabled. By default,
certificate verification is enabled.
A `TCP` check will perform an TCP connection attempt against the value of `TCP`
(expected to be an IP/hostname and port combination) every `Interval`. If the (expected to be an IP/hostname and port combination) every `Interval`. If the
connection attempt is successful, the check is `passing`. If the connection connection attempt is successful, the check is `passing`. If the connection
attempt is unsuccessful, the check is `critical`. In the case of a hostname attempt is unsuccessful, the check is `critical`. In the case of a hostname