Refactor Webhook + Add X-Hub-Signature (#16176)

This PR removes multiple unneeded fields from the `HookTask` struct and adds the two headers `X-Hub-Signature` and `X-Hub-Signature-256`.

## ⚠️ BREAKING ⚠️ 

* The `Secret` field is no longer passed as part of the payload.
* "Breaking" change (or fix?): The webhook history shows the real called url and not the url registered in the webhook (`deliver.go`@129).

Close #16115
Fixes #7788
Fixes #11755

Co-authored-by: zeripath <art27@cantab.net>
This commit is contained in:
KN4CK3R 2021-06-27 21:21:09 +02:00 committed by GitHub
parent 0b27b93728
commit 9b1b4b5433
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 130 additions and 179 deletions

View file

@ -6,8 +6,13 @@ package webhook
import (
"context"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
@ -26,27 +31,32 @@ import (
// Deliver deliver hook task
func Deliver(t *models.HookTask) error {
w, err := models.GetWebhookByID(t.HookID)
if err != nil {
return err
}
defer func() {
err := recover()
if err == nil {
return
}
// There was a panic whilst delivering a hook...
log.Error("PANIC whilst trying to deliver webhook[%d] for repo[%d] to %s Panic: %v\nStacktrace: %s", t.ID, t.RepoID, t.URL, err, log.Stack(2))
log.Error("PANIC whilst trying to deliver webhook[%d] for repo[%d] to %s Panic: %v\nStacktrace: %s", t.ID, t.RepoID, w.URL, err, log.Stack(2))
}()
t.IsDelivered = true
var req *http.Request
var err error
switch t.HTTPMethod {
switch w.HTTPMethod {
case "":
log.Info("HTTP Method for webhook %d empty, setting to POST as default", t.ID)
fallthrough
case http.MethodPost:
switch t.ContentType {
switch w.ContentType {
case models.ContentTypeJSON:
req, err = http.NewRequest("POST", t.URL, strings.NewReader(t.PayloadContent))
req, err = http.NewRequest("POST", w.URL, strings.NewReader(t.PayloadContent))
if err != nil {
return err
}
@ -57,16 +67,15 @@ func Deliver(t *models.HookTask) error {
"payload": []string{t.PayloadContent},
}
req, err = http.NewRequest("POST", t.URL, strings.NewReader(forms.Encode()))
req, err = http.NewRequest("POST", w.URL, strings.NewReader(forms.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
case http.MethodGet:
u, err := url.Parse(t.URL)
u, err := url.Parse(w.URL)
if err != nil {
return err
}
@ -78,31 +87,48 @@ func Deliver(t *models.HookTask) error {
return err
}
case http.MethodPut:
switch t.Typ {
switch w.Type {
case models.MATRIX:
req, err = getMatrixHookRequest(t)
req, err = getMatrixHookRequest(w, t)
if err != nil {
return err
}
default:
return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, t.HTTPMethod)
return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, w.HTTPMethod)
}
default:
return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, t.HTTPMethod)
return fmt.Errorf("Invalid http method for webhook: [%d] %v", t.ID, w.HTTPMethod)
}
var signatureSHA1 string
var signatureSHA256 string
if len(w.Secret) > 0 {
sig1 := hmac.New(sha1.New, []byte(w.Secret))
sig256 := hmac.New(sha256.New, []byte(w.Secret))
_, err = io.MultiWriter(sig1, sig256).Write([]byte(t.PayloadContent))
if err != nil {
log.Error("prepareWebhooks.sigWrite: %v", err)
}
signatureSHA1 = hex.EncodeToString(sig1.Sum(nil))
signatureSHA256 = hex.EncodeToString(sig256.Sum(nil))
}
req.Header.Add("X-Gitea-Delivery", t.UUID)
req.Header.Add("X-Gitea-Event", t.EventType.Event())
req.Header.Add("X-Gitea-Signature", t.Signature)
req.Header.Add("X-Gitea-Signature", signatureSHA256)
req.Header.Add("X-Gogs-Delivery", t.UUID)
req.Header.Add("X-Gogs-Event", t.EventType.Event())
req.Header.Add("X-Gogs-Signature", t.Signature)
req.Header.Add("X-Gogs-Signature", signatureSHA256)
req.Header.Add("X-Hub-Signature", "sha1="+signatureSHA1)
req.Header.Add("X-Hub-Signature-256", "sha256="+signatureSHA256)
req.Header["X-GitHub-Delivery"] = []string{t.UUID}
req.Header["X-GitHub-Event"] = []string{t.EventType.Event()}
// Record delivery information.
t.RequestInfo = &models.HookRequest{
Headers: map[string]string{},
URL: req.URL.String(),
HTTPMethod: req.Method,
Headers: map[string]string{},
}
for k, vals := range req.Header {
t.RequestInfo.Headers[k] = strings.Join(vals, ",")
@ -125,11 +151,6 @@ func Deliver(t *models.HookTask) error {
}
// Update webhook last delivery status.
w, err := models.GetWebhookByID(t.HookID)
if err != nil {
log.Error("GetWebhookByID: %v", err)
return
}
if t.IsSucceed {
w.LastStatus = models.HookStatusSucceed
} else {