Skip to content

Commit

Permalink
Bitbucket-server: adds payload validation using webhook secret
Browse files Browse the repository at this point in the history
this enables webhook secret validation for bitbucket-server.
user need to defined a secret and reference in repo cr, pac will
look for signature in event header and do a hmac validation of
payload and match it with hash in header.

Signed-off-by: Shivam Mukhade <[email protected]>
  • Loading branch information
Shivam Mukhade committed Apr 5, 2022
1 parent da52f50 commit 7b0d636
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 4 deletions.
41 changes: 37 additions & 4 deletions pkg/provider/bitbucketserver/bitbucketserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ package bitbucketserver

import (
"context"
"crypto/hmac"

//nolint
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
"net/http"
"path/filepath"
"strings"
Expand Down Expand Up @@ -31,13 +39,38 @@ type Provider struct {
}

func (v *Provider) Validate(ctx context.Context, params *params.Run, event *info.Event) error {
signature := event.Request.Header.Get("X-Hub-Signature")
key := event.Provider.WebhookSecret
if event.Provider.WebhookSecret == "" && signature != "" {
return fmt.Errorf("bitbucket-server failed validaton: failed to find webhook secret")
}

sigParts := strings.Split(signature, "=")

var hashFunc func() hash.Hash
switch sigParts[0] {
case "sha1":
hashFunc = sha1.New
case "sha256":
hashFunc = sha256.New
case "sha512":
hashFunc = sha512.New
default:
return fmt.Errorf("bitbucket-server failed validaton: unknown hash type prefix: %q", sigParts[0])
}

hash := hmac.New(hashFunc, []byte(key))
if _, err := hash.Write(event.Request.Payload); err != nil {
return fmt.Errorf("bitbucket-server failed validaton: cannot compute the HMAC for request: %w", err)
}
expectedHash := hex.EncodeToString(hash.Sum(nil))

if sigParts[1] != expectedHash {
return fmt.Errorf("bitbucket-server failed validaton: payload hash doesn't match with computed hash")
}
return nil
}

// func (v *Provider) ParseEventType(request *http.Request, event *info.Event) error {
// panic("implement me")
// }

// sanitizeTitle make sure we only get the tile by remove everything after \n.
func sanitizeTitle(s string) string {
return strings.Split(s, "\n")[0]
Expand Down
75 changes: 75 additions & 0 deletions pkg/provider/bitbucketserver/bitbucketserver_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package bitbucketserver

import (
"context"
"crypto/hmac"

//nolint
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
"net/http"
"path/filepath"
"strings"
Expand Down Expand Up @@ -440,3 +449,69 @@ func getLogger() *zap.SugaredLogger {
logger := zap.New(observer).Sugar()
return logger
}

func TestValidate(t *testing.T) {
tests := []struct {
name string
wantErr bool
secret string
payload string
hashFunc func() hash.Hash
prefixheader string
}{
{
name: "secret missing",
secret: "",
payload: `{"hello": "moto"}`,
hashFunc: sha256.New,
prefixheader: "sha256",
wantErr: true,
},
{
name: "good/SHA256Signature",
secret: "secrete",
payload: `{"hello": "moto"}`,
hashFunc: sha256.New,
prefixheader: "sha256",
},
{
name: "good/SHA1Signature",
secret: "secrete",
payload: `{"ola": "amigo"}`,
hashFunc: sha1.New,
prefixheader: "sha1",
},
{
name: "bad/signature",
payload: `{"ciao": "ragazzo"}`,
hashFunc: sha256.New,
prefixheader: "sha1",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := &Provider{}

hmac := hmac.New(tt.hashFunc, []byte(tt.secret))
hmac.Write([]byte(tt.payload))
signature := hex.EncodeToString(hmac.Sum(nil))

httpHeader := http.Header{}
httpHeader.Add("X-Hub-Signature", fmt.Sprintf("%s=%s", tt.prefixheader, signature))

event := info.NewEvent()
event.Request = &info.Request{
Header: httpHeader,
Payload: []byte(tt.payload),
}
event.Provider = &info.Provider{
WebhookSecret: tt.secret,
}

if err := v.Validate(context.TODO(), nil, event); (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

0 comments on commit 7b0d636

Please sign in to comment.