Compare commits

...

7 Commits

10 changed files with 108 additions and 36 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
gohookr

View File

@@ -3,15 +3,17 @@ all: install
clean:
rm -rf gohookr
install:
go mod tidy
go build -o gohookr
install: build
cp gohookr /usr/local/bin/
cp gohookr.service /usr/lib/systemd/system/
cp -n config.json /etc/gohookr.json
systemctl daemon-reload
systemctl enable --now gohookr
build:
go mod tidy
go build -o gohookr
uninstall:
systemctl disable --now gohookr
rm -rf /usr/local/bin/gohookr /usr/lib/systemd/system/gohookr.service

View File

@@ -6,8 +6,7 @@
"Program": "./example.sh",
"AppendPayload": true
},
"Secret": "THISISVERYSECRET",
"SignatureHeader": "X-Gitea-Signature",
"DisableSignatureVerification": true,
"Tests": [
{
"Program": "echo",

13
config.yml Normal file
View File

@@ -0,0 +1,13 @@
listenaddress: 127.0.0.1:8654
services:
test:
script:
program: "echo"
arguments:
- test
tests:
- program: ./example.sh
appendpayload: true
disablesignatureverification: false
signatureheader: test
secret: thisisasecret

View File

@@ -1,5 +1,12 @@
package config
import (
"encoding/json"
"io/ioutil"
"gopkg.in/yaml.v3"
)
// The struct that represents the config.json file
type Config struct {
ListenAddress string
@@ -8,6 +15,7 @@ type Config struct {
Secret string
SignaturePrefix string
SignatureHeader string
DisableSignatureVerification bool
Tests []Command
}
}
@@ -22,13 +30,33 @@ func (c Config) Validate() error {
if service.Script.Program == "" {
return requiredFieldError{"Script.Program", serviceName}
}
if service.SignatureHeader == "" {
if !service.DisableSignatureVerification && service.SignatureHeader == "" {
return requiredFieldError{"SignatureHeader", serviceName}
}
if service.Secret == "" {
if !service.DisableSignatureVerification && service.Secret == "" {
return requiredFieldError{"Secret", serviceName}
}
}
return nil
}
func (c *Config) Load(config_filename string) error {
raw_config, err := ioutil.ReadFile(config_filename)
if err != nil {
return err
}
err = json.Unmarshal(raw_config, &c)
if err == nil {
return c.Validate()
}
err = yaml.Unmarshal(raw_config, &c)
if err == nil {
return c.Validate()
}
return err
}

0
example.sh Executable file → Normal file
View File

7
go.mod
View File

@@ -1,5 +1,8 @@
module git.alra.uk/alvierahman90/gohookr
module git.alv.cx/alvierahman90/gohookr
go 1.16
require github.com/gorilla/mux v1.8.0
require (
github.com/gorilla/mux v1.8.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)

4
go.sum
View File

@@ -1,2 +1,6 @@
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

26
main.go
View File

@@ -4,7 +4,6 @@ import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
@@ -12,15 +11,16 @@ import (
"net/http"
"os"
"git.alra.uk/alvierahman90/gohookr/config"
"git.alv.cx/alvierahman90/gohookr/config"
"github.com/gorilla/mux"
)
var config_filename = "/etc/gohookr.json"
var checkSignature = true
var c config.Config
func main() {
var c config.Config
r := mux.NewRouter()
r.HandleFunc("/webhooks/{service}", webhookHandler)
@@ -32,20 +32,28 @@ func main() {
checkSignature = p != "true"
}
raw_config, err := ioutil.ReadFile(config_filename)
var err = c.Load(config_filename)
if err != nil {
panic(err.Error())
}
fmt.Printf("CONFIG OK: %s\n", config_filename)
fmt.Printf("LISTENING AT: %s\n", c.ListenAddress)
json.Unmarshal(raw_config, &c)
if err := c.Validate(); err != nil {
panic(err.Error())
for _, v := range os.Args {
if v == "checkConfig" {
return
}
}
log.Fatal(http.ListenAndServe(c.ListenAddress, r))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
var c config.Config
var err = c.Load(config_filename)
if err != nil {
writeResponse(w, 500, "Unable to read config file")
}
// Check what service is specified in URL (/webhooks/{service}) and if it exists
serviceName := string(mux.Vars(r)["service"])
service, ok := c.Services[serviceName]
@@ -75,14 +83,14 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) {
)
fmt.Printf("signature = %v\n", signature)
fmt.Printf("calcuatedSignature = %v\n", calculatedSignature)
if signature != calculatedSignature && checkSignature {
if checkSignature && !service.DisableSignatureVerification && signature != calculatedSignature {
writeResponse(w, 400, "Bad Request: Signatures do not match")
fmt.Println("Signatures do not match!")
return
}
// Run tests and script as goroutine to prevent timing out
go func(){
go func() {
// Run tests, immediately stop if one fails
for _, test := range service.Tests {
if _, err := test.Execute(payload); err != nil {

View File

@@ -2,11 +2,6 @@
A _really_ simple webhook receiver, which listens at `/webhooks/<webhook-name>`.
Default config path is `/etc/gohookr.conf` and can be overriden by setting environment variable
`CONFIG`.
Check below for an example configuration.
## Installation
After you [install go](https://golang.org/doc/install):
@@ -15,7 +10,24 @@ After you [install go](https://golang.org/doc/install):
make
```
## Signature Verification
## Configuration
Default config path is `/etc/gohookr.json`.
It can be overriden by setting environment variable `CONFIG`.
The config file will be re-read every request so service configs can be changed without restarting
the service (unless you want to change the listening port).
Check below for an example configuration, which should tell you most of the things you need to know
to configure gohookr.
You can test your config file by running
```
gohookr checkConfig
```
### Signature Verification
Signature verificaiton is done using SHA256 HMACs.
You **must** set which HTTP header gohookr will receive a signature from using the `SignatureHeader`
@@ -25,18 +37,21 @@ You should also specify a shared secret in the `Secret` key.
You may also need to specify a `SignaturePrefix`.
For GitHub it would be `sha256=`.
### Disable Signature Verification
#### Disable Signature Verification
You can disable signature verification altogether by setting environment variable
You can disable signature verification by setting `DisableSignatureVerification` for a service to
`true`.
You can disable signature verification for all services by setting environment variable
`NO_SIGNATURE_VERIFICATION` to `true`.
## Writing Commands
### Writing Commands
gohookr doesn't care what the command is as long as the `Program` is executable.
You can specify extra arguments with the `Arguments` field.
You can ask it to put the payload as the last argument by setting `AppendPayload` to true.
## Writing Tests
### Writing Tests
gohookr can run test before running your script.
Tests must be in the form of bash scripts.
@@ -46,7 +61,7 @@ deploy.
Tests are run in the order they're listed so any actions that need to be done before
tests are run can simply be put in this section before the tests.
## Example Config
### Example Config
Required config keys are `ListenAddress` and `Services`.
@@ -63,8 +78,7 @@ An example config file can be found [here](./config.json) but also below:
"Program": "./example.sh",
"AppendPayload": true
},
"Secret": "THISISVERYSECRET",
"SignatureHeader": "X-Gitea-Signature",
"DisableSignatureVerification": true,
"Tests": [
{
"Program": "echo",