Use signature field for verification
Instead of looking for a "secret" field hmac is used. Therefore the raw payload is hashed with all secrets consecutively in order to validate its content. If the content is certified the established behaviour is pursued..
This commit is contained in:
parent
a130bdc125
commit
ee32424f8c
4 changed files with 297 additions and 372 deletions
272
Cargo.lock
generated
272
Cargo.lock
generated
|
@ -91,7 +91,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
|||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -172,12 +172,6 @@ version = "1.0.67"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -234,7 +228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
|
@ -315,7 +309,7 @@ checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
|
|||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi 0.3.9",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -337,53 +331,6 @@ dependencies = [
|
|||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall 0.2.5",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fsevent-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fuchsia-zircon-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon-sys"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
|
@ -406,7 +353,7 @@ version = "0.1.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
@ -417,7 +364,7 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
]
|
||||
|
@ -453,6 +400,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.10.0"
|
||||
|
@ -537,51 +490,12 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.2.2"
|
||||
|
@ -594,12 +508,6 @@ version = "1.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.5"
|
||||
|
@ -608,7 +516,7 @@ checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374"
|
|||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
@ -640,7 +548,7 @@ version = "0.4.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -664,60 +572,6 @@ dependencies = [
|
|||
"log 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.6.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"fuchsia-zircon",
|
||||
"fuchsia-zircon-sys",
|
||||
"iovec",
|
||||
"kernel32-sys",
|
||||
"libc",
|
||||
"log 0.4.14",
|
||||
"miow",
|
||||
"net2",
|
||||
"slab",
|
||||
"winapi 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-extras"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
|
||||
dependencies = [
|
||||
"lazycell",
|
||||
"log 0.4.14",
|
||||
"mio",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
|
||||
dependencies = [
|
||||
"kernel32-sys",
|
||||
"net2",
|
||||
"winapi 0.2.8",
|
||||
"ws2_32-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net2"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "6.1.2"
|
||||
|
@ -731,24 +585,6 @@ dependencies = [
|
|||
"version_check 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "4.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"filetime",
|
||||
"fsevent",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio-extras",
|
||||
"walkdir",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
|
@ -904,15 +740,6 @@ version = "0.1.57"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.3.5"
|
||||
|
@ -920,7 +747,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
"redox_syscall 0.1.57",
|
||||
"redox_syscall",
|
||||
"rust-argon2",
|
||||
]
|
||||
|
||||
|
@ -989,19 +816,6 @@ dependencies = [
|
|||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocket_contrib"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7954a707f9ca18aa74ca8c1f5d1f900f52a4dceb68e96e3112143f759cfd20e"
|
||||
dependencies = [
|
||||
"log 0.4.14",
|
||||
"notify",
|
||||
"rocket",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocket_http"
|
||||
version = "0.4.7"
|
||||
|
@ -1059,15 +873,6 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.4.0"
|
||||
|
@ -1128,18 +933,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"cpuid-bool 0.1.2",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
|
@ -1208,7 +1007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1331,17 +1130,6 @@ version = "0.9.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi 0.3.9",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
|
@ -1361,14 +1149,16 @@ dependencies = [
|
|||
"anyhow",
|
||||
"dirs",
|
||||
"env_logger",
|
||||
"hex",
|
||||
"hmac",
|
||||
"log 0.4.14",
|
||||
"nom",
|
||||
"regex",
|
||||
"rocket",
|
||||
"rocket_contrib",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1391,12 +1181,6 @@ dependencies = [
|
|||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
@ -1407,12 +1191,6 @@ dependencies = [
|
|||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
@ -1425,7 +1203,7 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1434,16 +1212,6 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
|
|
|
@ -12,7 +12,6 @@ tls = ["rocket/tls"]
|
|||
|
||||
[dependencies]
|
||||
rocket = "0.4"
|
||||
rocket_contrib = { version = "0.4", default-features = false, features = ["json"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.8"
|
||||
|
@ -22,3 +21,6 @@ anyhow = "1.0"
|
|||
log = "0.4"
|
||||
env_logger = "0.8"
|
||||
nom = "6"
|
||||
hmac = "0.10"
|
||||
sha2 = "0.9"
|
||||
hex = "0.4"
|
||||
|
|
12
README.md
12
README.md
|
@ -55,11 +55,12 @@ Configuration syntax is YAML and has to be done in following order:
|
|||
|
||||
Right now there is only the configuration parameter for hooks, here
|
||||
each hook has to be configured, It contains following fields:
|
||||
- command: Optional string for a command to be executed when all
|
||||
filters match. Pointers ([RFC
|
||||
6901](https://tools.ietf.org/html/rfc6901)) to JSON fields may be
|
||||
used to be replaced with data from the JSON data with `{{
|
||||
/field/pointed/to }}`
|
||||
- command: String for a command to be executed when all filters
|
||||
match. Pointers ([RFC 6901](https://tools.ietf.org/html/rfc6901)) to
|
||||
JSON fields may be used to be replaced with data from the JSON data
|
||||
with `{{ /field/pointed/to }}`. Further `{{ event }}` and `{{
|
||||
signature }}` are valid variables as they contain the values from
|
||||
the regarding header fields of the http request.
|
||||
- secrets: List of secrets.
|
||||
- filters: List of filters.
|
||||
|
||||
|
@ -81,7 +82,6 @@ Whereas `<config_dir>` depends on the platform:
|
|||
- Windows: `{FOLDERID_RoamingAppData}`
|
||||
|
||||
# TODOs
|
||||
## Use `lazy_static` or `once_cell` for compiled regexes
|
||||
## Use `clap` to parse command line arguments
|
||||
## Implement the functionality to reply to certain webhooks
|
||||
## Configure rocket via config.yml
|
||||
|
|
357
src/main.rs
357
src/main.rs
|
@ -1,7 +1,8 @@
|
|||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use log::{debug, info, trace, warn};
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{tag, take_until},
|
||||
|
@ -11,12 +12,24 @@ use nom::{
|
|||
Finish, IResult,
|
||||
};
|
||||
use regex::Regex;
|
||||
use rocket::{fairing::AdHoc, get, http::Status, post, routes, Response, State};
|
||||
use rocket_contrib::json::Json;
|
||||
use rocket::{
|
||||
data::{self, FromDataSimple},
|
||||
fairing::AdHoc,
|
||||
get,
|
||||
http::{HeaderMap, Status},
|
||||
post, routes, Data,
|
||||
Outcome::{Failure, Success},
|
||||
Request, Response, State,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
|
||||
use std::{
|
||||
collections::HashMap, fs::File, io::BufReader, net::SocketAddr, process::Command,
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
io::{BufReader, Read},
|
||||
net::SocketAddr,
|
||||
process::Command,
|
||||
str::from_utf8,
|
||||
};
|
||||
|
||||
|
@ -27,7 +40,7 @@ struct Config {
|
|||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct Hook {
|
||||
command: Option<String>,
|
||||
command: String,
|
||||
secrets: Vec<String>,
|
||||
filters: HashMap<String, Filter>,
|
||||
}
|
||||
|
@ -38,28 +51,28 @@ struct Filter {
|
|||
regex: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct Data(serde_json::Value);
|
||||
#[derive(Debug)]
|
||||
struct Hooks(HashMap<String, Vec<String>>);
|
||||
|
||||
#[get("/")]
|
||||
fn index() -> &'static str {
|
||||
"Hello, webhookey!"
|
||||
}
|
||||
|
||||
fn replace_parameter(input: &str, data: &serde_json::Value) -> Result<String> {
|
||||
fn replace_parameter(input: &str, headers: &HeaderMap, data: &serde_json::Value) -> Result<String> {
|
||||
let parse: IResult<&str, Vec<&str>> = many0(alt((
|
||||
map_res(
|
||||
delimited(tag("{{"), take_until("}}"), tag("}}")),
|
||||
|param: &str| {
|
||||
if let Some(value) = data.pointer(param.trim()) {
|
||||
if let Some(value) = value.as_str() {
|
||||
Ok(value)
|
||||
|param: &str| match param.trim() {
|
||||
"event" => {
|
||||
if let Some(event) = headers.get_one("X-Gitea-Event") {
|
||||
Ok(event)
|
||||
} else {
|
||||
bail!("Could not convert field `{}` to string", param.trim());
|
||||
bail!("Could not extract event parameter from header");
|
||||
}
|
||||
} else {
|
||||
bail!("Could not find `{}` in received data", param.trim());
|
||||
}
|
||||
pointer => match data.pointer(pointer) {
|
||||
Some(value) => match value.as_str() {
|
||||
Some(value) => Ok(value),
|
||||
_ => bail!("Could not convert value `{}` to string", value),
|
||||
},
|
||||
_ => bail!("Could not convert field `{}` to string", param.trim()),
|
||||
},
|
||||
},
|
||||
),
|
||||
take_until("{{"),
|
||||
|
@ -73,92 +86,224 @@ fn replace_parameter(input: &str, data: &serde_json::Value) -> Result<String> {
|
|||
Ok(result.join(""))
|
||||
}
|
||||
|
||||
fn execute_hook(name: &str, hook: &Hook, data: &serde_json::Value) -> Result<()> {
|
||||
debug!("Running hook `{}`", name);
|
||||
impl FromDataSimple for Hooks {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
for (filter_name, filter) in hook.filters.iter() {
|
||||
debug!("Matching filter `{}`", filter_name);
|
||||
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
|
||||
let config = request.guard::<State<Config>>().unwrap(); // should never fail
|
||||
|
||||
let mut hooks = HashMap::new();
|
||||
|
||||
if let Some(signature) = request.headers().get_one("X-Gitea-Signature") {
|
||||
let mut data = data.open();
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
match data.read_to_end(&mut buffer) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("Could not read to end of data: {}", &e);
|
||||
return Failure((
|
||||
Status::BadRequest,
|
||||
anyhow!("Could not read to end of data: {}", &e),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Data received: {:?}", from_utf8(&buffer));
|
||||
|
||||
let mut valid = false;
|
||||
|
||||
for (hook_name, hook) in &config.hooks {
|
||||
let mut commands = Vec::new();
|
||||
|
||||
for secret in &hook.secrets {
|
||||
let mut mac = match Hmac::<Sha256>::new_varkey(&secret.as_bytes()) {
|
||||
Ok(mac) => mac,
|
||||
Err(e) => {
|
||||
error!("Could not instantiate hasher: {}", e);
|
||||
return Failure((
|
||||
Status::InternalServerError,
|
||||
anyhow!("Could not instantiate hasher: {}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
mac.update(&buffer);
|
||||
|
||||
match &hex::decode(&signature.as_bytes()) {
|
||||
Ok(raw_signature) => {
|
||||
if mac.verify(&raw_signature) == Ok(()) {
|
||||
trace!(
|
||||
"Valid signature found for hook `{}`: {}",
|
||||
hook_name,
|
||||
signature
|
||||
);
|
||||
|
||||
valid = true;
|
||||
|
||||
let data: serde_json::Value = match serde_json::from_slice(&buffer)
|
||||
{
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
error!("Could not parse json: {}", e);
|
||||
return Failure((
|
||||
Status::BadRequest,
|
||||
anyhow!("Could not parse json: {}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
for (filter_name, filter) in &hook.filters {
|
||||
trace!(
|
||||
"Matching filter `{}` of hook `{}`",
|
||||
filter_name,
|
||||
hook_name
|
||||
);
|
||||
|
||||
let regex = match Regex::new(&filter.regex) {
|
||||
Ok(regex) => regex,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Could not compile regex `{}`: {}",
|
||||
&filter.regex, e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(value) = data.pointer(&filter.pointer) {
|
||||
let regex = Regex::new(&filter.regex)?;
|
||||
|
||||
if let Some(value) = value.as_str() {
|
||||
if !regex.is_match(value) {
|
||||
info!("Filter `{}` in hook `{}` did not match", filter_name, name);
|
||||
return Ok(());
|
||||
if regex.is_match(value) {
|
||||
debug!(
|
||||
"Filter `{}` of hook `{}` matched",
|
||||
filter_name, hook_name
|
||||
);
|
||||
|
||||
match replace_parameter(
|
||||
&hook.command.to_string(),
|
||||
&request.headers(),
|
||||
&data,
|
||||
) {
|
||||
Ok(command) => commands.push(command),
|
||||
Err(e) => error!(
|
||||
"Could not replace all parameter in hook `{}`: {}",
|
||||
hook_name, e
|
||||
),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
anyhow!(
|
||||
"Could not parse pointer in hook `{}` from filter `{}`",
|
||||
name,
|
||||
hook_name,
|
||||
filter_name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
trace!(
|
||||
"Filter `{}` of hook `{}` did not match",
|
||||
filter_name,
|
||||
hook_name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Invalid configuration: {}", e);
|
||||
return Failure((
|
||||
Status::InternalServerError,
|
||||
anyhow!("Invalid configuration: {}", e),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(command) = &hook.command {
|
||||
let command = replace_parameter(&command, data)?;
|
||||
if !commands.is_empty() {
|
||||
hooks.insert(hook_name.to_string(), commands);
|
||||
}
|
||||
}
|
||||
|
||||
info!("Execute `{}` from hook `{}`", command, name);
|
||||
if hooks.is_empty() {
|
||||
if valid {
|
||||
warn!(
|
||||
"Unmatched hook from {:?} with signature {:?}",
|
||||
&request.client_ip(),
|
||||
&request.headers().get_one("X-Gitea-Signature")
|
||||
);
|
||||
Failure((
|
||||
Status::NotFound,
|
||||
anyhow!(
|
||||
"Unmatched hook from {:?} with signature {:?}",
|
||||
&request.client_ip(),
|
||||
&request.headers().get_one("X-Gitea-Signature")
|
||||
),
|
||||
))
|
||||
} else {
|
||||
warn!(
|
||||
"Unauthorized request from {:?} with signature {:?}",
|
||||
&request.client_ip(),
|
||||
&request.headers().get_one("X-Gitea-Signature")
|
||||
);
|
||||
Failure((
|
||||
Status::Unauthorized,
|
||||
anyhow!(
|
||||
"Unauthorized request from {:?} with signature {:?}",
|
||||
&request.client_ip(),
|
||||
&request.headers().get_one("X-Gitea-Signature")
|
||||
),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Success(Hooks(hooks))
|
||||
}
|
||||
} else {
|
||||
Failure((
|
||||
Status::BadRequest,
|
||||
anyhow!("Could not extract signature from header"),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index() -> &'static str {
|
||||
"Hello, webhookey!"
|
||||
}
|
||||
|
||||
#[post("/", format = "json", data = "<hooks>")]
|
||||
fn receive_hook<'a>(address: SocketAddr, hooks: Hooks) -> Result<Response<'a>> {
|
||||
info!("Post request received from: {}", address);
|
||||
|
||||
for hook in hooks.0 {
|
||||
for command in hook.1 {
|
||||
info!("Execute `{}` from hook `{}`", &command, &hook.0);
|
||||
|
||||
let command = command.split(' ').collect::<Vec<&str>>();
|
||||
let exec_command = Command::new(&command[0]).args(&command[1..]).output()?;
|
||||
|
||||
match Command::new(&command[0]).args(&command[1..]).output() {
|
||||
Ok(executed) => {
|
||||
info!(
|
||||
"Command `{}` exited with return code: {}",
|
||||
&command[0], &exec_command.status
|
||||
&command[0], &executed.status
|
||||
);
|
||||
trace!(
|
||||
"Output of command `{}` on stdout: {:?}",
|
||||
&command[0],
|
||||
from_utf8(&exec_command.stdout)?
|
||||
from_utf8(&executed.stdout)?
|
||||
);
|
||||
debug!(
|
||||
"Output of command `{}` on stderr: {:?}",
|
||||
&command[0],
|
||||
from_utf8(&exec_command.stderr)?
|
||||
from_utf8(&executed.stderr)?
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[post("/", format = "json", data = "<data>")]
|
||||
fn receive_hook(address: SocketAddr, config: State<Config>, data: Json<Data>) -> Result<Response> {
|
||||
info!("Post request received from: {}", address);
|
||||
|
||||
let mut response = Response::new();
|
||||
let data = serde_json::to_value(data.0)?;
|
||||
|
||||
trace!("Data received from: {}\n{}", address, data);
|
||||
|
||||
if let Some(secret) = data.pointer("/secret") {
|
||||
if let Some(secret) = secret.as_str() {
|
||||
let hooks: HashMap<&String, &Hook> = config
|
||||
.hooks
|
||||
.iter()
|
||||
.filter(|(_hook_name, hook)| hook.secrets.contains(&secret.to_string()))
|
||||
.collect();
|
||||
|
||||
if hooks.is_empty() {
|
||||
warn!("Secret from {} did not match any hook", address);
|
||||
response.set_status(Status::Unauthorized);
|
||||
} else {
|
||||
for (hook_name, hook) in hooks {
|
||||
execute_hook(&hook_name, &hook, &data)?;
|
||||
Err(e) => {
|
||||
error!("Execution of `{}` failed: {}", command[0], e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Data received from {} contains invalid data", address);
|
||||
response.set_status(Status::BadRequest);
|
||||
}
|
||||
} else {
|
||||
warn!("Data received from {} did not contain a secret", address);
|
||||
response.set_status(Status::NotFound);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
fn get_config() -> Result<File> {
|
||||
|
@ -210,7 +355,10 @@ fn main() -> Result<()> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rocket::{http::ContentType, local::Client};
|
||||
use rocket::{
|
||||
http::{ContentType, Header},
|
||||
local::Client,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
|
@ -230,7 +378,7 @@ mod tests {
|
|||
hooks.insert(
|
||||
"test_hook".to_string(),
|
||||
Hook {
|
||||
command: None,
|
||||
command: "".to_string(),
|
||||
secrets: vec!["valid".to_string()],
|
||||
filters: HashMap::new(),
|
||||
},
|
||||
|
@ -246,33 +394,33 @@ mod tests {
|
|||
let client = Client::new(rocket).unwrap();
|
||||
let response = client
|
||||
.post("/")
|
||||
.header(Header::new(
|
||||
"X-Gitea-Signature",
|
||||
"28175a0035f637f3cbb85afee9f9d319631580e7621cf790cd16ca063a2f820e",
|
||||
))
|
||||
.header(ContentType::JSON)
|
||||
.remote("127.0.0.1:8000".parse().unwrap())
|
||||
.body(r#"{ "secret": "valid" }"#)
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let response = client
|
||||
.post("/")
|
||||
.header(ContentType::JSON)
|
||||
.remote("127.0.0.1:8000".parse().unwrap())
|
||||
.body(r#"{ "secret": "invalid" }"#)
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.status(), Status::Unauthorized);
|
||||
|
||||
let response = client
|
||||
.post("/")
|
||||
.header(ContentType::JSON)
|
||||
.remote("127.0.0.1:8000".parse().unwrap())
|
||||
.body(r#"{ "not_secret": "invalid" }"#)
|
||||
.body(&serde_json::to_string(&json!({ "foo": "bar" })).unwrap())
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
let response = client
|
||||
.post("/")
|
||||
.header(Header::new("X-Gitea-Signature", "beef"))
|
||||
.header(ContentType::JSON)
|
||||
.remote("127.0.0.1:8000".parse().unwrap())
|
||||
.body(&serde_json::to_string(&json!({ "foo": "bar" })).unwrap())
|
||||
.dispatch();
|
||||
|
||||
assert_eq!(response.status(), Status::Unauthorized);
|
||||
|
||||
let response = client
|
||||
.post("/")
|
||||
.header(Header::new(
|
||||
"X-Gitea-Signature",
|
||||
"c5c315d76318362ec129ca629b50b626bba09ad3d7ba4cc0f4c0afe4a90537a0",
|
||||
))
|
||||
.header(ContentType::JSON)
|
||||
.remote("127.0.0.1:8000".parse().unwrap())
|
||||
.body(r#"{ "not_secret": "invalid" "#)
|
||||
|
@ -283,44 +431,48 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn parse_command() {
|
||||
let mut map = HeaderMap::new();
|
||||
map.add_raw("X-Gitea-Event", "something");
|
||||
|
||||
assert_eq!(
|
||||
replace_parameter("command", &serde_json::Value::Null).unwrap(),
|
||||
replace_parameter("command", &map, &serde_json::Value::Null).unwrap(),
|
||||
"command"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
replace_parameter(" command", &serde_json::Value::Null).unwrap(),
|
||||
replace_parameter(" command", &map, &serde_json::Value::Null).unwrap(),
|
||||
" command"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
replace_parameter("command ", &serde_json::Value::Null).unwrap(),
|
||||
replace_parameter("command ", &map, &serde_json::Value::Null).unwrap(),
|
||||
"command "
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
replace_parameter(" command ", &serde_json::Value::Null).unwrap(),
|
||||
replace_parameter(" command ", &map, &serde_json::Value::Null).unwrap(),
|
||||
" command "
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
replace_parameter("command command ", &serde_json::Value::Null).unwrap(),
|
||||
replace_parameter("command command ", &map, &serde_json::Value::Null).unwrap(),
|
||||
"command command "
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
replace_parameter("{{ /foo }} command", &json!({ "foo": "bar" })).unwrap(),
|
||||
replace_parameter("{{ /foo }} command", &map, &json!({ "foo": "bar" })).unwrap(),
|
||||
"bar command"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
replace_parameter(" command {{ /foo }} ", &json!({ "foo": "bar" })).unwrap(),
|
||||
replace_parameter(" command {{ /foo }} ", &map, &json!({ "foo": "bar" })).unwrap(),
|
||||
" command bar "
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
replace_parameter(
|
||||
"{{ /foo }} command{{/field1/foo}}",
|
||||
&map,
|
||||
&json!({ "foo": "bar", "field1": { "foo": "baz" } })
|
||||
)
|
||||
.unwrap(),
|
||||
|
@ -328,17 +480,20 @@ mod tests {
|
|||
);
|
||||
|
||||
assert_eq!(
|
||||
replace_parameter(" command {{ /foo }} ", &json!({ "foo": "bar" })).unwrap(),
|
||||
replace_parameter(" command {{ /foo }} ", &map, &json!({ "foo": "bar" })).unwrap(),
|
||||
" command bar "
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
replace_parameter(
|
||||
" {{ /field1/foo }} command",
|
||||
&map,
|
||||
&json!({ "field1": { "foo": "bar" } })
|
||||
)
|
||||
.unwrap(),
|
||||
" bar command"
|
||||
);
|
||||
|
||||
// Add tests with header fields
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue