-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwebhook.go
More file actions
114 lines (97 loc) · 2.77 KB
/
webhook.go
File metadata and controls
114 lines (97 loc) · 2.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io"
"log/slog"
"net/http"
"github.com/utilitywarehouse/git-mirror/repopool"
)
type GitHubEvent struct {
Repository struct {
Name string `json:"name"`
Owner struct {
Login string `json:"login"`
} `json:"owner"`
HtmlURL string `json:"html_url"`
GitURL string `json:"git_url"`
} `json:"repository"`
// The full git ref that was pushed. Example: refs/heads/main or refs/tags/v3.14.1.
Ref string `json:"ref"`
// The SHA of the most recent commit on ref before the push.
Before string `json:"before"`
// The SHA of the most recent commit on ref after the push.
After string `json:"after"`
}
type GithubWebhookHandler struct {
repoPool *repopool.RepoPool
secret string
skipSigValidation bool
log *slog.Logger
}
func (wh *GithubWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusBadRequest)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
wh.log.Error("cannot read request body", "error", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if !wh.skipSigValidation {
if !wh.isValidSignature(body, r.Header.Get("X-Hub-Signature-256")) {
wh.log.Error("invalid signature")
w.WriteHeader(http.StatusBadRequest)
return
}
}
event := r.Header.Get("X-GitHub-Event")
var payload GitHubEvent
if err := json.Unmarshal(body, &payload); err != nil {
wh.log.Error("cannot unmarshal json payload", "error", err)
w.WriteHeader(http.StatusBadRequest)
return
}
// The ping event is a confirmation from GitHub that
// the webhook is configured correctly.
if event == "ping" {
w.Write([]byte("pong"))
return
}
// only process 'push' event but but return ok for all events to mark
// successful delivery
if event == "push" {
go wh.processPushEvent(payload)
return
}
}
func (wh *GithubWebhookHandler) isValidSignature(message []byte, signature string) bool {
if signature == "" {
return false
}
return hmac.Equal([]byte(signature), []byte(wh.computeHMAC(message, wh.secret)))
}
func (wh *GithubWebhookHandler) computeHMAC(message []byte, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
if _, err := mac.Write(message); err != nil {
wh.log.Error("cannot compute hmac for request", "error", err)
return ""
}
// GH adds `sha256=` prefix in header value
return "sha256=" + hex.EncodeToString(mac.Sum(nil))
}
func (wh *GithubWebhookHandler) processPushEvent(event GitHubEvent) {
err := wh.repoPool.QueueMirrorRun(event.Repository.HtmlURL)
if err != nil {
if errors.Is(err, repopool.ErrNotExist) {
return
}
wh.log.Error("unable to process push event", "repo", event.Repository.HtmlURL, "err", err)
return
}
}