From 0370e58aec017cdd748f7a1d08bd7154fe79a20f Mon Sep 17 00:00:00 2001 From: Alvie Rahman Date: Wed, 28 Jul 2021 16:20:06 +0100 Subject: [PATCH] Initial commit --- config.json | 9 +++++ example.sh | 2 + go.mod | 5 +++ go.sum | 2 + main.go | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++ readme.md | 7 ++++ 6 files changed, 137 insertions(+) create mode 100644 config.json create mode 100755 example.sh create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 readme.md diff --git a/config.json b/config.json new file mode 100644 index 0000000..d7d9f38 --- /dev/null +++ b/config.json @@ -0,0 +1,9 @@ +{ + "Services": { + "test": { + "Script": "./example.sh", + "Secret": "THISISVERYSECRET", + "SignatureHeader": "X-Gitea-Signature" + } + } +} diff --git a/example.sh b/example.sh new file mode 100755 index 0000000..517a5c2 --- /dev/null +++ b/example.sh @@ -0,0 +1,2 @@ +#!/usr/bin/bash +date >> test_output diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ae6a7b3 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.alra.uk/alvierahman90/ghookr + +go 1.16 + +require github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5350288 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/main.go b/main.go new file mode 100644 index 0000000..d82a193 --- /dev/null +++ b/main.go @@ -0,0 +1,112 @@ +package main + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + + "github.com/gorilla/mux" +) + +var config_filename = "/etc/ghookr.json" + +func main() { + // Used for testing purposes... generates hmac string + if os.Getenv("HMACGEN") == "true" { + input, err := ioutil.ReadAll(os.Stdin) + secret := os.Getenv("SECRET") + if err != nil { + panic(err.Error()) + } + fmt.Println(getSha256HMACSignature([]byte(secret), string(input))) + return + } + r := mux.NewRouter() + r.HandleFunc("/webhook/{service}", webhook) + + port := ":80" + if p, ok := os.LookupEnv("PORT"); ok { + port = fmt.Sprintf(":%v", p) + } + + if p, ok := os.LookupEnv("CONFIG"); ok { + config_filename = p + } + + log.Fatal(http.ListenAndServe(port, r)) +} + +func webhook(w http.ResponseWriter, r *http.Request) { + // TODO run any specified tests before running script + + service_name := mux.Vars(r)["service"] + + payload := "" + if p, err := ioutil.ReadAll(r.Body); err != nil { + writeResponse(w, 500, "Internal Server Error: Could not read payload") + return + } else { + payload = string(p) + } + + raw_config, err := ioutil.ReadFile(config_filename) + if err != nil { + writeResponse(w, 500, "Internal Server Error: Could not open config file") + return + } + config := Config{} + json.Unmarshal(raw_config, &config) + + var service = Service{} + if val, ok := config.Services[string(service_name)]; !ok { + writeResponse(w, 404, "Service Not Found") + return + } else { + service = val + } + + signature := r.Header.Get(service.SignatureHeader) + if signature == getSha256HMACSignature([]byte(service.Secret), payload) { + writeResponse(w, 400, "Bad Request: Signatures do not match") + return + } + + if stdout, err := exec.Command(service.Script, payload).Output(); err != nil { + writeResponse(w, 500, err.Error()) + return + } else { + writeResponse(w, 200, string(stdout)) + return + } +} + +func writeResponse(w http.ResponseWriter, responseCode int, responseString string) { + w.WriteHeader(responseCode) + w.Write([]byte(fmt.Sprintf("%v %v", responseCode, responseString))) +} + +func getSha256HMACSignature(secret []byte, data string) string { + h := hmac.New(sha256.New, secret) + io.WriteString(h, data) + return hex.EncodeToString(h.Sum(nil)) +} + +type Service struct { + Gitea bool + Script string + Secret string + SignatureHeader string + Tests []string +} + +type Config struct { + Services map[string]Service +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..53f39e6 --- /dev/null +++ b/readme.md @@ -0,0 +1,7 @@ +# gohookr + +A _really_ simple webhook receiver. + +Check config.json for an example configuration. + +Default config path is `/etc/ghookr.conf`, can be overriden with `CONFIG` environment variable.