Skip to content

Commit 3978b66

Browse files
authored
Merge pull request #125 from adnanh/development
webhook 2.6.3
2 parents 5b567d1 + 81b1bd7 commit 3978b66

File tree

3 files changed

+168
-53
lines changed

3 files changed

+168
-53
lines changed

hook/hook.go

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package hook
33
import (
44
"crypto/hmac"
55
"crypto/sha1"
6+
"crypto/sha256"
67
"encoding/hex"
78
"encoding/json"
89
"errors"
910
"fmt"
1011
"io/ioutil"
12+
"net"
1113
"net/textproto"
1214
"reflect"
1315
"regexp"
@@ -100,6 +102,67 @@ func CheckPayloadSignature(payload []byte, secret string, signature string) (str
100102
return expectedMAC, err
101103
}
102104

105+
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
106+
func CheckPayloadSignature256(payload []byte, secret string, signature string) (string, error) {
107+
if strings.HasPrefix(signature, "sha256=") {
108+
signature = signature[7:]
109+
}
110+
111+
mac := hmac.New(sha256.New, []byte(secret))
112+
_, err := mac.Write(payload)
113+
if err != nil {
114+
return "", err
115+
}
116+
expectedMAC := hex.EncodeToString(mac.Sum(nil))
117+
118+
if !hmac.Equal([]byte(signature), []byte(expectedMAC)) {
119+
return expectedMAC, &SignatureError{signature}
120+
}
121+
return expectedMAC, err
122+
}
123+
124+
// CheckIPWhitelist makes sure the provided remote address (of the form IP:port) falls within the provided IP range
125+
// (in CIDR form or a single IP address).
126+
func CheckIPWhitelist(remoteAddr string, ipRange string) (bool, error) {
127+
// Extract IP address from remote address.
128+
129+
ip := remoteAddr
130+
131+
if strings.LastIndex(remoteAddr, ":") != -1 {
132+
ip = remoteAddr[0:strings.LastIndex(remoteAddr, ":")]
133+
}
134+
135+
ip = strings.TrimSpace(ip)
136+
137+
// IPv6 addresses will likely be surrounded by [], so don't forget to remove those.
138+
139+
if strings.HasPrefix(ip, "[") && strings.HasSuffix(ip, "]") {
140+
ip = ip[1 : len(ip)-1]
141+
}
142+
143+
parsedIP := net.ParseIP(strings.TrimSpace(ip))
144+
145+
if parsedIP == nil {
146+
return false, fmt.Errorf("invalid IP address found in remote address '%s'", remoteAddr)
147+
}
148+
149+
// Extract IP range in CIDR form. If a single IP address is provided, turn it into CIDR form.
150+
151+
ipRange = strings.TrimSpace(ipRange)
152+
153+
if strings.Index(ipRange, "/") == -1 {
154+
ipRange = ipRange + "/32"
155+
}
156+
157+
_, cidr, err := net.ParseCIDR(ipRange)
158+
159+
if err != nil {
160+
return false, err
161+
}
162+
163+
return cidr.Contains(parsedIP), nil
164+
}
165+
103166
// ReplaceParameter replaces parameter value with the passed value in the passed map
104167
// (please note you should pass pointer to the map, because we're modifying it)
105168
// based on the passed string
@@ -479,16 +542,16 @@ type Rules struct {
479542

480543
// Evaluate finds the first rule property that is not nil and returns the value
481544
// it evaluates to
482-
func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) (bool, error) {
545+
func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
483546
switch {
484547
case r.And != nil:
485-
return r.And.Evaluate(headers, query, payload, body)
548+
return r.And.Evaluate(headers, query, payload, body, remoteAddr)
486549
case r.Or != nil:
487-
return r.Or.Evaluate(headers, query, payload, body)
550+
return r.Or.Evaluate(headers, query, payload, body, remoteAddr)
488551
case r.Not != nil:
489-
return r.Not.Evaluate(headers, query, payload, body)
552+
return r.Not.Evaluate(headers, query, payload, body, remoteAddr)
490553
case r.Match != nil:
491-
return r.Match.Evaluate(headers, query, payload, body)
554+
return r.Match.Evaluate(headers, query, payload, body, remoteAddr)
492555
}
493556

494557
return false, nil
@@ -498,11 +561,11 @@ func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[
498561
type AndRule []Rules
499562

500563
// Evaluate AndRule will return true if and only if all of ChildRules evaluate to true
501-
func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) (bool, error) {
564+
func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
502565
res := true
503566

504567
for _, v := range r {
505-
rv, err := v.Evaluate(headers, query, payload, body)
568+
rv, err := v.Evaluate(headers, query, payload, body, remoteAddr)
506569
if err != nil {
507570
return false, err
508571
}
@@ -520,11 +583,11 @@ func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body
520583
type OrRule []Rules
521584

522585
// Evaluate OrRule will return true if any of ChildRules evaluate to true
523-
func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) (bool, error) {
586+
func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
524587
res := false
525588

526589
for _, v := range r {
527-
rv, err := v.Evaluate(headers, query, payload, body)
590+
rv, err := v.Evaluate(headers, query, payload, body, remoteAddr)
528591
if err != nil {
529592
return false, err
530593
}
@@ -542,8 +605,8 @@ func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *
542605
type NotRule Rules
543606

544607
// Evaluate NotRule will return true if and only if ChildRule evaluates to false
545-
func (r NotRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) (bool, error) {
546-
rv, err := Rules(r).Evaluate(headers, query, payload, body)
608+
func (r NotRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
609+
rv, err := Rules(r).Evaluate(headers, query, payload, body, remoteAddr)
547610
return !rv, err
548611
}
549612

@@ -554,17 +617,24 @@ type MatchRule struct {
554617
Secret string `json:"secret,omitempty"`
555618
Value string `json:"value,omitempty"`
556619
Parameter Argument `json:"parameter,omitempty"`
620+
IPRange string `json:"ip-range,omitempty"`
557621
}
558622

559623
// Constants for the MatchRule type
560624
const (
561-
MatchValue string = "value"
562-
MatchRegex string = "regex"
563-
MatchHashSHA1 string = "payload-hash-sha1"
625+
MatchValue string = "value"
626+
MatchRegex string = "regex"
627+
MatchHashSHA1 string = "payload-hash-sha1"
628+
MatchHashSHA256 string = "payload-hash-sha256"
629+
IPWhitelist string = "ip-whitelist"
564630
)
565631

566632
// Evaluate MatchRule will return based on the type
567-
func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) (bool, error) {
633+
func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
634+
if r.Type == IPWhitelist {
635+
return CheckIPWhitelist(remoteAddr, r.IPRange)
636+
}
637+
568638
if arg, ok := r.Parameter.Get(headers, query, payload); ok {
569639
switch r.Type {
570640
case MatchValue:
@@ -574,6 +644,9 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
574644
case MatchHashSHA1:
575645
_, err := CheckPayloadSignature(*body, r.Secret, arg)
576646
return err == nil, err
647+
case MatchHashSHA256:
648+
_, err := CheckPayloadSignature256(*body, r.Secret, arg)
649+
return err == nil, err
577650
}
578651
}
579652
return false, nil

0 commit comments

Comments
 (0)