forked from rancher/rancher
-
Notifications
You must be signed in to change notification settings - Fork 0
/
github.go
116 lines (98 loc) · 3.55 KB
/
github.go
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
115
116
package drivers
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/google/go-github/github"
"github.com/pkg/errors"
"github.com/rancher/rancher/pkg/controllers/user/pipeline/utils"
"github.com/rancher/types/apis/management.cattle.io/v3"
"github.com/sirupsen/logrus"
"io/ioutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
"strings"
)
const GithubWebhookHeader = "X-GitHub-Event"
type GithubDriver struct {
Pipelines v3.PipelineInterface
PipelineExecutions v3.PipelineExecutionInterface
}
func (g GithubDriver) Execute(req *http.Request) (int, error) {
var signature string
if signature = req.Header.Get("X-Hub-Signature"); len(signature) == 0 {
return http.StatusUnprocessableEntity, errors.New("github webhook missing signature")
}
event := req.Header.Get(GithubWebhookHeader)
if event == "ping" {
return http.StatusOK, nil
} else if event != "push" {
//or "pull_request"
return http.StatusUnprocessableEntity, fmt.Errorf("not trigger for event:%s", event)
}
pipelineID := req.URL.Query().Get("pipelineId")
parts := strings.Split(pipelineID, ":")
if len(parts) < 0 {
return http.StatusUnprocessableEntity, fmt.Errorf("pipeline id '%s' is not valid", pipelineID)
}
ns := parts[0]
id := parts[1]
pipeline, err := g.Pipelines.GetNamespaced(ns, id, metav1.GetOptions{})
if err != nil {
return http.StatusInternalServerError, err
}
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return http.StatusUnprocessableEntity, err
}
if match := VerifyGithubWebhookSignature([]byte(pipeline.Status.Token), signature, body); !match {
return http.StatusUnprocessableEntity, errors.New("github webhook invalid signature")
}
if pipeline.Status.PipelineState == "inactive" {
return http.StatusUnavailableForLegalReasons, errors.New("Pipeline is not active")
}
payload := &github.WebHookPayload{}
if err := json.Unmarshal(body, payload); err != nil {
return http.StatusUnprocessableEntity, err
}
if len(pipeline.Spec.Stages) < 1 || len(pipeline.Spec.Stages[0].Steps) < 1 || pipeline.Spec.Stages[0].Steps[0].SourceCodeConfig == nil {
return http.StatusInternalServerError, errors.New("Error invalid pipeline definition")
}
if !VerifyRef(pipeline.Spec.Stages[0].Steps[0].SourceCodeConfig, payload.GetRef()) {
return http.StatusUnprocessableEntity, errors.New("Error Ref is not match")
}
branch := strings.TrimLeft(payload.GetRef(), "refs/heads/")
logrus.Debugf("receieve github webhook, triggered '%s' on branch '%s'", pipeline.Spec.DisplayName, branch)
if _, err := utils.GenerateExecution(g.Pipelines, g.PipelineExecutions, pipeline, utils.TriggerTypeWebhook, "", branch); err != nil {
return http.StatusInternalServerError, err
}
return http.StatusOK, nil
}
func VerifyGithubWebhookSignature(secret []byte, signature string, body []byte) bool {
const signaturePrefix = "sha1="
const signatureLength = 45 // len(SignaturePrefix) + len(hex(sha1))
if len(signature) != signatureLength || !strings.HasPrefix(signature, signaturePrefix) {
return false
}
actual := make([]byte, 20)
hex.Decode(actual, []byte(signature[5:]))
computed := hmac.New(sha1.New, secret)
computed.Write(body)
return hmac.Equal([]byte(computed.Sum(nil)), actual)
}
func VerifyRef(config *v3.SourceCodeConfig, refs string) bool {
branch := strings.TrimLeft(refs, "refs/heads/")
if config.BranchCondition == "all" {
return true
} else if config.BranchCondition == "except" {
if config.Branch == branch {
return false
}
return true
} else if config.Branch == branch {
return true
}
return false
}