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.
- package/docs/ai-gateway/getting-started.mdx +14 -9
- package/docs/ai-gateway/integrations/ai-sdk.mdx +17 -0
- package/docs/ai-gateway/introduction.mdx +12 -10
- package/docs/ai-gateway/providers.mdx +2 -0
- package/docs/analytics/access-and-entitlements.md +71 -0
- package/docs/analytics/overview.md +63 -0
- package/docs/analytics/reference/metrics-glossary.md +105 -0
- package/docs/analytics/reference/url-parameters.md +66 -0
- package/docs/analytics/shared-controls.md +121 -0
- package/docs/analytics/tabs/agents.md +88 -0
- package/docs/analytics/tabs/consumers.md +73 -0
- package/docs/analytics/tabs/graphql.md +77 -0
- package/docs/analytics/tabs/mcp.md +80 -0
- package/docs/analytics/tabs/origins.md +82 -0
- package/docs/analytics/tabs/requests.md +96 -0
- package/docs/articles/api-key-buckets.mdx +4 -2
- package/docs/articles/archiving-requests-to-storage.mdx +4 -4
- package/docs/articles/branch-based-deployments.mdx +10 -8
- package/docs/articles/ci-cd-github/basic-deployment.mdx +10 -1
- package/docs/articles/ci-cd-github/cleanup-on-branch-delete.mdx +52 -31
- package/docs/articles/ci-cd-github/deploy-and-test.mdx +14 -1
- package/docs/articles/ci-cd-github/local-testing.mdx +3 -1
- package/docs/articles/ci-cd-github/pr-preview-environments.mdx +53 -10
- package/docs/articles/custom-ci-cd-azure.mdx +1 -1
- package/docs/articles/custom-ci-cd-bitbucket.mdx +1 -1
- package/docs/articles/custom-ci-cd-circleci.mdx +1 -1
- package/docs/articles/custom-ci-cd-github.mdx +12 -3
- package/docs/articles/custom-ci-cd-gitlab.mdx +1 -1
- package/docs/articles/graphql.mdx +276 -0
- package/docs/articles/monetization/api-access.mdx +184 -0
- package/docs/articles/monetization/meters.mdx +4 -4
- package/docs/articles/monetization/monetization-policy.md +4 -1
- package/docs/articles/monetization/private-plans.md +3 -4
- package/docs/articles/monetization/stripe-integration.md +9 -0
- package/docs/articles/monetization/subscription-lifecycle.md +12 -11
- package/docs/articles/monorepo-deployment.mdx +37 -5
- package/docs/articles/opentelemetry.mdx +5 -2
- package/docs/articles/securing-the-gateway-with-client-mtls.mdx +68 -43
- package/docs/articles/step-1-setup-basic-gateway.mdx +1 -3
- package/docs/articles/step-2-add-rate-limiting.mdx +1 -1
- package/docs/articles/testing.mdx +1 -1
- package/docs/articles/troubleshooting.md +7 -3
- package/docs/articles/waf-ddos-akamai.md +35 -16
- package/docs/articles/waf-ddos-aws-waf-shield.mdx +35 -16
- package/docs/articles/waf-ddos-fastly.mdx +10 -7
- package/docs/cli/deploy.mdx +44 -9
- package/docs/cli/deploy.partial.mdx +44 -9
- package/docs/concepts/api-keys.md +2 -2
- package/docs/dev-portal/zudoku/components/callout.mdx +11 -18
- package/docs/dev-portal/zudoku/components/sidecar-box.mdx +131 -0
- package/docs/dev-portal/zudoku/configuration/api-catalog.md +62 -42
- package/docs/dev-portal/zudoku/configuration/api-reference.md +5 -4
- package/docs/dev-portal/zudoku/configuration/navigation.mdx +70 -7
- package/docs/dev-portal/zudoku/configuration/search.md +36 -0
- package/docs/dev-portal/zudoku/configuration/site.md +38 -0
- package/docs/dev-portal/zudoku/customization/colors-theme.mdx +51 -40
- package/docs/errors/rate-limit-exceeded.mdx +30 -3
- package/docs/guides/canary-routing-for-employees.mdx +103 -39
- package/docs/guides/modify-openapi-paths.mdx +3 -3
- package/docs/handlers/legacy-dev-portal-handler.mdx +1 -1
- package/docs/handlers/mcp-server.mdx +13 -11
- package/docs/handlers/url-forward.mdx +5 -1
- package/docs/handlers/url-rewrite.mdx +7 -2
- package/docs/handlers/websocket-handler.mdx +5 -1
- package/docs/mcp-gateway/observability/logging.mdx +19 -12
- package/docs/mcp-server/resources.mdx +27 -15
- package/docs/mcp-server/testing.mdx +0 -2
- package/docs/policies/_index.md +2 -0
- package/docs/policies/archive-request-azure-storage-inbound/doc.md +1 -1
- package/docs/policies/archive-response-azure-storage-outbound/doc.md +1 -1
- package/docs/policies/data-loss-prevention-inbound/doc.md +116 -0
- package/docs/policies/data-loss-prevention-inbound/intro.md +15 -0
- package/docs/policies/data-loss-prevention-inbound/schema.json +220 -0
- package/docs/policies/data-loss-prevention-outbound/doc.md +116 -0
- package/docs/policies/data-loss-prevention-outbound/intro.md +18 -0
- package/docs/policies/data-loss-prevention-outbound/schema.json +220 -0
- package/docs/policies/ip-restriction-inbound/policy.ts +1 -1
- package/docs/programmable-api/background-dispatcher.mdx +6 -8
- package/docs/programmable-api/http-problems.mdx +0 -18
- package/docs/programmable-api/jwt-service-plugin.mdx +131 -109
- package/docs/programmable-api/runtime-behaviors.mdx +4 -2
- package/docs/programmable-api/streaming-zone-cache.mdx +4 -6
- package/docs/programmable-api/web-crypto-apis.mdx +10 -6
- package/docs/programmable-api/zone-cache.mdx +1 -1
- package/docs/rate-limiting/combining-policies.mdx +293 -0
- package/docs/rate-limiting/dynamic-rate-limiting.mdx +240 -0
- package/docs/rate-limiting/getting-started.mdx +339 -0
- package/docs/rate-limiting/how-it-works.md +225 -0
- package/docs/rate-limiting/monitoring-and-troubleshooting.mdx +243 -0
- package/docs/{articles → rate-limiting}/per-user-rate-limits-using-db.mdx +39 -28
- package/package.json +4 -4
- package/docs/concepts/rate-limiting.md +0 -246
- 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
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
"
|
|
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`.
|
|
226
|
-
|
|
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.
|
|
345
|
-
|
|
346
|
-
|
|
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:
|
|
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
|
-
--
|
|
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,
|
|
226
|
-
|
|
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
|
|
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
|
|
9
|
-
|
|
10
|
-
the TLS handshake
|
|
11
|
-
|
|
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
|
|
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
|
|
18
|
-
2. Zuplo's edge verifies the certificate against the CA
|
|
19
|
-
|
|
20
|
-
workers.
|
|
21
|
-
3. The [`mtls-auth-inbound`](../policies/mtls-auth-inbound.
|
|
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
|
|
28
|
-
verify presented client
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
100
|
+
## 2/ Add the mTLS auth inbound policy
|
|
95
101
|
|
|
96
|
-
Add the [`mtls-auth-inbound`](../policies/mtls-auth-inbound.
|
|
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
|
|
119
|
-
rejects requests that don't present a valid client certificate signed
|
|
120
|
-
on your account. When `true`, the policy lets traffic through
|
|
121
|
-
attaches certificate metadata when a parseable client certificate is
|
|
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.
|
|
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
|
|
132
|
-
|
|
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
|
|
142
|
+
openssl x509 -in client.pem -noout -issuer -nameopt RFC2253
|
|
136
143
|
```
|
|
137
144
|
|
|
138
|
-
This prints something like `
|
|
139
|
-
after `
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
334
|
+
## Additional resources
|
|
310
335
|
|
|
311
|
-
- [`mtls-auth-inbound` policy reference](../policies/mtls-auth-inbound.
|
|
312
|
-
- [`ca-certificate` CLI reference](../cli/ca-certificate-create.
|
|
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
|
-
[
|
|
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
|
|
@@ -258,7 +258,7 @@ For CI/CD examples with other providers, see
|
|
|
258
258
|
|
|
259
259
|
## Writing tests
|
|
260
260
|
|
|
261
|
-
Using Node.js
|
|
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
|
-
|
|
293
|
-
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
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-
|
|
37
|
+
"policyType": "ip-restriction-inbound",
|
|
35
38
|
"handler": {
|
|
36
|
-
"export": "
|
|
37
|
-
"module": "$import(
|
|
39
|
+
"export": "default",
|
|
40
|
+
"module": "$import(./modules/ip-restriction-inbound)",
|
|
38
41
|
"options": {
|
|
39
|
-
"allowedIpAddresses": ["
|
|
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
|
|
61
|
-
|
|
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": "
|
|
89
|
+
"policyType": "custom-code-inbound",
|
|
67
90
|
"handler": {
|
|
68
|
-
"export": "
|
|
69
|
-
"module": "$import(
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
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-
|
|
39
|
+
"policyType": "ip-restriction-inbound",
|
|
37
40
|
"handler": {
|
|
38
|
-
"export": "
|
|
39
|
-
"module": "$import(
|
|
41
|
+
"export": "default",
|
|
42
|
+
"module": "$import(./modules/ip-restriction-inbound)",
|
|
40
43
|
"options": {
|
|
41
|
-
"allowedIpAddresses": ["
|
|
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
|
|
59
|
-
|
|
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": "
|
|
87
|
+
"policyType": "custom-code-inbound",
|
|
65
88
|
"handler": {
|
|
66
|
-
"export": "
|
|
67
|
-
"module": "$import(
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
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-
|
|
40
|
+
"policyType": "ip-restriction-inbound",
|
|
38
41
|
"handler": {
|
|
39
|
-
"export": "
|
|
40
|
-
"module": "$import(
|
|
42
|
+
"export": "default",
|
|
43
|
+
"module": "$import(./modules/ip-restriction-inbound)",
|
|
41
44
|
"options": {
|
|
42
|
-
"allowedIpAddresses": ["
|
|
45
|
+
"allowedIpAddresses": ["151.101.0.0/16", "199.232.0.0/16"]
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
}
|