Repository: honeydipper/honeydipper
Pull request #417 (2021-06-28 18:46:45)

feat(drivers): webhook hmac support secret list
#### Description
Some systems such as pagerduty needs to have multiple secrets.

#### This PR fixes the following issues



Patch:
diff --git a/drivers/cmd/webhook/main.go b/drivers/cmd/webhook/main.go
index b0160bf..bbfd8a0 100644
--- a/drivers/cmd/webhook/main.go
+++ b/drivers/cmd/webhook/main.go
@@ -260,27 +260,37 @@ func verifySystems(eventData map[string]interface{}) {
 		}
 	}
 
-	if signatureHeader != "" {
-		var verifiedSystem []string
-		for name, sys := range sysMap {
-			expectedHeader, ok := sys["signatureHeader"]
-			if !ok || !strings.EqualFold(expectedHeader.(string), signatureHeader) {
-				// ignore unsupported headers or system without signatureHeader
-				continue
-			}
+	if signatureHeader == "" {
+		return
+	}
 
-			secret, ok := dipper.GetMapDataStr(sys, "signatureSecret")
-			if !ok {
-				log.Warningf("[%s] signature secret not defined for system %s", driver.Service, name)
+	var verifiedSystem []string
+	for name, sys := range sysMap {
+		expectedHeader, ok := sys["signatureHeader"]
+		if !ok || !strings.EqualFold(expectedHeader.(string), signatureHeader) {
+			// ignore unsupported headers or system without signatureHeader
+			continue
+		}
 
-				continue
-			}
+		secretValue, ok := dipper.GetMapData(sys, "signatureSecret")
+		if !ok {
+			log.Warningf("[%s] signature secret not defined for system %s", driver.Service, name)
 
-			if verifySignature(signatureHeader, signatureValue, secret, eventData) {
+			continue
+		}
+		secrets, ok := secretValue.([]interface{})
+		if !ok {
+			secrets = []interface{}{secretValue}
+		}
+
+		for _, secret := range secrets {
+			if verifySignature(signatureHeader, signatureValue, secret.(string), eventData) {
 				verifiedSystem = append(verifiedSystem, name)
+
+				break
 			}
 		}
-		log.Infof("[%s] HMAC verified for system(s) %+v", driver.Service, verifiedSystem)
-		eventData["verifiedSystem"] = verifiedSystem
 	}
+	log.Infof("[%s] HMAC verified for system(s) %+v", driver.Service, verifiedSystem)
+	eventData["verifiedSystem"] = verifiedSystem
 }


Test patch:
diff --git a/drivers/cmd/webhook/main_test.go b/drivers/cmd/webhook/main_test.go
index 6d15a2d..2607be4 100644
--- a/drivers/cmd/webhook/main_test.go
+++ b/drivers/cmd/webhook/main_test.go
@@ -157,6 +157,13 @@ func TestHookHandler(t *testing.T) {
 			"signatureHeader": "x-pagerduty-signature",
 			"signatureSecret": "test-secret",
 		},
+		"sys-secret-list": map[string]interface{}{
+			"signatureHeader": "x-pagerduty-signature",
+			"signatureSecret": []interface{}{
+				"test-secret1",
+				"test-secret2",
+			},
+		},
 		"sys-unsupported-header": map[string]interface{}{
 			"signatureHeader": "x-unknown-signature",
 		},
@@ -183,6 +190,11 @@ func TestHookHandler(t *testing.T) {
 				"match": map[string]interface{}{"verifiedSystem": "sys"},
 			},
 		},
+		"sys5.webhook": []interface{}{
+			map[string]interface{}{
+				"match": map[string]interface{}{"verifiedSystem": "sys-secret-list"},
+			},
+		},
 	}
 
 	buf := bytes.NewBuffer(make([]byte, 2048))
@@ -232,4 +244,17 @@ func TestHookHandler(t *testing.T) {
 	hookHandler(resp, req)
 	assert.Equalf(t, 404, resp.status, "should return 404 with unsupported signature header")
 	assert.Zerof(t, buf.Len(), "should not emit event to daemon")
+
+	resp = &mockResponseWriter{header: http.Header{}}
+	req = &http.Request{
+		Method: "POST",
+		URL:    &url.URL{Path: "/test/sys5"},
+		Header: http.Header{"X-Pagerduty-Signature": []string{"v1=7afdf53eec1c15fb75269b28fab95228ba591b11a103d8e0972087e6dee018ca"}},
+		Body:   ioutil.NopCloser(bytes.NewBufferString("hello")),
+	}
+	hookHandler(resp, req)
+	msg = dipper.FetchMessage(buf)
+	assert.Equalf(t, 200, resp.status, "should return 200 on success with proper signature")
+	assert.Equalf(t, "sys-secret-list", dipper.MustGetMapDataStr(msg.Payload, "data.verifiedSystem.0"), "should emit webhook event with verifiedSystem")
+	assert.Equalf(t, 1, len(dipper.MustGetMapData(msg.Payload, "data.verifiedSystem").([]interface{})), "should verify only 1 system")
 }