mirror of
				https://github.com/adnanh/webhook.git
				synced 2025-10-28 03:54:30 +00:00 
			
		
		
		
	Update to support Scalr Signature Verification
Add a new match rule type that checks for a Scalr webhook signature. Tracking ticket #200 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": "" }
This commit is contained in:
		
							parent
							
								
									ecbba514e5
								
							
						
					
					
						commit
						b595694658
					
				
					 2 changed files with 93 additions and 2 deletions
				
			
		
							
								
								
									
										47
									
								
								hook/hook.go
									
										
									
									
									
								
							
							
						
						
									
										47
									
								
								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: | ||||
|  |  | |||
|  | @ -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{} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue