Skip to content

Add support for X-Hub-Signature headers in webhooks #8473

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

Closed
wants to merge 7 commits into from
Closed
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
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ var migrations = []Migration{
NewMigration("add repo_admin_change_team_access to user", addRepoAdminChangeTeamAccessColumnForUser),
// v98 -> v99
NewMigration("add original author name and id on migrated release", addOriginalAuthorOnMigratedReleases),
// v99 -> v100
NewMigration("rename signature column to support sha1 and sha256 webhook signatures", specifyWebhookSignatureType),
}

// Migrate database to current version
Expand Down
30 changes: 30 additions & 0 deletions models/migrations/v99.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
"fmt"

"github.com/go-xorm/xorm"
)

func specifyWebhookSignatureType(x *xorm.Engine) error {
var err error

switch x.Dialect().DriverName() {
case "mysql":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things. 1. Rename columns is tricky (for example SQLite also needs to be added to this migration) so perhaps instead of a rename we keep signature column with same content we just have the strict field be renamed, and then add the new sha column. 2. Could you add the adding of the new column to this migration, while it is added by the default migration,sometimes we’ve been bitten by changing a column that doesn’t yet exist so usually we request explicit add of new columns.

Apologies if I am unclear, I am on mobile and can give better details when I get to a computer. Please ask any questions.

_, err = x.Exec("ALTER TABLE webhook CHANGE COLUMN signature signature_sha1 text")
case "postgres":
_, err = x.Exec("ALTER TABLE webhook RENAME COLUMN signature TO signature_sha1")
case "mssql":
_, err = x.Exec("sp_rename @objname = 'webhook.signature', @newname = 'signature_sha1', @objtype = 'COLUMN'")
}

if err != nil {
return fmt.Errorf("Error renaming webhook signature column to signature_sha1: %v", err)
}

return nil
}
77 changes: 48 additions & 29 deletions models/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package models

import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
Expand Down Expand Up @@ -106,21 +107,22 @@ const (

// Webhook represents a web hook object.
type Webhook struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
OrgID int64 `xorm:"INDEX"`
URL string `xorm:"url TEXT"`
Signature string `xorm:"TEXT"`
HTTPMethod string `xorm:"http_method"`
ContentType HookContentType
Secret string `xorm:"TEXT"`
Events string `xorm:"TEXT"`
*HookEvent `xorm:"-"`
IsSSL bool `xorm:"is_ssl"`
IsActive bool `xorm:"INDEX"`
HookTaskType HookTaskType
Meta string `xorm:"TEXT"` // store hook-specific attributes
LastStatus HookStatus // Last delivery status
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
OrgID int64 `xorm:"INDEX"`
URL string `xorm:"url TEXT"`
SignatureSha1 string `xorm:"TEXT"`
SignatureSha256 string `xorm:"TEXT"`
HTTPMethod string `xorm:"http_method"`
ContentType HookContentType
Secret string `xorm:"TEXT"`
Events string `xorm:"TEXT"`
*HookEvent `xorm:"-"`
IsSSL bool `xorm:"is_ssl"`
IsActive bool `xorm:"INDEX"`
HookTaskType HookTaskType
Meta string `xorm:"TEXT"` // store hook-specific attributes
LastStatus HookStatus // Last delivery status

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
Expand Down Expand Up @@ -573,6 +575,7 @@ type HookTask struct {
Type HookTaskType
URL string `xorm:"TEXT"`
Signature string `xorm:"TEXT"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a database migration

This comment was marked as outdated.

SignatureSha1 string `xorm:"TEXT"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SignatureSha1 string `xorm:"TEXT"`
Signature string `xorm:"TEXT"`

api.Payloader `xorm:"-"`
PayloadContent string `xorm:"TEXT"`
HTTPMethod string `xorm:"http_method"`
Expand Down Expand Up @@ -740,7 +743,21 @@ func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType,
payloader = p
}

var signature string
var signatureSha1 string
if len(w.Secret) > 0 {
data, err := payloader.JSONPayload()
if err != nil {
log.Error("prepareWebhooks.JSONPayload: %v", err)
}
sig := hmac.New(sha1.New, []byte(w.Secret))
_, err = sig.Write(data)
if err != nil {
log.Error("prepareWebhooks.sigWrite: %v", err)
}
signatureSha1 = hex.EncodeToString(sig.Sum(nil))
}

var signatureSha256 string
if len(w.Secret) > 0 {
data, err := payloader.JSONPayload()
if err != nil {
Expand All @@ -751,20 +768,21 @@ func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType,
if err != nil {
log.Error("prepareWebhooks.sigWrite: %v", err)
}
signature = hex.EncodeToString(sig.Sum(nil))
signatureSha256 = hex.EncodeToString(sig.Sum(nil))
}

if err = createHookTask(e, &HookTask{
RepoID: repo.ID,
HookID: w.ID,
Type: w.HookTaskType,
URL: w.URL,
Signature: signature,
Payloader: payloader,
HTTPMethod: w.HTTPMethod,
ContentType: w.ContentType,
EventType: event,
IsSSL: w.IsSSL,
RepoID: repo.ID,
HookID: w.ID,
Type: w.HookTaskType,
URL: w.URL,
SignatureSha1: signatureSha1,
SignatureSha256: signatureSha256,
Payloader: payloader,
HTTPMethod: w.HTTPMethod,
ContentType: w.ContentType,
EventType: event,
IsSSL: w.IsSSL,
}); err != nil {
return fmt.Errorf("CreateHookTask: %v", err)
}
Expand Down Expand Up @@ -852,10 +870,11 @@ func (t *HookTask) deliver() error {

req.Header.Add("X-Gitea-Delivery", t.UUID)
req.Header.Add("X-Gitea-Event", string(t.EventType))
req.Header.Add("X-Gitea-Signature", t.Signature)
req.Header.Add("X-Gitea-Signature", t.SignatureSha256)
req.Header.Add("X-Gogs-Delivery", t.UUID)
req.Header.Add("X-Gogs-Event", string(t.EventType))
req.Header.Add("X-Gogs-Signature", t.Signature)
req.Header.Add("X-Gogs-Signature", t.SignatureSha256)
req.Header.Add("X-Hub-Signature", fmt.Sprintf("sha1=%v", t.SignatureSha1))
req.Header["X-GitHub-Delivery"] = []string{t.UUID}
req.Header["X-GitHub-Event"] = []string{string(t.EventType)}

Expand Down