Merge branch 'development' into feature/context-provider-command
This commit is contained in:
commit
eece0137ef
23
.github/workflows/build.yml
vendored
Normal file
23
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: build
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: [1.14.x, 1.15.x]
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build -v
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -v ./...
|
@ -1,7 +1,7 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.13.x
|
- 1.14.x
|
||||||
- master
|
- master
|
||||||
|
|
||||||
os:
|
os:
|
||||||
|
18
Makefile
18
Makefile
@ -1,12 +1,18 @@
|
|||||||
OS = darwin freebsd linux openbsd
|
OS = darwin freebsd linux openbsd
|
||||||
ARCHS = 386 arm amd64 arm64
|
ARCHS = 386 arm amd64 arm64
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-16s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
all: build release release-windows
|
all: build release release-windows
|
||||||
|
|
||||||
build: deps
|
build: deps ## Build the project
|
||||||
go build
|
go build
|
||||||
|
|
||||||
release: clean deps
|
release: clean deps ## Generate releases for unix systems
|
||||||
@for arch in $(ARCHS);\
|
@for arch in $(ARCHS);\
|
||||||
do \
|
do \
|
||||||
for os in $(OS);\
|
for os in $(OS);\
|
||||||
@ -18,7 +24,7 @@ release: clean deps
|
|||||||
done \
|
done \
|
||||||
done
|
done
|
||||||
|
|
||||||
release-windows: clean deps
|
release-windows: clean deps ## Generate release for windows
|
||||||
@for arch in $(ARCHS);\
|
@for arch in $(ARCHS);\
|
||||||
do \
|
do \
|
||||||
echo "Building windows-$$arch"; \
|
echo "Building windows-$$arch"; \
|
||||||
@ -27,12 +33,12 @@ release-windows: clean deps
|
|||||||
tar cz -C build -f build/webhook-windows-$$arch.tar.gz webhook-windows-$$arch; \
|
tar cz -C build -f build/webhook-windows-$$arch.tar.gz webhook-windows-$$arch; \
|
||||||
done
|
done
|
||||||
|
|
||||||
test: deps
|
test: deps ## Execute tests
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
deps:
|
deps: ## Install dependencies using go get
|
||||||
go get -d -v -t ./...
|
go get -d -v -t ./...
|
||||||
|
|
||||||
clean:
|
clean: ## Remove building artifacts
|
||||||
rm -rf build
|
rm -rf build
|
||||||
rm -f webhook
|
rm -f webhook
|
||||||
|
22
README.md
22
README.md
@ -1,4 +1,4 @@
|
|||||||
# What is webhook?
|
# What is webhook? ![build-status][badge]
|
||||||
|
|
||||||
<img src="https://github.com/adnanh/webhook/raw/development/docs/logo/logo-128x128.png" alt="Webhook" align="left" />
|
<img src="https://github.com/adnanh/webhook/raw/development/docs/logo/logo-128x128.png" alt="Webhook" align="left" />
|
||||||
|
|
||||||
@ -26,11 +26,11 @@ If you don't have time to waste configuring, hosting, debugging and maintaining
|
|||||||
# Getting started
|
# Getting started
|
||||||
## Installation
|
## Installation
|
||||||
### Building from source
|
### Building from source
|
||||||
To get started, first make sure you've properly set up your [Go](http://golang.org/doc/install) 1.12 or newer environment and then run
|
To get started, first make sure you've properly set up your [Go](http://golang.org/doc/install) 1.14 or newer environment and then run
|
||||||
```bash
|
```bash
|
||||||
$ go get github.com/adnanh/webhook
|
$ go build github.com/adnanh/webhook
|
||||||
```
|
```
|
||||||
to get the latest version of the [webhook][w].
|
to build the latest version of the [webhook][w].
|
||||||
|
|
||||||
### Using package manager
|
### Using package manager
|
||||||
#### Snap store
|
#### Snap store
|
||||||
@ -46,7 +46,9 @@ If you are using Debian linux ("stretch" or later), you can install webhook usin
|
|||||||
Prebuilt binaries for different architectures are available at [GitHub Releases](https://github.com/adnanh/webhook/releases).
|
Prebuilt binaries for different architectures are available at [GitHub Releases](https://github.com/adnanh/webhook/releases).
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
Next step is to define some hooks you want [webhook][w] to serve. Begin by creating an empty file named `hooks.json`. This file will contain an array of hooks the [webhook][w] will serve. Check [Hook definition page](docs/Hook-Definition.md) to see the detailed description of what properties a hook can contain, and how to use them.
|
Next step is to define some hooks you want [webhook][w] to serve.
|
||||||
|
[webhook][w] supports JSON or YAML configuration files, but we'll focus primarily on JSON in the following example.
|
||||||
|
Begin by creating an empty file named `hooks.json`. This file will contain an array of hooks the [webhook][w] will serve. Check [Hook definition page](docs/Hook-Definition.md) to see the detailed description of what properties a hook can contain, and how to use them.
|
||||||
|
|
||||||
Let's define a simple hook named `redeploy-webhook` that will run a redeploy script located in `/var/scripts/redeploy.sh`. Make sure that your bash script has `#!/bin/sh` shebang on top.
|
Let's define a simple hook named `redeploy-webhook` that will run a redeploy script located in `/var/scripts/redeploy.sh`. Make sure that your bash script has `#!/bin/sh` shebang on top.
|
||||||
|
|
||||||
@ -61,6 +63,13 @@ Our `hooks.json` file will now look like this:
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**NOTE:** If you prefer YAML, the equivalent `hooks.yaml` file would be:
|
||||||
|
```yaml
|
||||||
|
- id: redeploy-webhook
|
||||||
|
execute-command: "/var/scripts/redeploy.sh"
|
||||||
|
command-working-directory: "/var/webhook"
|
||||||
|
```
|
||||||
|
|
||||||
You can now run [webhook][w] using
|
You can now run [webhook][w] using
|
||||||
```bash
|
```bash
|
||||||
$ /path/to/webhook -hooks hooks.json -verbose
|
$ /path/to/webhook -hooks hooks.json -verbose
|
||||||
@ -90,7 +99,7 @@ All files are ignored unless they match one of the following criteria:
|
|||||||
In either case, the given file part will be parsed as JSON and added to the `payload` map.
|
In either case, the given file part will be parsed as JSON and added to the `payload` map.
|
||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
[webhook][w] can parse the `hooks.json` input file as a Go template when given the `-template` [CLI parameter](docs/Webhook-Parameters.md). See the [Templates page](docs/Templates.md) for more details on template usage.
|
[webhook][w] can parse the hooks configuration file as a Go template when given the `-template` [CLI parameter](docs/Webhook-Parameters.md). See the [Templates page](docs/Templates.md) for more details on template usage.
|
||||||
|
|
||||||
## Using HTTPS
|
## Using HTTPS
|
||||||
[webhook][w] by default serves hooks using http. If you want [webhook][w] to serve secure content using https, you can use the `-secure` flag while starting [webhook][w]. 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.
|
[webhook][w] by default serves hooks using http. If you want [webhook][w] to serve secure content using https, you can use the `-secure` flag while starting [webhook][w]. 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.
|
||||||
@ -202,3 +211,4 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
[w]: https://github.com/adnanh/webhook
|
[w]: https://github.com/adnanh/webhook
|
||||||
[wc]: https://github.com/adnanh/webhook-contrib
|
[wc]: https://github.com/adnanh/webhook-contrib
|
||||||
|
[badge]: https://github.com/adnanh/webhook/workflows/build/badge.svg
|
||||||
|
102
cipher_suites.go
102
cipher_suites.go
@ -1,102 +0,0 @@
|
|||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Copied from Go 1.14 tip src/crypto/tls/cipher_suites.go
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CipherSuite is a TLS cipher suite. Note that most functions in this package
|
|
||||||
// accept and expose cipher suite IDs instead of this type.
|
|
||||||
type CipherSuite struct {
|
|
||||||
ID uint16
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Supported versions is the list of TLS protocol versions that can
|
|
||||||
// negotiate this cipher suite.
|
|
||||||
SupportedVersions []uint16
|
|
||||||
|
|
||||||
// Insecure is true if the cipher suite has known security issues
|
|
||||||
// due to its primitives, design, or implementation.
|
|
||||||
Insecure bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
supportedUpToTLS12 = []uint16{tls.VersionTLS10, tls.VersionTLS11, tls.VersionTLS12}
|
|
||||||
supportedOnlyTLS12 = []uint16{tls.VersionTLS12}
|
|
||||||
supportedOnlyTLS13 = []uint16{tls.VersionTLS13}
|
|
||||||
)
|
|
||||||
|
|
||||||
// CipherSuites returns a list of cipher suites currently implemented by this
|
|
||||||
// package, excluding those with security issues, which are returned by
|
|
||||||
// InsecureCipherSuites.
|
|
||||||
//
|
|
||||||
// The list is sorted by ID. Note that the default cipher suites selected by
|
|
||||||
// this package might depend on logic that can't be captured by a static list.
|
|
||||||
func CipherSuites() []*CipherSuite {
|
|
||||||
return []*CipherSuite{
|
|
||||||
{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, false},
|
|
||||||
{tls.TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
|
||||||
{tls.TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
|
||||||
{tls.TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
|
||||||
{tls.TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
|
||||||
|
|
||||||
{tls.TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", supportedOnlyTLS13, false},
|
|
||||||
{tls.TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384", supportedOnlyTLS13, false},
|
|
||||||
{tls.TLS_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256", supportedOnlyTLS13, false},
|
|
||||||
|
|
||||||
{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
|
||||||
{tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
|
||||||
{tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, false},
|
|
||||||
{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
|
||||||
{tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
|
||||||
{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
|
||||||
{tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
|
||||||
{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
|
||||||
{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
|
||||||
|
|
||||||
// go1.14
|
|
||||||
// {tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
|
|
||||||
// {tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsecureCipherSuites returns a list of cipher suites currently implemented by
|
|
||||||
// this package and which have security issues.
|
|
||||||
//
|
|
||||||
// Most applications should not use the cipher suites in this list, and should
|
|
||||||
// only use those returned by CipherSuites.
|
|
||||||
func InsecureCipherSuites() []*CipherSuite {
|
|
||||||
// RC4 suites are broken because RC4 is.
|
|
||||||
// CBC-SHA256 suites have no Lucky13 countermeasures.
|
|
||||||
return []*CipherSuite{
|
|
||||||
{tls.TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
|
||||||
{tls.TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
|
||||||
{tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
|
||||||
{tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
|
||||||
{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
|
||||||
{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CipherSuiteName returns the standard name for the passed cipher suite ID
|
|
||||||
// (e.g. "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"), or a fallback representation
|
|
||||||
// of the ID value if the cipher suite is not implemented by this package.
|
|
||||||
func CipherSuiteName(id uint16) string {
|
|
||||||
for _, c := range CipherSuites() {
|
|
||||||
if c.ID == id {
|
|
||||||
return c.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, c := range InsecureCipherSuites() {
|
|
||||||
if c.ID == id {
|
|
||||||
return c.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("0x%04X", id)
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
# Hook definition
|
# Hook definition
|
||||||
Hooks are defined as JSON objects. Please note that in order to be considered valid, a hook object must contain the `id` and `execute-command` properties. All other properties are considered optional.
|
|
||||||
|
Hooks are defined as objects in the JSON or YAML hooks configuration file. Please note that in order to be considered valid, a hook object must contain the `id` and `execute-command` properties. All other properties are considered optional.
|
||||||
|
|
||||||
## Properties (keys)
|
## Properties (keys)
|
||||||
|
|
||||||
|
@ -1,5 +1,25 @@
|
|||||||
# Hook examples
|
# Hook Examples
|
||||||
This page is still work in progress. Feel free to contribute!
|
|
||||||
|
Hooks are defined in a hooks configuration file in either JSON or YAML format,
|
||||||
|
although the examples on this page all use the JSON format.
|
||||||
|
|
||||||
|
🌱 This page is still a work in progress. Feel free to contribute!
|
||||||
|
|
||||||
|
### Table of Contents
|
||||||
|
|
||||||
|
* [Incoming Github webhook](#incoming-github-webhook)
|
||||||
|
* [Incoming Bitbucket webhook](#incoming-bitbucket-webhook)
|
||||||
|
* [Incoming Gitlab webhook](#incoming-gitlab-webhook)
|
||||||
|
* [Incoming Gogs webhook](#incoming-gogs-webhook)
|
||||||
|
* [Incoming Gitea webhook](#incoming-gitea-webhook)
|
||||||
|
* [Slack slash command](#slack-slash-command)
|
||||||
|
* [A simple webhook with a secret key in GET query](#a-simple-webhook-with-a-secret-key-in-get-query)
|
||||||
|
* [JIRA Webhooks](#jira-webhooks)
|
||||||
|
* [Pass File-to-command sample](#pass-file-to-command-sample)
|
||||||
|
* [Incoming Scalr Webhook](#incoming-scalr-webhook)
|
||||||
|
* [Travis CI webhook](#travis-ci-webhook)
|
||||||
|
* [XML Payload](#xml-payload)
|
||||||
|
* [Multipart Form Data](#multipart-form-data)
|
||||||
|
|
||||||
## Incoming Github webhook
|
## Incoming Github webhook
|
||||||
```json
|
```json
|
||||||
@ -30,7 +50,7 @@ This page is still work in progress. Feel free to contribute!
|
|||||||
{
|
{
|
||||||
"match":
|
"match":
|
||||||
{
|
{
|
||||||
"type": "payload-hash-sha1",
|
"type": "payload-hmac-sha1",
|
||||||
"secret": "mysecret",
|
"secret": "mysecret",
|
||||||
"parameter":
|
"parameter":
|
||||||
{
|
{
|
||||||
@ -150,7 +170,7 @@ Values in the request body can be accessed in the command or to the match rule b
|
|||||||
{
|
{
|
||||||
"match":
|
"match":
|
||||||
{
|
{
|
||||||
"type": "payload-hash-sha256",
|
"type": "payload-hmac-sha256",
|
||||||
"secret": "mysecret",
|
"secret": "mysecret",
|
||||||
"parameter":
|
"parameter":
|
||||||
{
|
{
|
||||||
@ -425,6 +445,57 @@ Travis sends webhooks as `payload=<JSON_STRING>`, so the payload needs to be par
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## JSON Array Payload
|
||||||
|
|
||||||
|
If the JSON payload is an array instead of an object, `webhook` will process the payload and place it into a "root" object.
|
||||||
|
Therefore, references to payload values must begin with `root.`.
|
||||||
|
|
||||||
|
For example, given the following payload (taken from the Sendgrid Event Webhook documentation):
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"email": "example@test.com",
|
||||||
|
"timestamp": 1513299569,
|
||||||
|
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>",
|
||||||
|
"event": "processed",
|
||||||
|
"category": "cat facts",
|
||||||
|
"sg_event_id": "sg_event_id",
|
||||||
|
"sg_message_id": "sg_message_id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": "example@test.com",
|
||||||
|
"timestamp": 1513299569,
|
||||||
|
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>",
|
||||||
|
"event": "deferred",
|
||||||
|
"category": "cat facts",
|
||||||
|
"sg_event_id": "sg_event_id",
|
||||||
|
"sg_message_id": "sg_message_id",
|
||||||
|
"response": "400 try again later",
|
||||||
|
"attempt": "5"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
A reference to the second item in the array would look like this:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "sendgrid",
|
||||||
|
"execute-command": "{{ .Hookecho }}",
|
||||||
|
"trigger-rule": {
|
||||||
|
"match": {
|
||||||
|
"type": "value",
|
||||||
|
"parameter": {
|
||||||
|
"source": "payload",
|
||||||
|
"name": "root.1.event"
|
||||||
|
},
|
||||||
|
"value": "deferred"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## XML Payload
|
## XML Payload
|
||||||
|
|
||||||
Given the following payload:
|
Given the following payload:
|
||||||
|
@ -1,5 +1,20 @@
|
|||||||
# Hook rules
|
# Hook rules
|
||||||
|
|
||||||
|
### Table of Contents
|
||||||
|
|
||||||
|
* [And](#and)
|
||||||
|
* [Or](#or)
|
||||||
|
* [Not](#not)
|
||||||
|
* [Multi-level](#multi-level)
|
||||||
|
* [Match](#match)
|
||||||
|
* [Match value](#match-value)
|
||||||
|
* [Match regex](#match-regex)
|
||||||
|
* [Match payload-hmac-sha1](#match-payload-hmac-sha1)
|
||||||
|
* [Match payload-hmac-sha256](#match-payload-hmac-sha256)
|
||||||
|
* [Match payload-hmac-sha512](#match-payload-hmac-sha512)
|
||||||
|
* [Match Whitelisted IP range](#match-whitelisted-ip-range)
|
||||||
|
* [Match scalr-signature](#match-scalr-signature)
|
||||||
|
|
||||||
## And
|
## And
|
||||||
*And rule* will evaluate to _true_, if and only if all of the sub rules evaluate to _true_.
|
*And rule* will evaluate to _true_, if and only if all of the sub rules evaluate to _true_.
|
||||||
```json
|
```json
|
||||||
@ -95,7 +110,7 @@
|
|||||||
"source": "header",
|
"source": "header",
|
||||||
"name": "X-Hub-Signature"
|
"name": "X-Hub-Signature"
|
||||||
},
|
},
|
||||||
"type": "payload-hash-sha1",
|
"type": "payload-hmac-sha1",
|
||||||
"secret": "mysecret"
|
"secret": "mysecret"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -135,9 +150,7 @@
|
|||||||
|
|
||||||
*Please note:* Due to technical reasons, _number_ and _boolean_ values in the _match rule_ must be wrapped around with a pair of quotes.
|
*Please note:* Due to technical reasons, _number_ and _boolean_ values in the _match rule_ must be wrapped around with a pair of quotes.
|
||||||
|
|
||||||
There are three different match rules:
|
### Match value
|
||||||
|
|
||||||
### 1. Match value
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"match":
|
"match":
|
||||||
@ -153,7 +166,7 @@ There are three different match rules:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Match regex
|
### Match regex
|
||||||
For the regex syntax, check out <http://golang.org/pkg/regexp/syntax/>
|
For the regex syntax, check out <http://golang.org/pkg/regexp/syntax/>
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -170,12 +183,13 @@ For the regex syntax, check out <http://golang.org/pkg/regexp/syntax/>
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Match payload-hash-sha1
|
### Match payload-hmac-sha1
|
||||||
|
Validate the HMAC of the payload using the SHA1 hash and the given *secret*.
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"match":
|
"match":
|
||||||
{
|
{
|
||||||
"type": "payload-hash-sha1",
|
"type": "payload-hmac-sha1",
|
||||||
"secret": "yoursecret",
|
"secret": "yoursecret",
|
||||||
"parameter":
|
"parameter":
|
||||||
{
|
{
|
||||||
@ -193,12 +207,13 @@ will be tried unless a match is found. For example:
|
|||||||
X-Hub-Signature: sha1=the-first-signature,sha1=the-second-signature
|
X-Hub-Signature: sha1=the-first-signature,sha1=the-second-signature
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Match payload-hash-sha256
|
### Match payload-hmac-sha256
|
||||||
|
Validate the HMAC of the payload using the SHA256 hash and the given *secret*.
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"match":
|
"match":
|
||||||
{
|
{
|
||||||
"type": "payload-hash-sha256",
|
"type": "payload-hmac-sha256",
|
||||||
"secret": "yoursecret",
|
"secret": "yoursecret",
|
||||||
"parameter":
|
"parameter":
|
||||||
{
|
{
|
||||||
@ -216,12 +231,13 @@ will be tried unless a match is found. For example:
|
|||||||
X-Hub-Signature: sha256=the-first-signature,sha256=the-second-signature
|
X-Hub-Signature: sha256=the-first-signature,sha256=the-second-signature
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Match payload-hash-sha512
|
### Match payload-hmac-sha512
|
||||||
|
Validate the HMAC of the payload using the SHA512 hash and the given *secret*.
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"match":
|
"match":
|
||||||
{
|
{
|
||||||
"type": "payload-hash-sha512",
|
"type": "payload-hmac-sha512",
|
||||||
"secret": "yoursecret",
|
"secret": "yoursecret",
|
||||||
"parameter":
|
"parameter":
|
||||||
{
|
{
|
||||||
@ -239,7 +255,7 @@ will be tried unless a match is found. For example:
|
|||||||
X-Hub-Signature: sha512=the-first-signature,sha512=the-second-signature
|
X-Hub-Signature: sha512=the-first-signature,sha512=the-second-signature
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Match Whitelisted IP range
|
### Match Whitelisted IP range
|
||||||
|
|
||||||
The IP can be IPv4- or IPv6-formatted, using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_blocks). To match a single IP address only, use `/32`.
|
The IP can be IPv4- or IPv6-formatted, using [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_blocks). To match a single IP address only, use `/32`.
|
||||||
|
|
||||||
@ -253,7 +269,7 @@ The IP can be IPv4- or IPv6-formatted, using [CIDR notation](https://en.wikipedi
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7. Match scalr-signature
|
### Match scalr-signature
|
||||||
|
|
||||||
The trigger rule checks the scalr signature and also checks that the request was signed less than 5 minutes before it was received.
|
The trigger rule checks the scalr signature and also checks that the request was signed less than 5 minutes before it was received.
|
||||||
A unqiue signing key is generated for each webhook endpoint URL you register in Scalr.
|
A unqiue signing key is generated for each webhook endpoint URL you register in Scalr.
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
# Templates in Webhook
|
# Templates in Webhook
|
||||||
|
|
||||||
[`webhook`][w] can parse the `hooks.json` input file as a Go template when given the `-template` [CLI parameter](Webhook-Parameters.md).
|
[`webhook`][w] can parse a hooks configuration file as a Go template when given the `-template` [CLI parameter](Webhook-Parameters.md).
|
||||||
|
|
||||||
In additional to the [built-in Go template functions and features][tt], `webhook` provides a `getenv` template function for inserting environment variables into a `hooks.json` file.
|
In additional to the [built-in Go template functions and features][tt], `webhook` provides a `getenv` template function for inserting environment variables into a templated configuration file.
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
In the example `hooks.json` file below, the `payload-hash-sha1` matching rule looks up the secret hash from the environment using the `getenv` template function.
|
In the example JSON template file below (YAML is also supported), the `payload-hmac-sha1` matching rule looks up the HMAC secret from the environment using the `getenv` template function.
|
||||||
Additionally, the result is piped through the built-in Go template function `js` to ensure that the result is a well-formed Javascript/JSON string.
|
Additionally, the result is piped through the built-in Go template function `js` to ensure that the result is a well-formed Javascript/JSON string.
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -44,7 +44,7 @@ Additionally, the result is piped through the built-in Go template function `js`
|
|||||||
{
|
{
|
||||||
"match":
|
"match":
|
||||||
{
|
{
|
||||||
"type": "payload-hash-sha1",
|
"type": "payload-hmac-sha1",
|
||||||
"secret": "{{ getenv "XXXTEST_SECRET" | js }}",
|
"secret": "{{ getenv "XXXTEST_SECRET" | js }}",
|
||||||
"parameter":
|
"parameter":
|
||||||
{
|
{
|
||||||
|
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/adnanh/webhook
|
module github.com/adnanh/webhook
|
||||||
|
|
||||||
go 1.13
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/clbanning/mxj v1.8.4
|
github.com/clbanning/mxj v1.8.4
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
{
|
{
|
||||||
"match":
|
"match":
|
||||||
{
|
{
|
||||||
"type": "payload-hash-sha1",
|
"type": "payload-hmac-sha1",
|
||||||
"secret": "mysecret",
|
"secret": "mysecret",
|
||||||
"parameter":
|
"parameter":
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
{
|
{
|
||||||
"match":
|
"match":
|
||||||
{
|
{
|
||||||
"type": "payload-hash-sha1",
|
"type": "payload-hmac-sha1",
|
||||||
"secret": "{{ getenv "XXXTEST_SECRET" | js }}",
|
"secret": "{{ getenv "XXXTEST_SECRET" | js }}",
|
||||||
"parameter":
|
"parameter":
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
trigger-rule:
|
trigger-rule:
|
||||||
and:
|
and:
|
||||||
- match:
|
- match:
|
||||||
type: payload-hash-sha1
|
type: payload-hmac-sha1
|
||||||
secret: mysecret
|
secret: mysecret
|
||||||
parameter:
|
parameter:
|
||||||
source: header
|
source: header
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
trigger-rule:
|
trigger-rule:
|
||||||
and:
|
and:
|
||||||
- match:
|
- match:
|
||||||
type: payload-hash-sha1
|
type: payload-hmac-sha1
|
||||||
secret: "{{ getenv "XXXTEST_SECRET" | js }}"
|
secret: "{{ getenv "XXXTEST_SECRET" | js }}"
|
||||||
parameter:
|
parameter:
|
||||||
source: header
|
source: header
|
||||||
|
@ -50,6 +50,33 @@ const (
|
|||||||
EnvNamespace string = "HOOK_"
|
EnvNamespace string = "HOOK_"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Request represents a webhook request.
|
||||||
|
type Request struct {
|
||||||
|
// The request ID set by the RequestID middleware.
|
||||||
|
ID string
|
||||||
|
|
||||||
|
// The Content-Type of the request.
|
||||||
|
ContentType string
|
||||||
|
|
||||||
|
// The raw request body.
|
||||||
|
Body []byte
|
||||||
|
|
||||||
|
// Headers is a map of the parsed headers.
|
||||||
|
Headers map[string]interface{}
|
||||||
|
|
||||||
|
// Query is a map of the parsed URL query values.
|
||||||
|
Query map[string]interface{}
|
||||||
|
|
||||||
|
// Payload is a map of the parsed payload.
|
||||||
|
Payload map[string]interface{}
|
||||||
|
|
||||||
|
// Context is a map of the parsed pre-hook command result
|
||||||
|
Context map[string]interface{}
|
||||||
|
|
||||||
|
// The underlying HTTP request.
|
||||||
|
RawRequest *http.Request
|
||||||
|
}
|
||||||
|
|
||||||
// ParameterNodeError describes an error walking a parameter node.
|
// ParameterNodeError describes an error walking a parameter node.
|
||||||
type ParameterNodeError struct {
|
type ParameterNodeError struct {
|
||||||
key string
|
key string
|
||||||
@ -76,6 +103,8 @@ func IsParameterNodeError(err error) bool {
|
|||||||
type SignatureError struct {
|
type SignatureError struct {
|
||||||
Signature string
|
Signature string
|
||||||
Signatures []string
|
Signatures []string
|
||||||
|
|
||||||
|
emptyPayload bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *SignatureError) Error() string {
|
func (e *SignatureError) Error() string {
|
||||||
@ -83,11 +112,16 @@ func (e *SignatureError) Error() string {
|
|||||||
return "<nil>"
|
return "<nil>"
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Signatures != nil {
|
var empty string
|
||||||
return fmt.Sprintf("invalid payload signatures %s", e.Signatures)
|
if e.emptyPayload {
|
||||||
|
empty = " on empty payload"
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("invalid payload signature %s", e.Signature)
|
if e.Signatures != nil {
|
||||||
|
return fmt.Sprintf("invalid payload signatures %s%s", e.Signatures, empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("invalid payload signature %s%s", e.Signature, empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArgumentError describes an invalid argument passed to Hook.
|
// ArgumentError describes an invalid argument passed to Hook.
|
||||||
@ -163,21 +197,24 @@ func ValidateMAC(payload []byte, mac hash.Hash, signatures []string) (string, er
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
actualMAC := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
for _, signature := range signatures {
|
for _, signature := range signatures {
|
||||||
if hmac.Equal([]byte(signature), []byte(expectedMAC)) {
|
if hmac.Equal([]byte(signature), []byte(actualMAC)) {
|
||||||
return expectedMAC, err
|
return actualMAC, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return expectedMAC, &SignatureError{
|
e := &SignatureError{Signatures: signatures}
|
||||||
Signatures: signatures,
|
if len(payload) == 0 {
|
||||||
|
e.emptyPayload = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return actualMAC, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
|
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
|
||||||
func CheckPayloadSignature(payload []byte, secret string, signature string) (string, error) {
|
func CheckPayloadSignature(payload []byte, secret, signature string) (string, error) {
|
||||||
if secret == "" {
|
if secret == "" {
|
||||||
return "", errors.New("signature validation secret can not be empty")
|
return "", errors.New("signature validation secret can not be empty")
|
||||||
}
|
}
|
||||||
@ -190,7 +227,7 @@ func CheckPayloadSignature(payload []byte, secret string, signature string) (str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
|
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
|
||||||
func CheckPayloadSignature256(payload []byte, secret string, signature string) (string, error) {
|
func CheckPayloadSignature256(payload []byte, secret, signature string) (string, error) {
|
||||||
if secret == "" {
|
if secret == "" {
|
||||||
return "", errors.New("signature validation secret can not be empty")
|
return "", errors.New("signature validation secret can not be empty")
|
||||||
}
|
}
|
||||||
@ -203,7 +240,7 @@ func CheckPayloadSignature256(payload []byte, secret string, signature string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckPayloadSignature512 calculates and verifies SHA512 signature of the given payload
|
// CheckPayloadSignature512 calculates and verifies SHA512 signature of the given payload
|
||||||
func CheckPayloadSignature512(payload []byte, secret string, signature string) (string, error) {
|
func CheckPayloadSignature512(payload []byte, secret, signature string) (string, error) {
|
||||||
if secret == "" {
|
if secret == "" {
|
||||||
return "", errors.New("signature validation secret can not be empty")
|
return "", errors.New("signature validation secret can not be empty")
|
||||||
}
|
}
|
||||||
@ -215,22 +252,26 @@ func CheckPayloadSignature512(payload []byte, secret string, signature string) (
|
|||||||
return ValidateMAC(payload, hmac.New(sha512.New, []byte(secret)), signatures)
|
return ValidateMAC(payload, hmac.New(sha512.New, []byte(secret)), signatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey string, checkDate bool) (bool, error) {
|
func CheckScalrSignature(r *Request, signingKey string, checkDate bool) (bool, error) {
|
||||||
// Check for the signature and date headers
|
if r.Headers == nil {
|
||||||
if _, ok := headers["X-Signature"]; !ok {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if _, ok := headers["Date"]; !ok {
|
|
||||||
|
// Check for the signature and date headers
|
||||||
|
if _, ok := r.Headers["X-Signature"]; !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if _, ok := r.Headers["Date"]; !ok {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if signingKey == "" {
|
if signingKey == "" {
|
||||||
return false, errors.New("signature validation signing key can not be empty")
|
return false, errors.New("signature validation signing key can not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
providedSignature := headers["X-Signature"].(string)
|
providedSignature := r.Headers["X-Signature"].(string)
|
||||||
dateHeader := headers["Date"].(string)
|
dateHeader := r.Headers["Date"].(string)
|
||||||
mac := hmac.New(sha1.New, []byte(signingKey))
|
mac := hmac.New(sha1.New, []byte(signingKey))
|
||||||
mac.Write(body)
|
mac.Write(r.Body)
|
||||||
mac.Write([]byte(dateHeader))
|
mac.Write([]byte(dateHeader))
|
||||||
expectedSignature := hex.EncodeToString(mac.Sum(nil))
|
expectedSignature := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
@ -257,7 +298,7 @@ func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey
|
|||||||
|
|
||||||
// CheckIPWhitelist makes sure the provided remote address (of the form IP:port) falls within the provided IP range
|
// CheckIPWhitelist makes sure the provided remote address (of the form IP:port) falls within the provided IP range
|
||||||
// (in CIDR form or a single IP address).
|
// (in CIDR form or a single IP address).
|
||||||
func CheckIPWhitelist(remoteAddr string, ipRange string) (bool, error) {
|
func CheckIPWhitelist(remoteAddr, ipRange string) (bool, error) {
|
||||||
// Extract IP address from remote address.
|
// Extract IP address from remote address.
|
||||||
|
|
||||||
// IPv6 addresses will likely be surrounded by [].
|
// IPv6 addresses will likely be surrounded by [].
|
||||||
@ -296,7 +337,7 @@ func CheckIPWhitelist(remoteAddr string, ipRange string) (bool, error) {
|
|||||||
// ReplaceParameter replaces parameter value with the passed value in the passed map
|
// ReplaceParameter replaces parameter value with the passed value in the passed map
|
||||||
// (please note you should pass pointer to the map, because we're modifying it)
|
// (please note you should pass pointer to the map, because we're modifying it)
|
||||||
// based on the passed string
|
// based on the passed string
|
||||||
func ReplaceParameter(s string, params interface{}, value interface{}) bool {
|
func ReplaceParameter(s string, params, value interface{}) bool {
|
||||||
if params == nil {
|
if params == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -385,15 +426,28 @@ func GetParameter(s string, params interface{}) (interface{}, error) {
|
|||||||
return nil, &ParameterNodeError{s}
|
return nil, &ParameterNodeError{s}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractParameterAsString extracts value from interface{} as string based on the passed string
|
// 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, error) {
|
||||||
pValue, err := GetParameter(s, params)
|
pValue, err := GetParameter(s, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", 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 string(r), nil
|
||||||
|
|
||||||
|
default:
|
||||||
return fmt.Sprintf("%v", pValue), nil
|
return fmt.Sprintf("%v", pValue), nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Argument type specifies the parameter key name and the source it should
|
// Argument type specifies the parameter key name and the source it should
|
||||||
// be extracted from
|
// be extracted from
|
||||||
@ -406,43 +460,43 @@ type Argument struct {
|
|||||||
|
|
||||||
// Get Argument method returns the value for the Argument's key name
|
// Get Argument method returns the value for the Argument's key name
|
||||||
// based on the Argument's source
|
// based on the Argument's source
|
||||||
func (ha *Argument) Get(headers, query, payload *map[string]interface{}, context *map[string]interface{}) (string, error) {
|
func (ha *Argument) Get(r *Request) (string, error) {
|
||||||
var source *map[string]interface{}
|
var source *map[string]interface{}
|
||||||
key := ha.Name
|
key := ha.Name
|
||||||
|
|
||||||
switch ha.Source {
|
switch ha.Source {
|
||||||
case SourceHeader:
|
case SourceHeader:
|
||||||
source = headers
|
source = &r.Headers
|
||||||
key = textproto.CanonicalMIMEHeaderKey(ha.Name)
|
key = textproto.CanonicalMIMEHeaderKey(ha.Name)
|
||||||
case SourceQuery, SourceQueryAlias:
|
case SourceQuery, SourceQueryAlias:
|
||||||
source = query
|
source = &r.Query
|
||||||
case SourcePayload:
|
case SourcePayload:
|
||||||
source = payload
|
source = &r.Payload
|
||||||
case SourceContext:
|
case SourceContext:
|
||||||
source = context
|
source = &r.Context
|
||||||
case SourceString:
|
case SourceString:
|
||||||
return ha.Name, nil
|
return ha.Name, nil
|
||||||
case SourceEntirePayload:
|
case SourceEntirePayload:
|
||||||
r, err := json.Marshal(payload)
|
res, err := json.Marshal(&r.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(r), nil
|
return string(res), nil
|
||||||
case SourceEntireHeaders:
|
case SourceEntireHeaders:
|
||||||
r, err := json.Marshal(headers)
|
res, err := json.Marshal(&r.Headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(r), nil
|
return string(res), nil
|
||||||
case SourceEntireQuery:
|
case SourceEntireQuery:
|
||||||
r, err := json.Marshal(query)
|
res, err := json.Marshal(&r.Query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(r), nil
|
return string(res), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if source != nil {
|
if source != nil {
|
||||||
@ -540,11 +594,11 @@ type Hook struct {
|
|||||||
|
|
||||||
// ParseJSONParameters decodes specified arguments to JSON objects and replaces the
|
// ParseJSONParameters decodes specified arguments to JSON objects and replaces the
|
||||||
// string with the newly created object
|
// string with the newly created object
|
||||||
func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface{}, context *map[string]interface{}) []error {
|
func (h *Hook) ParseJSONParameters(r *Request) []error {
|
||||||
errors := make([]error, 0)
|
errors := make([]error, 0)
|
||||||
|
|
||||||
for i := range h.JSONStringParameters {
|
for i := range h.JSONStringParameters {
|
||||||
arg, err := h.JSONStringParameters[i].Get(headers, query, payload, context)
|
arg, err := h.JSONStringParameters[i].Get(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors = append(errors, &ArgumentError{h.JSONStringParameters[i]})
|
errors = append(errors, &ArgumentError{h.JSONStringParameters[i]})
|
||||||
} else {
|
} else {
|
||||||
@ -563,13 +617,13 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
|
|||||||
|
|
||||||
switch h.JSONStringParameters[i].Source {
|
switch h.JSONStringParameters[i].Source {
|
||||||
case SourceHeader:
|
case SourceHeader:
|
||||||
source = headers
|
source = &r.Headers
|
||||||
case SourcePayload:
|
case SourcePayload:
|
||||||
source = payload
|
source = &r.Payload
|
||||||
case SourceContext:
|
case SourceContext:
|
||||||
source = context
|
source = &r.Context
|
||||||
case SourceQuery, SourceQueryAlias:
|
case SourceQuery, SourceQueryAlias:
|
||||||
source = query
|
source = &r.Query
|
||||||
}
|
}
|
||||||
|
|
||||||
if source != nil {
|
if source != nil {
|
||||||
@ -595,14 +649,14 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
|
|||||||
|
|
||||||
// ExtractCommandArguments creates a list of arguments, based on the
|
// ExtractCommandArguments creates a list of arguments, based on the
|
||||||
// PassArgumentsToCommand property that is ready to be used with exec.Command()
|
// PassArgumentsToCommand property that is ready to be used with exec.Command()
|
||||||
func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]interface{}, context *map[string]interface{}) ([]string, []error) {
|
func (h *Hook) ExtractCommandArguments(r *Request) ([]string, []error) {
|
||||||
args := make([]string, 0)
|
args := make([]string, 0)
|
||||||
errors := make([]error, 0)
|
errors := make([]error, 0)
|
||||||
|
|
||||||
args = append(args, h.ExecuteCommand)
|
args = append(args, h.ExecuteCommand)
|
||||||
|
|
||||||
for i := range h.PassArgumentsToCommand {
|
for i := range h.PassArgumentsToCommand {
|
||||||
arg, err := h.PassArgumentsToCommand[i].Get(headers, query, payload, context)
|
arg, err := h.PassArgumentsToCommand[i].Get(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, "")
|
args = append(args, "")
|
||||||
errors = append(errors, &ArgumentError{h.PassArgumentsToCommand[i]})
|
errors = append(errors, &ArgumentError{h.PassArgumentsToCommand[i]})
|
||||||
@ -622,11 +676,11 @@ func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]inter
|
|||||||
// ExtractCommandArgumentsForEnv creates a list of arguments in key=value
|
// ExtractCommandArgumentsForEnv creates a list of arguments in key=value
|
||||||
// format, based on the PassEnvironmentToCommand property that is ready to be used
|
// format, based on the PassEnvironmentToCommand property that is ready to be used
|
||||||
// with exec.Command().
|
// with exec.Command().
|
||||||
func (h *Hook) ExtractCommandArgumentsForEnv(headers, query, payload *map[string]interface{}, context *map[string]interface{}) ([]string, []error) {
|
func (h *Hook) ExtractCommandArgumentsForEnv(r *Request) ([]string, []error) {
|
||||||
args := make([]string, 0)
|
args := make([]string, 0)
|
||||||
errors := make([]error, 0)
|
errors := make([]error, 0)
|
||||||
for i := range h.PassEnvironmentToCommand {
|
for i := range h.PassEnvironmentToCommand {
|
||||||
arg, err := h.PassEnvironmentToCommand[i].Get(headers, query, payload, context)
|
arg, err := h.PassEnvironmentToCommand[i].Get(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors = append(errors, &ArgumentError{h.PassEnvironmentToCommand[i]})
|
errors = append(errors, &ArgumentError{h.PassEnvironmentToCommand[i]})
|
||||||
continue
|
continue
|
||||||
@ -658,11 +712,11 @@ type FileParameter struct {
|
|||||||
// ExtractCommandArgumentsForFile creates a list of arguments in key=value
|
// ExtractCommandArgumentsForFile creates a list of arguments in key=value
|
||||||
// format, based on the PassFileToCommand property that is ready to be used
|
// format, based on the PassFileToCommand property that is ready to be used
|
||||||
// with exec.Command().
|
// with exec.Command().
|
||||||
func (h *Hook) ExtractCommandArgumentsForFile(headers, query, payload *map[string]interface{}, context *map[string]interface{}) ([]FileParameter, []error) {
|
func (h *Hook) ExtractCommandArgumentsForFile(r *Request) ([]FileParameter, []error) {
|
||||||
args := make([]FileParameter, 0)
|
args := make([]FileParameter, 0)
|
||||||
errors := make([]error, 0)
|
errors := make([]error, 0)
|
||||||
for i := range h.PassFileToCommand {
|
for i := range h.PassFileToCommand {
|
||||||
arg, err := h.PassFileToCommand[i].Get(headers, query, payload, context)
|
arg, err := h.PassFileToCommand[i].Get(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors = append(errors, &ArgumentError{h.PassFileToCommand[i]})
|
errors = append(errors, &ArgumentError{h.PassFileToCommand[i]})
|
||||||
continue
|
continue
|
||||||
@ -769,16 +823,16 @@ type Rules struct {
|
|||||||
|
|
||||||
// Evaluate finds the first rule property that is not nil and returns the value
|
// Evaluate finds the first rule property that is not nil and returns the value
|
||||||
// it evaluates to
|
// it evaluates to
|
||||||
func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, context *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
func (r Rules) Evaluate(req *Request) (bool, error) {
|
||||||
switch {
|
switch {
|
||||||
case r.And != nil:
|
case r.And != nil:
|
||||||
return r.And.Evaluate(headers, query, payload, context, body, remoteAddr)
|
return r.And.Evaluate(req)
|
||||||
case r.Or != nil:
|
case r.Or != nil:
|
||||||
return r.Or.Evaluate(headers, query, payload, context, body, remoteAddr)
|
return r.Or.Evaluate(req)
|
||||||
case r.Not != nil:
|
case r.Not != nil:
|
||||||
return r.Not.Evaluate(headers, query, payload, context, body, remoteAddr)
|
return r.Not.Evaluate(req)
|
||||||
case r.Match != nil:
|
case r.Match != nil:
|
||||||
return r.Match.Evaluate(headers, query, payload, context, body, remoteAddr)
|
return r.Match.Evaluate(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -788,11 +842,11 @@ func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, context
|
|||||||
type AndRule []Rules
|
type AndRule []Rules
|
||||||
|
|
||||||
// Evaluate AndRule will return true if and only if all of ChildRules evaluate to true
|
// Evaluate AndRule will return true if and only if all of ChildRules evaluate to true
|
||||||
func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, context *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
func (r AndRule) Evaluate(req *Request) (bool, error) {
|
||||||
res := true
|
res := true
|
||||||
|
|
||||||
for _, v := range r {
|
for _, v := range r {
|
||||||
rv, err := v.Evaluate(headers, query, payload, context, body, remoteAddr)
|
rv, err := v.Evaluate(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -810,11 +864,11 @@ func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, conte
|
|||||||
type OrRule []Rules
|
type OrRule []Rules
|
||||||
|
|
||||||
// Evaluate OrRule will return true if any of ChildRules evaluate to true
|
// Evaluate OrRule will return true if any of ChildRules evaluate to true
|
||||||
func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, context *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
func (r OrRule) Evaluate(req *Request) (bool, error) {
|
||||||
res := false
|
res := false
|
||||||
|
|
||||||
for _, v := range r {
|
for _, v := range r {
|
||||||
rv, err := v.Evaluate(headers, query, payload, context, body, remoteAddr)
|
rv, err := v.Evaluate(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -832,8 +886,8 @@ func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, contex
|
|||||||
type NotRule Rules
|
type NotRule Rules
|
||||||
|
|
||||||
// Evaluate NotRule will return true if and only if ChildRule evaluates to false
|
// Evaluate NotRule will return true if and only if ChildRule evaluates to false
|
||||||
func (r NotRule) Evaluate(headers, query, payload *map[string]interface{}, context *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
func (r NotRule) Evaluate(req *Request) (bool, error) {
|
||||||
rv, err := Rules(r).Evaluate(headers, query, payload, context, body, remoteAddr)
|
rv, err := Rules(r).Evaluate(req)
|
||||||
return !rv, err
|
return !rv, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -851,6 +905,9 @@ type MatchRule struct {
|
|||||||
const (
|
const (
|
||||||
MatchValue string = "value"
|
MatchValue string = "value"
|
||||||
MatchRegex string = "regex"
|
MatchRegex string = "regex"
|
||||||
|
MatchHMACSHA1 string = "payload-hmac-sha1"
|
||||||
|
MatchHMACSHA256 string = "payload-hmac-sha256"
|
||||||
|
MatchHMACSHA512 string = "payload-hmac-sha512"
|
||||||
MatchHashSHA1 string = "payload-hash-sha1"
|
MatchHashSHA1 string = "payload-hash-sha1"
|
||||||
MatchHashSHA256 string = "payload-hash-sha256"
|
MatchHashSHA256 string = "payload-hash-sha256"
|
||||||
MatchHashSHA512 string = "payload-hash-sha512"
|
MatchHashSHA512 string = "payload-hash-sha512"
|
||||||
@ -859,16 +916,16 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Evaluate MatchRule will return based on the type
|
// Evaluate MatchRule will return based on the type
|
||||||
func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, context *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
func (r MatchRule) Evaluate(req *Request) (bool, error) {
|
||||||
if r.Type == IPWhitelist {
|
if r.Type == IPWhitelist {
|
||||||
return CheckIPWhitelist(remoteAddr, r.IPRange)
|
return CheckIPWhitelist(req.RawRequest.RemoteAddr, r.IPRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Type == ScalrSignature {
|
if r.Type == ScalrSignature {
|
||||||
return CheckScalrSignature(*headers, *body, r.Secret, true)
|
return CheckScalrSignature(req, r.Secret, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
arg, err := r.Parameter.Get(headers, query, payload, context)
|
arg, err := r.Parameter.Get(req)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
switch r.Type {
|
switch r.Type {
|
||||||
case MatchValue:
|
case MatchValue:
|
||||||
@ -876,13 +933,22 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, con
|
|||||||
case MatchRegex:
|
case MatchRegex:
|
||||||
return regexp.MatchString(r.Regex, arg)
|
return regexp.MatchString(r.Regex, arg)
|
||||||
case MatchHashSHA1:
|
case MatchHashSHA1:
|
||||||
_, err := CheckPayloadSignature(*body, r.Secret, arg)
|
log.Print(`warn: use of deprecated option payload-hash-sha1; use payload-hmac-sha1 instead`)
|
||||||
|
fallthrough
|
||||||
|
case MatchHMACSHA1:
|
||||||
|
_, err := CheckPayloadSignature(req.Body, r.Secret, arg)
|
||||||
return err == nil, err
|
return err == nil, err
|
||||||
case MatchHashSHA256:
|
case MatchHashSHA256:
|
||||||
_, err := CheckPayloadSignature256(*body, r.Secret, arg)
|
log.Print(`warn: use of deprecated option payload-hash-sha256: use payload-hmac-sha256 instead`)
|
||||||
|
fallthrough
|
||||||
|
case MatchHMACSHA256:
|
||||||
|
_, err := CheckPayloadSignature256(req.Body, r.Secret, arg)
|
||||||
return err == nil, err
|
return err == nil, err
|
||||||
case MatchHashSHA512:
|
case MatchHashSHA512:
|
||||||
_, err := CheckPayloadSignature512(*body, r.Secret, arg)
|
log.Print(`warn: use of deprecated option payload-hash-sha512: use payload-hmac-sha512 instead`)
|
||||||
|
fallthrough
|
||||||
|
case MatchHMACSHA512:
|
||||||
|
_, err := CheckPayloadSignature512(req.Body, r.Secret, arg)
|
||||||
return err == nil, err
|
return err == nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package hook
|
package hook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@ -49,12 +50,14 @@ var checkPayloadSignatureTests = []struct {
|
|||||||
{[]byte(`{"a": "z"}`), "secret", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
{[]byte(`{"a": "z"}`), "secret", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
{[]byte(`{"a": "z"}`), "secret", "sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e,sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e,sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
||||||
|
{[]byte(``), "secret", "25af6174a0fcecc4d346680a72b7ce644b9a88e8", "25af6174a0fcecc4d346680a72b7ce644b9a88e8", true},
|
||||||
// failures
|
// failures
|
||||||
{[]byte(`{"a": "z"}`), "secret", "XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
{[]byte(`{"a": "z"}`), "secret", "XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e,sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
{[]byte(`{"a": "z"}`), "secret", "sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e,sha1=XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
||||||
{[]byte(`{"a": "z"}`), "secreX", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "900225703e9342328db7307692736e2f7cc7b36e", false},
|
{[]byte(`{"a": "z"}`), "secreX", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "900225703e9342328db7307692736e2f7cc7b36e", false},
|
||||||
{[]byte(`{"a": "z"}`), "", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "", false},
|
{[]byte(`{"a": "z"}`), "", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "", false},
|
||||||
|
{[]byte(``), "secret", "XXXf6174a0fcecc4d346680a72b7ce644b9a88e8", "25af6174a0fcecc4d346680a72b7ce644b9a88e8", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckPayloadSignature(t *testing.T) {
|
func TestCheckPayloadSignature(t *testing.T) {
|
||||||
@ -80,11 +83,13 @@ var checkPayloadSignature256Tests = []struct {
|
|||||||
{[]byte(`{"a": "z"}`), "secret", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
{[]byte(`{"a": "z"}`), "secret", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
{[]byte(`{"a": "z"}`), "secret", "sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89,sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89,sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
||||||
|
{[]byte(``), "secret", "f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169", "f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169", true},
|
||||||
// failures
|
// failures
|
||||||
{[]byte(`{"a": "z"}`), "secret", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
{[]byte(`{"a": "z"}`), "secret", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89,sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
{[]byte(`{"a": "z"}`), "secret", "sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89,sha256=XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
||||||
{[]byte(`{"a": "z"}`), "", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "", false},
|
{[]byte(`{"a": "z"}`), "", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "", false},
|
||||||
|
{[]byte(``), "secret", "XXX66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169", "f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckPayloadSignature256(t *testing.T) {
|
func TestCheckPayloadSignature256(t *testing.T) {
|
||||||
@ -109,9 +114,11 @@ var checkPayloadSignature512Tests = []struct {
|
|||||||
}{
|
}{
|
||||||
{[]byte(`{"a": "z"}`), "secret", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", true},
|
{[]byte(`{"a": "z"}`), "secret", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", true},
|
||||||
{[]byte(`{"a": "z"}`), "secret", "sha512=4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", true},
|
{[]byte(`{"a": "z"}`), "secret", "sha512=4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", true},
|
||||||
|
{[]byte(``), "secret", "b0e9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901", "b0e9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901", true},
|
||||||
// failures
|
// failures
|
||||||
{[]byte(`{"a": "z"}`), "secret", "74a0081f5b5988f4f3e8b8dd34dadc6291611f2e6260635a7e1535f8e95edb97ff520ba8b152e8ca5760ac42639854f3242e29efc81be73a8bf52d474d31ffea", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", false},
|
{[]byte(`{"a": "z"}`), "secret", "74a0081f5b5988f4f3e8b8dd34dadc6291611f2e6260635a7e1535f8e95edb97ff520ba8b152e8ca5760ac42639854f3242e29efc81be73a8bf52d474d31ffea", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", false},
|
||||||
{[]byte(`{"a": "z"}`), "", "74a0081f5b5988f4f3e8b8dd34dadc6291611f2e6260635a7e1535f8e95edb97ff520ba8b152e8ca5760ac42639854f3242e29efc81be73a8bf52d474d31ffea", "", false},
|
{[]byte(`{"a": "z"}`), "", "74a0081f5b5988f4f3e8b8dd34dadc6291611f2e6260635a7e1535f8e95edb97ff520ba8b152e8ca5760ac42639854f3242e29efc81be73a8bf52d474d31ffea", "", false},
|
||||||
|
{[]byte(``), "secret", "XXX9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901", "b0e9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckPayloadSignature512(t *testing.T) {
|
func TestCheckPayloadSignature512(t *testing.T) {
|
||||||
@ -130,7 +137,7 @@ func TestCheckPayloadSignature512(t *testing.T) {
|
|||||||
var checkScalrSignatureTests = []struct {
|
var checkScalrSignatureTests = []struct {
|
||||||
description string
|
description string
|
||||||
headers map[string]interface{}
|
headers map[string]interface{}
|
||||||
payload []byte
|
body []byte
|
||||||
secret string
|
secret string
|
||||||
expectedSignature string
|
expectedSignature string
|
||||||
ok bool
|
ok bool
|
||||||
@ -169,7 +176,11 @@ var checkScalrSignatureTests = []struct {
|
|||||||
|
|
||||||
func TestCheckScalrSignature(t *testing.T) {
|
func TestCheckScalrSignature(t *testing.T) {
|
||||||
for _, testCase := range checkScalrSignatureTests {
|
for _, testCase := range checkScalrSignatureTests {
|
||||||
valid, err := CheckScalrSignature(testCase.headers, testCase.payload, testCase.secret, false)
|
r := &Request{
|
||||||
|
Headers: testCase.headers,
|
||||||
|
Body: testCase.body,
|
||||||
|
}
|
||||||
|
valid, err := CheckScalrSignature(r, testCase.secret, false)
|
||||||
if valid != testCase.ok {
|
if valid != testCase.ok {
|
||||||
t.Errorf("failed to check scalr signature fot test case: %s\nexpected ok:%#v, got ok:%#v}",
|
t.Errorf("failed to check scalr signature fot test case: %s\nexpected ok:%#v, got ok:%#v}",
|
||||||
testCase.description, testCase.ok, valid)
|
testCase.description, testCase.ok, valid)
|
||||||
@ -217,6 +228,9 @@ var extractParameterTests = []struct {
|
|||||||
{"a.b.0", map[string]interface{}{"a": map[string]interface{}{"b": []interface{}{"x", "y", "z"}}}, "x", true},
|
{"a.b.0", map[string]interface{}{"a": map[string]interface{}{"b": []interface{}{"x", "y", "z"}}}, "x", true},
|
||||||
{"a.1.b", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": "y"}, map[string]interface{}{"b": "z"}}}, "z", true},
|
{"a.1.b", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": "y"}, map[string]interface{}{"b": "z"}}}, "z", true},
|
||||||
{"a.1.b.c", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": map[string]interface{}{"c": "y"}}, map[string]interface{}{"b": map[string]interface{}{"c": "z"}}}}, "z", true},
|
{"a.1.b.c", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": map[string]interface{}{"c": "y"}}, map[string]interface{}{"b": map[string]interface{}{"c": "z"}}}}, "z", true},
|
||||||
|
{"b", map[string]interface{}{"b": map[string]interface{}{"z": 1}}, `{"z":1}`, true},
|
||||||
|
{"c", map[string]interface{}{"c": []interface{}{"y", "z"}}, `["y","z"]`, true},
|
||||||
|
{"d", map[string]interface{}{"d": [2]interface{}{"y", "z"}}, `["y","z"]`, true},
|
||||||
// failures
|
// failures
|
||||||
{"check_nil", nil, "", false},
|
{"check_nil", nil, "", false},
|
||||||
{"a.X", map[string]interface{}{"a": map[string]interface{}{"b": "z"}}, "", false}, // non-existent parameter reference
|
{"a.X", map[string]interface{}{"a": map[string]interface{}{"b": "z"}}, "", false}, // non-existent parameter reference
|
||||||
@ -233,63 +247,75 @@ func TestExtractParameter(t *testing.T) {
|
|||||||
for _, tt := range extractParameterTests {
|
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 {
|
if (err == nil) != tt.ok || value != tt.value {
|
||||||
t.Errorf("failed to extract parameter %q:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%s}", tt.s, tt.value, tt.ok, value, err)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var argumentGetTests = []struct {
|
var argumentGetTests = []struct {
|
||||||
source, name string
|
source, name string
|
||||||
headers, query, payload, context *map[string]interface{}
|
headers, query, payload, context map[string]interface{}
|
||||||
value string
|
value string
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
{"header", "a", &map[string]interface{}{"A": "z"}, nil, nil, nil, "z", true},
|
{"header", "a", map[string]interface{}{"A": "z"}, nil, nil, nil, "z", true},
|
||||||
{"url", "a", nil, &map[string]interface{}{"a": "z"}, nil, nil, "z", true},
|
{"url", "a", nil, map[string]interface{}{"a": "z"}, nil, nil, "z", true},
|
||||||
{"payload", "a", nil, nil, &map[string]interface{}{"a": "z"}, nil, "z", true},
|
{"payload", "a", nil, nil, map[string]interface{}{"a": "z"}, nil, "z", true},
|
||||||
{"context", "a", nil, nil, nil, &map[string]interface{}{"a": "z"}, "z", true},
|
{"context", "a", nil, nil, nil, map[string]interface{}{"a": "z"}, "z", true},
|
||||||
{"string", "a", nil, nil, nil, nil, "a", true},
|
{"string", "a", nil, nil, nil, nil, "a", true},
|
||||||
// failures
|
// failures
|
||||||
{"header", "a", nil, &map[string]interface{}{"a": "z"}, &map[string]interface{}{"a": "z"}, nil, "", false}, // nil headers
|
{"header", "a", nil, map[string]interface{}{"a": "z"}, map[string]interface{}{"a": "z"}, nil, "", false}, // nil headers
|
||||||
{"url", "a", &map[string]interface{}{"A": "z"}, nil, &map[string]interface{}{"a": "z"}, nil, "", false}, // nil query
|
{"url", "a", map[string]interface{}{"A": "z"}, nil, map[string]interface{}{"a": "z"}, nil, "", false}, // nil query
|
||||||
{"payload", "a", &map[string]interface{}{"A": "z"}, &map[string]interface{}{"a": "z"}, nil, nil, "", false}, // nil payload
|
{"payload", "a", map[string]interface{}{"A": "z"}, map[string]interface{}{"a": "z"}, nil, nil, "", false}, // nil payload
|
||||||
{"context", "a", nil, nil, nil, nil, "", false}, // nil context
|
{"context", "a", nil, nil, nil, nil, "", false}, // nil context
|
||||||
{"foo", "a", &map[string]interface{}{"A": "z"}, nil, nil, nil, "", false}, // invalid source
|
{"foo", "a", map[string]interface{}{"A": "z"}, nil, nil, nil, "", false}, // invalid source
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArgumentGet(t *testing.T) {
|
func TestArgumentGet(t *testing.T) {
|
||||||
for _, tt := range argumentGetTests {
|
for _, tt := range argumentGetTests {
|
||||||
a := Argument{tt.source, tt.name, "", false}
|
a := Argument{tt.source, tt.name, "", false}
|
||||||
value, err := a.Get(tt.headers, tt.query, tt.payload, tt.context)
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Context: tt.context,
|
||||||
|
}
|
||||||
|
value, err := a.Get(r)
|
||||||
if (err == nil) != tt.ok || value != tt.value {
|
if (err == nil) != tt.ok || value != tt.value {
|
||||||
t.Errorf("failed to get {%q, %q}:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%s}", tt.source, tt.name, tt.value, tt.ok, value, err)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hookParseJSONParametersTests = []struct {
|
var hookParseJSONParametersTests = []struct {
|
||||||
params []Argument
|
params []Argument
|
||||||
headers, query, payload, context *map[string]interface{}
|
headers, query, payload, context map[string]interface{}
|
||||||
rheaders, rquery, rpayload, rcontext *map[string]interface{}
|
rheaders, rquery, rpayload, rcontext map[string]interface{}
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
{[]Argument{Argument{"header", "a", "", false}}, &map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, nil, &map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, nil, true},
|
{[]Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, nil, true},
|
||||||
{[]Argument{Argument{"url", "a", "", false}}, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, nil, true},
|
{[]Argument{Argument{"url", "a", "", false}}, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, nil, true},
|
||||||
{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
|
{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
|
||||||
{[]Argument{Argument{"context", "a", "", false}}, nil, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
|
{[]Argument{Argument{"context", "a", "", false}}, nil, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
|
||||||
{[]Argument{Argument{"header", "z", "", false}}, &map[string]interface{}{"Z": `{}`}, nil, nil, nil, &map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, nil, true},
|
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": `{}`}, nil, nil, nil, map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, nil, true},
|
||||||
// failures
|
// failures
|
||||||
{[]Argument{Argument{"header", "z", "", false}}, &map[string]interface{}{"Z": ``}, nil, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, nil, false}, // empty string
|
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, nil, false}, // empty string
|
||||||
{[]Argument{Argument{"header", "y", "", false}}, &map[string]interface{}{"X": `{}`}, nil, nil, nil, &map[string]interface{}{"X": `{}`}, nil, nil, nil, false}, // missing parameter
|
{[]Argument{Argument{"header", "y", "", false}}, map[string]interface{}{"X": `{}`}, nil, nil, nil, map[string]interface{}{"X": `{}`}, nil, nil, nil, false}, // missing parameter
|
||||||
{[]Argument{Argument{"string", "z", "", false}}, &map[string]interface{}{"Z": ``}, nil, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, nil, false}, // invalid argument source
|
{[]Argument{Argument{"string", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, nil, false}, // invalid argument source
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHookParseJSONParameters(t *testing.T) {
|
func TestHookParseJSONParameters(t *testing.T) {
|
||||||
for _, tt := range hookParseJSONParametersTests {
|
for _, tt := range hookParseJSONParametersTests {
|
||||||
h := &Hook{JSONStringParameters: tt.params}
|
h := &Hook{JSONStringParameters: tt.params}
|
||||||
err := h.ParseJSONParameters(tt.headers, tt.query, tt.payload, tt.context)
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Context: tt.context,
|
||||||
|
}
|
||||||
|
err := h.ParseJSONParameters(r)
|
||||||
if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) {
|
if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) {
|
||||||
t.Errorf("failed to parse %v:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.params, *tt.rheaders, tt.ok, *tt.headers, (err == nil))
|
t.Errorf("failed to parse %v:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.params, tt.rheaders, tt.ok, tt.headers, (err == nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,19 +323,25 @@ func TestHookParseJSONParameters(t *testing.T) {
|
|||||||
var hookExtractCommandArgumentsTests = []struct {
|
var hookExtractCommandArgumentsTests = []struct {
|
||||||
exec string
|
exec string
|
||||||
args []Argument
|
args []Argument
|
||||||
headers, query, payload, context *map[string]interface{}
|
headers, query, payload, context map[string]interface{}
|
||||||
value []string
|
value []string
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
{"test", []Argument{Argument{"header", "a", "", false}}, &map[string]interface{}{"A": "z"}, nil, nil, nil, []string{"test", "z"}, true},
|
{"test", []Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []string{"test", "z"}, true},
|
||||||
// failures
|
// failures
|
||||||
{"fail", []Argument{Argument{"payload", "a", "", false}}, &map[string]interface{}{"A": "z"}, nil, nil, nil, []string{"fail", ""}, false},
|
{"fail", []Argument{Argument{"payload", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []string{"fail", ""}, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHookExtractCommandArguments(t *testing.T) {
|
func TestHookExtractCommandArguments(t *testing.T) {
|
||||||
for _, tt := range hookExtractCommandArgumentsTests {
|
for _, tt := range hookExtractCommandArgumentsTests {
|
||||||
h := &Hook{ExecuteCommand: tt.exec, PassArgumentsToCommand: tt.args}
|
h := &Hook{ExecuteCommand: tt.exec, PassArgumentsToCommand: tt.args}
|
||||||
value, err := h.ExtractCommandArguments(tt.headers, tt.query, tt.payload, tt.context)
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Context: tt.context,
|
||||||
|
}
|
||||||
|
value, err := h.ExtractCommandArguments(r)
|
||||||
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
||||||
t.Errorf("failed to extract args {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
|
t.Errorf("failed to extract args {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
|
||||||
}
|
}
|
||||||
@ -338,7 +370,7 @@ func TestHookExtractCommandArguments(t *testing.T) {
|
|||||||
var hookExtractCommandArgumentsForEnvTests = []struct {
|
var hookExtractCommandArgumentsForEnvTests = []struct {
|
||||||
exec string
|
exec string
|
||||||
args []Argument
|
args []Argument
|
||||||
headers, query, payload, context *map[string]interface{}
|
headers, query, payload, context map[string]interface{}
|
||||||
value []string
|
value []string
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
@ -346,14 +378,14 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
|
|||||||
{
|
{
|
||||||
"test",
|
"test",
|
||||||
[]Argument{Argument{"header", "a", "", false}},
|
[]Argument{Argument{"header", "a", "", false}},
|
||||||
&map[string]interface{}{"A": "z"}, nil, nil, nil,
|
map[string]interface{}{"A": "z"}, nil, nil, nil,
|
||||||
[]string{"HOOK_a=z"},
|
[]string{"HOOK_a=z"},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test",
|
"test",
|
||||||
[]Argument{Argument{"header", "a", "MYKEY", false}},
|
[]Argument{Argument{"header", "a", "MYKEY", false}},
|
||||||
&map[string]interface{}{"A": "z"}, nil, nil, nil,
|
map[string]interface{}{"A": "z"}, nil, nil, nil,
|
||||||
[]string{"MYKEY=z"},
|
[]string{"MYKEY=z"},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
@ -361,7 +393,7 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
|
|||||||
{
|
{
|
||||||
"fail",
|
"fail",
|
||||||
[]Argument{Argument{"payload", "a", "", false}},
|
[]Argument{Argument{"payload", "a", "", false}},
|
||||||
&map[string]interface{}{"A": "z"}, nil, nil, nil,
|
map[string]interface{}{"A": "z"}, nil, nil, nil,
|
||||||
[]string{},
|
[]string{},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
@ -370,7 +402,13 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
|
|||||||
func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
|
func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
|
||||||
for _, tt := range hookExtractCommandArgumentsForEnvTests {
|
for _, tt := range hookExtractCommandArgumentsForEnvTests {
|
||||||
h := &Hook{ExecuteCommand: tt.exec, PassEnvironmentToCommand: tt.args}
|
h := &Hook{ExecuteCommand: tt.exec, PassEnvironmentToCommand: tt.args}
|
||||||
value, err := h.ExtractCommandArgumentsForEnv(tt.headers, tt.query, tt.payload, tt.context)
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Context: tt.context,
|
||||||
|
}
|
||||||
|
value, err := h.ExtractCommandArgumentsForEnv(r)
|
||||||
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
||||||
t.Errorf("failed to extract args for env {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
|
t.Errorf("failed to extract args for env {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
|
||||||
}
|
}
|
||||||
@ -448,24 +486,31 @@ func TestHooksMatch(t *testing.T) {
|
|||||||
var matchRuleTests = []struct {
|
var matchRuleTests = []struct {
|
||||||
typ, regex, secret, value, ipRange string
|
typ, regex, secret, value, ipRange string
|
||||||
param Argument
|
param Argument
|
||||||
headers, query, payload, context *map[string]interface{}
|
headers, query, payload, context map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
remoteAddr string
|
remoteAddr string
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
}{
|
}{
|
||||||
{"value", "", "", "z", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", true, false},
|
{"value", "", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", true, false},
|
||||||
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", true, false},
|
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", true, false},
|
||||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||||
|
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||||
|
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||||
// failures
|
// failures
|
||||||
{"value", "", "", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, false},
|
{"value", "", "", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, false},
|
||||||
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, false},
|
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, false},
|
||||||
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"Y": "z"}, nil, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"Y": "z"}, nil, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
||||||
// errors
|
// errors
|
||||||
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, true}, // invalid regex
|
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, true}, // invalid regex
|
||||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
|
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
|
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
|
{"payload-hmac-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
|
{"payload-hash-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
|
|
||||||
// IP whitelisting, valid cases
|
// IP whitelisting, valid cases
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||||
@ -484,7 +529,17 @@ var matchRuleTests = []struct {
|
|||||||
func TestMatchRule(t *testing.T) {
|
func TestMatchRule(t *testing.T) {
|
||||||
for i, tt := range matchRuleTests {
|
for i, tt := range matchRuleTests {
|
||||||
r := MatchRule{tt.typ, tt.regex, tt.secret, tt.value, tt.param, tt.ipRange}
|
r := MatchRule{tt.typ, tt.regex, tt.secret, tt.value, tt.param, tt.ipRange}
|
||||||
ok, err := r.Evaluate(tt.headers, tt.query, tt.payload, tt.context, &tt.body, tt.remoteAddr)
|
req := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Context: tt.context,
|
||||||
|
Body: tt.body,
|
||||||
|
RawRequest: &http.Request{
|
||||||
|
RemoteAddr: tt.remoteAddr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ok, err := r.Evaluate(req)
|
||||||
if ok != tt.ok || (err != nil) != tt.err {
|
if ok != tt.ok || (err != nil) != tt.err {
|
||||||
t.Errorf("%d failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", i, r, tt.ok, tt.err, ok, err)
|
t.Errorf("%d failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", i, r, tt.ok, tt.err, ok, err)
|
||||||
}
|
}
|
||||||
@ -494,7 +549,7 @@ func TestMatchRule(t *testing.T) {
|
|||||||
var andRuleTests = []struct {
|
var andRuleTests = []struct {
|
||||||
desc string // description of the test case
|
desc string // description of the test case
|
||||||
rule AndRule
|
rule AndRule
|
||||||
headers, query, payload, context *map[string]interface{}
|
headers, query, payload, context map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
@ -505,7 +560,7 @@ var andRuleTests = []struct {
|
|||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "y"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "z", "B": "y"}, nil, nil, nil, []byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -514,7 +569,7 @@ var andRuleTests = []struct {
|
|||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "Y"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "z", "B": "Y"}, nil, nil, nil, []byte{},
|
||||||
false, false,
|
false, false,
|
||||||
},
|
},
|
||||||
// Complex test to cover Rules.Evaluate
|
// Complex test to cover Rules.Evaluate
|
||||||
@ -540,7 +595,7 @@ var andRuleTests = []struct {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil, nil, []byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
{"empty rule", AndRule{{}}, nil, nil, nil, nil, nil, false, false},
|
{"empty rule", AndRule{{}}, nil, nil, nil, nil, nil, false, false},
|
||||||
@ -548,14 +603,21 @@ var andRuleTests = []struct {
|
|||||||
{
|
{
|
||||||
"invalid rule",
|
"invalid rule",
|
||||||
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
|
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
|
||||||
&map[string]interface{}{"Y": "z"}, nil, nil, nil, nil,
|
map[string]interface{}{"Y": "z"}, nil, nil, nil, nil,
|
||||||
false, true,
|
false, true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAndRule(t *testing.T) {
|
func TestAndRule(t *testing.T) {
|
||||||
for _, tt := range andRuleTests {
|
for _, tt := range andRuleTests {
|
||||||
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, tt.context, &tt.body, "")
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Context: tt.context,
|
||||||
|
Body: tt.body,
|
||||||
|
}
|
||||||
|
ok, err := tt.rule.Evaluate(r)
|
||||||
if ok != tt.ok || (err != nil) != tt.err {
|
if ok != tt.ok || (err != nil) != tt.err {
|
||||||
t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.desc, tt.ok, tt.err, ok, err)
|
t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.desc, tt.ok, tt.err, ok, err)
|
||||||
}
|
}
|
||||||
@ -565,7 +627,7 @@ func TestAndRule(t *testing.T) {
|
|||||||
var orRuleTests = []struct {
|
var orRuleTests = []struct {
|
||||||
desc string // description of the test case
|
desc string // description of the test case
|
||||||
rule OrRule
|
rule OrRule
|
||||||
headers, query, payload, context *map[string]interface{}
|
headers, query, payload, context map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
@ -576,7 +638,7 @@ var orRuleTests = []struct {
|
|||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "X"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "z", "B": "X"}, nil, nil, nil, []byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -585,7 +647,7 @@ var orRuleTests = []struct {
|
|||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "X", "B": "y"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "X", "B": "y"}, nil, nil, nil, []byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -594,7 +656,7 @@ var orRuleTests = []struct {
|
|||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil, nil, []byte{},
|
||||||
false, false,
|
false, false,
|
||||||
},
|
},
|
||||||
// failures
|
// failures
|
||||||
@ -603,14 +665,21 @@ var orRuleTests = []struct {
|
|||||||
OrRule{
|
OrRule{
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"Y": "Z"}, nil, nil, nil, []byte{},
|
map[string]interface{}{"Y": "Z"}, nil, nil, nil, []byte{},
|
||||||
false, true,
|
false, true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrRule(t *testing.T) {
|
func TestOrRule(t *testing.T) {
|
||||||
for _, tt := range orRuleTests {
|
for _, tt := range orRuleTests {
|
||||||
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, tt.context, &tt.body, "")
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Context: tt.context,
|
||||||
|
Body: tt.body,
|
||||||
|
}
|
||||||
|
ok, err := tt.rule.Evaluate(r)
|
||||||
if ok != tt.ok || (err != nil) != tt.err {
|
if ok != tt.ok || (err != nil) != tt.err {
|
||||||
t.Errorf("%#v:\nexpected ok: %#v, err: %v\ngot ok: %#v err: %v", tt.desc, tt.ok, tt.err, ok, err)
|
t.Errorf("%#v:\nexpected ok: %#v, err: %v\ngot ok: %#v err: %v", tt.desc, tt.ok, tt.err, ok, err)
|
||||||
}
|
}
|
||||||
@ -620,18 +689,25 @@ func TestOrRule(t *testing.T) {
|
|||||||
var notRuleTests = []struct {
|
var notRuleTests = []struct {
|
||||||
desc string // description of the test case
|
desc string // description of the test case
|
||||||
rule NotRule
|
rule NotRule
|
||||||
headers, query, payload, context *map[string]interface{}
|
headers, query, payload, context map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
}{
|
}{
|
||||||
{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, &map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, true, false},
|
{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, true, false},
|
||||||
{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, &map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, false, false},
|
{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, false, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotRule(t *testing.T) {
|
func TestNotRule(t *testing.T) {
|
||||||
for _, tt := range notRuleTests {
|
for _, tt := range notRuleTests {
|
||||||
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, tt.context, &tt.body, "")
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Context: tt.context,
|
||||||
|
Body: tt.body,
|
||||||
|
}
|
||||||
|
ok, err := tt.rule.Evaluate(r)
|
||||||
if ok != tt.ok || (err != nil) != tt.err {
|
if ok != tt.ok || (err != nil) != tt.err {
|
||||||
t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.rule, tt.ok, tt.err, ok, err)
|
t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.rule, tt.ok, tt.err, ok, err)
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// responseDupper tees the response to a buffer and a response writer.
|
// responseDupper tees the response to a buffer and a response writer.
|
||||||
@ -34,70 +32,53 @@ func Dumper(w io.Writer) func(http.Handler) http.Handler {
|
|||||||
// Request ID
|
// Request ID
|
||||||
rid := r.Context().Value(RequestIDKey)
|
rid := r.Context().Value(RequestIDKey)
|
||||||
|
|
||||||
// Request URL
|
// Dump request
|
||||||
buf.WriteString(fmt.Sprintf("> [%s] %s %s", rid, r.Method, r.URL.String()))
|
|
||||||
|
|
||||||
// Request Headers
|
bd, err := httputil.DumpRequest(r, true)
|
||||||
keys := make([]string, len(r.Header))
|
|
||||||
i := 0
|
|
||||||
for k := range r.Header {
|
|
||||||
keys[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
for _, k := range keys {
|
|
||||||
buf.WriteString(fmt.Sprintf("\n> [%s] %s: %s", rid, k, strings.Join(r.Header[k], ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request parameters
|
|
||||||
params := mux.Vars(r)
|
|
||||||
keys = make([]string, len(params))
|
|
||||||
i = 0
|
|
||||||
for k := range params {
|
|
||||||
keys[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
for _, k := range keys {
|
|
||||||
buf.WriteString(fmt.Sprintf("\n> [%s] %s: %s", rid, k, strings.Join(r.Header[k], ", ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request body
|
|
||||||
b, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b = []byte("failed to read body: " + err.Error())
|
buf.WriteString(fmt.Sprintf("[%s] Error dumping request for debugging: %s\n", rid, err))
|
||||||
}
|
}
|
||||||
if len(b) > 0 {
|
|
||||||
buf.WriteByte('\n')
|
sc := bufio.NewScanner(bytes.NewBuffer(bd))
|
||||||
lines := strings.Split(string(b), "\n")
|
sc.Split(bufio.ScanLines)
|
||||||
for _, line := range lines {
|
for sc.Scan() {
|
||||||
buf.WriteString(fmt.Sprintf("> [%s] %s\n", rid, line))
|
buf.WriteString(fmt.Sprintf("> [%s] ", rid))
|
||||||
|
buf.WriteString(sc.Text() + "\n")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
|
w.Write(buf.Bytes())
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
// Dump Response
|
||||||
|
|
||||||
dupper := &responseDupper{ResponseWriter: rw, Buffer: &bytes.Buffer{}}
|
dupper := &responseDupper{ResponseWriter: rw, Buffer: &bytes.Buffer{}}
|
||||||
h.ServeHTTP(dupper, r)
|
h.ServeHTTP(dupper, r)
|
||||||
|
|
||||||
buf.WriteString(fmt.Sprintf("\n< [%s] %s", rid, http.StatusText(dupper.Status)))
|
// Response Status
|
||||||
keys = make([]string, len(dupper.Header()))
|
buf.WriteString(fmt.Sprintf("< [%s] %d %s\n", rid, dupper.Status, http.StatusText(dupper.Status)))
|
||||||
i = 0
|
|
||||||
|
// Response Headers
|
||||||
|
keys := make([]string, len(dupper.Header()))
|
||||||
|
i := 0
|
||||||
for k := range dupper.Header() {
|
for k := range dupper.Header() {
|
||||||
keys[i] = k
|
keys[i] = k
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
buf.WriteString(fmt.Sprintf("\n< [%s] %s: %s", rid, k, strings.Join(dupper.Header()[k], ", ")))
|
buf.WriteString(fmt.Sprintf("< [%s] %s: %s\n", rid, k, strings.Join(dupper.Header()[k], ", ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Response Body
|
||||||
if dupper.Buffer.Len() > 0 {
|
if dupper.Buffer.Len() > 0 {
|
||||||
buf.WriteByte('\n')
|
buf.WriteString(fmt.Sprintf("< [%s]\n", rid))
|
||||||
lines := strings.Split(dupper.Buffer.String(), "\n")
|
sc = bufio.NewScanner(dupper.Buffer)
|
||||||
for _, line := range lines {
|
sc.Split(bufio.ScanLines)
|
||||||
buf.WriteString(fmt.Sprintf("< [%s] %s\n", rid, line))
|
for sc.Scan() {
|
||||||
|
buf.WriteString(fmt.Sprintf("< [%s] ", rid))
|
||||||
|
buf.WriteString(sc.Text() + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf.WriteByte('\n')
|
|
||||||
w.Write(buf.Bytes())
|
w.Write(buf.Bytes())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -39,18 +39,18 @@ type LogEntry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write constructs and writes the final log entry.
|
// Write constructs and writes the final log entry.
|
||||||
func (l *LogEntry) Write(status, bytes int, elapsed time.Duration) {
|
func (l *LogEntry) Write(status, totalBytes int, elapsed time.Duration) {
|
||||||
rid := GetReqID(l.req.Context())
|
rid := GetReqID(l.req.Context())
|
||||||
if rid != "" {
|
if rid != "" {
|
||||||
fmt.Fprintf(l.buf, "[%s] ", rid)
|
fmt.Fprintf(l.buf, "[%s] ", rid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(l.buf, "%03d | %s | %s | ", status, humanize.IBytes(uint64(bytes)), elapsed)
|
fmt.Fprintf(l.buf, "%03d | %s | %s | ", status, humanize.IBytes(uint64(totalBytes)), elapsed)
|
||||||
l.buf.WriteString(l.req.Host + " | " + l.req.Method + " " + l.req.RequestURI)
|
l.buf.WriteString(l.req.Host + " | " + l.req.Method + " " + l.req.RequestURI)
|
||||||
log.Print(l.buf.String())
|
log.Print(l.buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Panic prints the call stack for a panic.
|
// Panic prints the call stack for a panic.
|
||||||
func (l *LogEntry) Panic(v interface{}, stack []byte) {
|
func (l *LogEntry) Panic(v interface{}, stack []byte) {
|
||||||
e := l.NewLogEntry(l.req).(*LogEntry)
|
e := l.NewLogEntry(l.req).(*LogEntry)
|
||||||
fmt.Fprintf(e.buf, "panic: %#v", v)
|
fmt.Fprintf(e.buf, "panic: %#v", v)
|
||||||
|
@ -35,10 +35,10 @@ func New(path string) (*PIDFile, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Note MkdirAll returns nil if a directory already exists
|
// Note MkdirAll returns nil if a directory already exists
|
||||||
if err := MkdirAll(filepath.Dir(path), os.FileMode(0755)); err != nil {
|
if err := MkdirAll(filepath.Dir(path), os.FileMode(0o755)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(path, []byte(fmt.Sprintf("%d", os.Getpid())), 0644); err != nil {
|
if err := ioutil.WriteFile(path, []byte(fmt.Sprintf("%d", os.Getpid())), 0o600); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
{
|
{
|
||||||
"match":
|
"match":
|
||||||
{
|
{
|
||||||
"type": "payload-hash-sha1",
|
"type": "payload-hmac-sha1",
|
||||||
"secret": "mysecret",
|
"secret": "mysecret",
|
||||||
"parameter":
|
"parameter":
|
||||||
{
|
{
|
||||||
@ -168,6 +168,22 @@
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "sendgrid",
|
||||||
|
"execute-command": "{{ .Hookecho }}",
|
||||||
|
"command-working-directory": "/",
|
||||||
|
"response-message": "success",
|
||||||
|
"trigger-rule": {
|
||||||
|
"match": {
|
||||||
|
"type": "value",
|
||||||
|
"parameter": {
|
||||||
|
"source": "payload",
|
||||||
|
"name": "root.0.event"
|
||||||
|
},
|
||||||
|
"value": "processed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "plex",
|
"id": "plex",
|
||||||
"execute-command": "{{ .Hookecho }}",
|
"execute-command": "{{ .Hookecho }}",
|
||||||
@ -259,5 +275,29 @@
|
|||||||
"name": "passed"
|
"name": "passed"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "empty-payload-signature",
|
||||||
|
"execute-command": "{{ .Hookecho }}",
|
||||||
|
"command-working-directory": "/",
|
||||||
|
"include-command-output-in-response": true,
|
||||||
|
"trigger-rule":
|
||||||
|
{
|
||||||
|
"and":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"match":
|
||||||
|
{
|
||||||
|
"type": "payload-hmac-sha1",
|
||||||
|
"secret": "mysecret",
|
||||||
|
"parameter":
|
||||||
|
{
|
||||||
|
"source": "header",
|
||||||
|
"name": "X-Hub-Signature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
source: header
|
source: header
|
||||||
name: X-Hub-Signature
|
name: X-Hub-Signature
|
||||||
secret: mysecret
|
secret: mysecret
|
||||||
type: payload-hash-sha1
|
type: payload-hmac-sha1
|
||||||
- match:
|
- match:
|
||||||
parameter:
|
parameter:
|
||||||
source: payload
|
source: payload
|
||||||
@ -97,6 +97,18 @@
|
|||||||
name: "app.messages.message.#text"
|
name: "app.messages.message.#text"
|
||||||
value: "Hello!!"
|
value: "Hello!!"
|
||||||
|
|
||||||
|
- id: sendgrid
|
||||||
|
execute-command: '{{ .Hookecho }}'
|
||||||
|
command-working-directory: /
|
||||||
|
response-message: success
|
||||||
|
trigger-rule:
|
||||||
|
match:
|
||||||
|
type: value
|
||||||
|
parameter:
|
||||||
|
source: payload
|
||||||
|
name: root.0.event
|
||||||
|
value: processed
|
||||||
|
|
||||||
- id: plex
|
- id: plex
|
||||||
trigger-rule:
|
trigger-rule:
|
||||||
match:
|
match:
|
||||||
@ -150,3 +162,16 @@
|
|||||||
- id: warn-on-space
|
- id: warn-on-space
|
||||||
execute-command: '{{ .Hookecho }} foo'
|
execute-command: '{{ .Hookecho }} foo'
|
||||||
include-command-output-in-response: true
|
include-command-output-in-response: true
|
||||||
|
|
||||||
|
- id: empty-payload-signature
|
||||||
|
include-command-output-in-response: true
|
||||||
|
execute-command: '{{ .Hookecho }}'
|
||||||
|
command-working-directory: /
|
||||||
|
trigger-rule:
|
||||||
|
and:
|
||||||
|
- match:
|
||||||
|
parameter:
|
||||||
|
source: header
|
||||||
|
name: X-Hub-Signature
|
||||||
|
secret: mysecret
|
||||||
|
type: payload-hmac-sha1
|
||||||
|
4
tls.go
4
tls.go
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func writeTLSSupportedCipherStrings(w io.Writer, min uint16) error {
|
func writeTLSSupportedCipherStrings(w io.Writer, min uint16) error {
|
||||||
for _, c := range CipherSuites() {
|
for _, c := range tls.CipherSuites() {
|
||||||
var found bool
|
var found bool
|
||||||
|
|
||||||
for _, v := range c.SupportedVersions {
|
for _, v := range c.SupportedVersions {
|
||||||
@ -50,7 +50,7 @@ func getTLSMinVersion(v string) uint16 {
|
|||||||
// getTLSCipherSuites converts a comma separated list of cipher suites into a
|
// getTLSCipherSuites converts a comma separated list of cipher suites into a
|
||||||
// slice of TLS cipher suite IDs.
|
// slice of TLS cipher suite IDs.
|
||||||
func getTLSCipherSuites(v string) []uint16 {
|
func getTLSCipherSuites(v string) []uint16 {
|
||||||
supported := CipherSuites()
|
supported := tls.CipherSuites()
|
||||||
|
|
||||||
if v == "" {
|
if v == "" {
|
||||||
suites := make([]uint16, len(supported))
|
suites := make([]uint16, len(supported))
|
||||||
|
17
vendor/modules.txt
vendored
17
vendor/modules.txt
vendored
@ -1,20 +1,37 @@
|
|||||||
# github.com/clbanning/mxj v1.8.4
|
# github.com/clbanning/mxj v1.8.4
|
||||||
|
## explicit
|
||||||
github.com/clbanning/mxj
|
github.com/clbanning/mxj
|
||||||
# github.com/dustin/go-humanize v1.0.0
|
# github.com/dustin/go-humanize v1.0.0
|
||||||
|
## explicit
|
||||||
github.com/dustin/go-humanize
|
github.com/dustin/go-humanize
|
||||||
|
# github.com/fsnotify/fsnotify v1.4.7
|
||||||
|
## explicit
|
||||||
# github.com/ghodss/yaml v1.0.0
|
# github.com/ghodss/yaml v1.0.0
|
||||||
|
## explicit
|
||||||
github.com/ghodss/yaml
|
github.com/ghodss/yaml
|
||||||
# github.com/go-chi/chi v4.0.2+incompatible
|
# github.com/go-chi/chi v4.0.2+incompatible
|
||||||
|
## explicit
|
||||||
github.com/go-chi/chi
|
github.com/go-chi/chi
|
||||||
github.com/go-chi/chi/middleware
|
github.com/go-chi/chi/middleware
|
||||||
# github.com/gofrs/uuid v3.2.0+incompatible
|
# github.com/gofrs/uuid v3.2.0+incompatible
|
||||||
|
## explicit
|
||||||
github.com/gofrs/uuid
|
github.com/gofrs/uuid
|
||||||
# github.com/gorilla/mux v1.7.3
|
# github.com/gorilla/mux v1.7.3
|
||||||
|
## explicit
|
||||||
github.com/gorilla/mux
|
github.com/gorilla/mux
|
||||||
|
# github.com/kr/pretty v0.1.0
|
||||||
|
## explicit
|
||||||
|
# golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
|
||||||
|
## explicit
|
||||||
# golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8
|
# golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8
|
||||||
|
## explicit
|
||||||
golang.org/x/sys/unix
|
golang.org/x/sys/unix
|
||||||
golang.org/x/sys/windows
|
golang.org/x/sys/windows
|
||||||
|
# gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
|
||||||
|
## explicit
|
||||||
# gopkg.in/fsnotify.v1 v1.4.2
|
# gopkg.in/fsnotify.v1 v1.4.2
|
||||||
|
## explicit
|
||||||
gopkg.in/fsnotify.v1
|
gopkg.in/fsnotify.v1
|
||||||
# gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7
|
# gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7
|
||||||
|
## explicit
|
||||||
gopkg.in/yaml.v2
|
gopkg.in/yaml.v2
|
||||||
|
309
webhook.go
309
webhook.go
@ -18,6 +18,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/adnanh/webhook/internal/hook"
|
"github.com/adnanh/webhook/internal/hook"
|
||||||
"github.com/adnanh/webhook/internal/middleware"
|
"github.com/adnanh/webhook/internal/middleware"
|
||||||
@ -30,7 +31,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "2.6.11"
|
version = "2.7.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -143,7 +144,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if *logPath != "" {
|
if *logPath != "" {
|
||||||
file, err := os.OpenFile(*logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
file, err := os.OpenFile(*logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logQueue = append(logQueue, fmt.Sprintf("error opening log file %q: %v", *logPath, err))
|
logQueue = append(logQueue, fmt.Sprintf("error opening log file %q: %v", *logPath, err))
|
||||||
// we'll bail out below
|
// we'll bail out below
|
||||||
@ -303,10 +304,14 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
rid := middleware.GetReqID(r.Context())
|
req := &hook.Request{
|
||||||
|
ID: middleware.GetReqID(r.Context()),
|
||||||
|
RawRequest: r,
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[%s] incoming HTTP %s request from %s\n", rid, r.Method, r.RemoteAddr)
|
log.Printf("[%s] incoming HTTP %s request from %s\n", req.ID, r.Method, r.RemoteAddr)
|
||||||
|
|
||||||
|
// TODO: rename this to avoid confusion with Request.ID
|
||||||
id := mux.Vars(r)["id"]
|
id := mux.Vars(r)["id"]
|
||||||
|
|
||||||
matchedHook := matchLoadedHook(id)
|
matchedHook := matchLoadedHook(id)
|
||||||
@ -342,138 +347,89 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if !allowedMethod {
|
if !allowedMethod {
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
log.Printf("[%s] HTTP %s method not allowed for hook %q", rid, r.Method, id)
|
log.Printf("[%s] HTTP %s method not allowed for hook %q", req.ID, r.Method, id)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[%s] %s got matched\n", rid, id)
|
log.Printf("[%s] %s got matched\n", req.ID, id)
|
||||||
|
|
||||||
for _, responseHeader := range responseHeaders {
|
for _, responseHeader := range responseHeaders {
|
||||||
w.Header().Set(responseHeader.Name, responseHeader.Value)
|
w.Header().Set(responseHeader.Name, responseHeader.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var err error
|
||||||
body []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// set contentType to IncomingPayloadContentType or header value
|
// set contentType to IncomingPayloadContentType or header value
|
||||||
contentType := r.Header.Get("Content-Type")
|
req.ContentType = r.Header.Get("Content-Type")
|
||||||
if len(matchedHook.IncomingPayloadContentType) != 0 {
|
if len(matchedHook.IncomingPayloadContentType) != 0 {
|
||||||
contentType = matchedHook.IncomingPayloadContentType
|
req.ContentType = matchedHook.IncomingPayloadContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
isMultipart := strings.HasPrefix(contentType, "multipart/form-data;")
|
isMultipart := strings.HasPrefix(req.ContentType, "multipart/form-data;")
|
||||||
|
|
||||||
if !isMultipart {
|
if !isMultipart {
|
||||||
body, err = ioutil.ReadAll(r.Body)
|
req.Body, err = ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error reading the request body: %+v\n", rid, err)
|
log.Printf("[%s] error reading the request body: %+v\n", req.ID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse headers
|
// parse headers
|
||||||
headers := valuesToMap(r.Header)
|
req.Headers = valuesToMap(r.Header)
|
||||||
|
|
||||||
// parse query variables
|
// parse query variables
|
||||||
query := valuesToMap(r.URL.Query())
|
req.Query = valuesToMap(r.URL.Query())
|
||||||
|
|
||||||
// parse body
|
// parse body
|
||||||
var payload map[string]interface{}
|
|
||||||
// parse context
|
|
||||||
var context map[string]interface{}
|
|
||||||
|
|
||||||
if matchedHook.PreHookCommand != "" {
|
|
||||||
// check the command exists
|
|
||||||
preHookCommandPath, err := exec.LookPath(matchedHook.PreHookCommand)
|
|
||||||
if err != nil {
|
|
||||||
// give a last chance, maybe it's a relative path
|
|
||||||
preHookCommandPathRelativeToCurrentWorkingDirectory := filepath.Join(matchedHook.CommandWorkingDirectory, matchedHook.PreHookCommand)
|
|
||||||
// check the command exists
|
|
||||||
preHookCommandPath, err = exec.LookPath(preHookCommandPathRelativeToCurrentWorkingDirectory)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[%s] unable to locate pre-hook command: '%s', %+v\n", rid, matchedHook.PreHookCommand, err)
|
|
||||||
// check if parameters specified in pre-hook command by mistake
|
|
||||||
if strings.IndexByte(matchedHook.PreHookCommand, ' ') != -1 {
|
|
||||||
s := strings.Fields(matchedHook.PreHookCommand)[0]
|
|
||||||
log.Printf("[%s] please use a wrapper script to provide arguments to pre-hook command for '%s'\n", rid, s)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
preHookCommandStdin := hook.PreHookContext{
|
|
||||||
HookID: matchedHook.ID,
|
|
||||||
Method: r.Method,
|
|
||||||
Base64EncodedBody: base64.StdEncoding.EncodeToString(body),
|
|
||||||
RemoteAddr: r.RemoteAddr,
|
|
||||||
URI: r.RequestURI,
|
|
||||||
Host: r.Host,
|
|
||||||
Headers: r.Header,
|
|
||||||
Query: r.URL.Query(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if preHookCommandStdinJSONString, err := json.Marshal(preHookCommandStdin); err != nil {
|
|
||||||
log.Printf("[%s] unable to encode pre-hook context as JSON string for the pre-hook command: %+v\n", rid, err)
|
|
||||||
} else {
|
|
||||||
preHookCommand := exec.Command(preHookCommandPath)
|
|
||||||
preHookCommand.Dir = matchedHook.CommandWorkingDirectory
|
|
||||||
preHookCommand.Env = append(os.Environ())
|
|
||||||
|
|
||||||
if preHookCommandStdinPipe, err := preHookCommand.StdinPipe(); err != nil {
|
|
||||||
log.Printf("[%s] unable to acquire stdin pipe for the pre-hook command: %+v\n", rid, err)
|
|
||||||
} else {
|
|
||||||
_, err := io.WriteString(preHookCommandStdinPipe, string(preHookCommandStdinJSONString))
|
|
||||||
preHookCommandStdinPipe.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[%s] unable to write to pre-hook command stdin: %+v\n", rid, err)
|
|
||||||
} else {
|
|
||||||
log.Printf("[%s] executing pre-hook command %s (%s) using %s as cwd\n", rid, matchedHook.PreHookCommand, preHookCommand.Path, preHookCommand.Dir)
|
|
||||||
|
|
||||||
if preHookCommandOutput, err := preHookCommand.CombinedOutput(); err != nil {
|
|
||||||
log.Printf("[%s] unable to execute pre-hook command: %+v\n", rid, err)
|
|
||||||
} else {
|
|
||||||
JSONDecoder := json.NewDecoder(strings.NewReader(string(preHookCommandOutput)))
|
|
||||||
JSONDecoder.UseNumber()
|
|
||||||
|
|
||||||
if err := JSONDecoder.Decode(&context); err != nil {
|
|
||||||
log.Printf("[%s] unable to parse pre-hook command output: %+v\npre-hook command output was: %+v\n", rid, err, string(preHookCommandOutput))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(contentType, "json"):
|
case strings.Contains(req.ContentType, "json"):
|
||||||
decoder := json.NewDecoder(bytes.NewReader(body))
|
decoder := json.NewDecoder(bytes.NewReader(req.Body))
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
|
|
||||||
err := decoder.Decode(&payload)
|
var firstChar byte
|
||||||
if err != nil {
|
for i := 0; i < len(req.Body); i++ {
|
||||||
log.Printf("[%s] error parsing JSON payload %+v\n", rid, err)
|
if unicode.IsSpace(rune(req.Body[i])) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
firstChar = req.Body[i]
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.Contains(contentType, "x-www-form-urlencoded"):
|
if firstChar == byte('[') {
|
||||||
fd, err := url.ParseQuery(string(body))
|
var arrayPayload interface{}
|
||||||
|
err := decoder.Decode(&arrayPayload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error parsing form payload %+v\n", rid, err)
|
log.Printf("[%s] error parsing JSON array payload %+v\n", req.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Payload = make(map[string]interface{}, 1)
|
||||||
|
req.Payload["root"] = arrayPayload
|
||||||
} else {
|
} else {
|
||||||
payload = valuesToMap(fd)
|
err := decoder.Decode(&req.Payload)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[%s] error parsing JSON payload %+v\n", req.ID, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.Contains(contentType, "xml"):
|
case strings.Contains(req.ContentType, "x-www-form-urlencoded"):
|
||||||
payload, err = mxj.NewMapXmlReader(bytes.NewReader(body))
|
fd, err := url.ParseQuery(string(req.Body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error parsing XML payload: %+v\n", rid, err)
|
log.Printf("[%s] error parsing form payload %+v\n", req.ID, err)
|
||||||
|
} else {
|
||||||
|
req.Payload = valuesToMap(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
case strings.Contains(req.ContentType, "xml"):
|
||||||
|
req.Payload, err = mxj.NewMapXmlReader(bytes.NewReader(req.Body))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[%s] error parsing XML payload: %+v\n", req.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
case isMultipart:
|
case isMultipart:
|
||||||
err = r.ParseMultipartForm(*maxMultipartMem)
|
err = r.ParseMultipartForm(*maxMultipartMem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("[%s] error parsing multipart form: %+v\n", rid, err)
|
msg := fmt.Sprintf("[%s] error parsing multipart form: %+v\n", req.ID, err)
|
||||||
log.Println(msg)
|
log.Println(msg)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
fmt.Fprint(w, "Error occurred while parsing multipart form.")
|
fmt.Fprint(w, "Error occurred while parsing multipart form.")
|
||||||
@ -481,14 +437,14 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range r.MultipartForm.Value {
|
for k, v := range r.MultipartForm.Value {
|
||||||
log.Printf("[%s] found multipart form value %q", rid, k)
|
log.Printf("[%s] found multipart form value %q", req.ID, k)
|
||||||
|
|
||||||
if payload == nil {
|
if req.Payload == nil {
|
||||||
payload = make(map[string]interface{})
|
req.Payload = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(moorereason): support duplicate, named values
|
// TODO(moorereason): support duplicate, named values
|
||||||
payload[k] = v[0]
|
req.Payload[k] = v[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range r.MultipartForm.File {
|
for k, v := range r.MultipartForm.File {
|
||||||
@ -517,11 +473,11 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if parseAsJSON {
|
if parseAsJSON {
|
||||||
log.Printf("[%s] parsing multipart form file %q as JSON\n", rid, k)
|
log.Printf("[%s] parsing multipart form file %q as JSON\n", req.ID, k)
|
||||||
|
|
||||||
f, err := v[0].Open()
|
f, err := v[0].Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("[%s] error parsing multipart form file: %+v\n", rid, err)
|
msg := fmt.Sprintf("[%s] error parsing multipart form file: %+v\n", req.ID, err)
|
||||||
log.Println(msg)
|
log.Println(msg)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
fmt.Fprint(w, "Error occurred while parsing multipart form file.")
|
fmt.Fprint(w, "Error occurred while parsing multipart form file.")
|
||||||
@ -534,24 +490,87 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
var part map[string]interface{}
|
var part map[string]interface{}
|
||||||
err = decoder.Decode(&part)
|
err = decoder.Decode(&part)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error parsing JSON payload file: %+v\n", rid, err)
|
log.Printf("[%s] error parsing JSON payload file: %+v\n", req.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload == nil {
|
if req.Payload == nil {
|
||||||
payload = make(map[string]interface{})
|
req.Payload = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
payload[k] = part
|
req.Payload[k] = part
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", rid, contentType)
|
log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", req.ID, req.ContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if matchedHook.PreHookCommand != "" {
|
||||||
|
// check the command exists
|
||||||
|
var lookpath string
|
||||||
|
if filepath.IsAbs(matchedHook.PreHookCommand) || matchedHook.CommandWorkingDirectory == "" {
|
||||||
|
lookpath = matchedHook.PreHookCommand
|
||||||
|
} else {
|
||||||
|
lookpath = filepath.Join(matchedHook.CommandWorkingDirectory, matchedHook.PreHookCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
preHookCommandPath, err := exec.LookPath(lookpath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[%s] unable to locate pre-hook command: '%s', %+v\n", req.ID, matchedHook.PreHookCommand, err)
|
||||||
|
// check if parameters specified in pre-hook command by mistake
|
||||||
|
if strings.IndexByte(matchedHook.PreHookCommand, ' ') != -1 {
|
||||||
|
s := strings.Fields(matchedHook.PreHookCommand)[0]
|
||||||
|
log.Printf("[%s] please use a wrapper script to provide arguments to pre-hook command for '%s'\n", req.ID, s)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
preHookCommandStdin := hook.PreHookContext{
|
||||||
|
HookID: matchedHook.ID,
|
||||||
|
Method: r.Method,
|
||||||
|
Base64EncodedBody: base64.StdEncoding.EncodeToString(req.Body),
|
||||||
|
RemoteAddr: r.RemoteAddr,
|
||||||
|
URI: r.RequestURI,
|
||||||
|
Host: r.Host,
|
||||||
|
Headers: r.Header,
|
||||||
|
Query: r.URL.Query(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if preHookCommandStdinJSONString, err := json.Marshal(preHookCommandStdin); err != nil {
|
||||||
|
log.Printf("[%s] unable to encode pre-hook context as JSON string for the pre-hook command: %+v\n", req.ID, err)
|
||||||
|
} else {
|
||||||
|
preHookCommand := exec.Command(preHookCommandPath)
|
||||||
|
preHookCommand.Dir = matchedHook.CommandWorkingDirectory
|
||||||
|
preHookCommand.Env = append(os.Environ())
|
||||||
|
|
||||||
|
if preHookCommandStdinPipe, err := preHookCommand.StdinPipe(); err != nil {
|
||||||
|
log.Printf("[%s] unable to acquire stdin pipe for the pre-hook command: %+v\n", req.ID, err)
|
||||||
|
} else {
|
||||||
|
_, err := io.WriteString(preHookCommandStdinPipe, string(preHookCommandStdinJSONString))
|
||||||
|
preHookCommandStdinPipe.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[%s] unable to write to pre-hook command stdin: %+v\n", req.ID, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("[%s] executing pre-hook command %s (%s) using %s as cwd\n", req.ID, matchedHook.PreHookCommand, preHookCommand.Path, preHookCommand.Dir)
|
||||||
|
|
||||||
|
if preHookCommandOutput, err := preHookCommand.CombinedOutput(); err != nil {
|
||||||
|
log.Printf("[%s] unable to execute pre-hook command: %+v\n", req.ID, err)
|
||||||
|
} else {
|
||||||
|
JSONDecoder := json.NewDecoder(strings.NewReader(string(preHookCommandOutput)))
|
||||||
|
JSONDecoder.UseNumber()
|
||||||
|
|
||||||
|
if err := JSONDecoder.Decode(&req.Context); err != nil {
|
||||||
|
log.Printf("[%s] unable to parse pre-hook command output: %+v\npre-hook command output was: %+v\n", req.ID, err, string(preHookCommandOutput))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle hook
|
// handle hook
|
||||||
errors := matchedHook.ParseJSONParameters(&headers, &query, &payload, &context)
|
errors := matchedHook.ParseJSONParameters(req)
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
log.Printf("[%s] error parsing JSON parameters: %s\n", rid, err)
|
log.Printf("[%s] error parsing JSON parameters: %s\n", req.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
@ -559,29 +578,29 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if matchedHook.TriggerRule == nil {
|
if matchedHook.TriggerRule == nil {
|
||||||
ok = true
|
ok = true
|
||||||
} else {
|
} else {
|
||||||
ok, err = matchedHook.TriggerRule.Evaluate(&headers, &query, &payload, &context, &body, r.RemoteAddr)
|
ok, err = matchedHook.TriggerRule.Evaluate(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !hook.IsParameterNodeError(err) {
|
if !hook.IsParameterNodeError(err) {
|
||||||
msg := fmt.Sprintf("[%s] error evaluating hook: %s", rid, err)
|
msg := fmt.Sprintf("[%s] error evaluating hook: %s", req.ID, err)
|
||||||
log.Println(msg)
|
log.Println(msg)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
fmt.Fprint(w, "Error occurred while evaluating hook rules.")
|
fmt.Fprint(w, "Error occurred while evaluating hook rules.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[%s] %v", rid, err)
|
log.Printf("[%s] %v", req.ID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
log.Printf("[%s] %s hook triggered successfully\n", rid, matchedHook.ID)
|
log.Printf("[%s] %s hook triggered successfully\n", req.ID, matchedHook.ID)
|
||||||
|
|
||||||
for _, responseHeader := range matchedHook.ResponseHeaders {
|
for _, responseHeader := range matchedHook.ResponseHeaders {
|
||||||
w.Header().Set(responseHeader.Name, responseHeader.Value)
|
w.Header().Set(responseHeader.Name, responseHeader.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if matchedHook.CaptureCommandOutput {
|
if matchedHook.CaptureCommandOutput {
|
||||||
response, err := handleHook(matchedHook, rid, &headers, &query, &payload, &context, &body)
|
response, err := handleHook(matchedHook, req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
@ -594,16 +613,16 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
// Check if a success return code is configured for the hook
|
// Check if a success return code is configured for the hook
|
||||||
if matchedHook.SuccessHttpResponseCode != 0 {
|
if matchedHook.SuccessHttpResponseCode != 0 {
|
||||||
writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
|
writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, response)
|
fmt.Fprint(w, response)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
go handleHook(matchedHook, rid, &headers, &query, &payload, &context, &body)
|
go handleHook(matchedHook, req)
|
||||||
|
|
||||||
// Check if a success return code is configured for the hook
|
// Check if a success return code is configured for the hook
|
||||||
if matchedHook.SuccessHttpResponseCode != 0 {
|
if matchedHook.SuccessHttpResponseCode != 0 {
|
||||||
writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
|
writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprint(w, matchedHook.ResponseMessage)
|
fmt.Fprint(w, matchedHook.ResponseMessage)
|
||||||
@ -613,34 +632,34 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Check if a return code is configured for the hook
|
// Check if a return code is configured for the hook
|
||||||
if matchedHook.TriggerRuleMismatchHttpResponseCode != 0 {
|
if matchedHook.TriggerRuleMismatchHttpResponseCode != 0 {
|
||||||
writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.TriggerRuleMismatchHttpResponseCode)
|
writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.TriggerRuleMismatchHttpResponseCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if none of the hooks got triggered
|
// if none of the hooks got triggered
|
||||||
log.Printf("[%s] %s got matched, but didn't get triggered because the trigger rules were not satisfied\n", rid, matchedHook.ID)
|
log.Printf("[%s] %s got matched, but didn't get triggered because the trigger rules were not satisfied\n", req.ID, matchedHook.ID)
|
||||||
|
|
||||||
fmt.Fprint(w, "Hook rules were not satisfied.")
|
fmt.Fprint(w, "Hook rules were not satisfied.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]interface{}, context *map[string]interface{}, body *[]byte) (string, error) {
|
func handleHook(h *hook.Hook, r *hook.Request) (string, error) {
|
||||||
var errors []error
|
var errors []error
|
||||||
|
|
||||||
// check the command exists
|
// check the command exists
|
||||||
cmdPath, err := exec.LookPath(h.ExecuteCommand)
|
var lookpath string
|
||||||
if err != nil {
|
if filepath.IsAbs(h.ExecuteCommand) || h.CommandWorkingDirectory == "" {
|
||||||
// give a last chance, maybe is a relative path
|
lookpath = h.ExecuteCommand
|
||||||
relativeToCwd := filepath.Join(h.CommandWorkingDirectory, h.ExecuteCommand)
|
} else {
|
||||||
// check the command exists
|
lookpath = filepath.Join(h.CommandWorkingDirectory, h.ExecuteCommand)
|
||||||
cmdPath, err = exec.LookPath(relativeToCwd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmdPath, err := exec.LookPath(lookpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] unable to locate command: '%s'\n", rid, h.ExecuteCommand)
|
log.Printf("[%s] error in %s", r.ID, err)
|
||||||
|
|
||||||
// check if parameters specified in execute-command by mistake
|
// check if parameters specified in execute-command by mistake
|
||||||
if strings.IndexByte(h.ExecuteCommand, ' ') != -1 {
|
if strings.IndexByte(h.ExecuteCommand, ' ') != -1 {
|
||||||
s := strings.Fields(h.ExecuteCommand)[0]
|
s := strings.Fields(h.ExecuteCommand)[0]
|
||||||
log.Printf("[%s] please use 'pass-arguments-to-command' to specify args for '%s'\n", rid, s)
|
log.Printf("[%s] use 'pass-arguments-to-command' to specify args for '%s'", r.ID, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", err
|
return "", err
|
||||||
@ -649,37 +668,37 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
|
|||||||
cmd := exec.Command(cmdPath)
|
cmd := exec.Command(cmdPath)
|
||||||
cmd.Dir = h.CommandWorkingDirectory
|
cmd.Dir = h.CommandWorkingDirectory
|
||||||
|
|
||||||
cmd.Args, errors = h.ExtractCommandArguments(headers, query, payload, context)
|
cmd.Args, errors = h.ExtractCommandArguments(r)
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
log.Printf("[%s] error extracting command arguments: %s\n", rid, err)
|
log.Printf("[%s] error extracting command arguments: %s\n", r.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var envs []string
|
var envs []string
|
||||||
envs, errors = h.ExtractCommandArgumentsForEnv(headers, query, payload, context)
|
envs, errors = h.ExtractCommandArgumentsForEnv(r)
|
||||||
|
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
log.Printf("[%s] error extracting command arguments for environment: %s\n", rid, err)
|
log.Printf("[%s] error extracting command arguments for environment: %s\n", r.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
files, errors := h.ExtractCommandArgumentsForFile(headers, query, payload, context)
|
files, errors := h.ExtractCommandArgumentsForFile(r)
|
||||||
|
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
log.Printf("[%s] error extracting command arguments for file: %s\n", rid, err)
|
log.Printf("[%s] error extracting command arguments for file: %s\n", r.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range files {
|
for i := range files {
|
||||||
tmpfile, err := ioutil.TempFile(h.CommandWorkingDirectory, files[i].EnvName)
|
tmpfile, err := ioutil.TempFile(h.CommandWorkingDirectory, files[i].EnvName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error creating temp file [%s]\n", rid, err)
|
log.Printf("[%s] error creating temp file [%s]", r.ID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("[%s] writing env %s file %s", rid, files[i].EnvName, tmpfile.Name())
|
log.Printf("[%s] writing env %s file %s", r.ID, files[i].EnvName, tmpfile.Name())
|
||||||
if _, err := tmpfile.Write(files[i].Data); err != nil {
|
if _, err := tmpfile.Write(files[i].Data); err != nil {
|
||||||
log.Printf("[%s] error writing file %s [%s]\n", rid, tmpfile.Name(), err)
|
log.Printf("[%s] error writing file %s [%s]", r.ID, tmpfile.Name(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := tmpfile.Close(); err != nil {
|
if err := tmpfile.Close(); err != nil {
|
||||||
log.Printf("[%s] error closing file %s [%s]\n", rid, tmpfile.Name(), err)
|
log.Printf("[%s] error closing file %s [%s]", r.ID, tmpfile.Name(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,32 +708,32 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
|
|||||||
|
|
||||||
cmd.Env = append(os.Environ(), envs...)
|
cmd.Env = append(os.Environ(), envs...)
|
||||||
|
|
||||||
log.Printf("[%s] executing %s (%s) with arguments %q and environment %s using %s as cwd\n", rid, h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)
|
log.Printf("[%s] executing %s (%s) with arguments %q and environment %s using %s as cwd\n", r.ID, h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
log.Printf("[%s] command output: %s\n", rid, out)
|
log.Printf("[%s] command output: %s\n", r.ID, out)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error occurred: %+v\n", rid, err)
|
log.Printf("[%s] error occurred: %+v\n", r.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range files {
|
for i := range files {
|
||||||
if files[i].File != nil {
|
if files[i].File != nil {
|
||||||
log.Printf("[%s] removing file %s\n", rid, files[i].File.Name())
|
log.Printf("[%s] removing file %s\n", r.ID, files[i].File.Name())
|
||||||
err := os.Remove(files[i].File.Name())
|
err := os.Remove(files[i].File.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error removing file %s [%s]\n", rid, files[i].File.Name(), err)
|
log.Printf("[%s] error removing file %s [%s]", r.ID, files[i].File.Name(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[%s] finished handling %s\n", rid, h.ID)
|
log.Printf("[%s] finished handling %s\n", r.ID, h.ID)
|
||||||
|
|
||||||
return string(out), err
|
return string(out), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeHttpResponseCode(w http.ResponseWriter, rid string, hookId string, responseCode int) {
|
func writeHttpResponseCode(w http.ResponseWriter, rid, hookId string, responseCode int) {
|
||||||
// Check if the given return code is supported by the http package
|
// Check if the given return code is supported by the http package
|
||||||
// by testing if there is a StatusText for this code.
|
// by testing if there is a StatusText for this code.
|
||||||
if len(http.StatusText(responseCode)) > 0 {
|
if len(http.StatusText(responseCode)) > 0 {
|
||||||
|
@ -53,7 +53,11 @@ func TestStaticParams(t *testing.T) {
|
|||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
log.SetOutput(b)
|
log.SetOutput(b)
|
||||||
|
|
||||||
_, err = handleHook(spHook, "test", &spHeaders, &map[string]interface{}{}, &map[string]interface{}{}, &map[string]interface{}{}, &[]byte{})
|
r := &hook.Request{
|
||||||
|
ID: "test",
|
||||||
|
Headers: spHeaders,
|
||||||
|
}
|
||||||
|
_, err = handleHook(spHook, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v\n", err)
|
t.Fatalf("Unexpected error: %v\n", err)
|
||||||
}
|
}
|
||||||
@ -77,7 +81,7 @@ func TestWebhook(t *testing.T) {
|
|||||||
for _, tt := range hookHandlerTests {
|
for _, tt := range hookHandlerTests {
|
||||||
t.Run(tt.desc+"@"+hookTmpl, func(t *testing.T) {
|
t.Run(tt.desc+"@"+hookTmpl, func(t *testing.T) {
|
||||||
ip, port := serverAddress(t)
|
ip, port := serverAddress(t)
|
||||||
args := []string{fmt.Sprintf("-hooks=%s", configPath), fmt.Sprintf("-ip=%s", ip), fmt.Sprintf("-port=%s", port), "-verbose"}
|
args := []string{fmt.Sprintf("-hooks=%s", configPath), fmt.Sprintf("-ip=%s", ip), fmt.Sprintf("-port=%s", port), "-debug"}
|
||||||
|
|
||||||
if len(tt.cliMethods) != 0 {
|
if len(tt.cliMethods) != 0 {
|
||||||
args = append(args, "-http-methods="+strings.Join(tt.cliMethods, ","))
|
args = append(args, "-http-methods="+strings.Join(tt.cliMethods, ","))
|
||||||
@ -111,6 +115,7 @@ func TestWebhook(t *testing.T) {
|
|||||||
var res *http.Response
|
var res *http.Response
|
||||||
|
|
||||||
req.Header.Add("Content-Type", tt.contentType)
|
req.Header.Add("Content-Type", tt.contentType)
|
||||||
|
req.ContentLength = int64(len(tt.body))
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
res, err = client.Do(req)
|
res, err = client.Do(req)
|
||||||
@ -171,7 +176,7 @@ func buildHookecho(t *testing.T) (binPath string, cleanupFn func()) {
|
|||||||
return binPath, func() { os.RemoveAll(tmp) }
|
return binPath, func() { os.RemoveAll(tmp) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func genConfig(t *testing.T, bin string, hookTemplate string) (configPath string, cleanupFn func()) {
|
func genConfig(t *testing.T, bin, hookTemplate string) (configPath string, cleanupFn func()) {
|
||||||
tmpl := template.Must(template.ParseFiles(hookTemplate))
|
tmpl := template.Must(template.ParseFiles(hookTemplate))
|
||||||
|
|
||||||
tmp, err := ioutil.TempDir("", "webhook-config-")
|
tmp, err := ioutil.TempDir("", "webhook-config-")
|
||||||
@ -546,6 +551,28 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
`success`,
|
`success`,
|
||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"payload-json-array",
|
||||||
|
"sendgrid",
|
||||||
|
nil,
|
||||||
|
"POST",
|
||||||
|
nil,
|
||||||
|
"application/json",
|
||||||
|
`[
|
||||||
|
{
|
||||||
|
"email": "example@test.com",
|
||||||
|
"timestamp": 1513299569,
|
||||||
|
"smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>",
|
||||||
|
"event": "processed",
|
||||||
|
"category": "cat facts",
|
||||||
|
"sg_event_id": "sg_event_id",
|
||||||
|
"sg_message_id": "sg_message_id"
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
http.StatusOK,
|
||||||
|
`success`,
|
||||||
|
``,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"multipart",
|
"multipart",
|
||||||
"plex",
|
"plex",
|
||||||
@ -663,6 +690,19 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
``,
|
``,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"empty-payload-signature", // allow empty payload signature validation
|
||||||
|
"empty-payload-signature",
|
||||||
|
nil,
|
||||||
|
"POST",
|
||||||
|
map[string]string{"X-Hub-Signature": "33f9d709782f62b8b4a0178586c65ab098a39fe2"},
|
||||||
|
"application/json",
|
||||||
|
``,
|
||||||
|
http.StatusOK,
|
||||||
|
``,
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
|
||||||
// test with disallowed global HTTP method
|
// test with disallowed global HTTP method
|
||||||
{"global disallowed method", "bitbucket", []string{"Post "}, "GET", nil, `{}`, "application/json", http.StatusMethodNotAllowed, ``, ``},
|
{"global disallowed method", "bitbucket", []string{"Post "}, "GET", nil, `{}`, "application/json", http.StatusMethodNotAllowed, ``, ``},
|
||||||
// test with disallowed HTTP method
|
// test with disallowed HTTP method
|
||||||
@ -684,7 +724,7 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
|||||||
|
|
||||||
// Check logs
|
// Check logs
|
||||||
{"static params should pass", "static-params-ok", nil, "POST", nil, "application/json", `{}`, http.StatusOK, "arg: passed\n", `(?s)command output: arg: passed`},
|
{"static params should pass", "static-params-ok", nil, "POST", nil, "application/json", `{}`, http.StatusOK, "arg: passed\n", `(?s)command output: arg: passed`},
|
||||||
{"command with space logs warning", "warn-on-space", nil, "POST", nil, "application/json", `{}`, http.StatusInternalServerError, "Error occurred while executing the hook's command. Please check your logs for more details.", `(?s)unable to locate command.*use 'pass[-]arguments[-]to[-]command' to specify args`},
|
{"command with space logs warning", "warn-on-space", nil, "POST", nil, "application/json", `{}`, http.StatusInternalServerError, "Error occurred while executing the hook's command. Please check your logs for more details.", `(?s)error in exec:.*use 'pass[-]arguments[-]to[-]command' to specify args`},
|
||||||
{"unsupported content type error", "github", nil, "POST", map[string]string{"Content-Type": "nonexistent/format"}, "application/json", `{}`, http.StatusBadRequest, `Hook rules were not satisfied.`, `(?s)error parsing body payload due to unsupported content type header:`},
|
{"unsupported content type error", "github", nil, "POST", map[string]string{"Content-Type": "nonexistent/format"}, "application/json", `{}`, http.StatusBadRequest, `Hook rules were not satisfied.`, `(?s)error parsing body payload due to unsupported content type header:`},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user