Add webhook handler to api server
ci/woodpecker/push/woodpecker Pipeline was successful
Details
ci/woodpecker/push/woodpecker Pipeline was successful
Details
This commit is contained in:
parent
b7922f9687
commit
891df91fee
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue