Repository: jenkins-x/lighthouse
Pull request #1350 (2021-07-08 11:37:15)

fix: support hmac signature on the poller


Patch:
diff --git a/charts/lighthouse/templates/poller-deployment.yaml b/charts/lighthouse/templates/poller-deployment.yaml
index b3e05bbe..6b0004f2 100644
--- a/charts/lighthouse/templates/poller-deployment.yaml
+++ b/charts/lighthouse/templates/poller-deployment.yaml
@@ -40,7 +40,7 @@ spec:
           - "--namespace={{ .Release.Namespace }}"
         ports:
           - name: http
-            containerPort: {{ .Values.poller.service.internalPort }}
+            containerPort: {{ .Values.poller.internalPort }}
             protocol: TCP
         livenessProbe:
           httpGet:
@@ -74,6 +74,12 @@ spec:
               name: lighthouse-oauth-token
               key: oauth
 {{- end }}
+        - name: "HMAC_TOKEN"
+          valueFrom:
+            secretKeyRef:
+              name: "lighthouse-hmac-token"
+              key: hmac
+              optional: {{ not .Values.hmacTokenEnabled }}
         - name: "JX_LOG_FORMAT"
           value: "{{ .Values.logFormat }}"
         - name: "LOGRUS_FORMAT"
diff --git a/charts/lighthouse/values.yaml b/charts/lighthouse/values.yaml
index 2fd7c371..2abfbbf4 100644
--- a/charts/lighthouse/values.yaml
+++ b/charts/lighthouse/values.yaml
@@ -349,7 +349,7 @@ keeper:
   # keeper.env -- Lets you define keeper specific environment variables
   env: {}
 
-  # keeper.service -- Service settings for the webhooks controller
+  # keeper.service -- Service settings for the keeper controller
   service:
     type: ClusterIP
     externalPort: 80
@@ -442,6 +442,9 @@ poller:
   probe:
     path: /
 
+  # keeper.internalPort -- The internal port used to view metrics etc
+  internalPort: 8888
+
   # poller.livenessProbe -- Liveness probe configuration
   livenessProbe:
     initialDelaySeconds: 120
diff --git a/cmd/poller/main.go b/cmd/poller/main.go
index 453652b1..4457160d 100644
--- a/cmd/poller/main.go
+++ b/cmd/poller/main.go
@@ -32,6 +32,8 @@ type options struct {
 	botName       string
 	gitServerURL  string
 	gitKind       string
+	gitToken      string
+	hmacToken     string
 	namespace     string
 	repoNames     string
 	hookEndpoint  string
@@ -41,6 +43,9 @@ type options struct {
 }
 
 func (o *options) Validate() error {
+	if o.hmacToken == "" {
+		o.hmacToken = os.Getenv("HMAC_TOKEN")
+	}
 	return nil
 }
 
@@ -121,10 +126,13 @@ func main() {
 	if gitKind == "" {
 		gitKind = util.GitKind(configAgent.Config)
 	}
-	gitToken, err := util.GetSCMToken(gitKind)
+	o.gitToken, err = util.GetSCMToken(gitKind)
 	if err != nil {
 		logrus.WithError(err).Fatal("Error creating Poller controller.")
 	}
+	if o.gitToken == "" {
+		logrus.WithError(err).Fatal("no git token.")
+	}
 
 	gitCloneUser := o.botName
 
@@ -135,7 +143,7 @@ func main() {
 
 	configureOpts := func(opts *gitv2.ClientFactoryOpts) {
 		opts.Token = func() []byte {
-			return []byte(gitToken)
+			return []byte(o.gitToken)
 		}
 		opts.GitUser = func() (name, email string, err error) {
 			name = gitCloneUser
@@ -232,6 +240,11 @@ func (o *options) notifier(hook *scm.WebhookWrapper) error {
 	req, err := http.NewRequest("POST", o.hookEndpoint, bytes.NewBuffer(data))
 	req.Header.Set("Content-Type", "application/json")
 
+	if o.hmacToken != "" {
+		sig := util.CreateHMACHeader(data, o.hmacToken)
+		req.Header.Set("X-Hub-Signature", sig)
+	}
+
 	client := &http.Client{}
 	resp, err := client.Do(req)
 	if err != nil {
diff --git a/pkg/util/hmac.go b/pkg/util/hmac.go
new file mode 100644
index 00000000..6facfdba
--- /dev/null
+++ b/pkg/util/hmac.go
@@ -0,0 +1,14 @@
+package util
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/hex"
+)
+
+// CreateHMACHeader creates the hmac token
+func CreateHMACHeader(data []byte, key string) string {
+	sig := hmac.New(sha256.New, []byte(key))
+	sig.Write(data)
+	return "sha256=" + hex.EncodeToString(sig.Sum(nil))
+}
diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go
index 6cc40dd6..7c388442 100644
--- a/pkg/webhook/webhook.go
+++ b/pkg/webhook/webhook.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"github.com/jenkins-x/go-scm/pkg/hmac"
 	"io/ioutil"
 	"net/http"
 	"net/url"
@@ -149,7 +150,23 @@ func (o *WebhooksController) HandlePollingRequests(w http.ResponseWriter, r *htt
 		if err != nil {
 			return nil, errors.Wrapf(err, "failed to unmarshal WebhookWrapper payload")
 		}
-		return wh.ToWebhook()
+		hook, err := wh.ToWebhook()
+		if err != nil {
+			return nil, err
+		}
+
+		key, err := o.secretFn(hook)
+		if err != nil {
+			return hook, err
+		} else if key == "" {
+			return hook, nil
+		}
+
+		sig := r.Header.Get("X-Hub-Signature")
+		if !hmac.ValidatePrefix(data, []byte(key), sig) {
+			return hook, scm.ErrSignatureInvalid
+		}
+		return hook, err
 	})
 }
 


Test patch:
diff --git a/pkg/util/hmac_test.go b/pkg/util/hmac_test.go
new file mode 100644
index 00000000..923a3d38
--- /dev/null
+++ b/pkg/util/hmac_test.go
@@ -0,0 +1,20 @@
+package util_test
+
+import (
+	"github.com/jenkins-x/go-scm/pkg/hmac"
+	"testing"
+
+	"github.com/jenkins-x/lighthouse/pkg/util"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCreateHMACHeader(t *testing.T) {
+	data := []byte(`{ "text": "hello world" }`)
+
+	key := "5c50537bc2a8cb57ad578ba90318392fd71cfa44c"
+	header := util.CreateHMACHeader(data, key)
+
+	assert.True(t, hmac.ValidatePrefix(data, []byte(key), header), "failed to validate header of %s", header)
+
+	t.Logf("got generated header %s\n", header)
+}