zuplo 6.70.69 → 6.70.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/docs/ai-gateway/getting-started.mdx +14 -9
  2. package/docs/ai-gateway/integrations/ai-sdk.mdx +17 -0
  3. package/docs/ai-gateway/introduction.mdx +12 -10
  4. package/docs/ai-gateway/providers.mdx +2 -0
  5. package/docs/analytics/access-and-entitlements.md +71 -0
  6. package/docs/analytics/overview.md +63 -0
  7. package/docs/analytics/reference/metrics-glossary.md +105 -0
  8. package/docs/analytics/reference/url-parameters.md +66 -0
  9. package/docs/analytics/shared-controls.md +121 -0
  10. package/docs/analytics/tabs/agents.md +88 -0
  11. package/docs/analytics/tabs/consumers.md +73 -0
  12. package/docs/analytics/tabs/graphql.md +77 -0
  13. package/docs/analytics/tabs/mcp.md +80 -0
  14. package/docs/analytics/tabs/origins.md +82 -0
  15. package/docs/analytics/tabs/requests.md +96 -0
  16. package/docs/articles/api-key-buckets.mdx +4 -2
  17. package/docs/articles/archiving-requests-to-storage.mdx +4 -4
  18. package/docs/articles/branch-based-deployments.mdx +10 -8
  19. package/docs/articles/ci-cd-github/basic-deployment.mdx +10 -1
  20. package/docs/articles/ci-cd-github/cleanup-on-branch-delete.mdx +52 -31
  21. package/docs/articles/ci-cd-github/deploy-and-test.mdx +14 -1
  22. package/docs/articles/ci-cd-github/local-testing.mdx +3 -1
  23. package/docs/articles/ci-cd-github/pr-preview-environments.mdx +53 -10
  24. package/docs/articles/custom-ci-cd-azure.mdx +1 -1
  25. package/docs/articles/custom-ci-cd-bitbucket.mdx +1 -1
  26. package/docs/articles/custom-ci-cd-circleci.mdx +1 -1
  27. package/docs/articles/custom-ci-cd-github.mdx +12 -3
  28. package/docs/articles/custom-ci-cd-gitlab.mdx +1 -1
  29. package/docs/articles/graphql.mdx +276 -0
  30. package/docs/articles/monetization/api-access.mdx +184 -0
  31. package/docs/articles/monetization/meters.mdx +4 -4
  32. package/docs/articles/monetization/monetization-policy.md +4 -1
  33. package/docs/articles/monetization/private-plans.md +3 -4
  34. package/docs/articles/monetization/stripe-integration.md +9 -0
  35. package/docs/articles/monetization/subscription-lifecycle.md +12 -11
  36. package/docs/articles/monorepo-deployment.mdx +37 -5
  37. package/docs/articles/opentelemetry.mdx +5 -2
  38. package/docs/articles/securing-the-gateway-with-client-mtls.mdx +68 -43
  39. package/docs/articles/step-1-setup-basic-gateway.mdx +1 -3
  40. package/docs/articles/step-2-add-rate-limiting.mdx +1 -1
  41. package/docs/articles/testing.mdx +1 -1
  42. package/docs/articles/troubleshooting.md +7 -3
  43. package/docs/articles/waf-ddos-akamai.md +35 -16
  44. package/docs/articles/waf-ddos-aws-waf-shield.mdx +35 -16
  45. package/docs/articles/waf-ddos-fastly.mdx +10 -7
  46. package/docs/cli/deploy.mdx +44 -9
  47. package/docs/cli/deploy.partial.mdx +44 -9
  48. package/docs/concepts/api-keys.md +2 -2
  49. package/docs/dev-portal/zudoku/components/callout.mdx +11 -18
  50. package/docs/dev-portal/zudoku/components/sidecar-box.mdx +131 -0
  51. package/docs/dev-portal/zudoku/configuration/api-catalog.md +62 -42
  52. package/docs/dev-portal/zudoku/configuration/api-reference.md +5 -4
  53. package/docs/dev-portal/zudoku/configuration/navigation.mdx +70 -7
  54. package/docs/dev-portal/zudoku/configuration/search.md +36 -0
  55. package/docs/dev-portal/zudoku/configuration/site.md +38 -0
  56. package/docs/dev-portal/zudoku/customization/colors-theme.mdx +51 -40
  57. package/docs/errors/rate-limit-exceeded.mdx +30 -3
  58. package/docs/guides/canary-routing-for-employees.mdx +103 -39
  59. package/docs/guides/modify-openapi-paths.mdx +3 -3
  60. package/docs/handlers/legacy-dev-portal-handler.mdx +1 -1
  61. package/docs/handlers/mcp-server.mdx +13 -11
  62. package/docs/handlers/url-forward.mdx +5 -1
  63. package/docs/handlers/url-rewrite.mdx +7 -2
  64. package/docs/handlers/websocket-handler.mdx +5 -1
  65. package/docs/mcp-gateway/observability/logging.mdx +19 -12
  66. package/docs/mcp-server/resources.mdx +27 -15
  67. package/docs/mcp-server/testing.mdx +0 -2
  68. package/docs/policies/_index.md +2 -0
  69. package/docs/policies/archive-request-azure-storage-inbound/doc.md +1 -1
  70. package/docs/policies/archive-response-azure-storage-outbound/doc.md +1 -1
  71. package/docs/policies/data-loss-prevention-inbound/doc.md +116 -0
  72. package/docs/policies/data-loss-prevention-inbound/intro.md +15 -0
  73. package/docs/policies/data-loss-prevention-inbound/schema.json +220 -0
  74. package/docs/policies/data-loss-prevention-outbound/doc.md +116 -0
  75. package/docs/policies/data-loss-prevention-outbound/intro.md +18 -0
  76. package/docs/policies/data-loss-prevention-outbound/schema.json +220 -0
  77. package/docs/policies/ip-restriction-inbound/policy.ts +1 -1
  78. package/docs/programmable-api/background-dispatcher.mdx +6 -8
  79. package/docs/programmable-api/http-problems.mdx +0 -18
  80. package/docs/programmable-api/jwt-service-plugin.mdx +131 -109
  81. package/docs/programmable-api/runtime-behaviors.mdx +4 -2
  82. package/docs/programmable-api/streaming-zone-cache.mdx +4 -6
  83. package/docs/programmable-api/web-crypto-apis.mdx +10 -6
  84. package/docs/programmable-api/zone-cache.mdx +1 -1
  85. package/docs/rate-limiting/combining-policies.mdx +293 -0
  86. package/docs/rate-limiting/dynamic-rate-limiting.mdx +240 -0
  87. package/docs/rate-limiting/getting-started.mdx +339 -0
  88. package/docs/rate-limiting/how-it-works.md +225 -0
  89. package/docs/rate-limiting/monitoring-and-troubleshooting.mdx +243 -0
  90. package/docs/{articles → rate-limiting}/per-user-rate-limits-using-db.mdx +39 -28
  91. package/package.json +4 -4
  92. package/docs/concepts/rate-limiting.md +0 -246
  93. package/docs/errors/get-head-body-error.mdx +0 -41
@@ -99,10 +99,9 @@ Save the returned `id` — you need it to publish and invite users.
99
99
 
100
100
  :::note
101
101
 
102
- The plan `id` is a 26-character ULID (regex
103
- `^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$`), separate from the human-friendly
104
- `key` you set on creation. The publish, invite, and other plan-scoped endpoints
105
- require the `id`, not the `key`.
102
+ The plan `id` is a 26-character ULID. It's distinct from the human-friendly
103
+ `key` field. Use the `id` (not the `key`) when calling `/publish` and
104
+ `/plan-invites`.
106
105
 
107
106
  :::
108
107
 
@@ -45,6 +45,15 @@ specifically Customers, Checkout Sessions, Customer Portal Sessions, Invoices,
45
45
  and Tax Calculations. See
46
46
  [What Zuplo creates in Stripe](#what-zuplo-creates-in-stripe) for the full list.
47
47
 
48
+ :::tip
49
+
50
+ To script the connection — for CI, infrastructure-as-code, or self-hosted
51
+ control planes — use the
52
+ [Stripe setup API endpoints](./api-access.mdx#stripe-setup-and-billing-readiness)
53
+ instead of the Portal flow.
54
+
55
+ :::
56
+
48
57
  ### Test mode vs. live mode
49
58
 
50
59
  Connect with a Stripe **test** key (`sk_test_...`) first to validate your
@@ -67,8 +67,9 @@ curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions \
67
67
  -H "Authorization: Bearer {API_KEY}" \
68
68
  -H "Content-Type: application/json" \
69
69
  -d '{
70
- "plan": { "key": "pro" },
71
- "customerId": "01J9ZX2A8R0K8H6VG2C1A0K3WP"
70
+ "plan": { "key": "pro", "version": 1 },
71
+ "customerKey": "user_external_id",
72
+ "timing": "immediate"
72
73
  }'
73
74
  ```
74
75
 
@@ -216,14 +217,15 @@ curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions/{subscri
216
217
  -H "Content-Type: application/json" \
217
218
  -d '{
218
219
  "timing": "immediate",
219
- "plan": { "key": "enterprise" }
220
+ "plan": { "key": "enterprise", "version": 1 }
220
221
  }'
221
222
  ```
222
223
 
223
224
  `timing` accepts `"immediate"`, `"next_billing_cycle"`, or an RFC 3339
224
225
  timestamp. Optional fields include `startingPhase`, `name`, `description`,
225
- `metadata`, `alignment`, and `billingAnchor`. To preview the proration credit
226
- before committing, call
226
+ `metadata`, `alignment`, and `billingAnchor`. The response includes both the
227
+ closed-out (`current`) and newly-started (`next`) subscriptions. To preview the
228
+ proration credit before committing, call
227
229
  `POST /v3/metering/{bucketId}/subscriptions/{subscriptionId}/change/estimate-credit`
228
230
  with the same body.
229
231
 
@@ -310,9 +312,7 @@ behavior does not apply.
310
312
  curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions/{subscriptionId}/cancel \
311
313
  -H "Authorization: Bearer {API_KEY}" \
312
314
  -H "Content-Type: application/json" \
313
- -d '{
314
- "timing": "next_billing_cycle"
315
- }'
315
+ -d '{ "timing": "next_billing_cycle" }'
316
316
  ```
317
317
 
318
318
  `timing` controls when the cancellation takes effect:
@@ -341,9 +341,10 @@ curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions/{subscri
341
341
  -H "Authorization: Bearer {API_KEY}"
342
342
  ```
