Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Nov 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type AgentServiceCheck struct {
HTTP string `json:",omitempty"`
TCP string `json:",omitempty"`
Status string `json:",omitempty"`
TLSSkipVerify string `json:",omitempty"`

// In Consul 0.7 and later, checks that are associated with a service
// may also contain this optional DeregisterCriticalServiceAfter field,
Expand Down
13 changes: 7 additions & 6 deletions command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -962,12 +962,13 @@ func (a *Agent) AddCheck(check *structs.HealthCheck, chkType *CheckType, persist
}

http := &CheckHTTP{
Notify: &a.state,
CheckID: check.CheckID,
HTTP: chkType.HTTP,
Interval: chkType.Interval,
Timeout: chkType.Timeout,
Logger: a.logger,
Notify: &a.state,
CheckID: check.CheckID,
HTTP: chkType.HTTP,
Interval: chkType.Interval,
Timeout: chkType.Timeout,
Logger: a.logger,
TLSSkipVerify: chkType.TLSSkipVerify,
}
http.Start()
a.checkHTTPs[check.CheckID] = http
Expand Down
24 changes: 18 additions & 6 deletions command/agent/check.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package agent

import (
"crypto/tls"
"fmt"
"io"
"log"
Expand Down Expand Up @@ -47,6 +48,7 @@ type CheckType struct {
Interval time.Duration
DockerContainerID string
Shell string
TLSSkipVerify bool

Timeout time.Duration
TTL time.Duration
Expand Down Expand Up @@ -340,12 +342,13 @@ type persistedCheckState struct {
// The check is critical if the response code is anything else
// or if the request returns an error
type CheckHTTP struct {
Notify CheckNotifier
CheckID types.CheckID
HTTP string
Interval time.Duration
Timeout time.Duration
Logger *log.Logger
Notify CheckNotifier
CheckID types.CheckID
HTTP string
Interval time.Duration
Timeout time.Duration
Logger *log.Logger
TLSSkipVerify bool

httpClient *http.Client
stop bool
Expand All @@ -365,6 +368,15 @@ func (c *CheckHTTP) Start() {
trans := cleanhttp.DefaultTransport()
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.
c.httpClient = &http.Client{
Timeout: 10 * time.Second,
Expand Down
127 changes: 127 additions & 0 deletions command/agent/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,19 @@ func mockHTTPServer(responseCode int) *httptest.Server {
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) {
mock := &MockNotify{
state: make(map[types.CheckID]string),
Expand Down Expand Up @@ -375,6 +388,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") {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryanuber Based on your feedback, I've updated this test to make sure we're failing at the transport layer

t.Fatalf("should fail with certificate error %v", mock.output)
}
}

func mockTCPServer(network string) net.Listener {
var (
addr string
Expand Down
3 changes: 3 additions & 0 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,9 @@ func FixupCheckType(raw interface{}) error {
case "docker_container_id":
rawMap["DockerContainerID"] = v
delete(rawMap, k)
case "tls_skip_verify":
rawMap["TLSSkipVerify"] = v
delete(rawMap, k)
}
}

Expand Down
39 changes: 39 additions & 0 deletions command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,23 @@ func TestDecodeConfig_Checks(t *testing.T) {
"interval": "10s",
"timeout": "100ms",
"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
}
]
}`
Expand Down Expand Up @@ -1182,6 +1199,28 @@ func TestDecodeConfig_Checks(t *testing.T) {
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,
},
},
},
}

Expand Down
5 changes: 4 additions & 1 deletion website/source/docs/agent/checks.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional documentation is added


* 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
Expand Down
11 changes: 8 additions & 3 deletions website/source/docs/agent/http/agent.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ body must look like:
"HTTP": "http://example.com",
"TCP": "example.com:22",
"Interval": "10s",
"TTL": "15s"
"TTL": "15s",
"TLSSkipVerify": true
}
```

Expand Down Expand Up @@ -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
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
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
connection attempt is successful, the check is `passing`. If the connection
attempt is unsuccessful, the check is `critical`. In the case of a hostname
Expand Down