@@ -3,11 +3,13 @@ package hook
33import (
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 *[
498561type 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
520583type 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 *
542605type 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
560624const (
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