343
343
 
344
- This removes the pending cancellation. The subscription continues as normal. For
345
- a subscription whose period has already ended, create a new subscription on the
346
- same plan instead.
344
+ This removes the pending cancellation. The subscription continues as normal.
345
+
346
+ If the subscription has already ended, create a new subscription rather than
347
+ restoring the old one.
347
348
 
348
349
  ## Subscriptions per customer
349
350
 
@@ -114,7 +114,9 @@ jobs:
114
114
  run: npm install
115
115
 
116
116
  - name: Deploy to Zuplo
117
- run: npx zuplo deploy --api-key "$ZUPLO_API_KEY"
117
+ run:
118
+ npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "${{
119
+ github.ref_name }}"
118
120
  env:
119
121
  ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }}
120
122
  ```
@@ -171,8 +173,12 @@ jobs:
171
173
  - name: Deploy to Zuplo
172
174
  id: deploy
173
175
  shell: bash
176
+ env:
177
+ # The PR's source branch — not the pull/<number>/merge ref that
178
+ # pull_request events check out
179
+ ENVIRONMENT: ${{ github.head_ref }}
174
180
  run: |
175
- OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" 2>&1)
181
+ OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "$ENVIRONMENT" 2>&1)
176
182
  echo "$OUTPUT"
177
183
  DEPLOYMENT_URL=$(echo "$OUTPUT" | grep -oP 'Deployed to \K(https://[^ ]+)')
178
184
  echo "url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
@@ -206,16 +212,42 @@ jobs:
206
212
  - name: Install dependencies
207
213
  run: npm install
208
214
 
215
+ - name: Get deployment URL
216
+ id: get-url
217
+ uses: actions/github-script@v7
218
+ with:
219
+ script: |
220
+ const comments = await github.rest.issues.listComments({
221
+ issue_number: context.issue.number,
222
+ owner: context.repo.owner,
223
+ repo: context.repo.repo,
224
+ });
225
+ const match = comments.data
226
+ .map((c) => c.body.match(/Deployed to: (https:\/\/\S+)/))
227
+ .find(Boolean);
228
+ core.setOutput("url", match ? match[1] : "");
229
+
209
230
  - name: Delete environment
231
+ if: steps.get-url.outputs.url != ''
210
232
  run: |
211
- BRANCH_NAME="${{ github.head_ref }}"
212
- ENV_NAME="${BRANCH_NAME//\//-}"
213
233
  npx zuplo delete \
214
- --environment "$ENV_NAME" \
234
+ --url "${{ steps.get-url.outputs.url }}" \
215
235
  --api-key "$ZUPLO_API_KEY" \
216
236
  --wait
217
237
  ```
218
238
 
239
+ :::caution
240
+
241
+ The deploy step passes `--environment` with the PR's source branch name
242
+ (`github.head_ref`). Workflows triggered by `pull_request` check out the PR
243
+ merge ref (`refs/pull/<number>/merge`), so without `--environment` the CLI names
244
+ the environment after that ref instead of your branch — and any other deploy of
245
+ the same branch creates a second environment with a different URL. See
246
+ [PR Preview Environments](./ci-cd-github/pr-preview-environments.mdx) for
247
+ details.
248
+
249
+ :::
250
+
219
251
  ### Secrets and environment variables
220
252
 
221
253
  Store your Zuplo API key as a GitHub Actions secret:
