Add HTTP basic auth support to the http_listener input (#3496)

This commit is contained in:
Evan Kinney 2018-03-23 14:56:49 -04:00 committed by Daniel Nelson
parent 5389f40057
commit 798fea3109
4 changed files with 78 additions and 7 deletions

View File

@ -2862,6 +2862,11 @@
# ## Add service certificate and key # ## Add service certificate and key
# tls_cert = "/etc/telegraf/cert.pem" # tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem" # tls_key = "/etc/telegraf/key.pem"
#
# ## Optional username and password to accept for HTTP basic authentication.
# ## You probably want to make sure you have TLS configured above for this.
# basic_username = "foobar"
# basic_password = "barfoo"
# # Read metrics from Kafka topic(s) # # Read metrics from Kafka topic(s)

View File

@ -12,6 +12,8 @@ Enable TLS by specifying the file names of a service TLS certificate and key.
Enable mutually authenticated TLS and authorize client connections by signing certificate authority by including a list of allowed CA certificate file names in ````tls_allowed_cacerts````. Enable mutually authenticated TLS and authorize client connections by signing certificate authority by including a list of allowed CA certificate file names in ````tls_allowed_cacerts````.
Enable basic HTTP authentication of clients by specifying a username and password to check for. These credentials will be received from the client _as plain text_ if TLS is not configured.
See: [Telegraf Input Data Formats](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md#influx). See: [Telegraf Input Data Formats](https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_INPUT.md#influx).
**Example:** **Example:**
@ -39,4 +41,8 @@ This is a sample configuration for the plugin.
## MTLS ## MTLS
tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"] tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]
## Basic authentication
basic_username = "foobar"
basic_password = "barfoo"
``` ```

View File

@ -3,6 +3,7 @@ package http_listener
import ( import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"crypto/subtle"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"io" "io"
@ -44,6 +45,9 @@ type HTTPListener struct {
TlsCert string TlsCert string
TlsKey string TlsKey string
BasicUsername string
BasicPassword string
mu sync.Mutex mu sync.Mutex
wg sync.WaitGroup wg sync.WaitGroup
@ -64,6 +68,7 @@ type HTTPListener struct {
PingsRecv selfstat.Stat PingsRecv selfstat.Stat
NotFoundsServed selfstat.Stat NotFoundsServed selfstat.Stat
BuffersCreated selfstat.Stat BuffersCreated selfstat.Stat
AuthFailures selfstat.Stat
} }
const sampleConfig = ` const sampleConfig = `
@ -90,6 +95,11 @@ const sampleConfig = `
## Add service certificate and key ## Add service certificate and key
tls_cert = "/etc/telegraf/cert.pem" tls_cert = "/etc/telegraf/cert.pem"
tls_key = "/etc/telegraf/key.pem" tls_key = "/etc/telegraf/key.pem"
## Optional username and password to accept for HTTP basic authentication.
## You probably want to make sure you have TLS configured above for this.
# basic_username = "foobar"
# basic_password = "barfoo"
` `
func (h *HTTPListener) SampleConfig() string { func (h *HTTPListener) SampleConfig() string {
@ -124,6 +134,7 @@ func (h *HTTPListener) Start(acc telegraf.Accumulator) error {
h.PingsRecv = selfstat.Register("http_listener", "pings_received", tags) h.PingsRecv = selfstat.Register("http_listener", "pings_received", tags)
h.NotFoundsServed = selfstat.Register("http_listener", "not_founds_served", tags) h.NotFoundsServed = selfstat.Register("http_listener", "not_founds_served", tags)
h.BuffersCreated = selfstat.Register("http_listener", "buffers_created", tags) h.BuffersCreated = selfstat.Register("http_listener", "buffers_created", tags)
h.AuthFailures = selfstat.Register("http_listener", "auth_failures", tags)
if h.MaxBodySize == 0 { if h.MaxBodySize == 0 {
h.MaxBodySize = DEFAULT_MAX_BODY_SIZE h.MaxBodySize = DEFAULT_MAX_BODY_SIZE
@ -194,25 +205,29 @@ func (h *HTTPListener) ServeHTTP(res http.ResponseWriter, req *http.Request) {
case "/write": case "/write":
h.WritesRecv.Incr(1) h.WritesRecv.Incr(1)
defer h.WritesServed.Incr(1) defer h.WritesServed.Incr(1)
h.serveWrite(res, req) h.AuthenticateIfSet(h.serveWrite, res, req)
case "/query": case "/query":
h.QueriesRecv.Incr(1) h.QueriesRecv.Incr(1)
defer h.QueriesServed.Incr(1) defer h.QueriesServed.Incr(1)
// Deliver a dummy response to the query endpoint, as some InfluxDB // Deliver a dummy response to the query endpoint, as some InfluxDB
// clients test endpoint availability with a query // clients test endpoint availability with a query
res.Header().Set("Content-Type", "application/json") h.AuthenticateIfSet(func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("X-Influxdb-Version", "1.0") res.Header().Set("Content-Type", "application/json")
res.WriteHeader(http.StatusOK) res.Header().Set("X-Influxdb-Version", "1.0")
res.Write([]byte("{\"results\":[]}")) res.WriteHeader(http.StatusOK)
res.Write([]byte("{\"results\":[]}"))
}, res, req)
case "/ping": case "/ping":
h.PingsRecv.Incr(1) h.PingsRecv.Incr(1)
defer h.PingsServed.Incr(1) defer h.PingsServed.Incr(1)
// respond to ping requests // respond to ping requests
res.WriteHeader(http.StatusNoContent) h.AuthenticateIfSet(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(http.StatusNoContent)
}, res, req)
default: default:
defer h.NotFoundsServed.Incr(1) defer h.NotFoundsServed.Incr(1)
// Don't know how to respond to calls to other endpoints // Don't know how to respond to calls to other endpoints
http.NotFound(res, req) h.AuthenticateIfSet(http.NotFound, res, req)
} }
} }
@ -376,6 +391,23 @@ func (h *HTTPListener) getTLSConfig() *tls.Config {
return tlsConf return tlsConf
} }
func (h *HTTPListener) AuthenticateIfSet(handler http.HandlerFunc, res http.ResponseWriter, req *http.Request) {
if h.BasicUsername != "" && h.BasicPassword != "" {
reqUsername, reqPassword, ok := req.BasicAuth()
if !ok ||
subtle.ConstantTimeCompare([]byte(reqUsername), []byte(h.BasicUsername)) != 1 ||
subtle.ConstantTimeCompare([]byte(reqPassword), []byte(h.BasicPassword)) != 1 {
h.AuthFailures.Incr(1)
http.Error(res, "Unauthorized.", http.StatusUnauthorized)
return
}
handler(res, req)
} else {
handler(res, req)
}
}
func init() { func init() {
inputs.Add("http_listener", func() telegraf.Input { inputs.Add("http_listener", func() telegraf.Input {
return &HTTPListener{ return &HTTPListener{

View File

@ -101,6 +101,9 @@ NsFlcGACj+/TvacFYlA6N2nyFeokzoqLX28Ddxdh2erXqJ4hYIhT1ik9tkLggs2z
1T1084BquCuO6lIcOwJBALX4xChoMUF9k0IxSQzlz//seQYDkQNsE7y9IgAOXkzp 1T1084BquCuO6lIcOwJBALX4xChoMUF9k0IxSQzlz//seQYDkQNsE7y9IgAOXkzp
RaR4pzgPbnKj7atG+2dBnffWfE+1Mcy0INDAO6WxPg0= RaR4pzgPbnKj7atG+2dBnffWfE+1Mcy0INDAO6WxPg0=
-----END RSA PRIVATE KEY-----` -----END RSA PRIVATE KEY-----`
basicUsername = "test-username-please-ignore"
basicPassword = "super-secure-password!"
) )
var ( var (
@ -120,6 +123,13 @@ func newTestHTTPListener() *HTTPListener {
return listener return listener
} }
func newTestHTTPAuthListener() *HTTPListener {
listener := newTestHTTPListener()
listener.BasicUsername = basicUsername
listener.BasicPassword = basicPassword
return listener
}
func newTestHTTPSListener() *HTTPListener { func newTestHTTPSListener() *HTTPListener {
initServiceCertFiles.Do(func() { initServiceCertFiles.Do(func() {
acaf, err := ioutil.TempFile("", "allowedCAFile.crt") acaf, err := ioutil.TempFile("", "allowedCAFile.crt")
@ -239,6 +249,24 @@ func TestWriteHTTPSWithClientAuth(t *testing.T) {
require.EqualValues(t, 204, resp.StatusCode) require.EqualValues(t, 204, resp.StatusCode)
} }
func TestWriteHTTPBasicAuth(t *testing.T) {
listener := newTestHTTPAuthListener()
acc := &testutil.Accumulator{}
require.NoError(t, listener.Start(acc))
defer listener.Stop()
client := &http.Client{}
req, err := http.NewRequest("POST", createURL(listener, "http", "/write", "db=mydb"), bytes.NewBuffer([]byte(testMsg)))
require.NoError(t, err)
req.SetBasicAuth(basicUsername, basicPassword)
resp, err := client.Do(req)
require.NoError(t, err)
resp.Body.Close()
require.EqualValues(t, http.StatusNoContent, resp.StatusCode)
}
func TestWriteHTTP(t *testing.T) { func TestWriteHTTP(t *testing.T) {
listener := newTestHTTPListener() listener := newTestHTTPListener()