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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
"go.arsenm.dev/logger"
|
"go.arsenm.dev/logger"
|
||||||
"go.arsenm.dev/logger/log"
|
"go.arsenm.dev/logger/log"
|
||||||
"go.arsenm.dev/lure/internal/api"
|
"go.arsenm.dev/lure/internal/api"
|
||||||
|
"go.arsenm.dev/lure/internal/repos"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -17,6 +19,8 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
addr := flag.String("a", ":8080", "Listen address for API server")
|
addr := flag.String("a", ":8080", "Listen address for API server")
|
||||||
logFile := flag.String("l", "", "Output file for JSON log")
|
logFile := flag.String("l", "", "Output file for JSON log")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
@ -31,10 +35,22 @@ func main() {
|
||||||
log.Logger = logger.NewMulti(log.Logger, logger.NewJSON(fl))
|
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},
|
lureWebAPI{db: gdb},
|
||||||
twirp.WithServerPathPrefix(""),
|
twirp.WithServerPathPrefix(""),
|
||||||
)
|
)
|
||||||
|
handler = allowAllCORSHandler(handler)
|
||||||
|
handler = handleWebhook(handler, sigCh)
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", *addr)
|
ln, err := net.Listen("tcp", *addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -43,7 +59,7 @@ func main() {
|
||||||
|
|
||||||
log.Info("Starting HTTP API server").Str("addr", ln.Addr().String()).Send()
|
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 {
|
if err != nil {
|
||||||
log.Fatal("Error while running server").Err(err).Send()
|
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