From 0164c8cb4eebec5423d1b36ca09f6de6e5e8e448 Mon Sep 17 00:00:00 2001 From: CTO Date: Thu, 1 Oct 2026 04:45:44 +0000 Subject: [PATCH] fix(velero-drill): switch webhook from bearer to github_hmac (DEV-590) The 2026-10-01 monthly drill passed but the post-run webhook got HTTP 401 from the home Paperclip. Root cause: the home server's global actorMiddleware (/app/server/src/middleware/auth.ts) rejects every Authorization: Bearer <...> that is not a board API key or an agent JWT before per-route handlers run, so the trigger's own `bearer` signingMode handler is unreachable with a 48-hex random secret. Only publicMcpGatewayProtocolPath is exempted. Fix: - Trigger 6983798e-018c-459d-95ea-4b5959441e2d switched to signingMode: github_hmac via PATCH (secret unchanged). - post_webhook now computes HMAC-SHA256(body, secret) with openssl (already in alpine/k8s) and sends X-Hub-Signature-256 instead of Authorization: Bearer. HMAC headers bypass the Bearer branch of the global middleware entirely. Verified end-to-end: synthetic HMAC-signed body posted to .../fire returned 202 issue_created and spawned DEV-591 under parent DEV-578. SealedSecret is unchanged (same secret value reused as the HMAC key). Co-Authored-By: Paperclip --- velero/restore-drill/10-drill-script-configmap.yaml | 9 ++++++++- velero/restore-drill/README.md | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/velero/restore-drill/10-drill-script-configmap.yaml b/velero/restore-drill/10-drill-script-configmap.yaml index 6998cb7..03d7362 100644 --- a/velero/restore-drill/10-drill-script-configmap.yaml +++ b/velero/restore-drill/10-drill-script-configmap.yaml @@ -58,9 +58,16 @@ data: printf '{"source":"velero-restore-drill","status":"%s","timestamp":"%s","backup":"%s","restore":"%s","restore_phase":"%s","restore_duration_s":"%s","target_namespace":"%s","source_namespace":"%s","diff_summary":"%s"}\n' \ "${status}" "${TS}" "${backup}" "${restore}" "${restore_phase}" \ "${restore_duration_s}" "${target_ns}" "${source_ns}" "${diff_summary}" > "${body_file}" + # DEV-590: trigger is in github_hmac signingMode. Compute HMAC-SHA256 over + # the raw body and send X-Hub-Signature-256; sending Authorization: Bearer + # here is a trap because the home Paperclip's global actor middleware + # rejects any Bearer header that is not a board API key or agent JWT + # before the per-trigger handler runs. + local sig + sig=$(openssl dgst -sha256 -hmac "${PAPERCLIP_WEBHOOK_SECRET}" -r "${body_file}" | awk '{print $1}') local hc hc=$(curl -sS -o /tmp/webhook.out -w '%{http_code}' -m 30 \ - -X POST -H "Authorization: Bearer ${PAPERCLIP_WEBHOOK_SECRET}" \ + -X POST -H "X-Hub-Signature-256: sha256=${sig}" \ -H "Content-Type: application/json" \ --data-binary "@${body_file}" \ "${PAPERCLIP_WEBHOOK_URL}" 2>&1) || hc="000" diff --git a/velero/restore-drill/README.md b/velero/restore-drill/README.md index b2a7cc2..024dec7 100644 --- a/velero/restore-drill/README.md +++ b/velero/restore-drill/README.md @@ -27,6 +27,14 @@ Velero cluster-reconstruction backup layer. the `velero-restore-drill-webhook` Secret is present. The CronJob uses a `hostAliases` entry to route `paperclip.home.imicros.de` at the home public IP so the webhook reaches the routine from Hetzner (DEV-579). + Authentication is `github_hmac` — the script computes an HMAC-SHA256 + of the raw body with the webhook secret as key and sends it as + `X-Hub-Signature-256: sha256=`. The matching trigger `signingMode` + is `github_hmac` on routine `c9d941fa-fb97-4e6a-a305-8e46f296e0cd`. + Previously it was `bearer`, but the home Paperclip's global actor + middleware rejects any `Authorization: Bearer …` whose value is not a + board API key or an agent JWT before the per-trigger handler runs, + making the bearer path unreachable (DEV-590). ## Failure signalling