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 mutual TLS support to prometheus_client output plugin #5473

Merged
merged 4 commits into from Feb 27, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions plugins/outputs/prometheus_client/README.md
Expand Up @@ -35,6 +35,10 @@ This plugin starts a [Prometheus](https://prometheus.io/) Client, it exposes all
## If set, enable TLS with the given certificate.
# tls_cert = "/etc/ssl/telegraf.crt"
# tls_key = "/etc/ssl/telegraf.key"

## Set one or more allowed client CA certificate file names to
## enable mutually authenticated TLS connections
# tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]

## Export metric collection time.
# export_timestamp = false
Expand Down
20 changes: 15 additions & 5 deletions plugins/outputs/prometheus_client/prometheus_client.go
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/internal/tls"
"github.com/influxdata/telegraf/plugins/outputs"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
Expand Down Expand Up @@ -56,8 +57,6 @@ type MetricFamily struct {

type PrometheusClient struct {
Listen string
TLSCert string `toml:"tls_cert"`
TLSKey string `toml:"tls_key"`
BasicUsername string `toml:"basic_username"`
BasicPassword string `toml:"basic_password"`
IPRange []string `toml:"ip_range"`
Expand All @@ -67,6 +66,8 @@ type PrometheusClient struct {
StringAsLabel bool `toml:"string_as_label"`
ExportTimestamp bool `toml:"export_timestamp"`

tls.ServerConfig

server *http.Server

sync.Mutex
Expand Down Expand Up @@ -105,6 +106,10 @@ var sampleConfig = `
## If set, enable TLS with the given certificate.
# tls_cert = "/etc/ssl/telegraf.crt"
# tls_key = "/etc/ssl/telegraf.key"

## Set one or more allowed client CA certificate file names to
## enable mutually authenticated TLS connections
# tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]

## Export metric collection time.
# export_timestamp = false
Expand Down Expand Up @@ -184,15 +189,20 @@ func (p *PrometheusClient) Connect() error {
mux.Handle(p.Path, p.auth(promhttp.HandlerFor(
registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError})))

tlsConfig, err := p.TLSConfig()
if err != nil {
return err
}
p.server = &http.Server{
Addr: p.Listen,
Handler: mux,
Addr: p.Listen,
Handler: mux,
TLSConfig: tlsConfig,
}

go func() {
var err error
if p.TLSCert != "" && p.TLSKey != "" {
err = p.server.ListenAndServeTLS(p.TLSCert, p.TLSKey)
err = p.server.ListenAndServeTLS("", "")
} else {
err = p.server.ListenAndServe()
}
Expand Down
104 changes: 104 additions & 0 deletions plugins/outputs/prometheus_client/prometheus_client_tls_test.go
@@ -0,0 +1,104 @@
package prometheus_client_test

import (
"crypto/tls"
"fmt"
"github.com/influxdata/telegraf/plugins/outputs/prometheus_client"
"github.com/influxdata/telegraf/testutil"
"github.com/influxdata/toml"
"github.com/stretchr/testify/require"
"net/http"
"testing"
)

var pki = testutil.NewPKI("../../../testutil/pki")

var configWithTLS = fmt.Sprintf(`
listen = "127.0.0.1:9090"
tls_allowed_cacerts = ["%s"]
tls_cert = "%s"
tls_key = "%s"
`, pki.TLSServerConfig().TLSAllowedCACerts[0], pki.TLSServerConfig().TLSCert, pki.TLSServerConfig().TLSKey)

var configWithoutTLS = `
listen = "127.0.0.1:9090"
`

type PrometheusClientTestContext struct {
Output *prometheus_client.PrometheusClient
Accumulator *testutil.Accumulator
Client *http.Client
}

func TestWorksWithoutTLS(t *testing.T) {
tc := buildTestContext(t, []byte(configWithoutTLS))
err := tc.Output.Connect()
defer tc.Output.Close()

require.NoError(t, err)

response, err := tc.Client.Get("http://localhost:9090/metrics")
require.NoError(t, err)

require.NoError(t, err)
require.Equal(t, response.StatusCode, http.StatusOK)
}

func TestWorksWithTLS(t *testing.T) {
tc := buildTestContext(t, []byte(configWithTLS))
err := tc.Output.Connect()
defer tc.Output.Close()
require.NoError(t, err)

response, err := tc.Client.Get("https://localhost:9090/metrics")
require.NoError(t, err)

require.NoError(t, err)
require.Equal(t, response.StatusCode, http.StatusOK)

response, err = tc.Client.Get("http://localhost:9090/metrics")
require.Error(t, err)

tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}

client := &http.Client{Transport: tr}
response, err = client.Get("https://localhost:9090/metrics")

require.Error(t, err)
}

func buildTestContext(t *testing.T, config []byte) *PrometheusClientTestContext {
output := prometheus_client.NewClient()
err := toml.Unmarshal(config, output)
require.NoError(t, err)

var (
httpClient *http.Client
)

if len(output.TLSAllowedCACerts) != 0 {
httpClient = buildClientWithTLS(t, output)
} else {
httpClient = buildClientWithoutTLS()
}

return &PrometheusClientTestContext{
Output: output,
Accumulator: &testutil.Accumulator{},
Client: httpClient,
}
}

func buildClientWithoutTLS() *http.Client {
return &http.Client{}
}

func buildClientWithTLS(t *testing.T, output *prometheus_client.PrometheusClient) *http.Client {
tlsConfig, err := pki.TLSClientConfig().TLSConfig()
require.NoError(t, err)

transport := &http.Transport{TLSClientConfig: tlsConfig}
return &http.Client{Transport: transport}
}