@@ -222,8 +222,11 @@ export function runtimeInit(runtime: RuntimeExtensions) {
222
222
  ### Tracing and Logging Configuration
223
223
 
224
224
  Logging is only enabled when specifically configured with its own endpoint using
225
- the `logUrl` property. When using both tracing and logging, you can configure
226
- them with separate endpoints.
225
+ the `logUrl` property. When using both tracing and logging, use the top-level
226
+ `traceUrl`, `logUrl`, and `headers` properties instead of the `exporter` object
227
+ shown above. The plugin supports both configuration shapes, but they're mutually
228
+ exclusive: `exporter` configures tracing only, while `traceUrl` and `logUrl`
229
+ configure tracing and logging together with a shared set of `headers`.
227
230
 
228
231
  ```ts title="zuplo.runtime.ts"
229
232
  import { OpenTelemetryPlugin } from "@zuplo/otel";
@@ -1,39 +1,45 @@
1
1
  ---
2
- title: Client mTLS Authentication
2
+ title: Client mTLS authentication
3
+ sidebar_label: Client mTLS
4
+ description:
5
+ Require API callers to present client certificates signed by a CA you trust
6
+ before they reach your Zuplo gateway.
3
7
  ---
4
8
 
5
9
  <EnterpriseFeature name="Client mTLS" />
6
10
 
7
11
  Client mTLS authentication lets your Zuplo gateway verify the identity of
8
- clients calling your API using certificates issued by your own Certificate
9
- Authority (CA). Both the client and the gateway authenticate each other during
10
- the TLS handshake, so only clients holding a certificate signed by your CA can
11
- reach your API.
12
+ clients calling your API with certificates issued by your own certificate
13
+ authority (CA). Both the client and the gateway authenticate each other during
14
+ the TLS handshake. Routes protected by the `mtls-auth-inbound` policy only allow
15
+ clients that present a valid certificate chain anchored by a CA you uploaded to
16
+ Zuplo.
12
17
 
13
- ## How Client mTLS Works
18
+ ## How client mTLS works
14
19
 
15
20
  When a client calls your Zuplo gateway:
16
21
 
17
- 1. The client presents a certificate issued by your CA during the TLS handshake.
18
- 2. Zuplo's edge verifies the certificate against the CA you've uploaded and
19
- passes the verification result (and parsed certificate) to your gateway
20
- workers.
21
- 3. The [`mtls-auth-inbound`](../policies/mtls-auth-inbound.md) policy on your
22
+ 1. The client presents a certificate during the TLS handshake.
23
+ 2. Zuplo's edge verifies the client certificate chain against the CA
24
+ certificates uploaded to your account, then passes the verification result
25
+ and parsed client certificate to your gateway workers.
26
+ 3. The [`mtls-auth-inbound`](../policies/mtls-auth-inbound.mdx) policy on your
22
27
  route reads the verification result, enforces it, and attaches the parsed
23
28
  certificate metadata to `request.user.data.mtlsAuth` for use in your handlers
24
29
  and downstream policies.
25
30
 
26
31
  CA certificates are scoped to your Zuplo **account**, not a single project or
27
- deployment. Once a CA is uploaded, every gateway domain on the account will
28
- verify presented client certificates against it. The policy on each route
32
+ deployment. Once a CA is uploaded, every gateway domain on the account can
33
+ verify presented client certificate chains against it. The policy on each route
29
34
  controls whether unverified traffic is rejected or allowed through.
30
35
 
31
36
  ## Prerequisites
32
37
 
33
38
  Before you begin, you need:
34
39
 
35
- - A public CA certificate (PEM-encoded) that issued or will issue the client
36
- certificates you want to accept
40
+ - A public CA certificate (PEM-encoded) that has issued, or will issue, the
41
+ client certificates you want to accept, either directly or through
42
+ intermediate CAs
37
43
  - The [Zuplo CLI](../cli/overview.mdx) installed and authenticated
38
44
  - A Zuplo project where you can add the `mtls-auth-inbound` policy to a route
39
45
 
@@ -44,7 +50,7 @@ client certificates stay with you and your clients.
44
50
 
45
51
  :::
46
52
 
47
- ## 1/ Upload Your CA Certificate
53
+ ## 1/ Upload your CA certificate
48
54
 
49
55
  Use the Zuplo CLI to upload your CA certificate. The CA is registered against
50
56
  your account and is automatically made available on all of your gateway domains.
@@ -79,21 +85,21 @@ CAs on the account at any time:
79
85
  zuplo ca-certificate list --account your-account
80
86
  ```
81
87
 
82
- See the [`ca-certificate` CLI reference](../cli/ca-certificate-create.md) for
88
+ See the [`ca-certificate` CLI reference](../cli/ca-certificate-create.mdx) for
83
89
  all available subcommands (`create`, `list`, `describe`, `update`, `delete`).
84
90
 
85
91
  :::tip{title="Using an intermediate CA"}
86
92
 
87
93
  If your client certificates are issued by an intermediate CA (rather than
88
- directly by your root), upload the **intermediate** itself as the CA not the
89
- root. The client certs used must be directly signed by the CA certificate you
90
- provide to Zuplo.
94
+ directly by your root), upload the root CA certificate that anchors the chain.
95
+ Clients must send the leaf certificate plus any intermediate certificates when
96
+ they connect.
91
97
 
