Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drone.io global webhook signature #455

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/Hook-Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,56 @@ Values in the request body can be accessed in the command or to the match rule b
]
```

## Icoming Drone.io hook
```json
[
{
"id": "redeploy-webhook",
"execute-command": "/home/adnan/deploy-go-webhook.sh",
"command-working-directory": "/home/adnan/go",
"response-message": "Executing deploy script",
"trigger-rule":
{
"and": [
{
"match":
{
"type": "value",
"value": "build",
"parameter": {
"source": "header",
"name": "X-Drone-Event"
}
}
},
{
"match":
{
"type": "value",
"value": "success",
"parameter": {
"source": "payload",
"name": "build.status"
}
}
},
{
"match":
{
"type": "payload-hmac-sha256",
"secret": "600a2774d248847509ba27482330d513",
"parameter": {
"source": "header",
"name": "Signature"
}
}
}
]
}
}
]
```

## A simple webhook with a secret key in GET query

__Not recommended in production due to low security__
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/adnanh/webhook
go 1.13

require (
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e
github.com/clbanning/mxj v1.8.4
github.com/dustin/go-humanize v1.0.0
github.com/fsnotify/fsnotify v1.4.7 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc=
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
Expand Down
42 changes: 42 additions & 0 deletions internal/hook/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import (
"strings"
"text/template"
"time"
"net/http"

"github.com/ghodss/yaml"
"github.com/99designs/httpsignatures-go"
)

// Constants used to specify the parameter source
Expand Down Expand Up @@ -212,6 +214,41 @@ func CheckPayloadSignature512(payload []byte, secret string, signature string) (
return ValidateMAC(payload, hmac.New(sha512.New, []byte(secret)), signatures)
}

func CheckHmacSHA256(headers map[string]interface{}, body []byte, signingKey string) (bool, error) {
// Check headers for relevant parts
if _,ok := headers["Signature"]; !ok {
return false, errors.New("Missing Signature header")
}
if _,ok := headers["Digest"]; !ok {
return false, errors.New("Missing Digest header")
}
if _,ok := headers["Date"]; !ok {
return false, errors.New("Missing Date header")
}
if signingKey == "" {
return false, errors.New("Secret key is required and cannot be empty")
}
headerSignature := headers["Signature"].(string)
headerDate := headers["Date"].(string)
headerDigest := headers["Digest"].(string)

tmpHttpRequest := &http.Request{
Header: http.Header{
"Date": []string{headerDate},
"Digest": []string{headerDigest},
},
}
sig,err := httpsignatures.FromString(headerSignature)
if err != nil {
return false, errors.New("httpsignature error")
}
if !sig.IsValid(signingKey, tmpHttpRequest) {
return false, errors.New("Invalid Signature")
}

return true, nil
}

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 {
Expand Down Expand Up @@ -835,6 +872,7 @@ const (
MatchHashSHA256 string = "payload-hash-sha256"
MatchHashSHA512 string = "payload-hash-sha512"
IPWhitelist string = "ip-whitelist"
MatchHmacSHA256 string = "payload-hmac-sha256"
ScalrSignature string = "scalr-signature"
)

Expand All @@ -847,6 +885,10 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
return CheckScalrSignature(*headers, *body, r.Secret, true)
}

if r.Type == MatchHmacSHA256 {
return CheckHmacSHA256(*headers, *body, r.Secret)
}

arg, err := r.Parameter.Get(headers, query, payload)
if err == nil {
switch r.Type {
Expand Down
98 changes: 98 additions & 0 deletions internal/hook/hook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,104 @@ func TestCheckScalrSignature(t *testing.T) {
}
}


var checkHmacSHA256SignatureTests = []struct {
description string
headers map[string]interface{}
payload []byte
secret string
expectedSignature string
ok bool
}{
{
"Valid Signature",
map[string]interface{}{
"Date": "Thu, 10 Sep 2020 19:09:14 GMT",
"Digest": "SHA-256=HQ0wDM4daEmV1R+8SD2bTXu5TPUn/EhMdNyfQL3G3sU=",
"Signature": `keyId="hmac-key",algorithm="hmac-sha256",signature="JD2+OsbOqw8DBil5n0a8XVIzvMYXLODcnzJ+R7aieT4=",headers="date digest"`,
},
[]byte(`"x", "y"`),
"600a2774d248847509ba27482330d513", "", true,
},
{
"Wrong Signature",
map[string]interface{}{
"Date": "Thu, 10 Sep 2020 19:09:14 GMT",
"Digest": "SHA-256=HQ0wDM4daEmV1R+8SD2bTXu5TPUn/EhMdNyfQL3G3sU=",
"Signature": `keyId="hmac-key",algorithm="hmac-sha256",signature="JD2+OsbOqw8DBil5n0a8XVIzvMYXLODcnzJ+R7aieT4",headers="date digest"`,
},
[]byte(`"x", "y"`),
"600a2774d248847509ba27482330d513", "Invalid Signature", false,
},
{
"Wrong Signature format upstream error",
map[string]interface{}{
"Date": "Thu, 10 Sep 2020 19:09:14 GMT",
"Digest": "SHA-256=HQ0wDM4daEmV1R+8SD2bTXu5TPUn/EhMdNyfQL3G3sU=",
"Signature": `algorithm="hmac-sha256",signature="JD2+OsbOqw8DBil5n0a8XVIzvMYXLODcnzJ+R7aieT4",headers="date digest"`,
},
[]byte(`"x", "y"`),
"600a2774d248847509ba27482330d513", "httpsignature error", false,
},
{
"Missing Date header",
map[string]interface{}{
"Digest": "SHA-256=HQ0wDM4daEmV1R+8SD2bTXu5TPUn/EhMdNyfQL3G3sU=",
"Signature": `keyId="hmac-key",algorithm="hmac-sha256",signature="JD2+OsbOqw8DBil5n0a8XVIzvMYXLODcnzJ+R7aieT4=",headers="date digest"`,
},
[]byte(`"x", "y"`), "600a2774d248847509ba27482330d513", "Missing Date header", false,
},
{
"Missing Digest header",
map[string]interface{}{
"Date": "Thu, 10 Sep 2020 19:09:14 GMT",
"Signature": `keyId="hmac-key",algorithm="hmac-sha256",signature="JD2+OsbOqw8DBil5n0a8XVIzvMYXLODcnzJ+R7aieT4=",headers="date digest"`,
},
[]byte(`"x", "y"`), "600a2774d248847509ba27482330d513", "Missing Digest header", false,
},
{
"Missing Secret",
map[string]interface{}{
"Date": "Thu, 10 Sep 2020 19:09:14 GMT",
"Digest": "SHA-256=HQ0wDM4daEmV1R+8SD2bTXu5TPUn/EhMdNyfQL3G3sU=",
"Signature": `keyId="hmac-key",algorithm="hmac-sha256",signature="JD2+OsbOqw8DBil5n0a8XVIzvMYXLODcnzJ+R7aieT4=",headers="date digest"`,
},
[]byte(`"x", "y"`), "", "Secret key is required and cannot be empty", false,
},
{
"Incorrect Secret",
map[string]interface{}{
"Date": "Thu, 10 Sep 2020 19:09:14 GMT",
"Digest": "SHA-256=HQ0wDM4daEmV1R+8SD2bTXu5TPUn/EhMdNyfQL3G3sU=",
"Signature": `keyId="hmac-key",algorithm="hmac-sha256",signature="JD2+OsbOqw8DBil5n0a8XVIzvMYXLODcnzJ+R7aieT4=",headers="date digest"`,
},
[]byte(`"x", "y"`), "600a2774d248847509ba27482330d51", "Invalid Signature", false,
},
{
"Incorrect Digest",
map[string]interface{}{
"Date": "Thu, 10 Sep 2020 19:09:14 GMT",
"Digest": "SHA-256=HQ0wDM4daEmV1R+8SD2bTXu5TPUn/EhMdNyfQL3G3sU",
"Signature": `keyId="hmac-key",algorithm="hmac-sha256",signature="JD2+OsbOqw8DBil5n0a8XVIzvMYXLODcnzJ+R7aieT4=",headers="date digest"`,
},
[]byte(`"x", "y"`), "600a2774d248847509ba27482330d513", "Invalid Signature", false,
},
}

func TestCheckHmacSHA256Signature(t *testing.T) {
for _, testCase := range checkHmacSHA256SignatureTests{
valid, err := CheckHmacSHA256(testCase.headers, testCase.payload, testCase.secret)
if valid != testCase.ok {
t.Errorf("failed to check hmac256 signature for test case: %s\nexpected ok:%#v, got ok:%#v}",
testCase.description, testCase.ok, valid)
}

if err != nil && err.Error() != testCase.expectedSignature {
t.Errorf("unexpected error message: %s on test case %s", err, testCase.description)
}
}
}

var checkIPWhitelistTests = []struct {
addr string
ipRange string
Expand Down