diff --git a/Gopkg.lock b/Gopkg.lock index 97c69b1b7..6a215cc60 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1566,6 +1566,7 @@ "github.com/go-sql-driver/mysql", "github.com/gobwas/glob", "github.com/golang/protobuf/proto", + "github.com/golang/protobuf/ptypes/duration", "github.com/golang/protobuf/ptypes/empty", "github.com/golang/protobuf/ptypes/timestamp", "github.com/google/go-cmp/cmp", @@ -1639,8 +1640,10 @@ "golang.org/x/sys/windows", "golang.org/x/sys/windows/svc", "golang.org/x/sys/windows/svc/mgr", + "google.golang.org/api/iterator", "google.golang.org/api/option", "google.golang.org/api/support/bundler", + "google.golang.org/genproto/googleapis/api/distribution", "google.golang.org/genproto/googleapis/api/metric", "google.golang.org/genproto/googleapis/api/monitoredres", "google.golang.org/genproto/googleapis/monitoring/v3", diff --git a/plugins/outputs/prometheus_client/README.md b/plugins/outputs/prometheus_client/README.md index c06fdbaf1..d1b4a1b0e 100644 --- a/plugins/outputs/prometheus_client/README.md +++ b/plugins/outputs/prometheus_client/README.md @@ -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 diff --git a/plugins/outputs/prometheus_client/prometheus_client.go b/plugins/outputs/prometheus_client/prometheus_client.go index d774b4088..b37718ab7 100644 --- a/plugins/outputs/prometheus_client/prometheus_client.go +++ b/plugins/outputs/prometheus_client/prometheus_client.go @@ -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" @@ -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"` @@ -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 @@ -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 @@ -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() } diff --git a/plugins/outputs/prometheus_client/prometheus_client_tls_test.go b/plugins/outputs/prometheus_client/prometheus_client_tls_test.go new file mode 100644 index 000000000..d7484d61f --- /dev/null +++ b/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} +}