92
98
  :::
93
99
 
94
- ## 2/ Add the mTLS Auth Inbound Policy
100
+ ## 2/ Add the mTLS auth inbound policy
95
101
 
96
- Add the [`mtls-auth-inbound`](../policies/mtls-auth-inbound.md) policy to any
102
+ Add the [`mtls-auth-inbound`](../policies/mtls-auth-inbound.mdx) policy to any
97
103
  route that should require a verified client certificate. The policy reads the
98
104
  verification result that Zuplo's edge attached to the request and either rejects
99
105
  unverified traffic or allows it through, depending on configuration.
@@ -115,34 +121,35 @@ unverified traffic or allows it through, depending on configuration.
115
121
 
116
122
  **Key options:**
117
123
 
118
- - `allowUnauthenticatedRequests` (default `false`): When `false`, the policy
119
- rejects requests that don't present a valid client certificate signed by a CA
120
- on your account. When `true`, the policy lets traffic through but still
121
- attaches certificate metadata when a parseable client certificate is present —
122
- useful for staged rollouts or logging-only modes.
124
+ - `allowUnauthenticatedRequests` (default `false`): When set to `false`, the
125
+ policy rejects requests that don't present a valid client certificate signed
126
+ by a CA on your account. When set to `true`, the policy lets traffic through
127
+ but still attaches certificate metadata when a parseable client certificate is
128
+ present, which is useful for staged rollouts or logging-only modes.
123
129
  - `certIssuerDN`: The fully qualified issuer distinguished name that the client
124
- certificate must be signed by.
130
+ certificate must be signed by. This is the issuer DN on the client
131
+ certificate, which may be an intermediate CA when the client sends a chain.
125
132
 
126
- See the full [policy reference](../policies/mtls-auth-inbound.md) for all
133
+ See the full [policy reference](../policies/mtls-auth-inbound.mdx) for all
127
134
  options.
128
135
 
129
136
  :::tip{title="Finding your certIssuerDN value"}
130
137
 
131
- The issuer DN of a client certificate is the subject DN of the CA that signed
132
- it. Read it directly from your CA's PEM file with `openssl`:
138
+ The issuer DN is stored on the client certificate itself. Read it from a client
139
+ certificate that Zuplo should accept:
133
140
 
134
141
  ```bash
135
- openssl x509 -in ca.pem -noout -subject -nameopt RFC2253
142
+ openssl x509 -in client.pem -noout -issuer -nameopt RFC2253
136
143
  ```
137
144
 
138
- This prints something like `subject=CN=example-ca,O=Example,C=US`. Copy the part
139
- after `subject=` into `certIssuerDN`. The policy tolerates casing and whitespace
145
+ This prints something like `issuer=CN=example-ca,O=Example,C=US`. Copy the part
146
+ after `issuer=` into `certIssuerDN`. The policy tolerates casing and whitespace
140
147
  differences, but not RDN reordering, so keep the order produced by `openssl`
141
148
  as-is.
142
149
 
143
150
  :::
144
151
 
145
- ## 3/ Read Certificate Metadata in Your Handler
152
+ ## 3/ Read certificate metadata in your handler
146
153
 
147
154
  When verification succeeds, the policy attaches parsed certificate metadata to
148
155
  `request.user.data.mtlsAuth`. If `request.user` does not already exist, the
@@ -188,7 +195,7 @@ forward it to a backend.
188
195
 
189
196
  Once your CA is uploaded and the policy is on the route, you can verify the
190
197
  end-to-end flow with `curl`. You'll need a client certificate and private key
191
- issued by the CA you uploaded.
198
+ issued by, or chained to, the CA you uploaded.
192
199
 
193
200
  Send the certificate and key with `--cert` and `--key`:
194
201
 
@@ -201,11 +208,27 @@ Confirm that:
201
208
 
202
209
  - A request **without** `--cert` is rejected with `401` when
203
210
  `allowUnauthenticatedRequests` is `false`.
204
- - A request with a certificate signed by your uploaded CA succeeds and your
211
+ - A request with a certificate that chains to your uploaded CA succeeds and your
205
212
  handler sees the parsed certificate on `request.user.data.mtlsAuth`.
