Compare commits

..

1 Commits

Author SHA1 Message Date
Adnan Hajdarevic d97d94537a Add IsNull and Exists types to the Match rule 2021-01-26 22:19:46 +01:00
6 changed files with 47 additions and 103 deletions
+4 -10
View File
@@ -18,13 +18,13 @@ If you use Mattermost or Slack, you can set up an "Outgoing webhook integration"
Everything else is the responsibility of the command's author.
# Hookdoo
<a href="https://www.hookdoo.com/?github"><img src="https://hookdoo.com/img/Hookdoo_Logo_1.png" height="96" alt="hookdoo" align="left" /></a>
<a href="https://www.hookdoo.com/?github"><img src="https://www.hookdoo.com/logo/logo.svg" height="96" alt="hookdoo" align="left" /></a>
If you don't have time to waste configuring, hosting, debugging and maintaining your webhook instance, we offer a __SaaS__ solution that has all of the capabilities webhook provides, plus a lot more, and all that packaged in a nice friendly web interface. If you are interested, find out more at [hookdoo website](https://www.hookdoo.com/?ref=github-webhook-readme). If you have any questions, you can contact us at info@hookdoo.com
#
<a href="https://www.hookdeck.com/?ref=adnanh-webhook"><img src="http://hajdarevic.net/hookdeck-logo.svg" height="17" alt="hookdeck" align="left" /></a> If you need a way of inspecting, monitoring and replaying webhooks without the back and forth troubleshooting, [give Hookdeck a try!](https://www.hookdeck.com/?ref=adnanh-webhook)
<a href="https://www.hookdeck.io/?ref=adnanh-webhook"><img src="http://hajdarevic.net/hookdeck-logo.svg" height="17" alt="hookdeck" align="left" /></a> If you need a way of inspecting, monitoring and replaying webhooks without the back and forth troubleshooting, [give Hookdeck a try!](https://www.hookdeck.io/?ref=adnanh-webhook)
# Getting started
## Installation
@@ -113,17 +113,13 @@ TLS version and cipher suite selection flags are available from the command line
If you want to set CORS headers, you can use the `-header name=value` flag while starting [webhook][w] to set the appropriate CORS headers that will be returned with each response.
## Interested in running webhook inside of a Docker container?
You can use one of the following Docker images, or create your own (please read [this discussion](https://github.com/adnanh/webhook/issues/63)):
- [almir/webhook](https://github.com/almir/docker-webhook)
- [roxedus/webhook](https://github.com/Roxedus/docker-webhook)
- [thecatlady/webhook](https://github.com/thecatlady/docker-webhook)
You can use [almir/webhook](https://hub.docker.com/r/almir/webhook/) docker image, or create your own (please read [this discussion](https://github.com/adnanh/webhook/issues/63)).
## Examples
Check out [Hook examples page](docs/Hook-Examples.md) for more complex examples of hooks.
### Guides featuring webhook
- [Plex 2 Telegram](https://gitlab.com/-/snippets/1972594) by [@psyhomb](https://github.com/psyhomb)
- [Webhook & JIRA](https://sites.google.com/site/mrxpalmeiras/more/jira-webhooks) by [@perfecto25](https://github.com/perfecto25)
- [Webhook & JIRA](https://sites.google.com/site/mrxpalmeiras/notes/jira-webhooks) by [@perfecto25](https://github.com/perfecto25)
- [Trigger Ansible AWX job runs on SCM (e.g. git) commit](http://jpmens.net/2017/10/23/trigger-awx-job-runs-on-scm-commit/) by [@jpmens](http://mens.de/)
- [Deploy using GitHub webhooks](https://davidauthier.wearemd.com/blog/deploy-using-github-webhooks.html) by [@awea](https://davidauthier.wearemd.com)
- [Setting up Automatic Deployment and Builds Using Webhooks](https://willbrowning.me/setting-up-automatic-deployment-and-builds-using-webhooks/) by [Will Browning](https://willbrowning.me/about/)
@@ -138,8 +134,6 @@ Check out [Hook examples page](docs/Hook-Examples.md) for more complex examples
- [XiaoMi Vacuum + Amazon Button = Dash Cleaning](https://www.instructables.com/id/XiaoMi-Vacuum-Amazon-Button-Dash-Cleaning/) by [c0mmensal](https://www.instructables.com/member/c0mmensal/)
- [Set up Automated Deployments From Github With Webhook](https://maximorlov.com/automated-deployments-from-github-with-webhook/) by [Maxim Orlov](https://twitter.com/_maximization)
- VIDEO: [Gitlab CI/CD configuration using Docker and adnanh/webhook to deploy on VPS - Tutorial #1](https://www.youtube.com/watch?v=Qhn-lXjyrZA&feature=youtu.be) by [Yes! Let's Learn Software Engineering](https://www.youtube.com/channel/UCH4XJf2BZ_52fbf8fOBMF3w)
- [Integrate automatic deployment in 20 minutes using webhooks + Nginx setup](https://anksus.me/blog/integrate-automatic-deployment-in-20-minutes-using-webhooks) by [Anksus](https://github.com/Anksus)
- [Automatically redeploy your static blog with Gitea, Uberspace & Webhook](https://by.arran.nz/posts/code/webhook-deploy/) by [Arran](https://arran.nz)
- ...
- Want to add your own? Open an Issue or create a PR :-)
+7 -59
View File
@@ -21,12 +21,8 @@ although the examples on this page all use the JSON format.
* [XML Payload](#xml-payload)
* [Multipart Form Data](#multipart-form-data)
* [Pass string arguments to command](#pass-string-arguments-to-command)
* [Receive Synology DSM notifications](#receive-synology-notifications)
## Incoming Github webhook
This example works on 2.8+ versions of Webhook - if you are on a previous series, change `payload-hmac-sha1` to `payload-hash-sha1`.
```json
[
{
@@ -84,7 +80,7 @@ This example works on 2.8+ versions of Webhook - if you are on a previous series
## Incoming Bitbucket webhook
Bitbucket does not pass any secrets back to the webhook. [Per their documentation](https://support.atlassian.com/organization-administration/docs/ip-addresses-and-domains-for-atlassian-cloud-products/#Outgoing-Connections), in order to verify that the webhook came from Bitbucket you must whitelist a set of IP ranges:
Bitbucket does not pass any secrets back to the webhook. [Per their documentation](https://confluence.atlassian.com/bitbucket/manage-webhooks-735643732.html#Managewebhooks-trigger_webhookTriggeringwebhooks), in order to verify that the webhook came from Bitbucket you must whitelist the IP range `104.192.143.0/24`:
```json
[
@@ -101,23 +97,11 @@ Bitbucket does not pass any secrets back to the webhook. [Per their documentati
],
"trigger-rule":
{
"or":
[
{ "match": { "type": "ip-whitelist", "ip-range": "13.52.5.96/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "13.236.8.224/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "18.136.214.96/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "18.184.99.224/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "18.234.32.224/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "18.246.31.224/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "52.215.192.224/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "104.192.137.240/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "104.192.138.240/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "104.192.140.240/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "104.192.142.240/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "104.192.143.240/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "185.166.143.240/28" } },
{ "match": { "type": "ip-whitelist", "ip-range": "185.166.142.240/28" } }
]
"match":
{
"type": "ip-whitelist",
"ip-range": "104.192.143.0/24"
}
}
}
]
@@ -325,7 +309,7 @@ __Not recommended in production due to low security__
```
## JIRA Webhooks
[Guide by @perfecto25](https://sites.google.com/site/mrxpalmeiras/more/jira-webhooks)
[Guide by @perfecto25](https://sites.google.com/site/mrxpalmeiras/notes/jira-webhooks)
## Pass File-to-command sample
@@ -637,39 +621,3 @@ The following example will pass two static string parameters ("-e 123123") to th
}
]
```
## Receive Synology DSM notifications
It's possible to securely receive Synology push notifications via webhooks.
Webhooks feature introduced in DSM 7.x seems to be incomplete & broken, but you can use Synology SMS notification service to push webhooks. To configure SMS notifications on DSM follow instructions found here: https://github.com/ryancurrah/synology-notifications this will allow you to set up everything needed for webhook to accept any and all notifications sent by Synology. During setup an 'api_key' is specified - you can generate your own 32-char string and use it as an authentication mechanism to secure your webhook. Additionally, you can specify what notifications to receive via this method by going and selecting the "SMS" checkboxes under topics of interes in DSM: Control Panel -> Notification -> Rules
```json
[
{
"id": "synology",
"execute-command": "do-something.sh",
"command-working-directory": "/opt/webhook-linux-amd64/synology",
"response-message": "Request accepted",
"pass-arguments-to-command":
[
{
"source": "payload",
"name": "message"
}
],
"trigger-rule":
{
"match":
{
"type": "value",
"value": "PUT_YOUR_API_KEY_HERE",
"parameter":
{
"source": "header",
"name": "api_key"
}
}
}
}
]
```
+32 -25
View File
@@ -394,7 +394,7 @@ func GetParameter(s string, params interface{}) (interface{}, error) {
return v, nil
}
// Checked for dotted references
// Check for dotted references
p := strings.SplitN(s, ".", 2)
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
if len(p) > 1 {
@@ -411,23 +411,23 @@ func GetParameter(s string, params interface{}) (interface{}, error) {
// ExtractParameterAsString extracts value from interface{} as string based on
// the passed string. Complex data types are rendered as JSON instead of the Go
// Stringer format.
func ExtractParameterAsString(s string, params interface{}) (string, error) {
func ExtractParameterAsString(s string, params interface{}) (string, interface{}, error) {
pValue, err := GetParameter(s, params)
if err != nil {
return "", err
return "", nil, err
}
switch v := reflect.ValueOf(pValue); v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice:
r, err := json.Marshal(pValue)
if err != nil {
return "", err
return "", pValue, err
}
return string(r), nil
return string(r), r, nil
default:
return fmt.Sprintf("%v", pValue), nil
return fmt.Sprintf("%v", pValue), pValue, nil
}
}
@@ -442,7 +442,7 @@ type Argument struct {
// Get Argument method returns the value for the Argument's key name
// based on the Argument's source
func (ha *Argument) Get(r *Request) (string, error) {
func (ha *Argument) Get(r *Request) (string, interface{}, error) {
var source *map[string]interface{}
key := ha.Name
@@ -458,55 +458,55 @@ func (ha *Argument) Get(r *Request) (string, error) {
source = &r.Payload
case SourceString:
return ha.Name, nil
return ha.Name, ha.Name, nil
case SourceRawRequestBody:
return string(r.Body), nil
return string(r.Body), r.Body, nil
case SourceRequest:
if r == nil || r.RawRequest == nil {
return "", errors.New("request is nil")
return "", nil, errors.New("request is nil")
}
switch strings.ToLower(ha.Name) {
case "remote-addr":
return r.RawRequest.RemoteAddr, nil
return r.RawRequest.RemoteAddr, r.RawRequest.RemoteAddr, nil
case "method":
return r.RawRequest.Method, nil
return r.RawRequest.Method, r.RawRequest.Method, nil
default:
return "", fmt.Errorf("unsupported request key: %q", ha.Name)
return "", nil, fmt.Errorf("unsupported request key: %q", ha.Name)
}
case SourceEntirePayload:
res, err := json.Marshal(&r.Payload)
if err != nil {
return "", err
return "", r.Payload, err
}
return string(res), nil
return string(res), r.Payload, nil
case SourceEntireHeaders:
res, err := json.Marshal(&r.Headers)
if err != nil {
return "", err
return "", r.Headers, err
}
return string(res), nil
return string(res), r.Headers, nil
case SourceEntireQuery:
res, err := json.Marshal(&r.Query)
if err != nil {
return "", err
return "", r.Query, err
}
return string(res), nil
return string(res), r.Query, nil
}
if source != nil {
return ExtractParameterAsString(key, *source)
}
return "", errors.New("no source for value retrieval")
return "", nil, errors.New("no source for value retrieval")
}
// Header is a structure containing header name and it's value
@@ -589,7 +589,7 @@ func (h *Hook) ParseJSONParameters(r *Request) []error {
errors := make([]error, 0)
for i := range h.JSONStringParameters {
arg, err := h.JSONStringParameters[i].Get(r)
arg, _, err := h.JSONStringParameters[i].Get(r)
if err != nil {
errors = append(errors, &ArgumentError{h.JSONStringParameters[i]})
} else {
@@ -645,7 +645,7 @@ func (h *Hook) ExtractCommandArguments(r *Request) ([]string, []error) {
args = append(args, h.ExecuteCommand)
for i := range h.PassArgumentsToCommand {
arg, err := h.PassArgumentsToCommand[i].Get(r)
arg, _, err := h.PassArgumentsToCommand[i].Get(r)
if err != nil {
args = append(args, "")
errors = append(errors, &ArgumentError{h.PassArgumentsToCommand[i]})
@@ -669,7 +669,7 @@ func (h *Hook) ExtractCommandArgumentsForEnv(r *Request) ([]string, []error) {
args := make([]string, 0)
errors := make([]error, 0)
for i := range h.PassEnvironmentToCommand {
arg, err := h.PassEnvironmentToCommand[i].Get(r)
arg, _, err := h.PassEnvironmentToCommand[i].Get(r)
if err != nil {
errors = append(errors, &ArgumentError{h.PassEnvironmentToCommand[i]})
continue
@@ -705,7 +705,7 @@ func (h *Hook) ExtractCommandArgumentsForFile(r *Request) ([]FileParameter, []er
args := make([]FileParameter, 0)
errors := make([]error, 0)
for i := range h.PassFileToCommand {
arg, err := h.PassFileToCommand[i].Get(r)
arg, _, err := h.PassFileToCommand[i].Get(r)
if err != nil {
errors = append(errors, &ArgumentError{h.PassFileToCommand[i]})
continue
@@ -898,6 +898,8 @@ type MatchRule struct {
const (
MatchValue string = "value"
MatchRegex string = "regex"
MatchIsNull string = "is-null"
MatchExists string = "exists"
MatchHMACSHA1 string = "payload-hmac-sha1"
MatchHMACSHA256 string = "payload-hmac-sha256"
MatchHMACSHA512 string = "payload-hmac-sha512"
@@ -917,13 +919,17 @@ func (r MatchRule) Evaluate(req *Request) (bool, error) {
return CheckScalrSignature(req, r.Secret, true)
}
arg, err := r.Parameter.Get(req)
arg, rawValue, err := r.Parameter.Get(req)
if err == nil {
switch r.Type {
case MatchValue:
return compare(arg, r.Value), nil
case MatchRegex:
return regexp.MatchString(r.Regex, arg)
case MatchIsNull:
return rawValue == nil, nil
case MatchExists:
return true, nil
case MatchHashSHA1:
log.Print(`warn: use of deprecated option payload-hash-sha1; use payload-hmac-sha1 instead`)
fallthrough
@@ -944,6 +950,7 @@ func (r MatchRule) Evaluate(req *Request) (bool, error) {
return err == nil, err
}
}
return false, err
}
+2 -2
View File
@@ -245,7 +245,7 @@ var extractParameterTests = []struct {
func TestExtractParameter(t *testing.T) {
for _, tt := range extractParameterTests {
value, err := ExtractParameterAsString(tt.s, tt.params)
value, _, err := ExtractParameterAsString(tt.s, tt.params)
if (err == nil) != tt.ok || value != tt.value {
t.Errorf("failed to extract parameter %q:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%v}", tt.s, tt.value, tt.ok, value, err)
}
@@ -281,7 +281,7 @@ func TestArgumentGet(t *testing.T) {
Payload: tt.payload,
RawRequest: tt.request,
}
value, err := a.Get(r)
value, _, err := a.Get(r)
if (err == nil) != tt.ok || value != tt.value {
t.Errorf("failed to get {%q, %q}:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%v}", tt.source, tt.name, tt.value, tt.ok, value, err)
}
+1 -5
View File
@@ -25,7 +25,7 @@ import (
)
const (
version = "2.8.1"
version = "2.8.0"
)
var (
@@ -265,10 +265,6 @@ func main() {
hooksURL := makeRoutePattern(hooksURLPrefix)
r.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
for _, responseHeader := range responseHeaders {
w.Header().Set(responseHeader.Name, responseHeader.Value)
}
fmt.Fprint(w, "OK")
})
Executable → Regular
+1 -2
View File
@@ -33,8 +33,7 @@ func TestStaticParams(t *testing.T) {
spHeaders["Accept"] = "*/*"
// case 2: binary with spaces in its name
d1 := []byte("#!/bin/sh\n/bin/echo\n")
err := ioutil.WriteFile("/tmp/with space", d1, 0755)
err := os.Symlink("/bin/echo", "/tmp/with space")
if err != nil {
t.Fatalf("%v", err)
}