",
- "parameter":
- {
- "source": "payload",
- "name": "token"
- }
- }
- }
- }
-]
-```
-
-## A simple webhook with a secret key in GET query
-
-__Not recommended in production due to low security__
-
-`example.com:9000/hooks/simple-one` - won't work
-`example.com:9000/hooks/simple-one?token=42` - will work
-
-```json
-[
- {
- "id": "simple-one",
- "execute-command": "/path/to/command.sh",
- "response-message": "Executing simple webhook...",
- "trigger-rule":
- {
- "match":
- {
- "type": "value",
- "value": "42",
- "parameter":
- {
- "source": "url",
- "name": "token"
- }
- }
- }
- }
-]
-```
-
-# JIRA Webhooks
-[Guide by @perfecto25](https://sites.google.com/site/mrxpalmeiras/notes/jira-webhooks)
-
-# Pass File-to-command sample
-
-## Webhook configuration
-
-
-[
- {
- "id": "test-file-webhook",
- "execute-command": "/bin/ls",
- "command-working-directory": "/tmp",
- "pass-file-to-command":
- [
- {
- "source": "payload",
- "name": "binary",
- "envname": "ENV_VARIABLE", // to use $ENV_VARIABLE in execute-command
- // if not defined, $HOOK_BINARY will be provided
- "base64decode": true, // defaults to false
- }
- ],
- "include-command-output-in-response": true
- }
-]
-
-
-## Sample client usage
-
-Store the following file as `testRequest.json`.
-
-
-{"binary":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2lpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wUmlnaHRzPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvcmlnaHRzLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcFJpZ2h0czpNYXJrZWQ9IkZhbHNlIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjEzMTA4RDI0QzMxQjExRTBCMzYzRjY1QUQ1Njc4QzFBIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjEzMTA4RDIzQzMxQjExRTBCMzYzRjY1QUQ1Njc4QzFBIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzMgV2luZG93cyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ1dWlkOkFDMUYyRTgzMzI0QURGMTFBQUI4QzUzOTBEODVCNUIzIiBzdFJlZjpkb2N1bWVudElEPSJ1dWlkOkM5RDM0OTY2NEEzQ0REMTFCMDhBQkJCQ0ZGMTcyMTU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+IBFgEwAAAmJJREFUeNqkk89rE1EQx2d/NNq0xcYYayPYJDWC9ODBsKIgAREjBmvEg2cvHnr05KHQ9iB49SL+/BMEfxBQKHgwCEbTNNIYaqgaoanFJi+rcXezye4689jYkIMIDnx47837zrx583YFx3Hgf0xA6/dJyAkkgUy4vgryAnmNWH9L4EVmotFoKplMHgoGg6PkrFarjXQ6/bFcLj/G5W1E+3NaX4KZeDx+dX5+7kg4HBlmrC6JoiDFYrGhROLM/mp1Y6JSqdCd3/SW0GUqEAjkl5ZyHTSHKBQKnO6a9khD2m5cr91IJBJ1VVWdiM/n6LruNJtNDs3JR3ukIW03SHTHi8iVsbG9I51OG1bW16HVasHQZopDc/JZVgdIQ1o3BmTkEnJXURS/KIpgGAYPkCQJPi0u8uzDKQN0XQPbtgE1MmrHs9nsfSqAEjxCNtHxZHLy4G4smUQgyzL4LzOegDGGp1ucVqsNqKVrpJCM7F4hg6iaZvhqtZrg8XjA4xnAU3XeKLqWaRImoIZeQXVjQO5pYp4xNVirsR1erxer2O4yfa227WCwhtWoJmn7m0h270NxmemFW4706zMm8GCgxBGEASCfhnukIW03iFdQnOPz0LNKp3362JqQzSw4u2LXBe+Bs3xD+/oc1NxN55RiC9fOme0LEQiRf2rBzaKEeJJ37ZWTVunBeGN2WmQjg/DeLTVP89nzAive2dMwlo9bpFVC2xWMZr+A720FVn88fAUb3wDMOjyN7YNc6TvUSHQ4AH6TOUdLL7em68UtWPsJqxgTpgeiLu1EBt1R+Me/mF7CQPTfAgwAGxY2vOTrR3oAAAAASUVORK5CYII="}
-
-
-use then the curl tool to execute a request to the webhook.
-
-
-#!/bin/bash
-curl -H "Content-Type:application/json" -X POST -d @testRequest.json \
-http://localhost:9000/hooks/test-file-webhook
-
-
-or in a single line, using https://github.com/jpmens/jo to generate the JSON code
-
-jo binary=%filename.zip | curl -H "Content-Type:application/json" -X POST -d @- \
-http://localhost:9000/hooks/test-file-webhook
-
-
-
-## Incoming Scalr Webhook
-[Guide by @hassanbabaie]
-Scalr makes webhook calls based on an event to a configured webhook endpoint (for example Host Down, Host Up). Webhook endpoints are URLs where Scalr will deliver Webhook notifications.
-Scalr assigns a unique signing key for every configured webhook endpoint.
-Refer to this URL for information on how to setup the webhook call on the Scalr side: [Scalr Wiki Webhooks](https://scalr-wiki.atlassian.net/wiki/spaces/docs/pages/6193173/Webhooks)
-In order to leverage the Signing Key for addtional authentication/security you must configure the trigger rule with a match type of "scalr-signature".
-
-```json
+# Hook examples
+This page is still work in progress. Feel free to contribute!
+
+## Incoming Github webhook
+```json
+[
+ {
+ "id": "webhook",
+ "execute-command": "/home/adnan/redeploy-go-webhook.sh",
+ "command-working-directory": "/home/adnan/go",
+ "pass-arguments-to-command":
+ [
+ {
+ "source": "payload",
+ "name": "head_commit.id"
+ },
+ {
+ "source": "payload",
+ "name": "pusher.name"
+ },
+ {
+ "source": "payload",
+ "name": "pusher.email"
+ }
+ ],
+ "trigger-rule":
+ {
+ "and":
+ [
+ {
+ "match":
+ {
+ "type": "payload-hash-sha1",
+ "secret": "mysecret",
+ "parameter":
+ {
+ "source": "header",
+ "name": "X-Hub-Signature"
+ }
+ }
+ },
+ {
+ "match":
+ {
+ "type": "value",
+ "value": "refs/heads/master",
+ "parameter":
+ {
+ "source": "payload",
+ "name": "ref"
+ }
+ }
+ }
+ ]
+ }
+ }
+]
+```
+
+## Incoming Bitbucket webhook
+
+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
+[
+ {
+ "id": "webhook",
+ "execute-command": "/home/adnan/redeploy-go-webhook.sh",
+ "command-working-directory": "/home/adnan/go",
+ "pass-arguments-to-command":
+ [
+ {
+ "source": "payload",
+ "name": "actor.username"
+ }
+ ],
+ "trigger-rule":
+ {
+ "match":
+ {
+ "type": "ip-whitelist",
+ "ip-range": "104.192.143.0/24"
+ }
+ }
+ }
+]
+```
+
+## Incoming Gitlab Webhook
+Gitlab provides webhooks for many kinds of events.
+Refer to this URL for example request body content: [gitlab-ce/integrations/webhooks](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/project/integrations/webhooks.md)
+Values in the request body can be accessed in the command or to the match rule by referencing 'payload' as the source:
+```json
+[
+ {
+ "id": "redeploy-webhook",
+ "execute-command": "/home/adnan/redeploy-go-webhook.sh",
+ "command-working-directory": "/home/adnan/go",
+ "pass-arguments-to-command":
+ [
+ {
+ "source": "payload",
+ "name": "user_name"
+ }
+ ],
+ "response-message": "Executing redeploy script",
+ "trigger-rule":
+ {
+ "match":
+ {
+ "type": "value",
+ "value": "",
+ "parameter":
+ {
+ "source": "header",
+ "name": "X-Gitlab-Token"
+ }
+ }
+ }
+ }
+]
+```
+
+## Incoming Gogs webhook
+```json
+[
+ {
+ "id": "webhook",
+ "execute-command": "/home/adnan/redeploy-go-webhook.sh",
+ "command-working-directory": "/home/adnan/go",
+ "pass-arguments-to-command":
+ [
+ {
+ "source": "payload",
+ "name": "head_commit.id"
+ },
+ {
+ "source": "payload",
+ "name": "pusher.name"
+ },
+ {
+ "source": "payload",
+ "name": "pusher.email"
+ }
+ ],
+ "trigger-rule":
+ {
+ "and":
+ [
+ {
+ "match":
+ {
+ "type": "payload-hash-sha256",
+ "secret": "mysecret",
+ "parameter":
+ {
+ "source": "header",
+ "name": "X-Gogs-Signature"
+ }
+ }
+ },
+ {
+ "match":
+ {
+ "type": "value",
+ "value": "refs/heads/master",
+ "parameter":
+ {
+ "source": "payload",
+ "name": "ref"
+ }
+ }
+ }
+ ]
+ }
+ }
+]
+```
+## Incoming Gitea webhook
+```json
+[
+ {
+ "id": "webhook",
+ "execute-command": "/home/adnan/redeploy-go-webhook.sh",
+ "command-working-directory": "/home/adnan/go",
+ "pass-arguments-to-command":
+ [
+ {
+ "source": "payload",
+ "name": "head_commit.id"
+ },
+ {
+ "source": "payload",
+ "name": "pusher.name"
+ },
+ {
+ "source": "payload",
+ "name": "pusher.email"
+ }
+ ],
+ "trigger-rule":
+ {
+ "and":
+ [
+ {
+ "match":
+ {
+ "type": "value",
+ "value": "mysecret",
+ "parameter":
+ {
+ "source": "payload",
+ "name": "secret"
+ }
+ }
+ },
+ {
+ "match":
+ {
+ "type": "value",
+ "value": "refs/heads/master",
+ "parameter":
+ {
+ "source": "payload",
+ "name": "ref"
+ }
+ }
+ }
+ ]
+ }
+ }
+]
+```
+
+## Slack slash command
+```json
+[
+ {
+ "id": "redeploy-webhook",
+ "execute-command": "/home/adnan/redeploy-go-webhook.sh",
+ "command-working-directory": "/home/adnan/go",
+ "response-message": "Executing redeploy script",
+ "trigger-rule":
+ {
+ "match":
+ {
+ "type": "value",
+ "value": "",
+ "parameter":
+ {
+ "source": "payload",
+ "name": "token"
+ }
+ }
+ }
+ }
+]
+```
+
+## A simple webhook with a secret key in GET query
+
+__Not recommended in production due to low security__
+
+`example.com:9000/hooks/simple-one` - won't work
+`example.com:9000/hooks/simple-one?token=42` - will work
+
+```json
+[
+ {
+ "id": "simple-one",
+ "execute-command": "/path/to/command.sh",
+ "response-message": "Executing simple webhook...",
+ "trigger-rule":
+ {
+ "match":
+ {
+ "type": "value",
+ "value": "42",
+ "parameter":
+ {
+ "source": "url",
+ "name": "token"
+ }
+ }
+ }
+ }
+]
+```
+
+# JIRA Webhooks
+[Guide by @perfecto25](https://sites.google.com/site/mrxpalmeiras/notes/jira-webhooks)
+
+# Pass File-to-command sample
+
+## Webhook configuration
+
+
+[
+ {
+ "id": "test-file-webhook",
+ "execute-command": "/bin/ls",
+ "command-working-directory": "/tmp",
+ "pass-file-to-command":
+ [
+ {
+ "source": "payload",
+ "name": "binary",
+ "envname": "ENV_VARIABLE", // to use $ENV_VARIABLE in execute-command
+ // if not defined, $HOOK_BINARY will be provided
+ "base64decode": true, // defaults to false
+ }
+ ],
+ "include-command-output-in-response": true
+ }
+]
+
+
+## Sample client usage
+
+Store the following file as `testRequest.json`.
+
+
+{"binary":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2lpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wUmlnaHRzPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvcmlnaHRzLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcFJpZ2h0czpNYXJrZWQ9IkZhbHNlIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjEzMTA4RDI0QzMxQjExRTBCMzYzRjY1QUQ1Njc4QzFBIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjEzMTA4RDIzQzMxQjExRTBCMzYzRjY1QUQ1Njc4QzFBIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzMgV2luZG93cyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ1dWlkOkFDMUYyRTgzMzI0QURGMTFBQUI4QzUzOTBEODVCNUIzIiBzdFJlZjpkb2N1bWVudElEPSJ1dWlkOkM5RDM0OTY2NEEzQ0REMTFCMDhBQkJCQ0ZGMTcyMTU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+IBFgEwAAAmJJREFUeNqkk89rE1EQx2d/NNq0xcYYayPYJDWC9ODBsKIgAREjBmvEg2cvHnr05KHQ9iB49SL+/BMEfxBQKHgwCEbTNNIYaqgaoanFJi+rcXezye4689jYkIMIDnx47837zrx583YFx3Hgf0xA6/dJyAkkgUy4vgryAnmNWH9L4EVmotFoKplMHgoGg6PkrFarjXQ6/bFcLj/G5W1E+3NaX4KZeDx+dX5+7kg4HBlmrC6JoiDFYrGhROLM/mp1Y6JSqdCd3/SW0GUqEAjkl5ZyHTSHKBQKnO6a9khD2m5cr91IJBJ1VVWdiM/n6LruNJtNDs3JR3ukIW03SHTHi8iVsbG9I51OG1bW16HVasHQZopDc/JZVgdIQ1o3BmTkEnJXURS/KIpgGAYPkCQJPi0u8uzDKQN0XQPbtgE1MmrHs9nsfSqAEjxCNtHxZHLy4G4smUQgyzL4LzOegDGGp1ucVqsNqKVrpJCM7F4hg6iaZvhqtZrg8XjA4xnAU3XeKLqWaRImoIZeQXVjQO5pYp4xNVirsR1erxer2O4yfa227WCwhtWoJmn7m0h270NxmemFW4706zMm8GCgxBGEASCfhnukIW03iFdQnOPz0LNKp3362JqQzSw4u2LXBe+Bs3xD+/oc1NxN55RiC9fOme0LEQiRf2rBzaKEeJJ37ZWTVunBeGN2WmQjg/DeLTVP89nzAive2dMwlo9bpFVC2xWMZr+A720FVn88fAUb3wDMOjyN7YNc6TvUSHQ4AH6TOUdLL7em68UtWPsJqxgTpgeiLu1EBt1R+Me/mF7CQPTfAgwAGxY2vOTrR3oAAAAASUVORK5CYII="}
+
+
+use then the curl tool to execute a request to the webhook.
+
+
+#!/bin/bash
+curl -H "Content-Type:application/json" -X POST -d @testRequest.json \
+http://localhost:9000/hooks/test-file-webhook
+
+
+or in a single line, using https://github.com/jpmens/jo to generate the JSON code
+
+jo binary=%filename.zip | curl -H "Content-Type:application/json" -X POST -d @- \
+http://localhost:9000/hooks/test-file-webhook
+
+
+
+## Incoming Scalr Webhook
+[Guide by @hassanbabaie]
+Scalr makes webhook calls based on an event to a configured webhook endpoint (for example Host Down, Host Up). Webhook endpoints are URLs where Scalr will deliver Webhook notifications.
+Scalr assigns a unique signing key for every configured webhook endpoint.
+Refer to this URL for information on how to setup the webhook call on the Scalr side: [Scalr Wiki Webhooks](https://scalr-wiki.atlassian.net/wiki/spaces/docs/pages/6193173/Webhooks)
+In order to leverage the Signing Key for addtional authentication/security you must configure the trigger rule with a match type of "scalr-signature".
+
+```json
[
{
"id": "redeploy-webhook",
@@ -319,6 +374,6 @@ In order to leverage the Signing Key for addtional authentication/security you m
}
]
}
-]
-
-```
\ No newline at end of file
+]
+
+```
diff --git a/hook/hook.go b/hook/hook.go
index 43bc225..2d2c70a 100644
--- a/hook/hook.go
+++ b/hook/hook.go
@@ -94,6 +94,10 @@ func (e *ParseError) Error() string {
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, error) {
+ if secret == "" {
+ return "", errors.New("signature validation secret can not be empty")
+ }
+
signature = strings.TrimPrefix(signature, "sha1=")
mac := hmac.New(sha1.New, []byte(secret))
@@ -111,6 +115,10 @@ func CheckPayloadSignature(payload []byte, secret string, signature string) (str
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
func CheckPayloadSignature256(payload []byte, secret string, signature string) (string, error) {
+ if secret == "" {
+ return "", errors.New("signature validation secret can not be empty")
+ }
+
signature = strings.TrimPrefix(signature, "sha256=")
mac := hmac.New(sha256.New, []byte(secret))
@@ -134,6 +142,10 @@ func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey
if _, ok := headers["Date"]; !ok {
return false, nil
}
+ if signingKey == "" {
+ return false, errors.New("signature validation signing key can not be empty")
+ }
+
providedSignature := headers["X-Signature"].(string)
dateHeader := headers["Date"].(string)
mac := hmac.New(sha1.New, []byte(signingKey))
@@ -168,41 +180,36 @@ func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey
func CheckIPWhitelist(remoteAddr string, ipRange string) (bool, error) {
// Extract IP address from remote address.
- ip := remoteAddr
+ // IPv6 addresses will likely be surrounded by [].
+ ip := strings.Trim(remoteAddr, " []")
- if strings.LastIndex(remoteAddr, ":") != -1 {
- ip = remoteAddr[0:strings.LastIndex(remoteAddr, ":")]
+ if i := strings.LastIndex(ip, ":"); i != -1 {
+ ip = ip[:i]
}
- ip = strings.TrimSpace(ip)
-
- // IPv6 addresses will likely be surrounded by [], so don't forget to remove those.
-
- if strings.HasPrefix(ip, "[") && strings.HasSuffix(ip, "]") {
- ip = ip[1 : len(ip)-1]
- }
-
- parsedIP := net.ParseIP(strings.TrimSpace(ip))
-
+ parsedIP := net.ParseIP(ip)
if parsedIP == nil {
return false, fmt.Errorf("invalid IP address found in remote address '%s'", remoteAddr)
}
- // Extract IP range in CIDR form. If a single IP address is provided, turn it into CIDR form.
+ for _, r := range strings.Fields(ipRange) {
+ // Extract IP range in CIDR form. If a single IP address is provided, turn it into CIDR form.
- ipRange = strings.TrimSpace(ipRange)
+ if !strings.Contains(r, "/") {
+ r = r + "/32"
+ }
- if !strings.Contains(ipRange, "/") {
- ipRange = ipRange + "/32"
+ _, cidr, err := net.ParseCIDR(r)
+ if err != nil {
+ return false, err
+ }
+
+ if cidr.Contains(parsedIP) {
+ return true, nil
+ }
}
- _, cidr, err := net.ParseCIDR(ipRange)
-
- if err != nil {
- return false, err
- }
-
- return cidr.Contains(parsedIP), nil
+ return false, nil
}
// ReplaceParameter replaces parameter value with the passed value in the passed map
diff --git a/hook/hook_test.go b/hook/hook_test.go
index 1794a7c..e6bfce2 100644
--- a/hook/hook_test.go
+++ b/hook/hook_test.go
@@ -19,6 +19,7 @@ var checkPayloadSignatureTests = []struct {
// failures
{[]byte(`{"a": "z"}`), "secret", "XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
{[]byte(`{"a": "z"}`), "secreX", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "900225703e9342328db7307692736e2f7cc7b36e", false},
+ {[]byte(`{"a": "z"}`), "", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "", false},
}
func TestCheckPayloadSignature(t *testing.T) {
@@ -28,7 +29,7 @@ func TestCheckPayloadSignature(t *testing.T) {
t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, (err == nil))
}
- if err != nil && strings.Contains(err.Error(), tt.mac) {
+ if err != nil && tt.mac != "" && strings.Contains(err.Error(), tt.mac) {
t.Errorf("error message should not disclose expected mac: %s", err)
}
}
@@ -45,6 +46,7 @@ var checkPayloadSignature256Tests = []struct {
{[]byte(`{"a": "z"}`), "secret", "sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
// failures
{[]byte(`{"a": "z"}`), "secret", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
+ {[]byte(`{"a": "z"}`), "", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "", false},
}
func TestCheckPayloadSignature256(t *testing.T) {
@@ -54,7 +56,7 @@ func TestCheckPayloadSignature256(t *testing.T) {
t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, (err == nil))
}
- if err != nil && strings.Contains(err.Error(), tt.mac) {
+ if err != nil && tt.mac != "" && strings.Contains(err.Error(), tt.mac) {
t.Errorf("error message should not disclose expected mac: %s", err)
}
}
@@ -92,6 +94,12 @@ var checkScalrSignatureTests = []struct {
[]byte(`{"a": "b"}`), "bilFGi4ZVZUdG+C6r0NIM9tuRq6PaG33R3eBUVhLwMAErGBaazvXe4Gq2DcJs5q+",
"48e395e38ac48988929167df531eb2da00063a7d", false,
},
+ {
+ "Missing signing key",
+ map[string]interface{}{"Date": "Thu 07 Sep 2017 06:30:04 UTC", "X-Signature": "48e395e38ac48988929167df531eb2da00063a7d"},
+ []byte(`{"a": "b"}`), "",
+ "48e395e38ac48988929167df531eb2da00063a7d", false,
+ },
}
func TestCheckScalrSignature(t *testing.T) {
@@ -102,12 +110,36 @@ func TestCheckScalrSignature(t *testing.T) {
testCase.description, testCase.ok, valid)
}
- if err != nil && strings.Contains(err.Error(), testCase.expectedSignature) {
+ if err != nil && testCase.secret != "" && strings.Contains(err.Error(), testCase.expectedSignature) {
t.Errorf("error message should not disclose expected mac: %s on test case %s", err, testCase.description)
}
}
}
+var checkIPWhitelistTests = []struct {
+ addr string
+ ipRange string
+ expect bool
+ ok bool
+}{
+ {"[ 10.0.0.1:1234 ] ", " 10.0.0.1 ", true, true},
+ {"[ 10.0.0.1:1234 ] ", " 10.0.0.0 ", false, true},
+ {"[ 10.0.0.1:1234 ] ", " 10.0.0.1 10.0.0.1 ", true, true},
+ {"[ 10.0.0.1:1234 ] ", " 10.0.0.0/31 ", true, true},
+ {" [2001:db8:1:2::1:1234] ", " 2001:db8:1::/48 ", true, true},
+ {" [2001:db8:1:2::1:1234] ", " 2001:db8:1::/48 2001:db8:1::/64", true, true},
+ {" [2001:db8:1:2::1:1234] ", " 2001:db8:1::/64 ", false, true},
+}
+
+func TestCheckIPWhitelist(t *testing.T) {
+ for _, tt := range checkIPWhitelistTests {
+ result, err := CheckIPWhitelist(tt.addr, tt.ipRange)
+ if (err == nil) != tt.ok || result != tt.expect {
+ t.Errorf("ip whitelist test failed {%q, %q}:\nwant {expect:%#v, ok:%#v},\ngot {result:%#v, ok:%#v}", tt.addr, tt.ipRange, tt.expect, tt.ok, result, err)
+ }
+ }
+}
+
var extractParameterTests = []struct {
s string
params interface{}
@@ -129,7 +161,7 @@ var extractParameterTests = []struct {
{"a.501.b", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": "y"}, map[string]interface{}{"b": "z"}}}, "", false}, // non-existent slice index
{"a.502.b", map[string]interface{}{"a": []interface{}{}}, "", false}, // non-existent slice index
{"a.b.503", map[string]interface{}{"a": map[string]interface{}{"b": []interface{}{"x", "y", "z"}}}, "", false}, // trailing, non-existent slice index
- {"a.b", interface{}("a"), "", false}, // non-map, non-slice input
+ {"a.b", interface{}("a"), "", false}, // non-map, non-slice input
}
func TestExtractParameter(t *testing.T) {
diff --git a/webhook.go b/webhook.go
index cb44032..36eea4c 100644
--- a/webhook.go
+++ b/webhook.go
@@ -23,7 +23,7 @@ import (
)
const (
- version = "2.6.8"
+ version = "2.6.9"
)
var (
@@ -185,6 +185,10 @@ func main() {
hooksURL = "/" + *hooksURLPrefix + "/{id}"
}
+ router.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+ fmt.Fprintf(w, "OK")
+ })
+
router.HandleFunc(hooksURL, hookHandler)
n.UseHandler(router)