From 497fdd8c064e3b81b8ab3acd073107895c0747cb Mon Sep 17 00:00:00 2001 From: Nathan Totten Date: Tue, 29 Jul 2025 18:27:55 -0400 Subject: [PATCH 1/3] Added local quickstart to sidebar --- docs/articles/add-api-to-backstage.md | 2 +- docs/articles/api-key-management.md | 4 +- docs/articles/source-control.md | 2 +- ...d => step-1-setup-basic-gateway-local.mdx} | 9 +- ...eway.md => step-1-setup-basic-gateway.mdx} | 7 +- ...miting.md => step-2-add-rate-limiting.mdx} | 7 +- ...ey-auth.md => step-3-add-api-key-auth.mdx} | 9 +- .../articles/step-4-deploying-to-the-edge.mdx | 11 +- ...ng.md => step-5-dynamic-rate-limiting.mdx} | 14 +- docs/articles/what-is-zuplo.md | 4 +- docs/dedicated/architecture.md | 2 +- .../dev-portal/dev-portal-supabase-auth.md | 2 +- sidebar.ts | 13 +- sidebar.zudoku.json | 8 ++ src/QuickstartPicker.tsx | 33 +++++ src/TutorialModeSelector.tsx | 127 ++++++++++++++++++ src/components.tsx | 5 + 17 files changed, 234 insertions(+), 25 deletions(-) rename docs/articles/{step-1-setup-basic-gateway-local.md => step-1-setup-basic-gateway-local.mdx} (96%) rename docs/articles/{step-1-setup-basic-gateway.md => step-1-setup-basic-gateway.mdx} (97%) rename docs/articles/{step-2-add-rate-limiting.md => step-2-add-rate-limiting.mdx} (94%) rename docs/articles/{step-3-add-api-key-auth.md => step-3-add-api-key-auth.mdx} (97%) rename docs/articles/{step-5-dynamic-rate-limiting.md => step-5-dynamic-rate-limiting.mdx} (90%) create mode 100644 src/QuickstartPicker.tsx create mode 100644 src/TutorialModeSelector.tsx diff --git a/docs/articles/add-api-to-backstage.md b/docs/articles/add-api-to-backstage.md index d7d1dd5d8..01ac6ac00 100644 --- a/docs/articles/add-api-to-backstage.md +++ b/docs/articles/add-api-to-backstage.md @@ -18,7 +18,7 @@ Add a new route with the path `/openapi`, and select the `OpenAPI Spec Handler` from the Request Handler selector. Save your changes and commit them to your production branch. If you haven't already connected your Zuplo API to a GitHub repository, you can follow -[these instructions](./step-4-deploying-to-the-edge.md) to do so. Once your +[these instructions](./step-4-deploying-to-the-edge.mdx) to do so. Once your Zuplo API is redeployed, you should now be able to retrieve your public-ready OpenAPI file by hitting `https:///openapi`. diff --git a/docs/articles/api-key-management.md b/docs/articles/api-key-management.md index 6302ffc61..06011d5c3 100644 --- a/docs/articles/api-key-management.md +++ b/docs/articles/api-key-management.md @@ -15,7 +15,7 @@ minutes. There are several benefits to using Zuplo's API Key solution including :::tip To start using Zuplo API Keys in only a few minutes -[see the quickstart](../articles/step-3-add-api-key-auth.md). +[see the quickstart](../articles/step-3-add-api-key-auth.mdx). ::: @@ -42,7 +42,7 @@ Zuplo manages all the complexity of replication, caching, and verifying your API keys so you don't have to. Adding API Key authentication using Zuplo takes only a few minutes. -[See the quickstart to get started](../articles/step-3-add-api-key-auth.md). +[See the quickstart to get started](../articles/step-3-add-api-key-auth.mdx). ## Key Concepts diff --git a/docs/articles/source-control.md b/docs/articles/source-control.md index 8ad6a6115..99f6ba911 100644 --- a/docs/articles/source-control.md +++ b/docs/articles/source-control.md @@ -27,7 +27,7 @@ benefits, including: adding a branch or making a commit. For full instructions on setting up GitHub see -[the quickstart tutorial](../articles/step-4-deploying-to-the-edge.md). +[the quickstart tutorial](../articles/step-4-deploying-to-the-edge.mdx). ## Connecting Existing Repositories diff --git a/docs/articles/step-1-setup-basic-gateway-local.md b/docs/articles/step-1-setup-basic-gateway-local.mdx similarity index 96% rename from docs/articles/step-1-setup-basic-gateway-local.md rename to docs/articles/step-1-setup-basic-gateway-local.mdx index f927aca34..5df5251c6 100644 --- a/docs/articles/step-1-setup-basic-gateway-local.md +++ b/docs/articles/step-1-setup-basic-gateway-local.mdx @@ -1,7 +1,12 @@ --- -title: Step 1 - Setup a Basic Gateway (Local) +title: Step 1 - Setup a Basic Gateway --- + + In this tutorial we'll setup a simple gateway using Zuplo's local development. We'll use a simple origin API at [echo.zuplo.io](https://echo.zuplo.io). In later steps, we'll setup a Zuplo project and deploy it to the cloud. @@ -122,4 +127,4 @@ later steps, we'll setup a Zuplo project and deploy it to the cloud. **NEXT** Try -[Step 2 - Add Rate Limiting to your API](./step-2-add-rate-limiting.md). +[Step 2 - Add Rate Limiting to your API](./step-2-add-rate-limiting.mdx). diff --git a/docs/articles/step-1-setup-basic-gateway.md b/docs/articles/step-1-setup-basic-gateway.mdx similarity index 97% rename from docs/articles/step-1-setup-basic-gateway.md rename to docs/articles/step-1-setup-basic-gateway.mdx index ca028feb2..c3805888d 100644 --- a/docs/articles/step-1-setup-basic-gateway.md +++ b/docs/articles/step-1-setup-basic-gateway.mdx @@ -3,6 +3,11 @@ title: Step 1 - Setup a Basic Gateway sidebar_label: "Step 1 - Setup Your Gateway" --- + + In this tutorial we'll setup a simple gateway. We'll use a simple origin API at [getting-started.zuplo.io](https://getting-started.zuplo.io). @@ -109,4 +114,4 @@ Note - Zuplo also supports building and running your API locally. To learn more **NEXT** Try -[Step 2 - Add Rate Limiting to your API](./step-2-add-rate-limiting.md). +[Step 2 - Add Rate Limiting to your API](./step-2-add-rate-limiting.mdx). diff --git a/docs/articles/step-2-add-rate-limiting.md b/docs/articles/step-2-add-rate-limiting.mdx similarity index 94% rename from docs/articles/step-2-add-rate-limiting.md rename to docs/articles/step-2-add-rate-limiting.mdx index 42e7aac9b..26245ece5 100644 --- a/docs/articles/step-2-add-rate-limiting.md +++ b/docs/articles/step-2-add-rate-limiting.mdx @@ -3,8 +3,13 @@ title: Step 2 - Add Rate Limiting sidebar_label: "Step 2 - Rate Limiting" --- + + In this guide we'll add simple Rate Limiting to a route. If you don't have one -ready, complete [Step 1](./step-1-setup-basic-gateway.md) first. +ready, complete [Step 1](./step-1-setup-basic-gateway.mdx) first. Rate Limiting is one of our most popular **policies** - you should never ship an API without rate limiting because your customers or internal developers **will** diff --git a/docs/articles/step-3-add-api-key-auth.md b/docs/articles/step-3-add-api-key-auth.mdx similarity index 97% rename from docs/articles/step-3-add-api-key-auth.md rename to docs/articles/step-3-add-api-key-auth.mdx index 3d1dfed52..a5d69de6d 100644 --- a/docs/articles/step-3-add-api-key-auth.md +++ b/docs/articles/step-3-add-api-key-auth.mdx @@ -3,9 +3,14 @@ title: Step 3 - API Key Authentication sidebar_label: "Step 3 - API Key Auth" --- + + In this guide we'll add API Key authentication to a route. You can do this for any Zuplo project but will need a route, consider completing -[Step 1](./step-1-setup-basic-gateway.md) first. +[Step 1](./step-1-setup-basic-gateway.mdx) first. API Key authentication is one of our most popular **policies** as implementing this authentication method is considered one of the easiest to use by developers @@ -45,7 +50,7 @@ Let's get started. :::tip The API key authentication policy should usually be one of the first policies - executed. If you came here from [Step 2](./step-2-add-rate-limiting.md) then + executed. If you came here from [Step 2](./step-2-add-rate-limiting.mdx) then you will want to drag it above the rate limiting policy. ::: diff --git a/docs/articles/step-4-deploying-to-the-edge.mdx b/docs/articles/step-4-deploying-to-the-edge.mdx index 1a0d84e19..54c031cbb 100644 --- a/docs/articles/step-4-deploying-to-the-edge.mdx +++ b/docs/articles/step-4-deploying-to-the-edge.mdx @@ -3,6 +3,11 @@ title: Step 4 - Deploying to the Edge sidebar_label: "Step 4 - Deploy" --- + + In this guide we'll show you how to deploy your gateway to the edge, at over 300 data-centers around the world. The act of deployment creates new [environments](./environments) and it's worth familiarizing yourself with @@ -16,9 +21,9 @@ also support GitLab, BitBucket To follow this tutorial you'll need - a GitHub account (it's free, sign up at [github.com](https://github.com)). -- a zuplo project - complete [Step 1](./step-1-setup-basic-gateway.md), - [Step 2](./step-2-add-rate-limiting.md) and - [Step 3](./step-3-add-api-key-auth.md) for a great start! +- a zuplo project - complete [Step 1](./step-1-setup-basic-gateway.mdx), + [Step 2](./step-2-add-rate-limiting.mdx) and + [Step 3](./step-3-add-api-key-auth.mdx) for a great start! - to install the [Zuplo GitHub deployment](https://github.com/apps/zuplo/installations/new) to GitHub - you can diff --git a/docs/articles/step-5-dynamic-rate-limiting.md b/docs/articles/step-5-dynamic-rate-limiting.mdx similarity index 90% rename from docs/articles/step-5-dynamic-rate-limiting.md rename to docs/articles/step-5-dynamic-rate-limiting.mdx index 7474b3111..6019e9f52 100644 --- a/docs/articles/step-5-dynamic-rate-limiting.md +++ b/docs/articles/step-5-dynamic-rate-limiting.mdx @@ -6,9 +6,9 @@ Fortune favors the bold. In this bonus getting started guide - we'll show you how to add dynamic rate limiting to your API. To follow this tutorial you'll need to have completed -[Step 1](./step-1-setup-basic-gateway.md) for a Zuplo project, -[Step 2](./step-2-add-rate-limiting.md) to add rate limiting to that route, and -[Step 3](./step-3-add-api-key-auth.md) to add API key authentication to that +[Step 1](./step-1-setup-basic-gateway.mdx) for a Zuplo project, +[Step 2](./step-2-add-rate-limiting.mdx) to add rate limiting to that route, and +[Step 3](./step-3-add-api-key-auth.mdx) to add API key authentication to that same route. :::info{title="What is Dynamic Rate Limiting?"} @@ -27,9 +27,9 @@ Let's get started. 1. Add Consumer Metadata Let's make our rate-limiting policy more dynamic, based on properties of the - customer. [Create a new consumer](./step-3-add-api-key-auth) (Services -> API - Key Service -> Configure -> Create Consumer), and in the Metadata field, set - the following: + customer. [Create a new consumer](./step-3-add-api-key-auth.mdx) (Services -> + API Key Service -> Configure -> Create Consumer), and in the Metadata field, + set the following: ```json { @@ -137,7 +137,7 @@ Let's get started. Using the Test modal, you can try your dynamic rate limiting. You can grab the key for each consumer back in the Services tab where you created them. - Like [Step 3](./step-3-add-api-key-auth.md), you can fill in the keys into + Like [Step 3](./step-3-add-api-key-auth.mdx), you can fill in the keys into the `Authorization` header and start making calls until you hit your rate limit. Try out the other key and observe the difference in rate limits. diff --git a/docs/articles/what-is-zuplo.md b/docs/articles/what-is-zuplo.md index 8451ea423..33a7bea58 100644 --- a/docs/articles/what-is-zuplo.md +++ b/docs/articles/what-is-zuplo.md @@ -7,8 +7,8 @@ developers. It offers fast deployment, GitOps-friendly workflows, and unlimited preview environments. Whether you're an individual developer or part of an engineering team, Zuplo makes it easy to: -- [Add authentication and access control](./step-3-add-api-key-auth.md) -- [Implement rate limiting](./step-2-add-rate-limiting.md) +- [Add authentication and access control](./step-3-add-api-key-auth.mdx) +- [Implement rate limiting](./step-2-add-rate-limiting.mdx) - Write custom logic to run at the gateway layer - Build a [rich developer portal](/docs/dev-portal/introduction) with self-serve tools for auth and monetization diff --git a/docs/dedicated/architecture.md b/docs/dedicated/architecture.md index 549a81861..dc27e1300 100644 --- a/docs/dedicated/architecture.md +++ b/docs/dedicated/architecture.md @@ -19,7 +19,7 @@ A managed dedicated instance of Zuplo consists of the following components: rate limiting, and other features. - **Gateway Services**: Zuplo, being a highly distributed API Gateway, uses services to facilitate features such as - [Rate Limiting](../articles/step-2-add-rate-limiting.md) and + [Rate Limiting](../articles/step-2-add-rate-limiting.mdx) and [API Key Authentication](../articles/api-key-management.md). - **Control Plane**: The Control Plane manages the configuration of the API Gateway. It deploys new configurations, manages the lifecycle of the API diff --git a/docs/legacy/dev-portal/dev-portal-supabase-auth.md b/docs/legacy/dev-portal/dev-portal-supabase-auth.md index 2504a9f05..4347a56da 100644 --- a/docs/legacy/dev-portal/dev-portal-supabase-auth.md +++ b/docs/legacy/dev-portal/dev-portal-supabase-auth.md @@ -515,5 +515,5 @@ some steps to try next: your Supabase Auth App. [Get started with our Builder plan today](https://zuplo.com/pricing)! - Want to get more out of your Developer Portal? You can setup - [API Key Authentication](../../articles/step-3-add-api-key-auth.md) to allow + [API Key Authentication](../../articles/step-3-add-api-key-auth.mdx) to allow your new users to manage their keys in the Developer Portal directly! diff --git a/sidebar.ts b/sidebar.ts index 53943ca2e..91846ab72 100644 --- a/sidebar.ts +++ b/sidebar.ts @@ -15,7 +15,7 @@ export const docs: Navigation = [ }, { type: "category", - label: "Getting Started", + label: "Getting Started (Portal)", items: [ "articles/step-1-setup-basic-gateway", "articles/step-2-add-rate-limiting", @@ -26,6 +26,17 @@ export const docs: Navigation = [ ], collapsed: false, }, + { + type: "category", + label: "Getting Started (Local)", + items: [ + "articles/step-1-setup-basic-gateway-local", + // "articles/step-2-add-rate-limiting-local", + // "articles/step-3-add-api-key-auth-local", + // "articles/step-4-deploying-to-the-edge-local", + ], + collapsed: false, + }, { type: "category", label: "Core Concepts", diff --git a/sidebar.zudoku.json b/sidebar.zudoku.json index 3bf237d80..7601bfb35 100644 --- a/sidebar.zudoku.json +++ b/sidebar.zudoku.json @@ -26,6 +26,14 @@ "dev-portal/zudoku/markdown/code-blocks" ] }, + { + "type": "category", + "label": "Concepts", + "icon": "shapes", + "items": [ + "dev-portal/zudoku/concepts/auth-provider-api-identities" + ] + }, { "type": "category", "label": "OpenAPI", diff --git a/src/QuickstartPicker.tsx b/src/QuickstartPicker.tsx new file mode 100644 index 000000000..ab0eae551 --- /dev/null +++ b/src/QuickstartPicker.tsx @@ -0,0 +1,33 @@ +import { CodeIcon, GlobeIcon } from "lucide-react"; +import { TutorialModeSelector, TutorialMode } from "./TutorialModeSelector.js"; + +export function QuickstartPicker({ + alternateLink, + mode, +}: { + alternateLink: string; + mode: "local" | "portal"; +}) { + // This component is used to select + return ( + + } + label="Local Development" + active={mode === "local"} + description="Develop and test your gateway locally using the Zuplo CLI. Full control over your environment." + link={mode === "local" ? undefined : alternateLink} + /> + } + active={mode === "portal"} + label="Portal Development" + description="Build and deploy your gateway using Zuplo's web-based portal. No local setup required." + link={mode === "portal" ? undefined : alternateLink} + /> + + ); +} diff --git a/src/TutorialModeSelector.tsx b/src/TutorialModeSelector.tsx new file mode 100644 index 000000000..66d93d589 --- /dev/null +++ b/src/TutorialModeSelector.tsx @@ -0,0 +1,127 @@ +import { Children, isValidElement, ReactNode } from "react"; +import { Link } from "zudoku/components"; +interface ModeProps { + icon?: ReactNode; + label: string; + active?: boolean; + description?: string; + link?: string; +} + +export function TutorialMode({ label }: ModeProps) { + return null; // This component is only used for passing props +} + +interface TutorialModeSelectorProps { + children: ReactNode; + title?: string; + description?: string; +} + +export function TutorialModeSelector({ + children, + title, + description, +}: TutorialModeSelectorProps) { + const childArray = Children.toArray(children); + + // Extract Mode components + const modes: ModeProps[] = []; + + childArray.forEach((child) => { + if (isValidElement(child) && child.type === TutorialMode) { + modes.push(child.props as ModeProps); + } + }); + + // Find the active mode + const activeMode = modes.find((mode) => mode.active); + + return ( +
+ {(title || description) && ( +
+ {title && ( +

+ {title} +

+ )} + {description && ( +

+ {description} +

+ )} +
+ )} + + {/* Combined tab bar and content */} +
+ {/* Mode selector tabs */} +
+ {modes.map((mode, index) => { + const isActive = mode.active; + + return isActive ? ( +
+ {mode.icon && ( + + {mode.icon} + + )} + {mode.label} +
+ ) : ( + + {mode.icon && ( + + {mode.icon} + + )} + {mode.label} + + ); + })} +
+ + {/* Active mode content */} + {activeMode && ( +
+
+ {activeMode.icon && ( + + {activeMode.icon} + + )} +
+

+ {activeMode.label} +

+ {activeMode.description && ( +

+ {activeMode.description} +

+ )} +
+
+
+ )} +
+
+ ); +} diff --git a/src/components.tsx b/src/components.tsx index 47489a667..21146edcf 100644 --- a/src/components.tsx +++ b/src/components.tsx @@ -9,6 +9,8 @@ import ZupIt from "./ZupIt.js"; import { LegacyDevPortal } from "./LegacyDevPortal.js"; import { LegacyMonetization } from "./LegacyMonetization.js"; import { ModalScreenshot } from "./ModalScreenshot.js"; +import { TutorialModeSelector, TutorialMode } from "./TutorialModeSelector.js"; +import { QuickstartPicker } from "./QuickstartPicker.js"; const iconStyle = { display: "inline", verticalAlign: "-0.125em" }; @@ -110,6 +112,9 @@ export const mdxComponents = { LegacyDevPortal, LegacyMonetization, ModalScreenshot, + TutorialModeSelector, + TutorialMode, + QuickstartPicker, EmbeddedChat: () => { return ( Loading…}> From cb7e9bb55de7e16448ea783f872494dd159734b0 Mon Sep 17 00:00:00 2001 From: Nathan Totten Date: Mon, 4 Aug 2025 15:28:53 -0400 Subject: [PATCH 2/3] updated selector, added akamai waf --- docs/articles/waf-ddos-akamai.md | 197 +++++++++++++++++++++++++++++++ sidebar.ts | 1 + src/TutorialModeSelector.tsx | 8 +- 3 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 docs/articles/waf-ddos-akamai.md diff --git a/docs/articles/waf-ddos-akamai.md b/docs/articles/waf-ddos-akamai.md new file mode 100644 index 000000000..339737873 --- /dev/null +++ b/docs/articles/waf-ddos-akamai.md @@ -0,0 +1,197 @@ +--- +title: Configuring Zuplo with Akamai App & API Protector +sidebar_label: Akamai App & API Protector +--- + +Akamai App & API Protector runs at Akamai edge locations. Zuplo can be +configured to run as a custom origin behind Akamai. + +## Securing Zuplo from Direct Access + +With any WAF product, you will want to ensure that network traffic can't bypass +your WAF and hit your API Gateway directly. Akamai offers several ways to ensure +that your API Gateway is only accessible through the WAF. + +The information below is a summary of Akamai's own recommendations for securing +your backend - regardless of whether you are using Zuplo, another API Gateway, +or Akamai origins. You can reference the +[Akamai documentation](https://techdocs.akamai.com/application-security/docs/origin-server-protection). + +### IP Address Restrictions + +Akamai maintains a list of IP addresses that you can use to restrict access to +your API Gateway. This is a good way to ensure that only Akamai can access your +API Gateway. However, as Akamai is a multi-tenant service, this method isn't +sufficient to protect unauthorized traffic from hitting your API Gateway. + +In Zuplo, you can utilize the IP Address Restriction policy to limit traffic to +only the Akamai IP addresses. You don't need to provide the address list +manually, instead you can utilize the built-in list as shown below. + +```json +{ + "name": "allow-akamai-only", + "policyType": "ip-address-restriction-inbound", + "handler": { + "export": "IPAddressRestrictionInbound", + "module": "$import(@zuplo/runtime)", + "options": { + "allowedIpAddresses": ["list:akamai"] + } + } +} +``` + +With this policy in place, only Akamai traffic will be allowed to hit your Zuplo +API Gateway. + +### Custom Headers + +Another way to ensure that traffic is coming from Akamai is to use custom +headers. Custom headers can be added to your Akamai configuration and then +checked by your API Gateway. This provides an additional layer of security on +top of IP address restrictions and prevents any unauthorized traffic from +hitting your API Gateway - regardless of the source. + +In Akamai, you can configure custom headers using the Property Manager or the +Akamai API. Add a custom header with a secret value that only you and Akamai +know. + +In Zuplo, you can utilize the Header Restriction policy to limit traffic to only +those requests that include the custom header and secret value. + +```json +{ + "name": "allow-akamai-custom-header", + "policyType": "require-header-inbound", + "handler": { + "export": "RequireHeaderInboundPolicyOptions", + "module": "$import(@zuplo/runtime)", + "options": { + "headerName": "x-akamai-auth", + "allowedValues": ["$env(AKAMAI_SECRET_HEADER_VALUE)"] + } + } +} +``` + +With this policy in place, only requests that include the custom header with the +secret value will be allowed to hit your Zuplo API Gateway. + +### Edge Authorization Tokens + +Akamai supports Edge Authorization Tokens (Auth 2.0) which provide a more secure +way to authenticate requests from Akamai to your origin. These tokens can be +generated at the edge and verified by your API Gateway. + +In Zuplo, you can create a custom code inbound policy to verify Akamai Edge +Authorization Tokens: + +```json title="/config/policies.json" +{ + "name": "akamai-auth-token-inbound", + "policyType": "custom-code-inbound", + "handler": { + "export": "default", + "module": "$import(./modules/akamai-auth-token-inbound)", + "options": { + "authKey": "$env(AKAMAI_AUTH_KEY)", + "tokenName": "akamai-auth-token" + } + } +} +``` + +```ts title="/modules/akamai-auth-token-inbound.ts" +import { HttpProblems, ZuploContext, ZuploRequest } from "@zuplo/runtime"; + +interface PolicyOptions { + authKey: string; + tokenName: string; + windowSeconds?: number; +} + +export default async function ( + request: ZuploRequest, + context: ZuploContext, + options: PolicyOptions, + policyName: string, +) { + // Validate the policy options + if (typeof options.authKey !== "string") { + throw new Error( + `The option 'authKey' on policy '${policyName}' must be a string. Received ${typeof options.authKey}.`, + ); + } + if (typeof options.tokenName !== "string") { + throw new Error( + `The option 'tokenName' on policy '${policyName}' must be a string. Received ${typeof options.tokenName}.`, + ); + } + + // Get the authorization token + const authToken = request.headers.get(options.tokenName); + + // No auth token, unauthorized + if (!authToken) { + return HttpProblems.unauthorized(request, context); + } + + // Parse the auth token + const tokenParts = authToken.split("~"); + if (tokenParts.length < 4) { + context.log.error("Invalid auth token format"); + return HttpProblems.unauthorized(request, context); + } + + const [authPrefix, timestamp, nonce, hash, ...rest] = tokenParts; + + // Validate timestamp + const tokenTime = parseInt(timestamp, 16); + const currentTime = Math.floor(Date.now() / 1000); + const windowSeconds = options.windowSeconds ?? 300; // Default 5 minutes + + if (Math.abs(currentTime - tokenTime) > windowSeconds) { + context.log.error("Auth token expired"); + return HttpProblems.unauthorized(request, context); + } + + // Recreate the auth string to verify + const authString = [authPrefix, timestamp, nonce, request.url].join("~"); + + // Create HMAC signature + const encoder = new TextEncoder(); + const key = await crypto.subtle.importKey( + "raw", + encoder.encode(options.authKey), + { name: "HMAC", hash: "SHA-256" }, + false, + ["verify"], + ); + + // Verify the signature + const signature = new Uint8Array( + hash.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)), + ); + const data = encoder.encode(authString); + const verified = await crypto.subtle.verify("HMAC", key, signature, data); + + if (!verified) { + context.log.error("Invalid auth token signature"); + return HttpProblems.unauthorized(request, context); + } + + // Request is authorized, continue + return request; +} +``` + +### Combining Security Methods + +For maximum security, we recommend combining multiple authentication methods: + +1. **IP Address Restrictions** - As a first layer of defense +2. **Custom Headers or Edge Authorization Tokens** - For request authentication + +This layered approach ensures that even if one security method is compromised, +your API Gateway remains protected. diff --git a/sidebar.ts b/sidebar.ts index 91846ab72..79d07063e 100644 --- a/sidebar.ts +++ b/sidebar.ts @@ -168,6 +168,7 @@ export const docs: Navigation = [ items: [ "articles/zuplo-waf", "articles/waf-ddos-fastly", + "articles/waf-ddos-akamai", "articles/waf-ddos-aws-waf-shield", ], }, diff --git a/src/TutorialModeSelector.tsx b/src/TutorialModeSelector.tsx index 66d93d589..df2c5c3ef 100644 --- a/src/TutorialModeSelector.tsx +++ b/src/TutorialModeSelector.tsx @@ -55,16 +55,16 @@ export function TutorialModeSelector({ )} {/* Combined tab bar and content */} -
+
{/* Mode selector tabs */} -
+
{modes.map((mode, index) => { const isActive = mode.active; return isActive ? (
{mode.icon && ( {activeMode.icon && ( {activeMode.icon} From 870d201ff996cf9e61a2612801d9b363aa6b07e5 Mon Sep 17 00:00:00 2001 From: Nathan Totten Date: Mon, 4 Aug 2025 15:29:14 -0400 Subject: [PATCH 3/3] WIP local tutorials --- .../step-2-add-rate-limiting-local.mdx | 81 +++++++ .../step-3-add-api-key-auth-local.mdx | 165 +++++++++++++ .../step-4-deploying-to-the-edge-local.mdx | 226 ++++++++++++++++++ 3 files changed, 472 insertions(+) create mode 100644 docs/articles/step-2-add-rate-limiting-local.mdx create mode 100644 docs/articles/step-3-add-api-key-auth-local.mdx create mode 100644 docs/articles/step-4-deploying-to-the-edge-local.mdx diff --git a/docs/articles/step-2-add-rate-limiting-local.mdx b/docs/articles/step-2-add-rate-limiting-local.mdx new file mode 100644 index 000000000..3c9bbb2d7 --- /dev/null +++ b/docs/articles/step-2-add-rate-limiting-local.mdx @@ -0,0 +1,81 @@ +--- +title: Step 2 - Add Rate Limiting +sidebar_label: "Step 2 - Rate Limiting" +--- + + + +In this guide we'll add simple Rate Limiting to a route. If you don't have one +ready, complete [Step 1](./step-1-setup-basic-gateway.mdx) first. + +Rate Limiting is one of our most popular **policies** - you should never ship an +API without rate limiting because your customers or internal developers **will** +accidentally DoS your API; usually with a rogue `useEffect` call in React code. + +:::info{title="What's a Policy?"} + +[Policies](./policies.md) are modules that can intercept and transform an +incoming request or outgoing response. Zuplo offers a wide range of policies +built-in (including rate limiting) to save you time. You can check out +[the full list](../policies.md). + +::: + +Zuplo offers a programmable approach to rate limiting that allows you to vary +how rate limiting is applied for each customer, or request.s + +In this example, we'll add a simple IP based rate limiter, but you should look +into dynamic rate limiting to see the full power of the world's best rate +limiter. + + + +1. Add the rate-limiting Policy + + Navigate to your route in the **Route Designer** (**Code** > `routes.oas.json`), + click the **Policies** dropdown, then click **Add Policy** on the request + pipeline. + + + + ![Add policy](../../public/media/step-2-add-rate-limiting/image.png) + + + + Search for the rate limiting policy (not the "Complex" one) and click it. + + + + ![Add rate-limiting policy](../../public/media/step-2-add-rate-limiting/choose-rate-limiter.png) + + + + By default, the policy will rate limit based on the caller's IP address (as + indicated by the `rateLimitBy` field). It will allow 2 requests + (`requestsAllowed`) every 1 minute (`timeWindowMinutes`). You can explore the + rest of the policy's documentation and configuration in the right panel. + + ![Rate limiting policy](../../public/media/step-2-add-rate-limiting/create-policy.png) + + To apply the policy, click **OK**. Then, save your changes to redeploy. + +1. Testing your Policy + + Now try firing some requests against your API. You should receive a **429 Too + many requests** on your 3rd request. + + + + ![429 response](../../public/media/step-2-add-rate-limiting/test-api.png) + + + + Your rate limiting policy is now intercepting excess requests, protecting the + `getting-started` API. + + + +**NEXT** Try [Step 3 - Add API Key Authentication](./step-3-add-api-key-auth). diff --git a/docs/articles/step-3-add-api-key-auth-local.mdx b/docs/articles/step-3-add-api-key-auth-local.mdx new file mode 100644 index 000000000..9e43effb2 --- /dev/null +++ b/docs/articles/step-3-add-api-key-auth-local.mdx @@ -0,0 +1,165 @@ +--- +title: Step 3 - API Key Authentication +sidebar_label: "Step 3 - API Key Auth" +--- + + + +In this guide we'll add API Key authentication to a route. You can do this for +any Zuplo project but will need a route, consider completing +[Step 1](./step-1-setup-basic-gateway.mdx) first. + +API Key authentication is one of our most popular **policies** as implementing +this authentication method is considered one of the easiest to use by developers +but hard for API developers to get right. We also support JWT tokens and most +other authentication methods. + +:::info{title="What's a Policy?"} + +[Policies](./policies.md) are modules that can intercept and transform an +incoming request or outgoing response. Zuplo offers a wide range of policies +built-in (including api key authentication) to save you time. You can check out +[the full list](./policies.md). + +::: + +Let's get started. + + + +1. Add the API Key Authentication Policy + + Navigate to your route in the **Route Designer** (**Code** > + `routes.oas.json`) and open the **Policies** section. Then click **Add + Policy**. + + ![Add Policy](../../public/media/step-3-add-api-key-auth/image.png) + + Search for the API key authentication policy, click on it, and then click OK + to accept the default policy JSON. + + + + ![Add API Key Authentication](../../public/media/step-3-add-api-key-auth/choose-policy.png) + + + + :::tip + + The API key authentication policy should usually be one of the first policies + executed. If you came here from [Step 2](./step-2-add-rate-limiting.mdx) then + you will want to drag it above the rate limiting policy. + + ::: + + ![reorder policies](../../public/media/step-3-add-api-key-auth/image-1.gif) + + If you test your route, you should get a 401 Unauthorized response + + ```json + { + "status": 401, + "title": "Unauthorized", + "type": "https://httpproblems.com/http-status/401" + } + ``` + +2. Set up an API Key + + In order to call your API, you need to configure an API consumer. Go to + **Services**, then click **Configure** on the "API Key Service". + + ![API Key Service](../../public/media/step-3-add-api-key-auth/image-2.png) + + Then click **Create Consumer**. + + ![Create Consumer](../../public/media/step-2-add-api-key-auth/image-8.png) + + Let's break down the configuration needed. + - Subject: Also known as `sub`. This is a unique identifier of the API + consumer. This is commonly the name of the user or organization consuming + your API + - Key managers: The email addresses of those who will be managing this API + key. + - Metadata: JSON metadata that will be made available to the runtime when a + key is used to authenticate. Common properties include the consumer's + subscription plan, organization, etc. + + Go ahead and fill in `test-consumer` for the Subject. Add your own email as a + Key manager, and leave the metadata empty for now. Click **Save consumer** + once you're done. + + + + ![New Consumer](../../public/media/step-3-add-api-key-auth/image-3.png) + + + +3. Copy your API Key + + After your API Key consumer is created, copy your new API Key by clicking the + copy button (next to the eye icon). + + ![Copy Key](../../public/media/step-3-add-api-key-auth/image-4.png) + +4. Test out your API Key + + Navigate back to the **Route Designer**, and select your route. Next to the + path of your route, click the **Test** button and fire off a request. + + + + ![Failed unauthorized error](../../public/media/step-3-add-api-key-auth/test-policy.png) + + + + You should get a 401 Unauthorized response - as we'ven't supplied the API key + yet. Add an new `authorization` header with the value `Bearer ` + and insert the API Key you got from the developer portal. + + You should now get a 200 OK. + + + + ![successful response](../../public/media/step-3-add-api-key-auth/image-6.png) + + + + :::note + + We also offer an API for our API key service that allows you to + programmatically create consumers and even create your own developer portal + or integrate key management into your existing dashboard. See + [this document for details](./api-key-api.md). + + ::: + +5. View your API Documentation + + Whenever you deploy a new endpoint on Zuplo, it will automatically be added + to your + [autogenerated developer documentation portal](../dev-portal/introduction.md). + + To access your API's developer portal, click the **Gateway deployed** button + in your toolbar and click the link under Developer Portal. + ![Developer portal menu](../../public/media/step-2-add-rate-limiting/image-5.png) + + When you use certain policies like API keys, Zuplo will document properties + like headers associated with that policy. As you can see on the right, the + API key policy's `Authorization` header has been documented for you. + + ![Developer Portal Endpoint](../../public/media/step-3-add-api-key-auth/image-7.png) + + Additionally, a new Authentication section has been added to your developer + portal. Users of your API can sign in, view & manage their API keys, test + your endpoints, track API usage, and much more! You can learn more about that + in + [our developer portal auth docs](/docs/dev-portal/zudoku/configuration/authentication). + + + +**NEXT** Try +[Step 4 - Connect Source Control and Deploy to the Edge](./step-4-deploying-to-the-edge.mdx). diff --git a/docs/articles/step-4-deploying-to-the-edge-local.mdx b/docs/articles/step-4-deploying-to-the-edge-local.mdx new file mode 100644 index 000000000..1212ab23a --- /dev/null +++ b/docs/articles/step-4-deploying-to-the-edge-local.mdx @@ -0,0 +1,226 @@ +--- +title: Step 4 - Deploying to the Edge +sidebar_label: "Step 4 - Deploy" +--- + + + +In this guide we'll show you how to deploy your gateway to the edge, at over 300 +data-centers around the world. The act of deployment creates new +[environments](./environments) and it's worth familiarizing yourself with +[how environments work](./environments). + +In this tutorial we'll show the default workflow via GitHub, but note that we +also support GitLab, BitBucket +([enterprise plan only](https://zuplo.com/pricing)) and +[custom CI/CD](./custom-ci-cd). + +To follow this tutorial you'll need + +- a GitHub account (it's free, sign up at [github.com](https://github.com)). +- a zuplo project - complete [Step 1](./step-1-setup-basic-gateway.mdx), + [Step 2](./step-2-add-rate-limiting.mdx) and + [Step 3](./step-3-add-api-key-auth.mdx) for a great start! +- to install the + [Zuplo GitHub deployment](https://github.com/apps/zuplo/installations/new) to + GitHub - you can + [follow these instructions](https://github.com/apps/zuplo/installations/new) + +Let's get started: + + + +1. Authorize to GitHub + + Next, go to your project in the Zuplo portal and open to the **Settings** + tab, then select **Source Control**. Control. If your project isn't already + connected to GitHub click the **Connect to GitHub** button and follow the + auth flow. You'll need to grant permissions for any GitHub organizations you + want to work with. + + ![Connect GitHub](../../public/media/step-4-deploying-to-the-edge/image-1.png) + + Next, a dialog will open asking you to authorize Zuplo. Click the **Authorize + Zuplo** button. + + + + ![Zuplo GitHub connection](../../public/media/step-4-deploying-to-the-edge/d6194a80-b6d6-429e-85a6-ae1cb4a3375e.png) + + + + + + The permission "Act on your behalf" sounds a bit scary - however, this is a + standard GitHub permission and by default Zuplo can't actually do anything + with this. In order to perform actions on your behalf you must grant Zuplo + access to a specific repository (shown in the next steps.). + + [read more about this permission on GitHub's docs](https://docs.github.com/en/apps/using-github-apps/authorizing-github-apps#about-github-apps-acting-on-your-behalf). + + + + After you have connected the GitHub app, it needs to be granted permission to + edit a repository. If this is your first time connecting Zuplo, you will be + immediately asked to select a GitHub Org to install Zuplo. Select the org you + want to use. + + + + ![Installing Zuplo app](../../public/media/step-4-deploying-to-the-edge/eef76bd7-4d26-4f86-96e8-89ebede03beb.png) + + + + Next, you will be asked to select the repositories that you want Zuplo to + access. The easiest thing is to just select **All Repositories**, but if you + want fine-grain control, you can select a specific repository. + + + + ![Choosing repository to install](../../public/media/step-4-deploying-to-the-edge/ff482269-9aa2-44c3-8266-b2682b3d6ea5.png) + + + + + + The next step is only if you already have Zuplo installed in a GitHub org and + need to add another organization. + + + + If you weren't prompted to select a GitHub org, it's likely that you are + already a member of an account that has authorized Zuplo. To add Zuplo to a + new organization click **Add GitHub Account** in the org picker list. + + ![Connect Org](../../public/media/step-4-deploying-to-the-edge/image-2.png) + +1. Connect GitHub to your Project + + With your GitHub App configured, you can now return to the Zuplo portal. In + the **Source Control** settings you should now see a list of GitHub + repositories. Create a new repository by clicking the **Create new + repository** button. You will be prompted that this will open GitHub. Click + to continue. + + ![Create Repository](../../public/media/step-4-deploying-to-the-edge/image-3.png) + + In the GitHub UI, you can rename your repository if you want. Click the + **Create repository** button at the bottom of the page and return to the + Zuplo Portal. + + The portal will reload and you will see your new repository listed. Click + **Connect** to connect Zuplo to that repository. + + ![Connect](../../public/media/step-4-deploying-to-the-edge/image-4.png) + + After the connection succeeds you will see a link to your GitHub repository. + + ![Connected Repository](../../public/media/step-4-deploying-to-the-edge/image-5.png) + + Click the link to return to GitHub. You should see a little brown dot next to + the commit hash (1). When you hover your mouse over that you'll see the Zuplo + deployment was successful. Click **Details** (2) to open the deployment info. + + This shows that your deployment is in progress, it will take about 50 + seconds. + + ![Zuplo deployment run](../../public/media/step-4-deploying-to-the-edge/brown-dot.png) + + On the deployment page, you will see **Deployment has Completed!!** and below + that's the link to your new environment. + + ![Zuplo deployment run result](../../public/media/step-4-deploying-to-the-edge/26fa58b6-7a5a-4627-bd9f-246972639f12.png) + +1. Deploy another Environment + + Zuplo makes it easy for teams to collaborate by allowing teams to create many + preview environments. To create a new environment, simple go to your + repository in GitHub and create a new branch. + + Let's create a branch called `development` + + ![Create new branch](../../public/media/step-4-deploying-to-the-edge/60cdeb36-ab7d-42f9-a8c2-1f7931f80ca6.png) + + Wait about 20s and head back to Zuplo - you should see a new entry in the + environment dropdown called `development`. + +1. Push a Change to 'development' + + Let's make a simple change to our `working-copy` environment. Let's do + something simple like capitalize the **Summary** field from 'Get all todos' + to 'Get All Todos'. It doesn't really matter what the change is. + + ![Changes Summary](../../public/media/step-4-deploying-to-the-edge/image-6.png) + + Save your changes. Click the GitHub button at bottom left and choose **Commit + & Push**. + + ![Commit & Push](../../public/media/step-4-deploying-to-the-edge/image-7.png) + + Enter a description of your change in the dialog that pops up: + + + + ![Change Summary](../../public/media/step-4-deploying-to-the-edge/image-8.png) + + + + Click **Commit & Push** will create a new temporary branch in GitHub with a + name `zup-...`. On the next dialog, click **Create Pull Request**. + + + + ![Create PR](../../public/media/step-4-deploying-to-the-edge/image-9.png) + + + + This will navigate you to the screen in GitHub that allows you to create a + Pull Request. Change the **base** branch to `development` (since that's the + environment we want to update first). Click **Create pull request**. + + ![GitHub PR](../../public/media/step-4-deploying-to-the-edge/875b164d-b7ef-4f46-9cdb-8d59354b5b93.png) + + When ready, click **Merge pull request**. + + ![Merge PR](../../public/media/step-4-deploying-to-the-edge/e8c68072-35dc-462a-8161-7a44e40fa1df.png) + + Once merged, you'll want to delete that temporary branch. + + ![Delete branch](../../public/media/step-4-deploying-to-the-edge/51a25aa0-cdce-4112-ba2e-e56f42a9044d.png) + + The successful merge will trigger a rebuild and deployment of `development` + with your change. You can check this by choosing the environment + `development` in Zuplo and navigating to the **read only** Route Designer. + + ![Navigating Environments](../../public/media/step-4-deploying-to-the-edge/image.png) + + This shows how you can use widely recognized GitOps practices to manage how + code flows through your environments using Pull Requests and protected + branches. + + + +## Troubleshooting + +I don't see my repository listed in Zuplo project settings. + +- Make sure you've granted access to the project with the Zuplo GitHub App, you + can + [check that configuration here](https://github.com/apps/zuplo/installations/new) + +I've connected my Zuplo project to a GitHub repository but get access errors +when running commits, pulls or try to open a pull request. + +- If you are the owner of the project, make sure you've granted access to the + project with the Zuplo GitHub App, you can + [check that configuration here](https://github.com/apps/zuplo/installations/new) +- If this Zuplo project was shared with you, make sure you are a GitHub + collaborator. This can be verified by going to the GitHub repository > + Settings > Collaborators section. You'll need admin permissions to the + repository. +- If the GitHub repository has been renamed or moved to a different + organization, try disconnecting and reconnecting to it. You can do this by + going to your Zuplo project's settings > Source Control section