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:
RhenCloud 2026-08-15 14:39:41 +08:00
parent 92ba2203a5
commit 737dd7af98
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
15 changed files with 378 additions and 26 deletions

View file

@ -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 安装事件

View file

@ -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