206
213
  - A request with a certificate signed by a different CA is rejected.
207
214
 
208
- ## Managing CA Certificates
215
+ :::tip{title="Using client certificates as part of a certificate chain"}
216
+
217
+ If your client certificates are issued by an intermediate CA (rather than
218
+ directly by your root), pass a certificate bundle to `curl` that includes the
219
+ leaf client certificate followed by any intermediate CA certificates. Do not
220
+ include the root CA in the client certificate bundle.
221
+
222
+ ```bash
223
+ cat client.pem intermediate.pem > client-chain.pem
224
+
225
+ curl --cert ./client-chain.pem --key ./client.key \
226
+ https://your-gateway.zuplo.app/v1/example
227
+ ```
228
+
229
+ :::
230
+
231
+ ## Manage CA certificates
209
232
 
210
233
  ### Listing CAs
211
234
 
@@ -270,7 +293,7 @@ the CA's subject DN, update `certIssuerDN` to match the new value before cutting
270
293
  clients over — or temporarily set `allowUnauthenticatedRequests: true` to allow
271
294
  both issuers during the transition.
272
295
 
273
- ## Local Development
296
+ ## Local development
274
297
 
275
298
  The `mtls-auth-inbound` policy relies on verification metadata supplied by
276
299
  Zuplo's edge proxy and does not work in local development with `zuplo dev`. Test
@@ -281,7 +304,9 @@ the policy in a working-copy or preview environment.
281
304
  ### Requests are rejected with 401
282
305
 
283
306
  - Confirm the client is presenting a certificate signed by a CA that's been
284
- uploaded with `zuplo ca-certificate list`.
307
+ uploaded with `zuplo ca-certificate list`. If the client certificate is issued
308
+ by an intermediate CA, confirm the client sends the intermediate certificate
309
+ chain.
285
310
  - If you've set `certIssuerDN`, verify it matches
286
311
  `request.user.data.mtlsAuth.issuer` exactly (casing and whitespace are
287
312
  tolerated, but RDN order is not).
@@ -306,10 +331,10 @@ that custom domain.
306
331
  If you add a custom domain later and your clients aren't being verified against
307
332
  it, contact [support@zuplo.com](mailto:support@zuplo.com).
308
333
 
309
- ## Additional Resources
334
+ ## Additional resources
310
335
 
311
- - [`mtls-auth-inbound` policy reference](../policies/mtls-auth-inbound.md)
312
- - [`ca-certificate` CLI reference](../cli/ca-certificate-create.md)
336
+ - [`mtls-auth-inbound` policy reference](../policies/mtls-auth-inbound.mdx)
337
+ - [`ca-certificate` CLI reference](../cli/ca-certificate-create.mdx)
313
338
  - [Gateway to Origin mTLS Authentication](./securing-backend-mtls.mdx) — the
314
339
  reverse direction, where Zuplo authenticates to your backend with a client
315
340
  certificate
@@ -9,7 +9,7 @@ sidebar_label: "1 - Setup Your Gateway"
9
9
  />
10
10
 
11
11
  In this tutorial we'll set up a simple gateway. We'll use a simple origin API at
