From aebd7ea612e73e7bcab41576de8661d7f209dec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 25 Oct 2026 16:00:03 +0100 Subject: [PATCH] docs(deploy): describe the deploy webhook's signature and strict mode The compose comment still called DEPLOY_WEBHOOK_REQUIRE_TIMESTAMP a requirement for a fresh timestamp header, which the route stopped accepting in strict mode once requests could be signed, and the env manifest named only the secret header and lacked the strict switch. Both now say what the route checks: the secret header, which has no replay protection, or X-Deploy-Webhook-Signature over the timestamp and body, and that the strict setting takes signed requests only. The development example says the same and points at the deploy runbook instead of a page that no longer exists. --- .env.example | 14 ++++++++++---- docker-compose.yml | 18 ++++++++++-------- scripts/env-manifest.json | 7 ++++++- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/.env.example b/.env.example index b398a7662..8ba3be7d0 100644 --- a/.env.example +++ b/.env.example @@ -361,11 +361,17 @@ API_TOKEN_HMAC_KEY="REPLACE-WITH-OUTPUT-OF-openssl-rand-hex-32" # Auto-deploy webhook (v1.4.15 — phase C2) # ----------------------------------------------------------------------------- # Coolify (or any CI) pings POST /api/internal/deploy-webhook after a deploy -# attempt with success/failure. We verify the request via this shared secret -# carried in the `X-Deploy-Webhook-Secret` header (timing-safe compare). -# Generate with `openssl rand -hex 32`. Configure the SAME value in Coolify's -# Notifications → Webhook custom-headers panel. See docs/audit/v1415-auto-deploy.md. +# attempt with success/failure. A request authenticates with this shared +# secret in the `X-Deploy-Webhook-Secret` header (timing-safe compare; what +# Coolify's stock sender does, with no replay protection), or by signing: +# `X-Deploy-Webhook-Signature: sha256=`, the HMAC-SHA256 under this +# secret of the `X-Deploy-Webhook-Timestamp` value, a dot and the raw body, +# the timestamp within 5 minutes. Generate with `openssl rand -hex 32`. +# Configure the SAME value in Coolify's Notifications → Webhook +# custom-headers panel. See docs/ops/deploy.md. # DEPLOY_WEBHOOK_SECRET="" +# "true" accepts signed requests only; leave unset for Coolify's stock sender. +# DEPLOY_WEBHOOK_REQUIRE_TIMESTAMP="" # ----------------------------------------------------------------------------- diff --git a/docker-compose.yml b/docker-compose.yml index 957177d69..c73d135f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -299,16 +299,18 @@ x-healthlog-env: &healthlog_env OIDC_SCOPES: "${OIDC_SCOPES:-}" OIDC_BUTTON_LABEL: "${OIDC_BUTTON_LABEL:-}" OIDC_ONLY: "${OIDC_ONLY:-}" - # Deploy-result webhook (optional). DEPLOY_WEBHOOK_SECRET authenticates - # POST /api/internal/deploy-webhook; DEPLOY_LOGS_URL is the - # deployment-dashboard URL (e.g. the Coolify UI) linked in - # deploy-failure notifications. Listed here so operator values in - # .env reach the container (the `environment:` block is a whitelist). + # Deploy-result webhook (optional). POST /api/internal/deploy-webhook + # authenticates either with DEPLOY_WEBHOOK_SECRET in the + # X-Deploy-Webhook-Secret header (what Coolify's stock sender can do; no + # replay protection) or with X-Deploy-Webhook-Signature: sha256=." under the secret>, the timestamp + # within 5 minutes. DEPLOY_WEBHOOK_REQUIRE_TIMESTAMP=true accepts signed + # requests only. DEPLOY_LOGS_URL is the deployment-dashboard URL (e.g. the + # Coolify UI) linked in deploy-failure notifications. Listed here so + # operator values in .env reach the container (the `environment:` block is + # a whitelist). See docs/ops/deploy.md. DEPLOY_WEBHOOK_SECRET: "${DEPLOY_WEBHOOK_SECRET:-}" DEPLOY_LOGS_URL: "${DEPLOY_LOGS_URL:-}" - # Optional replay hardening: when "true", the deploy webhook - # requires a fresh X-Deploy-Webhook-Timestamp header (±5 min). - # See docs/ops/deploy.md. DEPLOY_WEBHOOK_REQUIRE_TIMESTAMP: "${DEPLOY_WEBHOOK_REQUIRE_TIMESTAMP:-}" # TLS leaf SPKI-change alarm baseline (optional). The native client # pins the served TLS leaf; this is the known-good pin set the monitor diff --git a/scripts/env-manifest.json b/scripts/env-manifest.json index 9d7f38d9c..75339c7fd 100644 --- a/scripts/env-manifest.json +++ b/scripts/env-manifest.json @@ -260,11 +260,16 @@ "variables": [ { "name": "DEPLOY_WEBHOOK_SECRET", - "purpose": "Shared secret for the X-Deploy-Webhook-Secret header on POST /api/internal/deploy-webhook. 64 hex chars." + "purpose": "Shared secret of POST /api/internal/deploy-webhook. 64 hex chars. A request authenticates by sending it in the X-Deploy-Webhook-Secret header (Coolify's stock sender; no replay protection), or by signing: X-Deploy-Webhook-Signature: sha256=, the timestamp within 5 minutes." }, { "name": "DEPLOY_LOGS_URL", "purpose": "URL of the deployment dashboard (e.g. the Coolify UI) linked in deploy-failure notifications. Optional; when unset the notification omits the link." + }, + { + "name": "DEPLOY_WEBHOOK_REQUIRE_TIMESTAMP", + "purpose": "\"true\" (or \"1\") accepts only signed deploy-webhook requests (X-Deploy-Webhook-Signature with a fresh X-Deploy-Webhook-Timestamp); the secret header alone is refused with 401. Leave unset for Coolify's stock sender, which can only send the secret header.", + "example": "true" } ] },