mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
feat(reliability): idempotency store, custom webhook replay protection, correlation ids
Add LICENSE (MIT), an IdempotencyStore abstraction with a KV implementation and provider-scoped delivery keys, optional replay protection for custom webhooks (X-WebHooker-Timestamp + X-WebHooker-Nonce), and per-request correlation ids in webhook responses and logs.
This commit is contained in:
parent
92ba2203a5
commit
737dd7af98
15 changed files with 378 additions and 26 deletions
|
|
@ -94,7 +94,7 @@ Verifies the payload against the **group's** secret (KV `tenant:{groupId}`, gene
|
|||
|
||||
### Custom Webhooks
|
||||
|
||||
Any JSON payload signed with `X-WebHooker-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body, group or global secret) becomes a `custom` event. Route it with a route whose filter is `event: custom`. Payload schema: see [Configuration → Custom webhooks](../guide/ingress.md#custom-webhooks).
|
||||
Any JSON payload signed with `X-WebHooker-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body, group or global secret) becomes a `custom` event. Route it with a route whose filter is `event: custom`. Optional `X-WebHooker-Timestamp` + `X-WebHooker-Nonce` headers enable replay protection (signature over `{timestamp}.{nonce}.{body}`, ±5 min window, nonce dedup). Payload schema: see [Configuration → Custom webhooks](../guide/ingress.md#custom-webhooks).
|
||||
|
||||
### GitHub App Installation Events
|
||||
|
||||
|
|
|
|||
|
|
@ -23,13 +23,32 @@ Every group can opt into its own webhook ingress with an independent secret (gen
|
|||
|
||||
- Supported for any provider: GitHub (`X-Hub-Signature-256`), Gitea (`X-Gitea-Signature`), custom (`X-WebHooker-Signature`)
|
||||
- The secret is a 64-char hex string; regenerate from the console invalidates the old one immediately
|
||||
- Delivery-id dedup keys are tenant-scoped (`delivery:{groupId}:{id}`)
|
||||
- Delivery-id dedup keys are provider- and tenant-scoped (`delivery:{provider}:{groupId}:{id}`)
|
||||
- When the group has no secret (or no longer exists) the endpoint returns `404`
|
||||
|
||||
## Custom Webhooks
|
||||
|
||||
Post arbitrary JSON to `POST /webhook/{groupId}` (or the global endpoint) with the body signed as `X-WebHooker-Signature: sha256=<hmac-sha256 hex of the raw body>` using the group's secret. The payload becomes a `custom` event that flows through the normal route pipeline — create a route with `event: custom` (there is a console template) and it dispatches to that route's targets, records `send_logs`, and appears in the group's webhook log channel.
|
||||
|
||||
### Replay Protection
|
||||
|
||||
Custom webhooks support optional replay protection via two extra headers alongside the signature:
|
||||
|
||||
- `X-WebHooker-Timestamp` — Unix seconds the request was sent
|
||||
- `X-WebHooker-Nonce` — a unique, unpredictable value per request (e.g. a UUID)
|
||||
|
||||
When **both** headers are present, the signature is computed over `{timestamp}.{nonce}.{rawBody}` instead of the raw body, and the request is accepted only if:
|
||||
|
||||
1. The timestamp is within ±5 minutes of the server clock (rejects replays and clock-drift abusers)
|
||||
2. The nonce has never been seen before (stored in KV for 10 minutes; a replayed nonce is rejected)
|
||||
|
||||
```bash
|
||||
input="${timestamp}.${nonce}.${body}"
|
||||
signature="sha256=$(printf '%s' "$input" | openssl dgst -sha256 -hmac "$secret" -hex | sed 's/.*= //')"
|
||||
```
|
||||
|
||||
When the headers are omitted, WebHooker falls back to the legacy body-only signature, so existing senders keep working unchanged.
|
||||
|
||||
Payload schema:
|
||||
|
||||
```json
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ POST /webhook
|
|||
|
||||
### 自定义 Webhook
|
||||
|
||||
任意 JSON 载荷用 `X-WebHooker-Signature: sha256=<hex>`(对原始 body 的 HMAC-SHA256,使用分组或全局 secret)签名后即可成为 `custom` 事件。用 `event: custom` 过滤器的路由接收。载荷格式见[配置 → 自定义 Webhook](../guide/ingress.md#自定义-webhook)。
|
||||
任意 JSON 载荷用 `X-WebHooker-Signature: sha256=<hex>`(对原始 body 的 HMAC-SHA256,使用分组或全局 secret)签名后即可成为 `custom` 事件。用 `event: custom` 过滤器的路由接收。可选的 `X-WebHooker-Timestamp` + `X-WebHooker-Nonce` 请求头启用重放防护(对 `{timestamp}.{nonce}.{body}` 签名、±5 分钟窗口、nonce 去重)。载荷格式见[配置 → 自定义 Webhook](../guide/ingress.md#自定义-webhook)。
|
||||
|
||||
### GitHub App 安装事件
|
||||
|
||||
|
|
|
|||
|
|
@ -23,13 +23,32 @@ Gitea 载荷会被归一化为与 GitHub 事件相同的内部结构,因此路
|
|||
|
||||
- 支持任意提供方:GitHub(`X-Hub-Signature-256`)、Gitea(`X-Gitea-Signature`)、自定义(`X-WebHooker-Signature`)
|
||||
- 密钥为 64 位 hex 字符串;在控制台重新生成会立即失效旧密钥
|
||||
- 投递 id 去重键按租户隔离(`delivery:{groupId}:{id}`)
|
||||
- 投递 id 去重键按提供方与租户隔离(`delivery:{provider}:{groupId}:{id}`)
|
||||
- 分组没有密钥(或已不存在)时端点返回 `404`
|
||||
|
||||
## 自定义 Webhook
|
||||
|
||||
向 `POST /webhook/{groupId}`(或全局端点)POST 任意 JSON,并使用分组密钥将原始 body 的 HMAC-SHA256 以 `X-WebHooker-Signature: sha256=<hex>` 签名。载荷会变成 `custom` 事件,走正常的路由管线——创建一条 `event: custom` 的路由(控制台有模板),即可分发到该路由的目标、记录 `send_logs`,并出现在分组的 webhook 日志频道中。
|
||||
|
||||
### 重放防护
|
||||
|
||||
自定义 webhook 支持可选的防重放机制,在签名之外再附带两个请求头:
|
||||
|
||||
- `X-WebHooker-Timestamp` — 请求发送时的 Unix 秒数
|
||||
- `X-WebHooker-Nonce` — 每次请求唯一且不可预测的值(如 UUID)
|
||||
|
||||
当**同时**提供这两个请求头时,签名改为对 `{timestamp}.{nonce}.{原始body}` 计算(而非仅原始 body),且仅当以下条件满足时才被接受:
|
||||
|
||||
1. 时间戳与服务器时钟相差不超过 ±5 分钟(拒绝重放与时钟漂移滥用)
|
||||
2. nonce 从未被使用过(存入 KV 保留 10 分钟;重放的 nonce 会被拒绝)
|
||||
|
||||
```bash
|
||||
input="${timestamp}.${nonce}.${body}"
|
||||
signature="sha256=$(printf '%s' "$input" | openssl dgst -sha256 -hmac "$secret" -hex | sed 's/.*= //')"
|
||||
```
|
||||
|
||||
省略这些请求头时,WebHooker 回退到旧版的仅对 body 签名,现有发送方无需改动即可继续工作。
|
||||
|
||||
载荷模式:
|
||||
|
||||
```json
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue