Compare commits

...

8 Commits

4 changed files with 79 additions and 22 deletions

View File

@ -3,7 +3,13 @@
"test": { "test": {
"Script": "./example.sh", "Script": "./example.sh",
"Secret": "THISISVERYSECRET", "Secret": "THISISVERYSECRET",
"SignatureHeader": "X-Gitea-Signature" "SignatureHeader": "X-Gitea-Signature",
"Tests": [
{
"Command": "git",
"Arguments": [ "pull" ]
}
]
} }
} }
} }

2
go.mod
View File

@ -1,4 +1,4 @@
module git.alra.uk/alvierahman90/ghookr module git.alra.uk/alvierahman90/gohookr
go 1.16 go 1.16

47
main.go
View File

@ -17,18 +17,9 @@ import (
) )
var config_filename = "/etc/ghookr.json" var config_filename = "/etc/ghookr.json"
var noSignatureCheck = false
func main() { 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 := mux.NewRouter()
r.HandleFunc("/webhook/{service}", webhook) r.HandleFunc("/webhook/{service}", webhook)
@ -41,14 +32,14 @@ func main() {
config_filename = p config_filename = p
} }
if p, ok := os.LookupEnv("NO_SIGNATURE_CHECK"); ok {
noSignatureCheck = p == "true"
}
log.Fatal(http.ListenAndServe(port, r)) log.Fatal(http.ListenAndServe(port, r))
} }
func webhook(w http.ResponseWriter, r *http.Request) { func webhook(w http.ResponseWriter, r *http.Request) {
// TODO run any specified tests before running script
service_name := mux.Vars(r)["service"]
payload := "" payload := ""
if p, err := ioutil.ReadAll(r.Body); err != nil { if p, err := ioutil.ReadAll(r.Body); err != nil {
writeResponse(w, 500, "Internal Server Error: Could not read payload") writeResponse(w, 500, "Internal Server Error: Could not read payload")
@ -65,20 +56,31 @@ func webhook(w http.ResponseWriter, r *http.Request) {
config := Config{} config := Config{}
json.Unmarshal(raw_config, &config) json.Unmarshal(raw_config, &config)
var service = Service{} // check what service is specified in URL (/webhook/{service}) and if it exists
if val, ok := config.Services[string(service_name)]; !ok { service, ok := config.Services[string(mux.Vars(r)["service"])]
if !ok {
writeResponse(w, 404, "Service Not Found") writeResponse(w, 404, "Service Not Found")
return return
} else {
service = val
} }
// Verify that signature provided matches signature calculated using secretsss
signature := r.Header.Get(service.SignatureHeader) signature := r.Header.Get(service.SignatureHeader)
if signature == getSha256HMACSignature([]byte(service.Secret), payload) { calculatedSignature := getSha256HMACSignature([]byte(service.Secret), payload)
if noSignatureCheck || signature == calculatedSignature {
writeResponse(w, 400, "Bad Request: Signatures do not match") writeResponse(w, 400, "Bad Request: Signatures do not match")
return return
} }
// Run tests, immediately stop if one fails
for _, test := range service.Tests {
if _, err := exec.Command(test.Command, test.Arguments...).Output(); err != nil {
writeResponse(w, 409,
fmt.Sprintf("409 Conflict: Test failed: %v", err.Error()),
)
return
}
}
if stdout, err := exec.Command(service.Script, payload).Output(); err != nil { if stdout, err := exec.Command(service.Script, payload).Output(); err != nil {
writeResponse(w, 500, err.Error()) writeResponse(w, 500, err.Error())
return return
@ -99,12 +101,17 @@ func getSha256HMACSignature(secret []byte, data string) string {
return hex.EncodeToString(h.Sum(nil)) return hex.EncodeToString(h.Sum(nil))
} }
type Test struct {
Command string
Arguments []string
}
type Service struct { type Service struct {
Gitea bool Gitea bool
Script string Script string
Secret string Secret string
SignatureHeader string SignatureHeader string
Tests []string Tests []Test
} }
type Config struct { type Config struct {

View File

@ -5,3 +5,47 @@ A _really_ simple webhook receiver.
Check config.json for an example configuration. Check config.json for an example configuration.
Default config path is `/etc/ghookr.conf`, can be overriden with `CONFIG` environment variable. Default config path is `/etc/ghookr.conf`, can be overriden with `CONFIG` environment variable.
## Signature Verification
Signature verificaiton is done using SHA256 HMACs.
You **must** set which HTTP header gohookr will receive a signature from using the `SignatureHeader`
key for each service.
You should also specify a shared secret in the `Secret` key.
### Disable Signature Verification
You can disable signature verification altogether by setting environment variable `NO_SIGNATURE_VERIFICATION`
to `true`.
## Tests
gohookr can run test before running your script.
Tests must be in the form of bash scripts.
A non-zero return code is considered a fail and gohookr will run no further tests and will not
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 before the tests.
## Example Config
An example config file can be found [here](./config.json) but also below:
```json
{
"Services": {
"test": {
"Script": "./example.sh",
"Secret": "THISISVERYSECRET",
"SignatureHeader": "X-Gitea-Signature",
"Tests": [
{
"Command": "git",
"Arguments": [ "pull" ]
}
]
}
}
}
```