mirror of
https://github.com/adnanh/webhook.git
synced 2025-06-27 06:48:32 +00:00
feat: new rule type specifically for signature checks
Move the signature checking rules out of MatchRule into their own dedicated SignatureRule, configured as "check-signature" in the hooks file. This takes an algorithm, secret and Argument giving the source of the signature, and by default behaves exactly like the old payload-hmac-<algorithm> match rules. However it can also take a second optional Argument to customize how to generate the "string to sign", allowing signatures to be computed over something other than the full request body content. This could be a single header or payload item but more likely will be a "template" argument to combine items from different places in the request, such as the body content and one or more headers, e.g. to compute a signature over the X-Request-Id header, Date header, and request body, concatenated with CRLF, you could specify check-signature: algorithm: sha512 secret: 5uper5eecret signature: source: header name: X-Hook-Signature string-to-sign: source: template name: | {{- printf "%s\r\n" (.GetHeader "x-request-id") -}} {{- printf "%s\r\n" (.GetHeader "date") -}} {{- .BodyText -}}
This commit is contained in:
parent
bb8be5ed9a
commit
9abdb1fffb
2 changed files with 202 additions and 172 deletions
|
@ -196,45 +196,6 @@ func ValidateMAC(payload []byte, mac hash.Hash, signatures []string) (string, er
|
||||||
return actualMAC, e
|
return actualMAC, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
|
|
||||||
func CheckPayloadSignature(payload []byte, secret, signature string) (string, error) {
|
|
||||||
if secret == "" {
|
|
||||||
return "", errors.New("signature validation secret can not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the signatures.
|
|
||||||
signatures := ExtractSignatures(signature, "sha1=")
|
|
||||||
|
|
||||||
// Validate the MAC.
|
|
||||||
return ValidateMAC(payload, hmac.New(sha1.New, []byte(secret)), signatures)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
|
|
||||||
func CheckPayloadSignature256(payload []byte, secret, signature string) (string, error) {
|
|
||||||
if secret == "" {
|
|
||||||
return "", errors.New("signature validation secret can not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the signatures.
|
|
||||||
signatures := ExtractSignatures(signature, "sha256=")
|
|
||||||
|
|
||||||
// Validate the MAC.
|
|
||||||
return ValidateMAC(payload, hmac.New(sha256.New, []byte(secret)), signatures)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckPayloadSignature512 calculates and verifies SHA512 signature of the given payload
|
|
||||||
func CheckPayloadSignature512(payload []byte, secret, signature string) (string, error) {
|
|
||||||
if secret == "" {
|
|
||||||
return "", errors.New("signature validation secret can not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the signatures.
|
|
||||||
signatures := ExtractSignatures(signature, "sha512=")
|
|
||||||
|
|
||||||
// Validate the MAC.
|
|
||||||
return ValidateMAC(payload, hmac.New(sha512.New, []byte(secret)), signatures)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckScalrSignature(r *Request, signingKey string, checkDate bool) (bool, error) {
|
func CheckScalrSignature(r *Request, signingKey string, checkDate bool) (bool, error) {
|
||||||
if r.Headers == nil {
|
if r.Headers == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -854,7 +815,12 @@ func (h *Hooks) LoadFromFile(path string, asTemplate bool, delimsStr string) err
|
||||||
file = buf.Bytes()
|
file = buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
return yaml.Unmarshal(file, h)
|
err := yaml.Unmarshal(file, h)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.postProcess()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append appends hooks unless the new hooks contain a hook with an ID that already exists
|
// Append appends hooks unless the new hooks contain a hook with an ID that already exists
|
||||||
|
@ -882,12 +848,81 @@ func (h *Hooks) Match(id string) *Hook {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hooks) postProcess() error {
|
||||||
|
for i := range *h {
|
||||||
|
rules := (*h)[i].TriggerRule
|
||||||
|
if rules != nil {
|
||||||
|
if err := postProcess(rules); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Rules is a structure that contains one of the valid rule types
|
// Rules is a structure that contains one of the valid rule types
|
||||||
type Rules struct {
|
type Rules struct {
|
||||||
And *AndRule `json:"and,omitempty"`
|
And *AndRule `json:"and,omitempty"`
|
||||||
Or *OrRule `json:"or,omitempty"`
|
Or *OrRule `json:"or,omitempty"`
|
||||||
Not *NotRule `json:"not,omitempty"`
|
Not *NotRule `json:"not,omitempty"`
|
||||||
Match *MatchRule `json:"match,omitempty"`
|
Match *MatchRule `json:"match,omitempty"`
|
||||||
|
Signature *SignatureRule `json:"check-signature,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// postProcess is called on each Rules instance after loading it from JSON/YAML,
|
||||||
|
// to replace any legacy constructs with their modern equivalents.
|
||||||
|
func postProcess(r *Rules) error {
|
||||||
|
if r.And != nil {
|
||||||
|
for i := range *(r.And) {
|
||||||
|
if err := postProcess(&(*r.And)[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.Or != nil {
|
||||||
|
for i := range *(r.Or) {
|
||||||
|
if err := postProcess(&(*r.Or)[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.Not != nil {
|
||||||
|
return postProcess((*Rules)(r.Not))
|
||||||
|
}
|
||||||
|
if r.Match != nil {
|
||||||
|
// convert any signature matching rules to the equivalent SignatureRule
|
||||||
|
if r.Match.Type == MatchHashSHA1 || r.Match.Type == MatchHMACSHA1 {
|
||||||
|
log.Printf(`warn: use of deprecated match type %s; use a check-signature rule instead`, r.Match.Type)
|
||||||
|
r.Signature = &SignatureRule{
|
||||||
|
Algorithm: AlgorithmSHA1,
|
||||||
|
Secret: r.Match.Secret,
|
||||||
|
Signature: r.Match.Parameter,
|
||||||
|
}
|
||||||
|
r.Match = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if r.Match.Type == MatchHashSHA256 || r.Match.Type == MatchHMACSHA256 {
|
||||||
|
log.Printf(`warn: use of deprecated match type %s; use a check-signature rule instead`, r.Match.Type)
|
||||||
|
r.Signature = &SignatureRule{
|
||||||
|
Algorithm: AlgorithmSHA256,
|
||||||
|
Secret: r.Match.Secret,
|
||||||
|
Signature: r.Match.Parameter,
|
||||||
|
}
|
||||||
|
r.Match = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if r.Match.Type == MatchHashSHA512 || r.Match.Type == MatchHMACSHA512 {
|
||||||
|
log.Printf(`warn: use of deprecated match type %s; use a check-signature rule instead`, r.Match.Type)
|
||||||
|
r.Signature = &SignatureRule{
|
||||||
|
Algorithm: AlgorithmSHA512,
|
||||||
|
Secret: r.Match.Secret,
|
||||||
|
Signature: r.Match.Parameter,
|
||||||
|
}
|
||||||
|
r.Match = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate finds the first rule property that is not nil and returns the value
|
// Evaluate finds the first rule property that is not nil and returns the value
|
||||||
|
@ -902,6 +937,8 @@ func (r Rules) Evaluate(req *Request) (bool, error) {
|
||||||
return r.Not.Evaluate(req)
|
return r.Not.Evaluate(req)
|
||||||
case r.Match != nil:
|
case r.Match != nil:
|
||||||
return r.Match.Evaluate(req)
|
return r.Match.Evaluate(req)
|
||||||
|
case r.Signature != nil:
|
||||||
|
return r.Signature.Evaluate(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -978,14 +1015,17 @@ type MatchRule struct {
|
||||||
const (
|
const (
|
||||||
MatchValue string = "value"
|
MatchValue string = "value"
|
||||||
MatchRegex string = "regex"
|
MatchRegex string = "regex"
|
||||||
|
IPWhitelist string = "ip-whitelist"
|
||||||
|
ScalrSignature string = "scalr-signature"
|
||||||
|
|
||||||
|
// legacy match types that have migrated to SignatureRule
|
||||||
|
|
||||||
MatchHMACSHA1 string = "payload-hmac-sha1"
|
MatchHMACSHA1 string = "payload-hmac-sha1"
|
||||||
MatchHMACSHA256 string = "payload-hmac-sha256"
|
MatchHMACSHA256 string = "payload-hmac-sha256"
|
||||||
MatchHMACSHA512 string = "payload-hmac-sha512"
|
MatchHMACSHA512 string = "payload-hmac-sha512"
|
||||||
MatchHashSHA1 string = "payload-hash-sha1"
|
MatchHashSHA1 string = "payload-hash-sha1"
|
||||||
MatchHashSHA256 string = "payload-hash-sha256"
|
MatchHashSHA256 string = "payload-hash-sha256"
|
||||||
MatchHashSHA512 string = "payload-hash-sha512"
|
MatchHashSHA512 string = "payload-hash-sha512"
|
||||||
IPWhitelist string = "ip-whitelist"
|
|
||||||
ScalrSignature string = "scalr-signature"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Evaluate MatchRule will return based on the type
|
// Evaluate MatchRule will return based on the type
|
||||||
|
@ -1004,29 +1044,74 @@ func (r MatchRule) Evaluate(req *Request) (bool, error) {
|
||||||
return compare(arg, r.Value), nil
|
return compare(arg, r.Value), nil
|
||||||
case MatchRegex:
|
case MatchRegex:
|
||||||
return regexp.MatchString(r.Regex, arg)
|
return regexp.MatchString(r.Regex, arg)
|
||||||
case MatchHashSHA1:
|
|
||||||
log.Print(`warn: use of deprecated option payload-hash-sha1; use payload-hmac-sha1 instead`)
|
|
||||||
fallthrough
|
|
||||||
case MatchHMACSHA1:
|
|
||||||
_, err := CheckPayloadSignature(req.Body, r.Secret, arg)
|
|
||||||
return err == nil, err
|
|
||||||
case MatchHashSHA256:
|
|
||||||
log.Print(`warn: use of deprecated option payload-hash-sha256: use payload-hmac-sha256 instead`)
|
|
||||||
fallthrough
|
|
||||||
case MatchHMACSHA256:
|
|
||||||
_, err := CheckPayloadSignature256(req.Body, r.Secret, arg)
|
|
||||||
return err == nil, err
|
|
||||||
case MatchHashSHA512:
|
|
||||||
log.Print(`warn: use of deprecated option payload-hash-sha512: use payload-hmac-sha512 instead`)
|
|
||||||
fallthrough
|
|
||||||
case MatchHMACSHA512:
|
|
||||||
_, err := CheckPayloadSignature512(req.Body, r.Secret, arg)
|
|
||||||
return err == nil, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SignatureRule struct {
|
||||||
|
Algorithm string `json:"algorithm,omitempty"`
|
||||||
|
Secret string `json:"secret,omitempty"`
|
||||||
|
Signature Argument `json:"signature,omitempty"`
|
||||||
|
Prefix string `json:"prefix,omitempty"`
|
||||||
|
StringToSign *Argument `json:"string-to-sign,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constants for the SignatureRule type
|
||||||
|
const (
|
||||||
|
AlgorithmSHA1 string = "sha1"
|
||||||
|
AlgorithmSHA256 string = "sha256"
|
||||||
|
AlgorithmSHA512 string = "sha512"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Evaluate extracts the signature payload and signature value from the request
|
||||||
|
// and checks whether the signature matches
|
||||||
|
func (r SignatureRule) Evaluate(req *Request) (bool, error) {
|
||||||
|
if r.Secret == "" {
|
||||||
|
return false, errors.New("signature validation secret can not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashConstructor func() hash.Hash
|
||||||
|
switch r.Algorithm {
|
||||||
|
case AlgorithmSHA1:
|
||||||
|
hashConstructor = sha1.New
|
||||||
|
case AlgorithmSHA256:
|
||||||
|
hashConstructor = sha256.New
|
||||||
|
case AlgorithmSHA512:
|
||||||
|
hashConstructor = sha512.New
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("unknown hash algorithm %s", r.Algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := r.Prefix
|
||||||
|
if prefix == "" {
|
||||||
|
// default prefix is "sha1=" for SHA1, etc.
|
||||||
|
prefix = fmt.Sprintf("%s=", r.Algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the signature
|
||||||
|
sig, err := r.Signature.Get(req)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("could not extract signature string: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine the payload that is signed
|
||||||
|
payload := req.Body
|
||||||
|
if r.StringToSign != nil {
|
||||||
|
payloadStr, err := r.StringToSign.Get(req)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("could not build string-to-sign: %w", err)
|
||||||
|
}
|
||||||
|
payload = []byte(payloadStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the signature
|
||||||
|
signatures := ExtractSignatures(sig, prefix)
|
||||||
|
_, err = ValidateMAC(payload, hmac.New(hashConstructor, []byte(r.Secret)), signatures)
|
||||||
|
|
||||||
|
return err == nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// compare is a helper function for constant time string comparisons.
|
// compare is a helper function for constant time string comparisons.
|
||||||
func compare(a, b string) bool {
|
func compare(a, b string) bool {
|
||||||
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
|
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
|
||||||
|
|
|
@ -40,100 +40,6 @@ func TestGetParameter(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkPayloadSignatureTests = []struct {
|
|
||||||
payload []byte
|
|
||||||
secret string
|
|
||||||
signature string
|
|
||||||
mac string
|
|
||||||
ok bool
|
|
||||||
}{
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e,sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
|
||||||
{[]byte(``), "secret", "25af6174a0fcecc4d346680a72b7ce644b9a88e8", "25af6174a0fcecc4d346680a72b7ce644b9a88e8", true},
|
|
||||||
// failures
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e,sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
|
||||||
{[]byte(`{"a": "z"}`), "secreX", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "900225703e9342328db7307692736e2f7cc7b36e", false},
|
|
||||||
{[]byte(`{"a": "z"}`), "", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "", false},
|
|
||||||
{[]byte(``), "secret", "XXXf6174a0fcecc4d346680a72b7ce644b9a88e8", "25af6174a0fcecc4d346680a72b7ce644b9a88e8", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckPayloadSignature(t *testing.T) {
|
|
||||||
for _, tt := range checkPayloadSignatureTests {
|
|
||||||
mac, err := CheckPayloadSignature(tt.payload, tt.secret, tt.signature)
|
|
||||||
if (err == nil) != tt.ok || mac != tt.mac {
|
|
||||||
t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, (err == nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && tt.mac != "" && strings.Contains(err.Error(), tt.mac) {
|
|
||||||
t.Errorf("error message should not disclose expected mac: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var checkPayloadSignature256Tests = []struct {
|
|
||||||
payload []byte
|
|
||||||
secret string
|
|
||||||
signature string
|
|
||||||
mac string
|
|
||||||
ok bool
|
|
||||||
}{
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89,sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
|
||||||
{[]byte(``), "secret", "f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169", "f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169", true},
|
|
||||||
// failures
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89,sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
|
||||||
{[]byte(`{"a": "z"}`), "", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "", false},
|
|
||||||
{[]byte(``), "secret", "XXX66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169", "f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckPayloadSignature256(t *testing.T) {
|
|
||||||
for _, tt := range checkPayloadSignature256Tests {
|
|
||||||
mac, err := CheckPayloadSignature256(tt.payload, tt.secret, tt.signature)
|
|
||||||
if (err == nil) != tt.ok || mac != tt.mac {
|
|
||||||
t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, (err == nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && tt.mac != "" && strings.Contains(err.Error(), tt.mac) {
|
|
||||||
t.Errorf("error message should not disclose expected mac: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var checkPayloadSignature512Tests = []struct {
|
|
||||||
payload []byte
|
|
||||||
secret string
|
|
||||||
signature string
|
|
||||||
mac string
|
|
||||||
ok bool
|
|
||||||
}{
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", true},
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha512=4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", true},
|
|
||||||
{[]byte(``), "secret", "b0e9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901", "b0e9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901", true},
|
|
||||||
// failures
|
|
||||||
{[]byte(`{"a": "z"}`), "secret", "74a0081f5b5988f4f3e8b8dd34dadc6291611f2e6260635a7e1535f8e95edb97ff520ba8b152e8ca5760ac42639854f3242e29efc81be73a8bf52d474d31ffea", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", false},
|
|
||||||
{[]byte(`{"a": "z"}`), "", "74a0081f5b5988f4f3e8b8dd34dadc6291611f2e6260635a7e1535f8e95edb97ff520ba8b152e8ca5760ac42639854f3242e29efc81be73a8bf52d474d31ffea", "", false},
|
|
||||||
{[]byte(``), "secret", "XXX9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901", "b0e9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckPayloadSignature512(t *testing.T) {
|
|
||||||
for _, tt := range checkPayloadSignature512Tests {
|
|
||||||
mac, err := CheckPayloadSignature512(tt.payload, tt.secret, tt.signature)
|
|
||||||
if (err == nil) != tt.ok || mac != tt.mac {
|
|
||||||
t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, (err == nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && tt.mac != "" && strings.Contains(err.Error(), tt.mac) {
|
|
||||||
t.Errorf("error message should not disclose expected mac: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var checkScalrSignatureTests = []struct {
|
var checkScalrSignatureTests = []struct {
|
||||||
description string
|
description string
|
||||||
headers map[string]interface{}
|
headers map[string]interface{}
|
||||||
|
@ -456,7 +362,7 @@ func TestHooksTemplateLoadFromFile(t *testing.T) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s := (*h.Match("webhook").TriggerRule.And)[0].Match.Secret
|
s := (*h.Match("webhook").TriggerRule.And)[0].Signature.Secret
|
||||||
if s != secret {
|
if s != secret {
|
||||||
t.Errorf("Expected secret of %q, got %q", secret, s)
|
t.Errorf("Expected secret of %q, got %q", secret, s)
|
||||||
}
|
}
|
||||||
|
@ -492,22 +398,12 @@ var matchRuleTests = []struct {
|
||||||
}{
|
}{
|
||||||
{"value", "", "", "z", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
{"value", "", "", "z", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
||||||
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
||||||
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
|
||||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
|
||||||
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
|
||||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
|
||||||
// failures
|
// failures
|
||||||
{"value", "", "", "X", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
{"value", "", "", "X", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
||||||
{"regex", "^X", "", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
{"regex", "^X", "", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
||||||
{"value", "", "2", "X", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
{"value", "", "2", "X", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
||||||
// errors
|
// errors
|
||||||
{"regex", "*", "", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
|
{"regex", "*", "", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
|
||||||
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
|
||||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
|
||||||
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
|
||||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
|
||||||
{"payload-hmac-sha512", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
|
||||||
{"payload-hash-sha512", "", "secret", "", "", Argument{"header", "a", "", false, nil}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
|
||||||
// IP whitelisting, valid cases
|
// IP whitelisting, valid cases
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||||
|
@ -542,6 +438,55 @@ func TestMatchRule(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var signatureRuleTests = []struct {
|
||||||
|
algorithm, secret string
|
||||||
|
sigSource Argument
|
||||||
|
stringToSign *Argument
|
||||||
|
headers, query, payload map[string]interface{}
|
||||||
|
body []byte
|
||||||
|
ok bool
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"sha1", "secret", Argument{"header", "a", "", false, nil}, nil, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true, false},
|
||||||
|
{"sha1", "secret", Argument{"header", "a", "", false, nil}, nil, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true, false},
|
||||||
|
{"sha256", "secret", Argument{"header", "a", "", false, nil}, nil, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), true, false},
|
||||||
|
{"sha256", "secret", Argument{"header", "a", "", false, nil}, nil, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), true, false},
|
||||||
|
// errors
|
||||||
|
{"sha1", "secret", Argument{"header", "a", "", false, nil}, nil, map[string]interface{}{"A": ""}, nil, nil, []byte{}, false, true}, // invalid hmac
|
||||||
|
{"sha1", "secret", Argument{"header", "a", "", false, nil}, nil, map[string]interface{}{"A": ""}, nil, nil, []byte{}, false, true}, // invalid hmac
|
||||||
|
{"sha256", "secret", Argument{"header", "a", "", false, nil}, nil, map[string]interface{}{"A": ""}, nil, nil, []byte{}, false, true}, // invalid hmac
|
||||||
|
{"sha256", "secret", Argument{"header", "a", "", false, nil}, nil, map[string]interface{}{"A": ""}, nil, nil, []byte{}, false, true}, // invalid hmac
|
||||||
|
{"sha512", "secret", Argument{"header", "a", "", false, nil}, nil, map[string]interface{}{"A": ""}, nil, nil, []byte{}, false, true}, // invalid hmac
|
||||||
|
{"sha512", "secret", Argument{"header", "a", "", false, nil}, nil, map[string]interface{}{"A": ""}, nil, nil, []byte{}, false, true}, // invalid hmac
|
||||||
|
|
||||||
|
// template to build custom string-to-sign
|
||||||
|
{"sha256", "secret", Argument{"header", "a", "", false, nil}, &Argument{"template", "{{ printf \"%s\\n%s\" .BodyText (.GetHeader \"x-id\") }}", "", false, nil}, map[string]interface{}{"A": "sha256=4f1d62e6e6de1e31537a5faefabeffd7dce115bc499584feefbf8db6d2da4027", "X-Id": "test"}, nil, nil, []byte(`{"a": "z"}`), true, false},
|
||||||
|
{"sha256", "secret", Argument{"header", "a", "", false, nil}, &Argument{"template", "{{ printf \"%s\\n%s\" .BodyText (.GetHeader \"x-id\") }}", "", false, nil}, map[string]interface{}{"A": "sha256=4f1d62e6e6de1e31537a5faefabeffd7dce115bc499584feefbf8db6d2da4027", "X-Id": "unexpected"}, nil, nil, []byte(`{"a": "z"}`), false, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignatureRule(t *testing.T) {
|
||||||
|
for i, tt := range signatureRuleTests {
|
||||||
|
if tt.stringToSign != nil {
|
||||||
|
// post process the argument, as it would have been if it were loaded from a hooks file
|
||||||
|
tt.stringToSign.postProcess()
|
||||||
|
}
|
||||||
|
r := SignatureRule{tt.algorithm, tt.secret, tt.sigSource, "", tt.stringToSign}
|
||||||
|
req := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Body: tt.body,
|
||||||
|
RawRequest: &http.Request{
|
||||||
|
RemoteAddr: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ok, err := r.Evaluate(req)
|
||||||
|
if ok != tt.ok || (err != nil) != tt.err {
|
||||||
|
t.Errorf("%d failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", i, r, tt.ok, tt.err, ok, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var andRuleTests = []struct {
|
var andRuleTests = []struct {
|
||||||
desc string // description of the test case
|
desc string // description of the test case
|
||||||
rule AndRule
|
rule AndRule
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue