Skip to content

Commit

Permalink
tls_config: Allow specifying SNI hostnames
Browse files Browse the repository at this point in the history
Add a new configration field `tls_server_name` that allows specifying
the server name that'll be sent in the ClientHello when telegraf makes
a request to TLS servers. This allows checking against load balancers
responding to specific hostnames that otherwise wouldn't resolve to
their addresses.

Add the setting to the documentation of common TLS options, as well as
to the http_response plugin.

Fixes influxdata#7598.
  • Loading branch information
antifuchs committed Dec 12, 2020
1 parent 0fe2386 commit 43f6526
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 8 deletions.
2 changes: 2 additions & 0 deletions docs/TLS.md
Expand Up @@ -18,6 +18,8 @@ For client TLS support we have the following options:
# tls_key = "/etc/telegraf/key.pem"
## Skip TLS verification.
# insecure_skip_verify = false
## Send the specified TLS server name via SNI.
# tls_server_name = "foo.example.com"
```

### Server Configuration
Expand Down
7 changes: 6 additions & 1 deletion plugins/common/tls/config.go
Expand Up @@ -14,6 +14,7 @@ type ClientConfig struct {
TLSCert string `toml:"tls_cert"`
TLSKey string `toml:"tls_key"`
InsecureSkipVerify bool `toml:"insecure_skip_verify"`
ServerName string `toml:"tls_server_name"`

// Deprecated in 1.7; use TLS variables above
SSLCA string `toml:"ssl_ca"`
Expand Down Expand Up @@ -49,7 +50,7 @@ func (c *ClientConfig) TLSConfig() (*tls.Config, error) {
// want TLS, this will require using another option to determine. In the
// case of an HTTP plugin, you could use `https`. Other plugins may need
// the dedicated option `TLSEnable`.
if c.TLSCA == "" && c.TLSKey == "" && c.TLSCert == "" && !c.InsecureSkipVerify {
if c.TLSCA == "" && c.TLSKey == "" && c.TLSCert == "" && !c.InsecureSkipVerify && c.ServerName == "" {
return nil, nil
}

Expand All @@ -73,6 +74,10 @@ func (c *ClientConfig) TLSConfig() (*tls.Config, error) {
}
}

if c.ServerName != "" {
tlsConfig.ServerName = c.ServerName
}

return tlsConfig, nil
}

Expand Down
17 changes: 13 additions & 4 deletions plugins/common/tls/config_test.go
Expand Up @@ -15,10 +15,11 @@ var pki = testutil.NewPKI("../../../testutil/pki")

func TestClientConfig(t *testing.T) {
tests := []struct {
name string
client tls.ClientConfig
expNil bool
expErr bool
name string
client tls.ClientConfig
expNil bool
expErr bool
serverName string
}{
{
name: "unset",
Expand Down Expand Up @@ -86,6 +87,14 @@ func TestClientConfig(t *testing.T) {
SSLKey: pki.ClientKeyPath(),
},
},
{
name: "set SNI server name",
client: tls.ClientConfig{
ServerName: "foo.example.com",
},
expNil: false,
expErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
4 changes: 3 additions & 1 deletion plugins/inputs/http_response/README.md
Expand Up @@ -63,6 +63,8 @@ This input plugin checks HTTP/HTTPS connections.
# tls_key = "/etc/telegraf/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
## Use the given name as the SNI server name on each URL
# tls_server_name = ""

## HTTP Request Headers (all values must be strings)
# [inputs.http_response.headers]
Expand Down Expand Up @@ -91,7 +93,7 @@ This input plugin checks HTTP/HTTPS connections.
- response_string_match (int, 0 = mismatch / body read error, 1 = match)
- response_status_code_match (int, 0 = mismatch, 1 = match)
- http_response_code (int, response status code)
- result_type (string, deprecated in 1.6: use `result` tag and `result_code` field)
- result_type (string, deprecated in 1.6: use `result` tag and `result_code` field)
- result_code (int, [see below](#result--result_code))

#### `result` / `result_code`
Expand Down
4 changes: 2 additions & 2 deletions plugins/inputs/http_response/http_response.go
Expand Up @@ -97,8 +97,8 @@ var sampleConfig = `
# {'fake':'data'}
# '''
## Optional name of the field that will contain the body of the response.
## By default it is set to an empty String indicating that the body's content won't be added
## Optional name of the field that will contain the body of the response.
## By default it is set to an empty String indicating that the body's content won't be added
# response_body_field = ''
## Maximum allowed HTTP response body size in bytes.
Expand Down
38 changes: 38 additions & 0 deletions plugins/inputs/http_response/http_response_test.go
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/common/tls"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -1266,3 +1267,40 @@ func TestStatusCodeAndStringMatchFail(t *testing.T) {
}
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
}

func TestSNI(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "super-special-hostname.example.com", r.TLS.ServerName)
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()

h := &HTTPResponse{
Log: testutil.Logger{},
URLs: []string{ts.URL + "/good"},
Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
ClientConfig: tls.ClientConfig{
InsecureSkipVerify: true,
ServerName: "super-special-hostname.example.com",
},
}
var acc testutil.Accumulator
err := h.Gather(&acc)
require.NoError(t, err)
expectedFields := map[string]interface{}{
"http_response_code": http.StatusOK,
"result_type": "success",
"result_code": 0,
"response_time": nil,
"content_length": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "200",
"result": "success",
}
absentFields := []string{"response_string_match"}
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
}

0 comments on commit 43f6526

Please sign in to comment.