diff --git a/plugins/common/auth/basic_auth.go b/plugins/common/auth/basic_auth.go new file mode 100644 index 000000000..6f92de809 --- /dev/null +++ b/plugins/common/auth/basic_auth.go @@ -0,0 +1,23 @@ +package auth + +import ( + "crypto/subtle" + "net/http" +) + +type BasicAuth struct { + Username string `toml:"username"` + Password string `toml:"password"` +} + +func (b *BasicAuth) Verify(r *http.Request) bool { + if b.Username == "" && b.Password == "" { + return true + } + + username, password, ok := r.BasicAuth() + + usernameComparison := subtle.ConstantTimeCompare([]byte(username), []byte(b.Username)) == 1 + passwordComparison := subtle.ConstantTimeCompare([]byte(password), []byte(b.Password)) == 1 + return ok && usernameComparison && passwordComparison +} diff --git a/plugins/common/auth/basic_auth_test.go b/plugins/common/auth/basic_auth_test.go new file mode 100644 index 000000000..781f36ab8 --- /dev/null +++ b/plugins/common/auth/basic_auth_test.go @@ -0,0 +1,33 @@ +package auth + +import ( + "github.com/stretchr/testify/require" + "net/http/httptest" + "testing" +) + +func TestBasicAuth_VerifyWithCredentials(t *testing.T) { + auth := BasicAuth{"username", "password"} + + r := httptest.NewRequest("GET", "/github", nil) + r.SetBasicAuth(auth.Username, auth.Password) + + require.True(t, auth.Verify(r)) +} + +func TestBasicAuth_VerifyWithoutCredentials(t *testing.T) { + auth := BasicAuth{} + + r := httptest.NewRequest("GET", "/github", nil) + + require.True(t, auth.Verify(r)) +} + +func TestBasicAuth_VerifyWithInvalidCredentials(t *testing.T) { + auth := BasicAuth{"username", "password"} + + r := httptest.NewRequest("GET", "/github", nil) + r.SetBasicAuth("wrong-username", "wrong-password") + + require.False(t, auth.Verify(r)) +} diff --git a/plugins/inputs/webhooks/README.md b/plugins/inputs/webhooks/README.md index 6b90dc45c..1a559fb08 100644 --- a/plugins/inputs/webhooks/README.md +++ b/plugins/inputs/webhooks/README.md @@ -23,21 +23,45 @@ sudo service telegraf start [inputs.webhooks.filestack] path = "/filestack" + ## HTTP basic auth + #username = "" + #password = "" + [inputs.webhooks.github] path = "/github" # secret = "" + ## HTTP basic auth + #username = "" + #password = "" + [inputs.webhooks.mandrill] path = "/mandrill" + ## HTTP basic auth + #username = "" + #password = "" + [inputs.webhooks.rollbar] path = "/rollbar" + ## HTTP basic auth + #username = "" + #password = "" + [inputs.webhooks.papertrail] path = "/papertrail" + ## HTTP basic auth + #username = "" + #password = "" + [inputs.webhooks.particle] path = "/particle" + + ## HTTP basic auth + #username = "" + #password = "" ``` ## Available webhooks diff --git a/plugins/inputs/webhooks/filestack/filestack_webhooks.go b/plugins/inputs/webhooks/filestack/filestack_webhooks.go index e379608ea..d69b17900 100644 --- a/plugins/inputs/webhooks/filestack/filestack_webhooks.go +++ b/plugins/inputs/webhooks/filestack/filestack_webhooks.go @@ -2,19 +2,21 @@ package filestack import ( "encoding/json" - "io" + "io/ioutil" "net/http" "time" "github.com/gorilla/mux" "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/common/auth" ) type FilestackWebhook struct { Path string acc telegraf.Accumulator log telegraf.Logger + auth.BasicAuth } func (fs *FilestackWebhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) { @@ -27,7 +29,13 @@ func (fs *FilestackWebhook) Register(router *mux.Router, acc telegraf.Accumulato func (fs *FilestackWebhook) eventHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - body, err := io.ReadAll(r.Body) + + if !fs.Verify(r) { + w.WriteHeader(http.StatusUnauthorized) + return + } + + body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) return diff --git a/plugins/inputs/webhooks/github/github_webhooks.go b/plugins/inputs/webhooks/github/github_webhooks.go index 585f5daa5..a653535c1 100644 --- a/plugins/inputs/webhooks/github/github_webhooks.go +++ b/plugins/inputs/webhooks/github/github_webhooks.go @@ -11,6 +11,7 @@ import ( "github.com/gorilla/mux" "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/common/auth" ) type GithubWebhook struct { @@ -18,6 +19,7 @@ type GithubWebhook struct { Secret string acc telegraf.Accumulator log telegraf.Logger + auth.BasicAuth } func (gh *GithubWebhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) { @@ -30,6 +32,12 @@ func (gh *GithubWebhook) Register(router *mux.Router, acc telegraf.Accumulator, func (gh *GithubWebhook) eventHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() + + if !gh.Verify(r) { + w.WriteHeader(http.StatusUnauthorized) + return + } + eventType := r.Header.Get("X-Github-Event") data, err := io.ReadAll(r.Body) if err != nil { diff --git a/plugins/inputs/webhooks/mandrill/mandrill_webhooks.go b/plugins/inputs/webhooks/mandrill/mandrill_webhooks.go index b23cc2a1d..4eebac6de 100644 --- a/plugins/inputs/webhooks/mandrill/mandrill_webhooks.go +++ b/plugins/inputs/webhooks/mandrill/mandrill_webhooks.go @@ -2,20 +2,22 @@ package mandrill import ( "encoding/json" - "io" + "io/ioutil" "net/http" "net/url" "time" - "github.com/influxdata/telegraf" - "github.com/gorilla/mux" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/common/auth" ) type MandrillWebhook struct { Path string acc telegraf.Accumulator log telegraf.Logger + auth.BasicAuth } func (md *MandrillWebhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) { @@ -33,7 +35,13 @@ func (md *MandrillWebhook) returnOK(w http.ResponseWriter, _ *http.Request) { func (md *MandrillWebhook) eventHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - body, err := io.ReadAll(r.Body) + + if !md.Verify(r) { + w.WriteHeader(http.StatusUnauthorized) + return + } + + body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) return diff --git a/plugins/inputs/webhooks/papertrail/papertrail_webhooks.go b/plugins/inputs/webhooks/papertrail/papertrail_webhooks.go index 5fac939bb..ae2f3222c 100644 --- a/plugins/inputs/webhooks/papertrail/papertrail_webhooks.go +++ b/plugins/inputs/webhooks/papertrail/papertrail_webhooks.go @@ -9,12 +9,14 @@ import ( "github.com/gorilla/mux" "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/common/auth" ) type PapertrailWebhook struct { Path string acc telegraf.Accumulator log telegraf.Logger + auth.BasicAuth } func (pt *PapertrailWebhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) { @@ -30,6 +32,11 @@ func (pt *PapertrailWebhook) eventHandler(w http.ResponseWriter, r *http.Request return } + if !pt.Verify(r) { + w.WriteHeader(http.StatusUnauthorized) + return + } + data := r.PostFormValue("payload") if data == "" { http.Error(w, "Bad Request", http.StatusBadRequest) diff --git a/plugins/inputs/webhooks/particle/particle_webhooks.go b/plugins/inputs/webhooks/particle/particle_webhooks.go index 4be512686..de9729448 100644 --- a/plugins/inputs/webhooks/particle/particle_webhooks.go +++ b/plugins/inputs/webhooks/particle/particle_webhooks.go @@ -8,6 +8,7 @@ import ( "github.com/gorilla/mux" "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/common/auth" ) type event struct { @@ -40,6 +41,7 @@ type ParticleWebhook struct { Path string acc telegraf.Accumulator log telegraf.Logger + auth.BasicAuth } func (rb *ParticleWebhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) { @@ -51,6 +53,12 @@ func (rb *ParticleWebhook) Register(router *mux.Router, acc telegraf.Accumulator func (rb *ParticleWebhook) eventHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() + + if !rb.Verify(r) { + w.WriteHeader(http.StatusUnauthorized) + return + } + e := newEvent() if err := json.NewDecoder(r.Body).Decode(e); err != nil { rb.acc.AddError(err) diff --git a/plugins/inputs/webhooks/rollbar/rollbar_webhooks.go b/plugins/inputs/webhooks/rollbar/rollbar_webhooks.go index 3b8d2b02c..b2f49d095 100644 --- a/plugins/inputs/webhooks/rollbar/rollbar_webhooks.go +++ b/plugins/inputs/webhooks/rollbar/rollbar_webhooks.go @@ -3,19 +3,21 @@ package rollbar import ( "encoding/json" "errors" - "io" + "io/ioutil" "net/http" "time" "github.com/gorilla/mux" "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/common/auth" ) type RollbarWebhook struct { Path string acc telegraf.Accumulator log telegraf.Logger + auth.BasicAuth } func (rb *RollbarWebhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) { @@ -27,7 +29,13 @@ func (rb *RollbarWebhook) Register(router *mux.Router, acc telegraf.Accumulator, func (rb *RollbarWebhook) eventHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - data, err := io.ReadAll(r.Body) + + if !rb.Verify(r) { + w.WriteHeader(http.StatusUnauthorized) + return + } + + data, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) return diff --git a/plugins/inputs/webhooks/webhooks.go b/plugins/inputs/webhooks/webhooks.go index 2156d9309..f468d35eb 100644 --- a/plugins/inputs/webhooks/webhooks.go +++ b/plugins/inputs/webhooks/webhooks.go @@ -53,21 +53,45 @@ func (wb *Webhooks) SampleConfig() string { [inputs.webhooks.filestack] path = "/filestack" + ## HTTP basic auth + #username = "" + #password = "" + [inputs.webhooks.github] path = "/github" # secret = "" + ## HTTP basic auth + #username = "" + #password = "" + [inputs.webhooks.mandrill] path = "/mandrill" + ## HTTP basic auth + #username = "" + #password = "" + [inputs.webhooks.rollbar] path = "/rollbar" + ## HTTP basic auth + #username = "" + #password = "" + [inputs.webhooks.papertrail] path = "/papertrail" + ## HTTP basic auth + #username = "" + #password = "" + [inputs.webhooks.particle] path = "/particle" + + ## HTTP basic auth + #username = "" + #password = "" ` }