From 916c8e777a549311a291b18b11c00a2b21915dca Mon Sep 17 00:00:00 2001 From: Allen Helton Date: Wed, 10 Jan 2024 15:46:53 -0600 Subject: [PATCH] update security page and move webhook index to an overview page --- docs/topics/develop/api-reference/webhooks.md | 2 +- .../validating-inbound-webhook-requests.md | 130 ----------------- docs/topics/webhooks/_category_.json | 9 ++ .../webhooks/{index.mdx => overview.mdx} | 13 +- docs/topics/webhooks/security.md | 136 ++++++++++++++++++ docusaurus.config.js | 4 + .../webhooks/{index.mdx => overview.mdx} | 2 +- .../current/topics/webhooks/security.md | 15 ++ static/img/webhook-secret-string.png | Bin 0 -> 23495 bytes 9 files changed, 173 insertions(+), 138 deletions(-) delete mode 100644 docs/topics/develop/guides/validating-inbound-webhook-requests.md create mode 100644 docs/topics/webhooks/_category_.json rename docs/topics/webhooks/{index.mdx => overview.mdx} (88%) create mode 100644 docs/topics/webhooks/security.md rename i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/{index.mdx => overview.mdx} (96%) create mode 100644 i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/security.md create mode 100644 static/img/webhook-secret-string.png diff --git a/docs/topics/develop/api-reference/webhooks.md b/docs/topics/develop/api-reference/webhooks.md index fcb7e6875..59ef73dbd 100644 --- a/docs/topics/develop/api-reference/webhooks.md +++ b/docs/topics/develop/api-reference/webhooks.md @@ -267,7 +267,7 @@ The `WebhookDestination` object contains the information about where the webhook
How do I validate incoming webhook requests? -Check out our dedicated guide on validating inbound webhook requests. +Check out our [webhook security page](./../../webhooks/security.md) on validating inbound webhook requests.
diff --git a/docs/topics/develop/guides/validating-inbound-webhook-requests.md b/docs/topics/develop/guides/validating-inbound-webhook-requests.md deleted file mode 100644 index 09568ce8d..000000000 --- a/docs/topics/develop/guides/validating-inbound-webhook-requests.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -sidebar_position: 7 -sidebar_label: Validating webhook requests -title: Validating webhook requests -description: Learn how to secure your endpoint accepting webhook requests with request signatures and verification. -pagination_next: null ---- - -# Validating Webhook requests - -Webhooks offer a powerful means to connect with a broader event-driven architecture (EDA) by enabling you to trigger any -POST API endpoint whenever a new message is published on a Momento Topic. While this capability is a powerful tool for developers, one of -the first questions many developers ask when building a webhook endpoint is “how do I guarantee that messages received -on this API are truly being sent by Momento?” - -Lets deep dive on request signing and how it can be used to verify both the sender and integrity of a message received. -But before we dive into [Momento webhooks](https://docs.momentohq.com/topics/webhooks) and how to validate messages, we -first need to talk about request signing in -general. - -## What is request signing? - -> Request signing is a security measure used in computer systems to verify the authenticity and integrity of a message -> or request. - -Request signing is commonly employed in web applications, APIs, and other distributed systems where communication occurs -between different components or services. It provides several security benefits, including: - -* **Authentication:** Ensures the sender of the request is who they claim to be. -* **Integrity:** Verifies that the request data has not been altered during transmission. -* **Non-repudiation:** The sender cannot deny sending the request, as the digital signature serves as proof of origin. - -## How to implement request signing - -1. **Sender generates a request**: - When a client or user initiates a request, the sender (client) generates a request message that includes information - such as the request method, headers, body, and other relevant details. -2. **Generate a signature** - The sender then creates a digital signature based on the request data. This signature is typically generated using a - secret key or some form of cryptographic algorithm. The secret key is known only to the sender and the receiver. -3. **Include the signature in the request** - The generated signature is appended to the original request, often in a specific header or as a query parameter. -4. **Send the signed request** - The signed request is sent to the recipient (server). -5. **Verification at the receiver's end** - Upon receiving the request, the recipient (server) performs the following steps: - Extracts the request data and the included signature. - Recalculates the signature using its own copy of the secret key and compares it to the received signature. - If the calculated signature matches the received signature, the request is considered authentic and has not been - tampered with during transit. - -## How does Momento Webhooks implement request signing? - -Now that we have a general idea of what request signing is and the benefits it provides, let's talk about how it is -implemented with Momento Webhooks, specifically addressing webhook security. - -Each webhook that gets created will get a unique signing secret. Using this signing secret, you can verify whether this -request came from Momento. Each HTTP request sent by Momento includes a momento-signature header. This signature is -created by combining the signing secret with the request body using a standard HMAC hash. - -### Obtaining your signing secret: - -When creating a new webhook using the `putWebhook` API in the Momento SDK, you receive a secret in the response. - -```typescript -const result = await webhookClient.putWebhook( - 'exampleCache', 'exampleWebhook', { - destination: new PostUrlWebhookDestination('https://example.com/webhook'), - topicName: 'exampleTopic', - }); -if (result instanceof PutWebhook.Success) { - console.log('Webhook created successfully. Secret:', result.secret); -} -``` - -You can also get a copy of the signing secret by calling `getWebhookSecret` API - -```typescript -const result = await webhookClient.getWebhookSecret( - 'exampleCache', - 'exampleWebhook' -); -if (result instanceof GetWebhookSecret.Success) { - console.log('Webhook secret retrieved successfully:', result.secret); -} -``` - -### Validating inbound request: - -1. Retrieve the momento-signature header from the request -2. Using HMAC SHA3-256, hash the request body using the signing secret associated with the webhook -3. Compare the computed signature to the momento-signature header on the request - -```typescript -import * as crypto from 'crypto'; - -const didRequestComeFromMomento = (req: Request): boolean => { - const hash = crypto.createHmac("SHA3-256", "the signing secret"); - const hashed = hash.update(req.rawBody).digest('hex'); - return hashed === req.headers['momento-signature']; -} -``` - -Momento also provides utility functions in certain [SDKs](https://docs.momentohq.com/topics/develop) to assist with the -request validation. For example, you could use -our [Javascript SDK](https://docs.momentohq.com/topics/develop/sdks/nodejs) to do the validation for you: - -```typescript -import {WebhookUtils} from '@gomomento/sdk'; - -const res = WebhookUtils.validateWebhookRequest({ - body: requestBody, - signature, - signingSecret, -}); - -if (res === WebhookUtils.RequestValidation.VALID) { - // request is valid -} else { - // request is invalid -} -``` - -## Closing Thoughts - -By incorporating signature validation into your webhook destination, you can ensure that the messages received originate -exclusively from Momento. This adds a layer of certainty by confirming that the message content aligns with the attached -signature. After you validate the request, you can confidently proceed with processing the message. Congratulations, you -genuinely verified your messages are from Momento, guarding against potential malicious attempts to spoof your endpoint -and enhancing webhook security. diff --git a/docs/topics/webhooks/_category_.json b/docs/topics/webhooks/_category_.json new file mode 100644 index 000000000..6842c1846 --- /dev/null +++ b/docs/topics/webhooks/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Webhooks", + "position": 2, + "collapsible": true, + "collapsed": true, + "link": { + "description": "Learn about Webhooks, an event-driven mechanism to listen to Momento Topics" + } + } diff --git a/docs/topics/webhooks/index.mdx b/docs/topics/webhooks/overview.mdx similarity index 88% rename from docs/topics/webhooks/index.mdx rename to docs/topics/webhooks/overview.mdx index 0ffa0f74f..cdec351f1 100644 --- a/docs/topics/webhooks/index.mdx +++ b/docs/topics/webhooks/overview.mdx @@ -3,8 +3,8 @@ title: Webhooks description: Learn about Webhooks, an event-driven mechanism to listen to Momento Topics image: https://assets-global.website-files.com/628fadb065a50abf13a11485/659580dc80a9699d3d1ae72a_webhooks-launch-blog-header.png hide_title: true -sidebar_position: 3 -sidebar_label: Webhooks +sidebar_position: 1 +sidebar_label: Overview pagination_prev: null keywords: - topics @@ -74,7 +74,7 @@ Message number for the topic. As messages are published, the `topic_sequence_num #### token_id -Unique identifier from a [token](./../develop/authentication/tokens.md). When a token is created via the Momento `AuthClient`, you can provide an identifier to be passed along all events published to Momento Topics. [Learn more](https://www.gomomento.com/blog/momento-topics-just-got-more-secure-introducing-embedded-token-identifiers). +Unique identifier from a [token](./topics/develop/authentication/tokens). When a token is created via the Momento `AuthClient`, you can provide an identifier to be passed along all events published to Momento Topics. [Learn more](https://www.gomomento.com/blog/momento-topics-just-got-more-secure-introducing-embedded-token-identifiers). #### text @@ -86,9 +86,10 @@ Each request includes two specific headers provided by Momento, enabling integra #### Momento-Signature -A signature of the request payload, signed with the secret you specified when setting up your webhook, used to verify the integrity and origin of the request. Check out our [dedicated guide](../topics/develop/guides/validating-inbound-webhook-requests) on validating inbound webhook requests. +A signature of the request payload, signed with the secret you specified when setting up your webhook, used to verify the integrity and origin of the request. Check out our [webhook security page](./security.md) on validating inbound requests. #### User-Agent + A static header sent by Momento to identify the source of the request, with the format `Momento/1.0 (Webhooks; +https://docs.momentohq.com/topics/webhooks)`. ## Event delivery @@ -100,11 +101,11 @@ A failed delivery, which will not be redriven automatically, could be due to one * A non-2XX response (e.g., 400, 403, 500, etc.). * No response after 5 seconds. * A connection error to your endpoint. -* Rate limit exceeded ([see default limits](./../limits.md)). +* Rate limit exceeded ([see default limits](./topics/limits)). ## API calls for Webhooks -Interested in using our SDKs to programmatically setup webhooks? See our [API reference page for Webhooks](./develop/api-reference/webhooks). +Interested in using our SDKs to programmatically setup webhooks? See our [API reference page for webhooks](./topics/develop/api-reference/webhooks). ## See More diff --git a/docs/topics/webhooks/security.md b/docs/topics/webhooks/security.md new file mode 100644 index 000000000..a96f5746c --- /dev/null +++ b/docs/topics/webhooks/security.md @@ -0,0 +1,136 @@ +--- +sidebar_position: 2 +sidebar_label: Security +title: Webhook Security +description: Learn how to secure your endpoint accepting webhook requests with request signatures and verification. +pagination_next: null +hide_title: true +keywords: + - momento + - webhooks + - security + - eda + - event-driven architectures + - serverless +--- + +# Secure your webhook endpoints + +After you successfully connect a webhook to Momento, it is recommended to validate incoming requests to guarantee the authenticity. Since your endpoint is publicly accessible, guaranteeing the sender is critical in protecting against bad actors. Authenticity and integrity of the requests can be validated in a couple of ways. + +## Request signing + +Request signing is a security measure used in software systems to verify the authenticity and integrity of a message. Prior to publishing, Momento signs webhook events with a signature contained in the request's `momento-signature` header. This signature uses your webhook's [signing secret](#signing-secret) and event request body to confirm authenticity. This allows you to verify the event originated from Momento, not from a 3rd party or malicious actor. + +### Verifying signatures with the Momento SDK + +The Momento [Node.js SDK](./../develop/sdks/nodejs) includes native support for verifying signatures. This is the recommended approach to verify signatures. To verify, provide the raw request body, the signature, and your signing secret. + +```javascript +import express from 'express'; +import { WebhookUtils } from '@gomomento/sdk'; +const app = express(); + +app.post('/', (req, res) => { + const response = WebhookUtils.validateWebhookRequest({ + body: req.rawBody, + signature: req.headers['momento-signature'], + signingSecret: process.env.SIGNING_SECRET + }); + + if (response === WebhookUtils.RequestValidation.VALID) { + // Request is valid. Continue processing. + } else { + res.status(403).send('Signature invalid'); + } +}); +``` + +### Verifying signatures manually + +For other languages, or if you prefer to verify the signature yourself, you can use an *HMAC SHA3-256* with your signing secret to validate the incoming request. + +```javascript +import * as crypto from 'crypto'; + +const didRequestComeFromMomento = (req, secret) => { + const hash = crypto.createHmac("SHA3-256", secret); + const hashed = hash.update(req.rawBody).digest('hex'); + return hashed === req.headers['momento-signature']; +}; +``` + +## User Agent + +In addition to the digital signature, all webhook events also receive a `User-Agent` header. This can be used to in conjunction with the signature to guarantee authenticity or used to divert the workflow for testing scenarios. This header is a static value that only changes on major version releases: + +`Momento/1.0 (Webhooks; +https://docs.momentohq.com/topics/webhooks)` + +The value is composed of the following pieces: + +* **Momento** - Indicates the origin is from Momento +* **1.0** - Major version number of the service +* **Webhooks** - States the specific origin from the request +* **+https://docs.momentohq.com/topics/webhooks** - Additional information about the webhook service can be found here + +:::warning + +Do not exlusively use this value to validate incoming requests. This header is easily spoofed and is not intended to verify authenticity on its own. + +::: + +### Running test scenarios + +Before bringing your app to production, you might want to test your webhook is in working order and you can verify requests without executing your production code. This is where the `User-Agent` header can be used to divert execution of your code. By creating a request, signing it yourself using the [manual verification code](#verifying-signatures-manually), and publishing to your endpoint with a different `User-Agent` header value, you can shortcut the execution after verifying the payload. + +```javascript +const MOMENTO_USER_AGENT = 'Momento/1.0 (Webhooks; +https://docs.momentohq.com/topics/webhooks)'; +const TEST_USER_AGENT = 'Test Harness/PostmanRuntime'; + +app.post('/', (req, res) => { + if(didRequestComeFromMomento(req, process.env.SIGNING_SECRET)){ + if(req.headers['User-Agent'] === MOMENTO_USER_AGENT){ + // Continue processing + } else if (req.headers['User-Agent'] === TEST_USER_AGENT) { + // This is a test request, return success + res.status(200).send('Skipping execution'); + } + } else { + res.status(403).send('Signature invalid'); + } +}); +``` + +## Signing secret + +When you create a new webhook in Momento you will be provided a signing secret. This secret value is used to digitally sign event payloads from Momento, allowing you to verify authenticity of a request. You can obtain the secret in either programmatically or via the [Momento console](https://console.gomomento.com); + +### Obtaining your signing secret programmatically + +When you create a new webhook with the [putWebhook API](./../develop/api-reference/webhooks#put-webhook-api), you will receive the signing secret as part of a successful response. If you lose the secret or forget to save it as part of the request, the [getWebhookSecret API](./../develop/api-reference/webhooks#get-webhook-secret-api) will return the current value. + +### Obtaining your signing secret via the Momento console + +If you prefer using the Momento console, you can always navigate to your webhook and click on the *copy* button next on the secret string row. + +![Webhook detail page in the Momento Console](@site/static/img/webhook-secret-string.png) + +To navigate to the webhook detail page, click on the cache of your choice on the [cache list page](https://console.gomomento.com/caches). Navigate to the *Webhooks* menu option and click on the webhook you wish to view. + +:::tip +When you first create a webhook you will be navigated to the webhook list page. To retrieve the signing secret, you must then click into the webhook and press the copy button. +::: + +## Considerations + +The verification information listed above is the minimum required to guarantee authenticity of an incoming request. However, further security best practices should also be considered when building your webhook. + +### Replay attacking + +If a valid webhook event is intercepted by a malicious actor and your endpoint implements sender verification only, you are susceptible to a *replay attack*. A replay attack occurs when a bad actor sends a valid request to your system repeatedly, forcing your system to process the same event multiple times. To prevent these attacks from harming your system, consider also validating the age of the event. Any event that comes in older than your allowed age threshold would be automatically discarded. + +You can use the [publish_timestamp](./index#publish_timestamp) property of the event to determine age. As best practice, consider rejecting events *older than 60 seconds*. + +### Regularly rotate secrets + +If your webhook signing secret is compromised, you cannot guarantee authenticity of a request. Consider rotating the secret periodically to ensure safety of your endpoints. You can rotate a secret using the [rotateWebhookSecret API](./../develop/api-reference/webhooks#rotate-webhook-secret-api). diff --git a/docusaurus.config.js b/docusaurus.config.js index 2fde3c151..5fca2d2d4 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -243,6 +243,10 @@ const config = { { from: '/develop/sdks/nodejs/topics-cheat-sheet', to: '/topics/develop/sdks/nodejs/cheat-sheet' + }, + { + from: '/topics/webhooks', + to: '/topics/webhooks/overview' } ], // This came in with v1.5.0 of the docs where we split out by service. diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/index.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/overview.mdx similarity index 96% rename from i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/index.mdx rename to i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/overview.mdx index 8f38ff2a9..abfb75948 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/index.mdx +++ b/i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/overview.mdx @@ -38,4 +38,4 @@ Slackへの投稿、データレイクへの送信、データベースエント ## WebhooksのAPIコール -[WebhooksのAPIリファレンスページ](./develop/api-reference/webhooks)を参照してください。 +[WebhooksのAPIリファレンスページ](./../develop/api-reference/webhooks)を参照してください。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/security.md b/i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/security.md new file mode 100644 index 000000000..750c6c7da --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/topics/webhooks/security.md @@ -0,0 +1,15 @@ +--- +sidebar_position: 2 +sidebar_label: Security +title: Webhook Security +description: Learn how to secure your endpoint accepting webhook requests with request signatures and verification. +pagination_next: null +hide_title: true +keywords: + - momento + - webhooks + - security + - eda + - event-driven architectures + - serverless +--- diff --git a/static/img/webhook-secret-string.png b/static/img/webhook-secret-string.png new file mode 100644 index 0000000000000000000000000000000000000000..9c3e8ea3dd53933861385fab510d5d2ab16c9047 GIT binary patch literal 23495 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