12
- [getting-started.zuplo.io](https://getting-started.zuplo.io).
12
+ [echo.zuplo.io](https://echo.zuplo.io).
13
13
 
14
14
  Note - Zuplo also supports building and running your API locally. To learn more
15
15
  [see the documentation](./local-development.mdx).
@@ -78,8 +78,6 @@ Note - Zuplo also supports building and running your API locally. To learn more
78
78
  }
79
79
  ```
80
80
 
81
- A secret? Let's try and find out what this API is hiding!
82
-
83
81
  1. Put the base URL in an **Environment Variable**
84
82
 
85
83
  When working with Zuplo, you'll eventually want each
@@ -69,7 +69,7 @@ limiter.
69
69
  </ModalScreenshot>
70
70
 
71
71
  Your rate limiting policy is now intercepting excess requests, protecting the
72
- `getting-started` API.
72
+ echo API.
73
73
 
74
74
  </Stepper>
75
75
 
@@ -258,7 +258,7 @@ For CI/CD examples with other providers, see
258
258
 
259
259
  ## Writing tests
260
260
 
261
- Using Node.js 18 and the Zuplo CLI, it's very easy to write tests that make
261
+ Using Node.js and the Zuplo CLI, it's very easy to write tests that make
262
262
  requests to your API using `fetch` and then validate expectations with `expect`
263
263
  from [chai](https://www.chaijs.com/api/bdd/).
264
264
 
@@ -289,11 +289,15 @@ details.
289
289
 
290
290
  ### GET or HEAD requests with a body
291
291
 
292
- Sending a body with a `GET` or `HEAD` request results in a `GET_HEAD_BODY_ERROR`
293
- response. Some HTTP clients attach a body by default.
292
+ Zuplo removes the body from any `GET` or `HEAD` request on entry and adds a
293
+ `zp-body-removed: true` header so the backend knows the body was removed. The
294
+ request then proceeds as normal, so the symptom is a missing body at the backend
295
+ rather than an error response. Some HTTP clients attach a body by default.
294
296
 
295
297
  **Fix:** Remove the request body for `GET` and `HEAD` requests, or change the
296
- HTTP method to `POST` or `PUT` if a body is required.
298
+ HTTP method to `POST` or `PUT` if a body is required. See
299
+ [zp-body-removed](../programmable-api/zp-body-removed.mdx) for details,
300
+ including an example policy that rejects these requests instead.
297
301
 
298
302
  ## Getting help
299
303
 
@@ -24,19 +24,22 @@ your API Gateway. This is a good way to ensure that only Akamai can access your
24
24
  API Gateway. However, as Akamai is a multi-tenant service, this method isn't
25
25
  sufficient to protect unauthorized traffic from hitting your API Gateway.
26
26
 
27
- In Zuplo, you can utilize the IP Address Restriction policy to limit traffic to
28
- only the Akamai IP addresses. You don't need to provide the address list
29
- manually, instead you can utilize the built-in list as shown below.
27
+ In Zuplo, you can use the custom
28
+ [IP Restriction policy](../policies/ip-restriction-inbound.mdx) to limit traffic
29
+ to only the Akamai IP addresses. Copy the policy code from that page into a
30
+ module in your project (for example, `modules/ip-restriction-inbound.ts`), then
31
+ configure the policy with the IP ranges that Akamai publishes for your account
32
+ in Akamai Control Center.
30
33
 
31
34
  ```json
32
35
  {
33
36
  "name": "allow-akamai-only",
34
- "policyType": "ip-address-restriction-inbound",
37
+ "policyType": "ip-restriction-inbound",
35
38
  "handler": {
36
- "export": "IPAddressRestrictionInbound",
37
- "module": "$import(@zuplo/runtime)",
39
+ "export": "default",
40
+ "module": "$import(./modules/ip-restriction-inbound)",
38
41
  "options": {
39
- "allowedIpAddresses": ["list:akamai"]
42
+ "allowedIpAddresses": ["23.32.0.0/11", "104.64.0.0/10"]
40
43
  }
41
44
  }
42
45
  }
@@ -57,20 +60,36 @@ In Akamai, you can configure custom headers using the Property Manager or the
57
60
  Akamai API. Add a custom header with a secret value that only you and Akamai
58
61
  know.
59
62
 
60
- In Zuplo, you can utilize the Header Restriction policy to limit traffic to only
61
- those requests that include the custom header and secret value.
63
+ In Zuplo, you can use a small custom code policy to limit traffic to only those
64
+ requests that include the custom header and secret value.
65
+
66
+ ```ts title="modules/require-akamai-header.ts"
67
+ import {
68
+ environment,
69
+ HttpProblems,
70
+ ZuploContext,
71
+ ZuploRequest,
72
+ } from "@zuplo/runtime";
73
+
74
+ export default async function policy(
75
+ request: ZuploRequest,
76
+ context: ZuploContext,
77
+ ) {
78
+ const headerValue = request.headers.get("x-akamai-auth");
79
+ if (!headerValue || headerValue !== environment.AKAMAI_SECRET_HEADER_VALUE) {
80
+ return HttpProblems.unauthorized(request, context);
81
+ }
82
+ return request;
83
+ }
84
+ ```
62
85
 
63
86
  ```json
64
87
  {
65
88
  "name": "allow-akamai-custom-header",
66
- "policyType": "require-header-inbound",
89
+ "policyType": "custom-code-inbound",
67
90
  "handler": {
68
- "export": "RequireHeaderInboundPolicy",
69
- "module": "$import(@zuplo/runtime)",
70
- "options": {
71
- "headerName": "x-akamai-auth",
72
- "allowedValues": ["$env(AKAMAI_SECRET_HEADER_VALUE)"]
73
- }
91
+ "export": "default",
92
+ "module": "$import(./modules/require-akamai-header)"
74
93
  }
75
94
  }
76
95
  ```
@@ -26,19 +26,22 @@ way to ensure that only CloudFront can access your API Gateway. However, as
26
26
  CloudFront is available to any AWS customer, this method isn't sufficient to
27
27
  protect unauthorized traffic from hitting your API Gateway.
28
28
 
29
- In Zuplo, you can utilize the IP Address Restriction policy to limit traffic to
30
- only the CloudFront IP addresses. You don't need to provide the address list
31
- manually, instead you can utilize the built-in list as shown below.
29
+ In Zuplo, you can use the custom
30
+ [IP Restriction policy](../policies/ip-restriction-inbound.mdx) to limit traffic
31
+ to only the CloudFront IP addresses. Copy the policy code from that page into a
32
+ module in your project (for example, `modules/ip-restriction-inbound.ts`), then
33
+ configure the policy with the `CLOUDFRONT` ranges from the
34
+ [AWS IP address ranges list](https://ip-ranges.amazonaws.com/ip-ranges.json).
32
35
 
33
36
  ```json
34
37
  {
35
38
  "name": "allow-cloudfront-only",
36
- "policyType": "ip-address-restriction-inbound",
39
+ "policyType": "ip-restriction-inbound",
37
40
  "handler": {
38
- "export": "IPAddressRestrictionInbound",
39
- "module": "$import(@zuplo/runtime)",
41
+ "export": "default",
42
+ "module": "$import(./modules/ip-restriction-inbound)",
40
43
  "options": {
41
- "allowedIpAddresses": ["list:aws-cloudfront"]
44
+ "allowedIpAddresses": ["13.32.0.0/15", "13.35.0.0/16"]
42
45
  }
43
46
  }
44
47
  }
@@ -55,20 +58,36 @@ checked by your API Gateway. This provides an additional layer of security on
55
58
  top of IP address restrictions and prevents any unauthorized traffic from
56
59
  hitting your API Gateway - regardless of the source.
57
60
 
58
- In Zuplo, you can utilize the Header Restriction policy to limit traffic to only
59
- those requests that include the custom header and secret value.
61
+ In Zuplo, you can use a small custom code policy to limit traffic to only those
62
+ requests that include the custom header and secret value.
63
+
64
+ ```ts title="modules/require-secure-header.ts"
65
+ import {
66
+ environment,
67
+ HttpProblems,
68
+ ZuploContext,
69
+ ZuploRequest,
70
+ } from "@zuplo/runtime";
71
+
72
+ export default async function policy(
73
+ request: ZuploRequest,
74
+ context: ZuploContext,
75
+ ) {
76
+ const headerValue = request.headers.get("secure-header");
77
+ if (!headerValue || headerValue !== environment.MY_SECRET_HEADER_VALUE) {
78
+ return HttpProblems.unauthorized(request, context);
79
+ }
80
+ return request;
81
+ }
82
+ ```
60
83
 
61
84
  ```json
62
85
  {
63
86
  "name": "allow-cloudfront-custom-header",
64
- "policyType": "require-header-inbound",
87
+ "policyType": "custom-code-inbound",
65
88
  "handler": {
66
- "export": "RequireHeaderInboundPolicy",
67
- "module": "$import(@zuplo/runtime)",
68
- "options": {
69
- "headerName": "secure-header",
70
- "allowedValues": ["$env(MY_SECRET_HEADER_VALUE)"]
71
- }
89
+ "export": "default",
90
+ "module": "$import(./modules/require-secure-header)"
72
91
  }
73
92
  }
74
93
  ```
@@ -27,19 +27,22 @@ your API Gateway. This is a good way to ensure that only Fastly can access your
27
27
  API Gateway. However, as Fastly is a multi-tenant service, this method isn't
28
28
  sufficient to protect unauthorized traffic from hitting your API Gateway.
29
29
 
30
- In Zuplo, you can utilize the IP Address Restriction policy to limit traffic to
31
- only the Fastly IP addresses. You don't need to provide the address list
32
- manually, instead you can utilize the built-in list as shown below.
30
+ In Zuplo, you can use the custom
31
+ [IP Restriction policy](../policies/ip-restriction-inbound.mdx) to limit traffic
32
+ to only the Fastly IP addresses. Copy the policy code from that page into a
33
+ module in your project (for example, `modules/ip-restriction-inbound.ts`), then
34
+ configure the policy with the address ranges from
35
+ [Fastly's public IP list](https://api.fastly.com/public-ip-list).
33
36
 
34
37
  ```json
35
38
  {
36
39
  "name": "allow-fastly-only",
37
- "policyType": "ip-address-restriction-inbound",
40
+ "policyType": "ip-restriction-inbound",
38
41
  "handler": {
39
- "export": "IPAddressRestrictionInbound",
40
- "module": "$import(@zuplo/runtime)",
42
+ "export": "default",
43
+ "module": "$import(./modules/ip-restriction-inbound)",
41
44
  "options": {
42
- "allowedIpAddresses": ["list:fastly"]
45
+ "allowedIpAddresses": ["151.101.0.0/16", "199.232.0.0/16"]
43
46
  }
44
47
  }
45
48
  }