From 37698e63b6f479bd7d9e2b6f8ac12649dfd92d24 Mon Sep 17 00:00:00 2001 From: Adnan Hajdarevic Date: Sat, 27 Feb 2016 22:13:09 +0100 Subject: [PATCH] add support for setting global response headers using -header flag add support for setting response headers for a successfuly triggered hook --- README.md | 5 ++++- hook/hook.go | 56 ++++++++++++++++++++++++++++++++++++++-------- hooks.json.example | 7 ++++++ webhook.go | 15 ++++++++++++- 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 02d236f..b509927 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![ghit.me](https://ghit.me/badge.svg?repo=adnanh/webhook)](https://ghit.me/repo/adnanh/webhook)[![Join the chat at https://gitter.im/adnanh/webhook](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/adnanh/webhook?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Flattr this](https://button.flattr.com/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=adnanh&url=https%3A%2F%2Fwww.github.com%2Fadnanh%2Fwebhook) +[![ghit.me](https://ghit.me/badge.svg?repo=adnanh/webhook)](https://ghit.me/repo/adnanh/webhook)[![Join the chat at https://gitter.im/adnanh/webhook](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/adnanh/webhook?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Flattr this](https://button.flattr.com/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=adnanh&url=https%3A%2F%2Fwww.github.com%2Fadnanh%2Fwebhook) # What is webhook? [webhook](https://github.com/adnanh/webhook/) is a lightweight configurable tool written in Go, that allows you to easily create HTTP endpoints (hooks) on your server, which you can use to execute configured commands. You can also pass data from the HTTP request (such as headers, payload or query variables) to your commands. [webhook](https://github.com/adnanh/webhook/) also allows you to specify rules which have to be satisfied in order for the hook to be triggered. @@ -59,6 +59,9 @@ However, hook defined like that could pose a security threat to your system, bec # Using HTTPS [webhook](https://github.com/adnanh/webhook/) by default serves hooks using http. If you want [webhook](https://github.com/adnanh/webhook/) to serve secure content using https, you can use the `-secure` flag while starting [webhook](https://github.com/adnanh/webhook/). Files containing a certificate and matching private key for the server must be provided using the `-cert /path/to/cert.pem` and `-key /path/to/key.pem` flags. If the certificate is signed by a certificate authority, the cert file should be the concatenation of the server's certificate followed by the CA's certificate. +# CORS Headers +If you want to set CORS headers, you can use the `-header name=value` flag while starting [webhook](https://github.com/adnanh/webhook/) to set the appropriate CORS headers that will be returned with each response. + # Interested in running webhook inside of a Docker container? Please read [this discussion](https://github.com/adnanh/webhook/issues/63). diff --git a/hook/hook.go b/hook/hook.go index 7bff5d8..d23993e 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" "encoding/hex" "encoding/json" + "errors" "fmt" "io/ioutil" "reflect" @@ -246,17 +247,54 @@ func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string return "", false } +// Header is a structure containing header name and it's value +type Header struct { + Name string `json:"name"` + Value string `json:"value"` +} + +// ResponseHeaders is a slice of Header objects +type ResponseHeaders []Header + +func (h *ResponseHeaders) String() string { + // a 'hack' to display name=value in flag usage listing + if len(*h) == 0 { + return "name=value" + } + + result := make([]string, len(*h)) + + for idx, responseHeader := range *h { + result[idx] = fmt.Sprintf("%s=%s", responseHeader.Name, responseHeader.Value) + } + + return fmt.Sprint(strings.Join(result, ", ")) +} + +// Set method appends new Header object from header=value notation +func (h *ResponseHeaders) Set(value string) error { + splitResult := strings.SplitN(value, "=", 2) + + if len(splitResult) != 2 { + return errors.New("header flag must be in name=value format") + } + + *h = append(*h, Header{Name: splitResult[0], Value: splitResult[1]}) + return nil +} + // Hook type is a structure containing details for a single hook type Hook struct { - ID string `json:"id,omitempty"` - ExecuteCommand string `json:"execute-command,omitempty"` - CommandWorkingDirectory string `json:"command-working-directory,omitempty"` - ResponseMessage string `json:"response-message,omitempty"` - CaptureCommandOutput bool `json:"include-command-output-in-response,omitempty"` - PassEnvironmentToCommand []Argument `json:"pass-environment-to-command,omitempty"` - PassArgumentsToCommand []Argument `json:"pass-arguments-to-command,omitempty"` - JSONStringParameters []Argument `json:"parse-parameters-as-json,omitempty"` - TriggerRule *Rules `json:"trigger-rule,omitempty"` + ID string `json:"id,omitempty"` + ExecuteCommand string `json:"execute-command,omitempty"` + CommandWorkingDirectory string `json:"command-working-directory,omitempty"` + ResponseMessage string `json:"response-message,omitempty"` + ResponseHeaders ResponseHeaders `json:"response-headers,omitempty"` + CaptureCommandOutput bool `json:"include-command-output-in-response,omitempty"` + PassEnvironmentToCommand []Argument `json:"pass-environment-to-command,omitempty"` + PassArgumentsToCommand []Argument `json:"pass-arguments-to-command,omitempty"` + JSONStringParameters []Argument `json:"parse-parameters-as-json,omitempty"` + TriggerRule *Rules `json:"trigger-rule,omitempty"` } // ParseJSONParameters decodes specified arguments to JSON objects and replaces the diff --git a/hooks.json.example b/hooks.json.example index 36ad7a3..00f6ff3 100644 --- a/hooks.json.example +++ b/hooks.json.example @@ -4,6 +4,13 @@ "execute-command": "/home/adnan/redeploy-go-webhook.sh", "command-working-directory": "/home/adnan/go", "response-message": "I got the payload!", + "response-headers": + [ + { + "name": "Access-Control-Allow-Origin", + "value": "*" + } + ], "pass-arguments-to-command": [ { diff --git a/webhook.go b/webhook.go index 94db972..9237593 100644 --- a/webhook.go +++ b/webhook.go @@ -21,7 +21,7 @@ import ( ) const ( - version = "2.3.7" + version = "2.4.0" ) var ( @@ -36,6 +36,8 @@ var ( cert = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file") key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file") + responseHeaders hook.ResponseHeaders + watcher *fsnotify.Watcher signals chan os.Signal @@ -45,6 +47,8 @@ var ( func main() { hooks = hook.Hooks{} + flag.Var(&responseHeaders, "header", "response header to return, specified in format name=value, use multiple times to set multiple headers") + flag.Parse() log.SetPrefix("[webhook] ") @@ -137,6 +141,10 @@ func main() { } func hookHandler(w http.ResponseWriter, r *http.Request) { + for _, responseHeader := range responseHeaders { + w.Header().Set(responseHeader.Name, responseHeader.Value) + } + id := mux.Vars(r)["id"] matchedHooks := hooks.MatchAll(id) @@ -180,6 +188,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { // handle hook for _, h := range matchedHooks { + err := h.ParseJSONParameters(&headers, &query, &payload) if err != nil { msg := fmt.Sprintf("error parsing JSON: %s", err) @@ -207,6 +216,10 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { if ok { log.Printf("%s hook triggered successfully\n", h.ID) + for _, responseHeader := range h.ResponseHeaders { + w.Header().Set(responseHeader.Name, responseHeader.Value) + } + if h.CaptureCommandOutput { response := handleHook(h, &headers, &query, &payload, &body) fmt.Fprintf(w, response)