Add webhook handler to api server
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Arsen Musayelyan 2022-12-20 21:46:04 -08:00
parent b7922f9687
commit 891df91fee
3 changed files with 124 additions and 3 deletions

View File

@ -0,0 +1,16 @@
package main
import (
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/types"
)
var cfg types.Config
func init() {
err := config.Decode(&cfg)
if err != nil {
log.Fatal("Error decoding config file").Err(err).Send()
}
}

View File

@ -1,6 +1,7 @@
package main
import (
"context"
"flag"
"net"
"net/http"
@ -10,6 +11,7 @@ import (
"go.arsenm.dev/logger"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/api"
"go.arsenm.dev/lure/internal/repos"
)
func init() {
@ -17,6 +19,8 @@ func init() {
}
func main() {
ctx := context.Background()
addr := flag.String("a", ":8080", "Listen address for API server")
logFile := flag.String("l", "", "Output file for JSON log")
flag.Parse()
@ -31,10 +35,22 @@ func main() {
log.Logger = logger.NewMulti(log.Logger, logger.NewJSON(fl))
}
srv := api.NewAPIServer(
err := repos.Pull(ctx, gdb, cfg.Repos)
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
}
sigCh := make(chan struct{}, 200)
go repoPullWorker(ctx, sigCh)
var handler http.Handler
handler = api.NewAPIServer(
lureWebAPI{db: gdb},
twirp.WithServerPathPrefix(""),
)
handler = allowAllCORSHandler(handler)
handler = handleWebhook(handler, sigCh)
ln, err := net.Listen("tcp", *addr)
if err != nil {
@ -42,8 +58,8 @@ func main() {
}
log.Info("Starting HTTP API server").Str("addr", ln.Addr().String()).Send()
err = http.Serve(ln, allowAllCORSHandler(srv))
err = http.Serve(ln, handler)
if err != nil {
log.Fatal("Error while running server").Err(err).Send()
}

View File

@ -0,0 +1,89 @@
package main
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"net/http"
"os"
"strings"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/repos"
)
func handleWebhook(next http.Handler, sigCh chan<- struct{}) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/webhook" {
if req.Method != http.MethodPost {
res.WriteHeader(http.StatusMethodNotAllowed)
return
}
if req.Header.Get("X-GitHub-Event") != "push" {
http.Error(res, "Only push events are accepted by this bot", http.StatusBadRequest)
return
}
err := verifySecure(req)
if err != nil {
http.Error(res, err.Error(), http.StatusInternalServerError)
return
}
sigCh <- struct{}{}
return
}
next.ServeHTTP(res, req)
})
}
func verifySecure(req *http.Request) error {
sigStr := req.Header.Get("X-Hub-Signature-256")
sig, err := hex.DecodeString(strings.TrimPrefix(sigStr, "sha256="))
if err != nil {
return err
}
secretStr, ok := os.LookupEnv("LURE_API_GITHUB_SECRET")
if !ok {
return errors.New("LURE_API_GITHUB_SECRET must be set to the secret used for setting up the github webhook")
}
secret := []byte(secretStr)
h := hmac.New(sha256.New, secret)
_, err = io.Copy(h, req.Body)
if err != nil {
return err
}
if !hmac.Equal(h.Sum(nil), sig) {
log.Warn("Insecure webhook request").
Str("from", req.RemoteAddr).
Bytes("sig", sig).
Bytes("hmac", h.Sum(nil)).
Send()
return errors.New("webhook signature mismatch")
}
return nil
}
func repoPullWorker(ctx context.Context, sigCh <-chan struct{}) {
for {
select {
case <-sigCh:
err := repos.Pull(ctx, gdb, cfg.Repos)
if err != nil {
log.Warn("Error while pulling repositories").Err(err).Send()
}
case <-ctx.Done():
return
}
}
}