From 013415bbfc6e12cbdade5b623743a33dbd677b57 Mon Sep 17 00:00:00 2001 From: Hass_SEA Date: Sat, 16 Dec 2017 14:33:32 -0800 Subject: [PATCH 1/4] Add files via upload This pull request is to import into the upstream release the updates @AloysAugustin performed on folk scalr-tutorials/webhook to support scalr signing key with Adnanh/webhook. I've done a very basic copy accross and testing Add a new match rule type that checks for a Scalr webhook signature. The signature algorithm is described here: https://scalr-wiki.atlassian.net/wiki/spaces/docs/pages/6193247/Webhook+Security+and+Authentication An example match rule ifor a Scalr webhook will look like: "match": { "type": "scalr-signature", "secret": "" } adds Scalr webhook signature verification. To verify the Scalr signature on a hook, use a match rule similar to this example: [ { "id": "scalr-test", "execute-command": "test.sh", "trigger-rule": { "match": { "type": "scalr-signature", "secret": "Scalr-provided signing key" } } } ] Note that the trigger rule checks the scalr signature and checks that the request was signed less than 5 minutes before it was received. Please make sure that NTP is enabled on both your Scalr server and your webhook handler to prevent any issues. --- hook/hook.go | 47 ++++++++++++++++++++++++++++++++++++++++++++-- hook/hook_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/hook/hook.go b/hook/hook.go index 7fd8c1f..fb1b6fc 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io/ioutil" + "math" "log" "net" "net/textproto" @@ -20,6 +21,7 @@ import ( "strconv" "strings" "text/template" + "time" "github.com/ghodss/yaml" ) @@ -127,7 +129,44 @@ func CheckPayloadSignature256(payload []byte, secret string, signature string) ( } return expectedMAC, err } - + +func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey string, checkDate bool) (bool, error) { + // Check for the signature and date headers + if _, ok := headers["X-Signature"]; !ok { + return false, nil + } + if _, ok := headers["Date"]; !ok { + return false, nil + } + providedSignature := headers["X-Signature"].(string) + dateHeader := headers["Date"].(string) + mac := hmac.New(sha1.New, []byte(signingKey)) + mac.Write(body) + mac.Write([]byte(dateHeader)) + expectedSignature := hex.EncodeToString(mac.Sum(nil)) + + if !hmac.Equal([]byte(providedSignature), []byte(expectedSignature)) { + return false, &SignatureError{providedSignature} + } + + if !checkDate { + return true, nil + } + // Example format: Fri 08 Sep 2017 11:24:32 UTC + date, err := time.Parse("Mon 02 Jan 2006 15:04:05 MST", dateHeader) + //date, err := time.Parse(time.RFC1123, dateHeader) + if err != nil { + return false, err + } + now := time.Now() + delta := math.Abs(now.Sub(date).Seconds()) + + if delta > 300 { + return false, &SignatureError{"outdated"} + } + return true, nil + } + // CheckIPWhitelist makes sure the provided remote address (of the form IP:port) falls within the provided IP range // (in CIDR form or a single IP address). func CheckIPWhitelist(remoteAddr string, ipRange string) (bool, error) { @@ -704,6 +743,7 @@ const ( MatchHashSHA1 string = "payload-hash-sha1" MatchHashSHA256 string = "payload-hash-sha256" IPWhitelist string = "ip-whitelist" + ScalrSignature string = "scalr-signature" ) // Evaluate MatchRule will return based on the type @@ -711,7 +751,10 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod if r.Type == IPWhitelist { return CheckIPWhitelist(remoteAddr, r.IPRange) } - + if r.Type == ScalrSignature { + return CheckScalrSignature(*headers, *body, r.Secret, true) + } + if arg, ok := r.Parameter.Get(headers, query, payload); ok { switch r.Type { case MatchValue: diff --git a/hook/hook_test.go b/hook/hook_test.go index 52f273b..d1134f1 100644 --- a/hook/hook_test.go +++ b/hook/hook_test.go @@ -60,6 +60,54 @@ func TestCheckPayloadSignature256(t *testing.T) { } } +var checkScalrSignatureTests = []struct { + description string + headers map[string]interface{} + payload []byte + secret string + expectedSignature string + ok bool +}{ + { + "Valid signature", + map[string]interface{}{"Date": "Thu 07 Sep 2017 06:30:04 UTC", "X-Signature": "48e395e38ac48988929167df531eb2da00063a7d"}, + []byte(`{"a": "b"}`), "bilFGi4ZVZUdG+C6r0NIM9tuRq6PaG33R3eBUVhLwMAErGBaazvXe4Gq2DcJs5q+", + "48e395e38ac48988929167df531eb2da00063a7d", true, + }, + { + "Wrong signature", + map[string]interface{}{"Date": "Thu 07 Sep 2017 06:30:04 UTC", "X-Signature": "999395e38ac48988929167df531eb2da00063a7d"}, + []byte(`{"a": "b"}`), "bilFGi4ZVZUdG+C6r0NIM9tuRq6PaG33R3eBUVhLwMAErGBaazvXe4Gq2DcJs5q+", + "48e395e38ac48988929167df531eb2da00063a7d", false, + }, + { + "Missing Date header", + map[string]interface{}{"X-Signature": "999395e38ac48988929167df531eb2da00063a7d"}, + []byte(`{"a": "b"}`), "bilFGi4ZVZUdG+C6r0NIM9tuRq6PaG33R3eBUVhLwMAErGBaazvXe4Gq2DcJs5q+", + "48e395e38ac48988929167df531eb2da00063a7d", false, + }, + { + "Missing X-Signature header", + map[string]interface{}{"Date": "Thu 07 Sep 2017 06:30:04 UTC"}, + []byte(`{"a": "b"}`), "bilFGi4ZVZUdG+C6r0NIM9tuRq6PaG33R3eBUVhLwMAErGBaazvXe4Gq2DcJs5q+", + "48e395e38ac48988929167df531eb2da00063a7d", false, + }, +} + +func TestCheckScalrSignature(t *testing.T) { + for _, testCase := range checkScalrSignatureTests { + valid, err := CheckScalrSignature(testCase.headers, testCase.payload, testCase.secret, false) + if valid != testCase.ok { + t.Errorf("failed to check scalr signature fot test case: %s\nexpected ok:%#v, got ok:%#v}", + testCase.description, testCase.ok, valid) + } + + if err != nil && strings.Contains(err.Error(), testCase.expectedSignature) { + t.Errorf("error message should not disclose expected mac: %s on test case %s", err, testCase.description) + } + } +} + var extractParameterTests = []struct { s string params interface{} From 9ee7f1eb96cd7bcd589de64c4c83783481ad6661 Mon Sep 17 00:00:00 2001 From: Hass_SEA Date: Sat, 16 Dec 2017 14:34:34 -0800 Subject: [PATCH 2/4] Add files via upload Updates for Scalr --- webhook.go | 2 +- webhook_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webhook.go b/webhook.go index fd3ce1d..d323027 100644 --- a/webhook.go +++ b/webhook.go @@ -94,7 +94,7 @@ func main() { // set os signal watcher setupSignals() - // load and parse hooks + // load and parse hooks hass edit for _, hooksFilePath := range hooksFiles { log.Printf("attempting to load hooks from %s\n", hooksFilePath) diff --git a/webhook_test.go b/webhook_test.go index 4373b16..4b2ff88 100644 --- a/webhook_test.go +++ b/webhook_test.go @@ -17,7 +17,7 @@ import ( "text/template" "time" - "github.com/adnanh/webhook/hook" + "github.com/hassanbabaie/webhook/hook" ) func TestStaticParams(t *testing.T) { From be48bc035900696da82b500f51cab4fab3a54002 Mon Sep 17 00:00:00 2001 From: Hass_SEA Date: Mon, 18 Dec 2017 09:24:19 -0800 Subject: [PATCH 3/4] Edit import paths Edit import paths --- webhook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook.go b/webhook.go index d323027..4548531 100644 --- a/webhook.go +++ b/webhook.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "github.com/adnanh/webhook/hook" + "github.com/hassanbabaie/webhook/hook" "github.com/codegangsta/negroni" "github.com/gorilla/mux" From 7166c8cd48f0f7b32aa356697b4c4778a37689d2 Mon Sep 17 00:00:00 2001 From: Hass_SEA Date: Tue, 19 Dec 2017 08:34:38 -0800 Subject: [PATCH 4/4] set import paths back to source set import paths back to github.com/adnanh/webhook/hook instead of forked location --- webhook.go | 2 +- webhook_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webhook.go b/webhook.go index 4548531..d323027 100644 --- a/webhook.go +++ b/webhook.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "github.com/hassanbabaie/webhook/hook" + "github.com/adnanh/webhook/hook" "github.com/codegangsta/negroni" "github.com/gorilla/mux" diff --git a/webhook_test.go b/webhook_test.go index 4b2ff88..4373b16 100644 --- a/webhook_test.go +++ b/webhook_test.go @@ -17,7 +17,7 @@ import ( "text/template" "time" - "github.com/hassanbabaie/webhook/hook" + "github.com/adnanh/webhook/hook" ) func TestStaticParams(t *testing.T) {