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 2 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
25 changes: 25 additions & 0 deletions Gopkg.lock

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

6 changes: 6 additions & 0 deletions plugins/outputs/prometheus_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ 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"

## If set, enable TLS client authentication with the given CA.
# tls_ca = "/etc/ssl/telegraf_ca.crt"

## Boolean value indicating whether or not to skip SSL verification
# insecure_skip_verify = false

## Export metric collection time.
# export_timestamp = false
Expand Down
22 changes: 17 additions & 5 deletions plugins/outputs/prometheus_client/prometheus_client.go
Original file line number Diff line number Diff line change
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,12 @@ var sampleConfig = `
## If set, enable TLS with the given certificate.
# tls_cert = "/etc/ssl/telegraf.crt"
# tls_key = "/etc/ssl/telegraf.key"

## If set, enable TLS client authentication with the given CA.
# tls_ca = "/etc/ssl/telegraf_ca.crt"
Copy link
Contributor

Choose a reason for hiding this comment

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

Update this option:

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Done


## Boolean value indicating whether or not to skip SSL verification
# insecure_skip_verify = false
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove this option, it's not available for the server configuration. Don't forget to update the README as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done


## Export metric collection time.
# export_timestamp = false
Expand Down Expand Up @@ -184,15 +191,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
129 changes: 129 additions & 0 deletions plugins/outputs/prometheus_client/prometheus_client_tls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
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/onsi/gomega"
Copy link
Contributor

Choose a reason for hiding this comment

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

We will need to remove this dependency, I don't want to add it only for testing this one aspect.

Copy link
Contributor

Choose a reason for hiding this comment

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

Replaced with 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

*GomegaWithT
}

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

if err != nil {
panic(err)
}

var response *http.Response
tc.Eventually(func() bool {
response, err = tc.Client.Get("http://localhost:9090/metrics")
return err == nil
}, "5s").Should(BeTrue())

if err != nil {
panic(err)
}

tc.Expect(response.StatusCode).To(Equal(http.StatusOK))
}

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

if err != nil {
panic(err)
}

var response *http.Response
tc.Eventually(func() bool {
response, err = tc.Client.Get("https://localhost:9090/metrics")
return err == nil
}, "5s").Should(BeTrue())

if err != nil {
panic(err)
}

tc.Expect(response.StatusCode).To(Equal(http.StatusOK))

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

tc.Expect(err).To(HaveOccurred())

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

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

tc.Expect(err).To(HaveOccurred())
}

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

if err != nil {
panic(err)
}

var (
httpClient *http.Client
)

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

return &PrometheusClientTestContext{
Output: output,
Accumulator: &testutil.Accumulator{},
Client: httpClient,
GomegaWithT: NewGomegaWithT(t),
}
}

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

func buildClientWithTLS(output *prometheus_client.PrometheusClient) *http.Client {
tlsConfig, err := pki.TLSClientConfig().TLSConfig()
if err != nil {
panic(err)
}
transport := &http.Transport{TLSClientConfig: tlsConfig}
return &http.Client{Transport: transport}
}