mirror of https://github.com/status-im/consul.git
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:
parent
5a4a2191a6
commit
73b281a27c
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue