From 52c98f939c1efdb3daa01ac461c96784795de8c7 Mon Sep 17 00:00:00 2001 From: brad-dow <162852233+brad-dow@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:25:02 -0500 Subject: [PATCH 1/4] docs: add info for webhook signature validation Adding content for optional but recommended webhook signature validation --- .../requirements/webhook-events.mdx | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx index 6140fc887e..75b3948894 100644 --- a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx @@ -139,6 +139,64 @@ We provide an Bruno collection that can be used to test your webhook service integration. +## Verify webhook signatures + +To protect your endpoint from unauthorized or spoofed requests, Rafiki supports an optional, but highly recommended, webhook signature verification process. By enabling signature verification, you can ensure that webhook requests are genuinely from Rafiki. + +Each webhook request includes a `Rafiki-Signature` header with a timestamp and signature digest. If you instance is configured with a `SIGNATURE_SECRET` environment variable, you can verify the authenticity of each webhook request using the steps below. + +### Extract the timestamp and signature from the header + +The `Rafiki-Signature` header in each webhook request has the following format: + + + +``` +Rafiki-Signature: t=, v= +``` + + + +- `t=`: The UNIX timestamp (in seconds) when the signature was generated. +- `v=`: The versioned HMAC SHA-256 signature digest. The default version is `v1`. + +### Prepare the signed payload string + +To recreate the signed payload string, concatenate the following. +- The timestamp extracted from the header +- A period (.) character +- The actual JSON payload from the request body, containing the `id`, `type`, and `data` attributes + +This string format is essential for accurate signature validation. + +### Generate the expected signature + +Use HMAC SHA-256 with the `SIGNATURE_SECRET` environment variable as the key and the signed payload string as the message. + +### Compare the signatures + +Finally, compare the signature in the header to the expected signature you generated. For security, use a constant-time comparison function to prevent timing attacks. Also, check the timestamp to ensure that it is within the allowed TTL (configured in the `ADMIN_API_SIGNATURE_TTL_SECONDS` environment variable) to ensure freshness. + +### Example +Below is an example in `` to verify Rafiki's webhook signature: + + + +``` +// Really cool code example in some commonly used language (JavaScript, Python?) + +// Extract timestamp and signatures from header + +// Prepare the signed payload string + +// Generate the expected signature + +// Compare the signatures and check the timestamp + +``` + + + ## Event handling ### Asynchronous handling From 52ae073054649a6713ff397db4fe942be8129102 Mon Sep 17 00:00:00 2001 From: brad-dow <162852233+brad-dow@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:29:50 -0600 Subject: [PATCH 2/4] Integrating Max's feedback Refined content based on feedback from Max Added JavaScript example --- .../requirements/webhook-events.mdx | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx index 75b3948894..4d0930df91 100644 --- a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx @@ -143,7 +143,7 @@ Additionally, the [local playground](/integration/playground/overview) contains To protect your endpoint from unauthorized or spoofed requests, Rafiki supports an optional, but highly recommended, webhook signature verification process. By enabling signature verification, you can ensure that webhook requests are genuinely from Rafiki. -Each webhook request includes a `Rafiki-Signature` header with a timestamp and signature digest. If you instance is configured with a `SIGNATURE_SECRET` environment variable, you can verify the authenticity of each webhook request using the steps below. +Each webhook request includes a `Rafiki-Signature` header with a timestamp, version, and signature digest. If you instance is configured with both the `SIGNATURE_SECRET` (to generate the signature) and the `SIGNATURE_VERSION` (to set the version, defaults to v1) environment variables, you can verify the authenticity of each webhook request using the steps below. ### Extract the timestamp and signature from the header @@ -175,24 +175,29 @@ Use HMAC SHA-256 with the `SIGNATURE_SECRET` environment variable as the key and ### Compare the signatures -Finally, compare the signature in the header to the expected signature you generated. For security, use a constant-time comparison function to prevent timing attacks. Also, check the timestamp to ensure that it is within the allowed TTL (configured in the `ADMIN_API_SIGNATURE_TTL_SECONDS` environment variable) to ensure freshness. +Finally, compare the signature in the header to the expected signature you generated. For security, use a constant-time comparison function to prevent timing attacks. ### Example -Below is an example in `` to verify Rafiki's webhook signature: +Below is an example in JavaScript to verify Rafiki's webhook signature: -``` -// Really cool code example in some commonly used language (JavaScript, Python?) - -// Extract timestamp and signatures from header - -// Prepare the signed payload string - -// Generate the expected signature - -// Compare the signatures and check the timestamp - +```js +function verifyWebhookSignature(request: Request): boolean { + const signatureParts = request.headers['Rafiki-Signature'].split(', ') + const timestamp = signatureParts[0].split('=')[1] + const signatureVersionAndDigest = signatureParts[1].split('=') + const signatureVersion = signatureVersionAndDigest[0].replace('v', '') + const signatureDigest = signatureVersionAndDigest[1] + if (signatureVersion !== config['SIGNATURE_VERSION']) { + return false + } + const payload = `${timestamp}.${canonicalize(request.body)}` + const hmac = createHmac('sha256', config['SIGNATURE_SECRET']) + hmac.update(payload) + const digest = hmac.digest('hex') + return digest === signatureDigest +} ``` From 3b71af35900ff3312632ed0e9579b9458168e148 Mon Sep 17 00:00:00 2001 From: brad-dow <162852233+brad-dow@users.noreply.github.com> Date: Thu, 21 Nov 2024 08:17:59 -0600 Subject: [PATCH 3/4] Update packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx Co-authored-by: Max Kurapov --- .../content/docs/integration/requirements/webhook-events.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx index 4d0930df91..ab38e3fd49 100644 --- a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx @@ -143,7 +143,7 @@ Additionally, the [local playground](/integration/playground/overview) contains To protect your endpoint from unauthorized or spoofed requests, Rafiki supports an optional, but highly recommended, webhook signature verification process. By enabling signature verification, you can ensure that webhook requests are genuinely from Rafiki. -Each webhook request includes a `Rafiki-Signature` header with a timestamp, version, and signature digest. If you instance is configured with both the `SIGNATURE_SECRET` (to generate the signature) and the `SIGNATURE_VERSION` (to set the version, defaults to v1) environment variables, you can verify the authenticity of each webhook request using the steps below. +Each webhook request includes a `Rafiki-Signature` header with a timestamp, version, and signature digest. If your instance is configured with both the `SIGNATURE_SECRET` (to generate the signature) and the `SIGNATURE_VERSION` (to set the version, defaults to v1) environment variables, you can verify the authenticity of each webhook request using the steps below. ### Extract the timestamp and signature from the header From cc18ff0322ac95792ce888dfcd6671a9a3b2cf07 Mon Sep 17 00:00:00 2001 From: brad-dow <162852233+brad-dow@users.noreply.github.com> Date: Thu, 21 Nov 2024 08:18:07 -0600 Subject: [PATCH 4/4] Update packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx Co-authored-by: Max Kurapov --- .../content/docs/integration/requirements/webhook-events.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx index ab38e3fd49..9a34ce9ae8 100644 --- a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx +++ b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx @@ -141,7 +141,7 @@ Additionally, the [local playground](/integration/playground/overview) contains ## Verify webhook signatures -To protect your endpoint from unauthorized or spoofed requests, Rafiki supports an optional, but highly recommended, webhook signature verification process. By enabling signature verification, you can ensure that webhook requests are genuinely from Rafiki. +To protect your endpoint from unauthorized or spoofed requests, Rafiki supports an optional, but highly recommended, webhook signature verification process. By enabling signature verification, you can ensure that webhook requests are genuinely from Rafiki, and have not been tampered with. Each webhook request includes a `Rafiki-Signature` header with a timestamp, version, and signature digest. If your instance is configured with both the `SIGNATURE_SECRET` (to generate the signature) and the `SIGNATURE_VERSION` (to set the version, defaults to v1) environment variables, you can verify the authenticity of each webhook request using the steps below.