feat(webhook): HMAC-SHA256 signature verification on inbound webhooks
Adds an opt-in inbound HMAC scheme so a leaked URL alone is not enough
to forge deploy/sync requests — the caller must also know a separate
signing secret. Header format is X-Hub-Signature-256, matching the
Gitea/GitHub/GitLab convention so existing CI integrations work without
custom code.
Behaviour:
- per-project / per-site signing_secret is independent of the URL secret
- require_signature flag does a hard 401 on missing/invalid signatures
- even when require_signature is off, an *invalid* submitted signature
returns 401 — surfaces CI misconfiguration instead of silently passing
- comparison uses subtle/hmac.Equal (constant time)
Backend:
- store: webhook_signing_secret + webhook_require_signature columns on
projects + static_sites; scanProject helper, scan helpers updated; new
Set* helpers for both fields
- webhook/handler: verifyHMAC helper, body read once, integrated into
both project and site handlers
- api: per-entity signing-secret rotate / disable / require-toggle
endpoints under /api/{projects,sites}/{id}/webhook/...
Frontend:
- WebhookPanel gains optional signing handlers (no breaking change for
existing callers; signing UI hides when handlers aren't wired)
- one-shot reveal of the issued secret with copy + dismiss
- ToggleSwitch for require-signature, disabled until a secret is issued
- en/ru i18n strings
Tests:
- HMACRequiredAndValid (200 + deploy fires)
- HMACRequiredButMissing (401, no deploy)
- HMACPresentButWrong (401 even when require_signature=false)
- HMACOptionalUnsignedAccepted (200 when neither configured)
This commit is contained in:
@@ -1184,7 +1184,26 @@
|
||||
"regenerateWarning": "Перегенерация инвалидирует текущий URL. Обновите CI-пайплайны и Git-вебхуки, использующие его.",
|
||||
"confirmRegenerate": "Заменить текущий URL?",
|
||||
"confirmYes": "Перегенерировать",
|
||||
"confirmNo": "Отмена"
|
||||
"confirmNo": "Отмена",
|
||||
"signingTitle": "Подпись входящих вебхуков (HMAC)",
|
||||
"signingDesc": "Проверка подписи HMAC-SHA256 — утечка только URL не позволит подделать запрос. Совместимо с секретами вебхуков Gitea/GitHub.",
|
||||
"signingActive": "Секрет подписи настроен.",
|
||||
"signingInactive": "Секрет подписи не задан — входящие запросы не проверяются помимо URL.",
|
||||
"signingIssue": "Сгенерировать секрет",
|
||||
"signingRotate": "Перевыпустить секрет",
|
||||
"signingDisable": "Отключить подпись",
|
||||
"signingDisableConfirm": "Отключить",
|
||||
"signingIssued": "Новый секрет подписи выпущен — скопируйте его сейчас",
|
||||
"signingIssueFailed": "Не удалось сгенерировать секрет подписи",
|
||||
"signingDisabled": "Подпись отключена",
|
||||
"signingDisableFailed": "Не удалось отключить подпись",
|
||||
"signingShownOnce": "Скопируйте секрет сейчас — он больше не будет показан.",
|
||||
"signingDismiss": "Скрыть",
|
||||
"signingHint": "Используйте это значение как webhook-секрет в Gitea/GitHub/GitLab. Tinyforge ожидает заголовок {header}.",
|
||||
"signingCopied": "Секрет подписи скопирован в буфер обмена",
|
||||
"requireSignature": "Требовать подпись",
|
||||
"requireSignatureHelp": "Отклонять запросы без действительной подписи. Сначала сгенерируйте секрет.",
|
||||
"signingRequireFailed": "Не удалось обновить требование подписи"
|
||||
},
|
||||
"outgoingWebhook": {
|
||||
"signingOn": "Подпись включена",
|
||||
|
||||
Reference in New Issue
Block a user