Skip to content

Commit 58ee962

Browse files
francois2metzdanielnelson
authored andcommitted
GitHub webhooks: check signature (influxdata#2493)
1 parent dc5779e commit 58ee962

File tree

6 files changed

+63
-3
lines changed

6 files changed

+63
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ be deprecated eventually.
7070
- [#2636](https://github.com/influxdata/telegraf/pull/2636): Add `message_len_max` option to `kafka_consumer` input
7171
- [#1100](https://github.com/influxdata/telegraf/issues/1100): Add collectd parser
7272
- [#1820](https://github.com/influxdata/telegraf/issues/1820): easier plugin testing without outputs
73+
- [#2493](https://github.com/influxdata/telegraf/pull/2493): Check signature in the GitHub webhook plugin
7374

7475
### Bugfixes
7576

etc/telegraf.conf

+1
Original file line numberDiff line numberDiff line change
@@ -2382,6 +2382,7 @@
23822382
#
23832383
# [inputs.webhooks.github]
23842384
# path = "/github"
2385+
# # secret = ""
23852386
#
23862387
# [inputs.webhooks.mandrill]
23872388
# path = "/mandrill"

plugins/inputs/webhooks/github/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
You should configure your Organization's Webhooks to point at the `webhooks` service. To do this go to `github.com/{my_organization}` and click `Settings > Webhooks > Add webhook`. In the resulting menu set `Payload URL` to `http://<my_ip>:1619/github`, `Content type` to `application/json` and under the section `Which events would you like to trigger this webhook?` select 'Send me <b>everything</b>'. By default all of the events will write to the `github_webhooks` measurement, this is configurable by setting the `measurement_name` in the config file.
44

5+
You can also add a secret that will be used by telegraf to verify the authenticity of the requests.
6+
57
## Events
68

79
The titles of the following sections are links to the full payloads and details for each event. The body contains what information from the event is persisted. The format is as follows:

plugins/inputs/webhooks/github/github_webhooks.go

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package github
22

33
import (
4+
"crypto/hmac"
5+
"crypto/sha1"
6+
"encoding/hex"
47
"encoding/json"
58
"io/ioutil"
69
"log"
@@ -11,8 +14,9 @@ import (
1114
)
1215

1316
type GithubWebhook struct {
14-
Path string
15-
acc telegraf.Accumulator
17+
Path string
18+
Secret string
19+
acc telegraf.Accumulator
1620
}
1721

1822
func (gh *GithubWebhook) Register(router *mux.Router, acc telegraf.Accumulator) {
@@ -23,12 +27,19 @@ func (gh *GithubWebhook) Register(router *mux.Router, acc telegraf.Accumulator)
2327

2428
func (gh *GithubWebhook) eventHandler(w http.ResponseWriter, r *http.Request) {
2529
defer r.Body.Close()
26-
eventType := r.Header["X-Github-Event"][0]
30+
eventType := r.Header.Get("X-Github-Event")
2731
data, err := ioutil.ReadAll(r.Body)
2832
if err != nil {
2933
w.WriteHeader(http.StatusBadRequest)
3034
return
3135
}
36+
37+
if gh.Secret != "" && !checkSignature(gh.Secret, data, r.Header.Get("X-Hub-Signature")) {
38+
log.Printf("E! Fail to check the github webhook signature\n")
39+
w.WriteHeader(http.StatusBadRequest)
40+
return
41+
}
42+
3243
e, err := NewEvent(data, eventType)
3344
if err != nil {
3445
w.WriteHeader(http.StatusBadRequest)
@@ -108,3 +119,14 @@ func NewEvent(data []byte, name string) (Event, error) {
108119
}
109120
return nil, &newEventError{"Not a recognized event type"}
110121
}
122+
123+
func checkSignature(secret string, data []byte, signature string) bool {
124+
return hmac.Equal([]byte(signature), []byte(generateSignature(secret, data)))
125+
}
126+
127+
func generateSignature(secret string, data []byte) string {
128+
mac := hmac.New(sha1.New, []byte(secret))
129+
mac.Write(data)
130+
result := mac.Sum(nil)
131+
return "sha1=" + hex.EncodeToString(result)
132+
}

plugins/inputs/webhooks/github/github_webhooks_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ func GithubWebhookRequest(event string, jsonString string, t *testing.T) {
2121
}
2222
}
2323

24+
func GithubWebhookRequestWithSignature(event string, jsonString string, t *testing.T, signature string, expectedStatus int) {
25+
var acc testutil.Accumulator
26+
gh := &GithubWebhook{Path: "/github", Secret: "signature", acc: &acc}
27+
req, _ := http.NewRequest("POST", "/github", strings.NewReader(jsonString))
28+
req.Header.Add("X-Github-Event", event)
29+
req.Header.Add("X-Hub-Signature", signature)
30+
w := httptest.NewRecorder()
31+
gh.eventHandler(w, req)
32+
if w.Code != expectedStatus {
33+
t.Errorf("POST "+event+" returned HTTP status code %v.\nExpected %v", w.Code, expectedStatus)
34+
}
35+
}
36+
2437
func TestCommitCommentEvent(t *testing.T) {
2538
GithubWebhookRequest("commit_comment", CommitCommentEventJSON(), t)
2639
}
@@ -100,3 +113,23 @@ func TestTeamAddEvent(t *testing.T) {
100113
func TestWatchEvent(t *testing.T) {
101114
GithubWebhookRequest("watch", WatchEventJSON(), t)
102115
}
116+
117+
func TestEventWithSignatureFail(t *testing.T) {
118+
GithubWebhookRequestWithSignature("watch", WatchEventJSON(), t, "signature", http.StatusBadRequest)
119+
}
120+
121+
func TestEventWithSignatureSuccess(t *testing.T) {
122+
GithubWebhookRequestWithSignature("watch", WatchEventJSON(), t, generateSignature("signature", []byte(WatchEventJSON())), http.StatusOK)
123+
}
124+
125+
func TestCheckSignatureSuccess(t *testing.T) {
126+
if !checkSignature("my_little_secret", []byte("random-signature-body"), "sha1=3dca279e731c97c38e3019a075dee9ebbd0a99f0") {
127+
t.Errorf("check signature failed")
128+
}
129+
}
130+
131+
func TestCheckSignatureFailed(t *testing.T) {
132+
if checkSignature("m_little_secret", []byte("random-signature-body"), "sha1=3dca279e731c97c38e3019a075dee9ebbd0a99f0") {
133+
t.Errorf("check signature failed")
134+
}
135+
}

plugins/inputs/webhooks/webhooks.go

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func (wb *Webhooks) SampleConfig() string {
4747
4848
[inputs.webhooks.github]
4949
path = "/github"
50+
# secret = ""
5051
5152
[inputs.webhooks.mandrill]
5253
path = "/mandrill"

0 commit comments

Comments
 (0)