zuplo 7.4.1 → 7.4.3
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/api-management/introduction.md +1 -1
- package/docs/articles/api-key-api.mdx +3 -3
- package/docs/articles/custom-ci-cd-azure.mdx +1 -1
- package/docs/articles/custom-ci-cd-bitbucket.mdx +2 -2
- package/docs/articles/custom-code-patterns.md +1 -1
- package/docs/articles/custom-domains.mdx +2 -2
- package/docs/articles/development-options.mdx +4 -4
- package/docs/articles/fastly-zuplo-host-setup.mdx +5 -5
- package/docs/articles/feature-flags.mdx +442 -0
- package/docs/articles/gke-with-upstream-auth-policy.mdx +7 -7
- package/docs/articles/health-checks.mdx +4 -4
- package/docs/articles/hosting-options.mdx +2 -2
- package/docs/articles/local-development-env-variables.mdx +1 -1
- package/docs/articles/metrics-plugins.mdx +1 -1
- package/docs/articles/migration-overview.md +1 -1
- package/docs/articles/monorepo-deployment.mdx +10 -10
- package/docs/articles/performance-testing.mdx +10 -9
- package/docs/articles/plugin-azure-blob.mdx +1 -1
- package/docs/articles/plugin-azure-event-hubs.mdx +2 -2
- package/docs/articles/policies.mdx +2 -2
- package/docs/articles/securing-backend-mtls.mdx +3 -3
- package/docs/articles/securing-the-gateway-with-client-mtls.mdx +3 -3
- package/docs/articles/securing-your-backend.mdx +1 -1
- package/docs/articles/security.mdx +1 -1
- package/docs/articles/terraform.mdx +2 -2
- package/docs/articles/testing.mdx +6 -6
- package/docs/articles/troubleshooting-slow-responses.mdx +5 -6
- package/docs/articles/troubleshooting.md +1 -1
- package/docs/articles/use-openapi-extension-data.mdx +4 -4
- package/docs/articles/waf-ddos-akamai.md +13 -13
- package/docs/articles/waf-ddos-aws-waf-shield.mdx +14 -14
- package/docs/articles/waf-ddos-fastly.mdx +14 -14
- package/docs/articles/waf-ddos.mdx +7 -7
- package/docs/cli/authentication.mdx +1 -1
- package/docs/cli/lint.mdx +103 -0
- package/docs/concepts/how-zuplo-works.mdx +5 -4
- package/docs/concepts/upstream-credentials.mdx +1 -1
- package/docs/dedicated/akamai/architecture.mdx +23 -24
- package/docs/dedicated/akamai/cdn.mdx +20 -20
- package/docs/dedicated/architecture.mdx +28 -27
- package/docs/dedicated/custom-domains.mdx +3 -4
- package/docs/dedicated/federated-gateways.mdx +1 -1
- package/docs/dedicated/networking.mdx +10 -10
- package/docs/dedicated/overview.mdx +3 -3
- package/docs/dedicated/source-control.mdx +6 -5
- package/docs/handlers/custom-handler.mdx +4 -4
- package/docs/handlers/mcp-server.mdx +1 -1
- package/docs/handlers/url-forward.mdx +1 -2
- package/docs/mcp-server/introduction.mdx +4 -4
- package/docs/policies/ai-gateway-dlp-inbound/doc.md +79 -45
- package/docs/policies/ai-gateway-dlp-inbound/schema.json +5679 -3359
- package/docs/programmable-api/environment.mdx +1 -1
- package/docs/programmable-api/jwt-service-plugin.mdx +1 -1
- package/docs/programmable-api/logger.mdx +1 -1
- package/docs/programmable-api/runtime-errors.mdx +1 -1
- package/docs/programmable-api/zuplo-context.mdx +3 -3
- package/docs/self-hosted/overview.md +3 -3
- package/package.json +5 -5
|
@@ -3,7 +3,7 @@ title: Zuplo API Management
|
|
|
3
3
|
sidebar_label: Introduction
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
The Zuplo
|
|
6
|
+
The Zuplo Gateway is a fully-managed, lightweight API management platform
|
|
7
7
|
designed for developers. It offers fast deployment, GitOps-friendly workflows,
|
|
8
8
|
and unlimited environments. Whether you're an individual developer or part of an
|
|
9
9
|
engineering team, Zuplo makes it easy to:
|
|
@@ -45,8 +45,8 @@ Consumers can contain one or more API Keys.
|
|
|
45
45
|
|
|
46
46
|
Buckets are the top level group for this service. A bucket could be used with a
|
|
47
47
|
single Zuplo environment or shared among multiple environments or projects. By
|
|
48
|
-
default a Zuplo
|
|
49
|
-
|
|
48
|
+
default a Zuplo Gateway project will be created with several buckets that map to
|
|
49
|
+
production, preview, and development (working copy) environments.
|
|
50
50
|
|
|
51
51
|
Enterprise plan customers run complex configurations where buckets are shared
|
|
52
52
|
across gateway projects or even accounts. This can allow your end-users to
|
|
@@ -148,7 +148,7 @@ The response will look like this:
|
|
|
148
148
|
}
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
You can use this API Key to call your Zuplo
|
|
151
|
+
You can use this API Key to call your Zuplo Gateway that's protected by the
|
|
152
152
|
[API Key Authentication](/docs/policies/api-key-inbound) policy.
|
|
153
153
|
|
|
154
154
|
### Query Consumers with API Keys By Tags
|
|
@@ -3,7 +3,7 @@ title: "Azure Pipelines"
|
|
|
3
3
|
sidebar_label: Azure Pipelines
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Azure Pipelines brings enterprise-grade CI/CD to your Zuplo
|
|
6
|
+
Azure Pipelines brings enterprise-grade CI/CD to your Zuplo Gateway. Build
|
|
7
7
|
complex deployment workflows with stages, approvals, and integrations across
|
|
8
8
|
your Azure ecosystem.
|
|
9
9
|
|
|
@@ -4,8 +4,8 @@ sidebar_label: Bitbucket Pipelines
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Bitbucket Pipelines brings CI/CD directly into your Bitbucket workflow. Define
|
|
7
|
-
pipelines in `bitbucket-pipelines.yml` and deploy your Zuplo
|
|
8
|
-
|
|
7
|
+
pipelines in `bitbucket-pipelines.yml` and deploy your Zuplo Gateway alongside
|
|
8
|
+
your code reviews and pull requests.
|
|
9
9
|
|
|
10
10
|
## Why Bitbucket Pipelines with Zuplo?
|
|
11
11
|
|
|
@@ -5,7 +5,7 @@ sidebar_label: Custom Code
|
|
|
5
5
|
|
|
6
6
|
Zuplo is fully programmable. You can write custom TypeScript code for inbound
|
|
7
7
|
policies, outbound policies, and request handlers to extend every part of your
|
|
8
|
-
|
|
8
|
+
Zuplo Gateway. This guide covers the function signatures, common patterns, and
|
|
9
9
|
best practices for writing custom code.
|
|
10
10
|
|
|
11
11
|
## Custom Inbound Policy
|
|
@@ -208,8 +208,8 @@ enabled. If you wish to upgrade to a higher TLS version, please contact
|
|
|
208
208
|
|
|
209
209
|
<EnterpriseFeature name="Alias Domains" />
|
|
210
210
|
|
|
211
|
-
Sometimes your
|
|
212
|
-
CDN, WAF or load balancer. In this case, your
|
|
211
|
+
Sometimes your Zuplo Gateway might be running behind another service such as a
|
|
212
|
+
CDN, WAF or load balancer. In this case, your Zuplo Gateway's domain may not be
|
|
213
213
|
the same as the domain your clients use to access your API. In this case, you
|
|
214
214
|
can add an alias domain to your Zuplo project. An alias domain will configure
|
|
215
215
|
your API and documentation to use the alias domain for any public facing URLs
|
|
@@ -8,9 +8,9 @@ flexible and fastest way to work with your Zuplo API and Developer Portal.
|
|
|
8
8
|
|
|
9
9
|
## Local Development
|
|
10
10
|
|
|
11
|
-
The [local experience](./local-development.mdx) allows users to run both the
|
|
12
|
-
Gateway and Dev Portal locally. It also includes a local version of the
|
|
13
|
-
Designer that's integrated into the Zuplo Portal (portal.zuplo.com). Local
|
|
11
|
+
The [local experience](./local-development.mdx) allows users to run both the
|
|
12
|
+
Zuplo Gateway and Dev Portal locally. It also includes a local version of the
|
|
13
|
+
Route Designer that's integrated into the Zuplo Portal (portal.zuplo.com). Local
|
|
14
14
|
development allows customers to use the IDE of their choice and run everything
|
|
15
15
|
locally.
|
|
16
16
|
|
|
@@ -20,7 +20,7 @@ iterate on their APIs without needing to deploy to the cloud.
|
|
|
20
20
|
|
|
21
21
|
Local development is the fastest way to develop and test your APIs as you can
|
|
22
22
|
test changes immediately without waiting for a deployment. Changes to your API
|
|
23
|
-
are automatically loaded into the local
|
|
23
|
+
are automatically loaded into the local Zuplo Gateway and Dev Portal.
|
|
24
24
|
|
|
25
25
|
## Web-Based Development Experience
|
|
26
26
|
|
|
@@ -8,7 +8,7 @@ necessary setup for most people as Zuplo already runs at the edge and can be
|
|
|
8
8
|
used for CDN like caching. However, there are some scenarios where you may want
|
|
9
9
|
to put Zuplo behind Fastly. For example, if you are using Fastly's
|
|
10
10
|
[WAF or DDoS protection](./waf-ddos-fastly.mdx) and want to ensure that all
|
|
11
|
-
traffic goes through Fastly before hitting your Zuplo
|
|
11
|
+
traffic goes through Fastly before hitting your Zuplo Gateway.
|
|
12
12
|
|
|
13
13
|
## Configuring Zuplo as a Fastly Host
|
|
14
14
|
|
|
@@ -16,15 +16,15 @@ The following settings will allow you to run Zuplo as a host behind Fastly.
|
|
|
16
16
|
|
|
17
17
|
### Zuplo Configuration
|
|
18
18
|
|
|
19
|
-
It's recommended that you use a custom domain for your Zuplo
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
It's recommended that you use a custom domain for your Zuplo Gateway. This will
|
|
20
|
+
allow you to more configure your Fastly host as well as ensure that your domain
|
|
21
|
+
is stable regardless of your Zuplo configuration.
|
|
22
22
|
|
|
23
23
|
### Fastly Configuration
|
|
24
24
|
|
|
25
25
|
1. Create a new Fastly CDN service or use an existing one.
|
|
26
26
|
2. Create a new Origin and name it whatever you like.
|
|
27
|
-
3. Set the **Address** to your Zuplo
|
|
27
|
+
3. Set the **Address** to your Zuplo Gateway domain. For example,
|
|
28
28
|
`api.example.com` if using a custom domain, or
|
|
29
29
|
`my-project-main-021839d.zuplo.app` without a custom domain.
|
|
30
30
|
4. Enable TLS and keep the port as `443`.
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Feature Flags to Gate Your API
|
|
3
|
+
sidebar_label: Feature Flags & Gating
|
|
4
|
+
description:
|
|
5
|
+
Evaluate feature flags from providers like Statsig, PostHog, Flagsmith, and
|
|
6
|
+
GrowthBook inside Zuplo policies. Cache per-user evaluations for low latency
|
|
7
|
+
and load global flags in the background so they never block a request.
|
|
8
|
+
tags:
|
|
9
|
+
- integrations
|
|
10
|
+
- custom-code
|
|
11
|
+
- caching
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Zuplo doesn't have a dedicated feature-flag policy — instead, every provider
|
|
15
|
+
works, because you evaluate flags in custom code. This guide shows the patterns
|
|
16
|
+
that keep flag checks fast on a gateway, with worked examples for Statsig,
|
|
17
|
+
PostHog, Flagsmith, and GrowthBook:
|
|
18
|
+
|
|
19
|
+
- **Live evaluation with caching** — call the provider's API on the request and
|
|
20
|
+
cache the result per user for a short TTL. Use for flags targeted at
|
|
21
|
+
individual users, plans, or tenants.
|
|
22
|
+
- **Background loading** — load flags that apply globally (kill switches,
|
|
23
|
+
maintenance mode, flag definitions) with the
|
|
24
|
+
[BackgroundLoader](../programmable-api/background-loader.mdx), which refreshes
|
|
25
|
+
out of band instead of blocking requests.
|
|
26
|
+
- **Provider SDK with local evaluation** — evaluate flags in-process with no
|
|
27
|
+
per-request provider call, at the cost of a heavier cold start. See
|
|
28
|
+
[Statsig, fully local](#statsig-fully-local).
|
|
29
|
+
|
|
30
|
+
## Choose a pattern
|
|
31
|
+
|
|
32
|
+
| Pattern | Best for | Latency cost per request |
|
|
33
|
+
| ---------------------------------- | -------------------------------------------------- | --------------------------------------------------- |
|
|
34
|
+
| Live evaluation + cache | Per-user or per-plan targeting | One provider call per user per TTL window |
|
|
35
|
+
| Background loading | Global flags, kill switches, flag definition files | None in the steady state; refreshes run out of band |
|
|
36
|
+
| Provider SDK with local evaluation | Complex targeting rules, many gates per request | None after initialization, but heavier cold start |
|
|
37
|
+
|
|
38
|
+
Most APIs mix these: a background-loaded global flag set plus a small number of
|
|
39
|
+
cached per-user checks on the routes that need them.
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
- A Zuplo project with an authentication policy on the routes you want to gate
|
|
44
|
+
(for example [API key authentication](../policies/api-key-inbound.mdx)). The
|
|
45
|
+
examples read `request.user`, which an inbound authentication policy populates
|
|
46
|
+
— `request.user.sub` is the user identifier and `request.user.data` carries
|
|
47
|
+
JWT claims or API key metadata such as the customer's `plan`. See
|
|
48
|
+
[Request User](../programmable-api/request-user.mdx).
|
|
49
|
+
- An API key or token from your flag provider, stored as a secret
|
|
50
|
+
[environment variable](./environment-variables.mdx). Where to find each one:
|
|
51
|
+
|
|
52
|
+
| Variable | Provider | Where to find it | Secret? |
|
|
53
|
+
| --------------------------- | ---------- | -------------------------------------------------- | ------- |
|
|
54
|
+
| `STATSIG_SERVER_KEY` | Statsig | Console → Settings → API Keys → Server Secret Key | Yes |
|
|
55
|
+
| `STATSIG_CLIENT_KEY` | Statsig | Console → Settings → API Keys → Client SDK Key | No |
|
|
56
|
+
| `POSTHOG_PROJECT_TOKEN` | PostHog | Project Settings → Project ID → project token | No |
|
|
57
|
+
| `FLAGSMITH_ENVIRONMENT_KEY` | Flagsmith | Environment settings → client-side environment key | No |
|
|
58
|
+
| `GROWTHBOOK_CLIENT_KEY` | GrowthBook | SDK Connections → client key | No |
|
|
59
|
+
|
|
60
|
+
New environment variables require a redeployment before your code sees them.
|
|
61
|
+
|
|
62
|
+
You only need the variables for the providers you use — skip the rest.
|
|
63
|
+
|
|
64
|
+
## Pattern 1: Live evaluation with caching
|
|
65
|
+
|
|
66
|
+
Every major provider exposes an HTTP endpoint that evaluates a flag for a user
|
|
67
|
+
and returns the result. Call it on the first request, then cache the result in a
|
|
68
|
+
[MemoryZoneReadThroughCache](../programmable-api/memory-zone-read-through-cache.mdx)
|
|
69
|
+
so subsequent requests for the same user skip the network call.
|
|
70
|
+
|
|
71
|
+
This example checks a Statsig gate and caches the result for 60 seconds:
|
|
72
|
+
|
|
73
|
+
```ts title="modules/feature-gate-policy.ts"
|
|
74
|
+
import {
|
|
75
|
+
HttpProblems,
|
|
76
|
+
MemoryZoneReadThroughCache,
|
|
77
|
+
ZuploContext,
|
|
78
|
+
ZuploRequest,
|
|
79
|
+
environment,
|
|
80
|
+
} from "@zuplo/runtime";
|
|
81
|
+
|
|
82
|
+
const CACHE_NAME = "feature-flags";
|
|
83
|
+
const CACHE_TTL_SECONDS = 60;
|
|
84
|
+
|
|
85
|
+
async function isGateEnabled(
|
|
86
|
+
gateName: string,
|
|
87
|
+
userId: string,
|
|
88
|
+
context: ZuploContext,
|
|
89
|
+
): Promise<boolean> {
|
|
90
|
+
const cache = new MemoryZoneReadThroughCache<boolean>(CACHE_NAME, context);
|
|
91
|
+
const cacheKey = `statsig:${gateName}:${userId}`;
|
|
92
|
+
|
|
93
|
+
const cached = await cache.get(cacheKey);
|
|
94
|
+
if (cached !== undefined) {
|
|
95
|
+
return cached;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const response = await fetch("https://api.statsig.com/v1/check_gate", {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: {
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
"statsig-api-key": environment.STATSIG_SERVER_KEY,
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
gateName,
|
|
106
|
+
user: { userID: userId },
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
// Fail closed: if Statsig is unreachable, deny the feature rather
|
|
112
|
+
// than exposing something that isn't rolled out. Choose deliberately —
|
|
113
|
+
// see "Decide how to fail" below.
|
|
114
|
+
context.log.error(`Statsig check failed: ${response.status}`);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const result = await response.json();
|
|
119
|
+
const enabled = result.value === true;
|
|
120
|
+
cache.put(cacheKey, enabled, CACHE_TTL_SECONDS);
|
|
121
|
+
return enabled;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default async function policy(
|
|
125
|
+
request: ZuploRequest,
|
|
126
|
+
context: ZuploContext,
|
|
127
|
+
) {
|
|
128
|
+
const userId = request.user?.sub;
|
|
129
|
+
if (!userId) {
|
|
130
|
+
return HttpProblems.unauthorized(request, context);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const enabled = await isGateEnabled("new-beta-endpoint", userId, context);
|
|
134
|
+
if (!enabled) {
|
|
135
|
+
return HttpProblems.forbidden(request, context, {
|
|
136
|
+
detail: "This feature is not enabled for your account",
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return request;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Only the first request for each user pays for the Statsig call; every request
|
|
145
|
+
after that reads from memory until the TTL expires. Keep the TTL short (30–120
|
|
146
|
+
seconds) so flag changes in the provider propagate quickly. To check several
|
|
147
|
+
gates, fire the `check_gate` calls in parallel with `Promise.all` and cache each
|
|
148
|
+
result under its own key.
|
|
149
|
+
|
|
150
|
+
### Wire up the policy
|
|
151
|
+
|
|
152
|
+
Register the module as a custom code policy in `config/policies.json`:
|
|
153
|
+
|
|
154
|
+
```json title="config/policies.json"
|
|
155
|
+
{
|
|
156
|
+
"name": "feature-gate",
|
|
157
|
+
"policyType": "custom-code-inbound",
|
|
158
|
+
"handler": {
|
|
159
|
+
"export": "default",
|
|
160
|
+
"module": "$import(./modules/feature-gate-policy)"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Then add it to the inbound policy chain of each route you want to gate, after
|
|
166
|
+
authentication:
|
|
167
|
+
|
|
168
|
+
```json title="config/routes.oas.json"
|
|
169
|
+
{
|
|
170
|
+
"paths": {
|
|
171
|
+
"/beta/insights": {
|
|
172
|
+
"get": {
|
|
173
|
+
"x-zuplo-route": {
|
|
174
|
+
"policies": {
|
|
175
|
+
"inbound": ["api-key-auth", "feature-gate"]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Verify the gate
|
|
185
|
+
|
|
186
|
+
Call the route twice — once with the flag off and once on:
|
|
187
|
+
|
|
188
|
+
- **Flag off** — the request returns `403` with an `application/problem+json`
|
|
189
|
+
body: `"detail": "This feature is not enabled for your account"`.
|
|
190
|
+
- **Flag on** — the request passes through to the handler normally.
|
|
191
|
+
|
|
192
|
+
Because the example fails closed, a bad key or unreachable Statsig returns the
|
|
193
|
+
same 403 as a disabled flag. Watch the logs for the `Statsig check failed:`
|
|
194
|
+
error line to tell a provider outage apart from a legitimately denied request.
|
|
195
|
+
|
|
196
|
+
## Pattern 2: Background loading for global flags
|
|
197
|
+
|
|
198
|
+
Some flags apply to everyone: kill switches, maintenance mode, or a global
|
|
199
|
+
rollout. Others are cheapest to fetch as one shared definition file rather than
|
|
200
|
+
per-user evaluations. Either way, there's no reason to make a request wait on
|
|
201
|
+
that fetch — load it in the background instead.
|
|
202
|
+
|
|
203
|
+
The [BackgroundLoader](../programmable-api/background-loader.mdx) returns cached
|
|
204
|
+
data immediately when available, refreshes it asynchronously when the TTL
|
|
205
|
+
expires, and only blocks while the cache is empty on a cold start. Create it at
|
|
206
|
+
module level so the loader is shared across requests handled by the same worker
|
|
207
|
+
process ([isolate](../concepts/how-zuplo-works.mdx)).
|
|
208
|
+
|
|
209
|
+
GrowthBook is a natural fit here: its CDN returns the full feature definition
|
|
210
|
+
payload as a single cacheable JSON document.
|
|
211
|
+
|
|
212
|
+
```ts title="modules/global-flags-policy.ts"
|
|
213
|
+
import {
|
|
214
|
+
BackgroundLoader,
|
|
215
|
+
HttpProblems,
|
|
216
|
+
ZuploContext,
|
|
217
|
+
ZuploRequest,
|
|
218
|
+
environment,
|
|
219
|
+
} from "@zuplo/runtime";
|
|
220
|
+
|
|
221
|
+
interface GrowthBookFeatures {
|
|
222
|
+
[flagName: string]: { defaultValue: unknown };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const featureLoader = new BackgroundLoader<GrowthBookFeatures>(
|
|
226
|
+
async () => {
|
|
227
|
+
const response = await fetch(
|
|
228
|
+
`https://cdn.growthbook.io/api/features/${environment.GROWTHBOOK_CLIENT_KEY}`,
|
|
229
|
+
);
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
throw new Error(`Failed to load features: ${response.status}`);
|
|
232
|
+
}
|
|
233
|
+
const data = await response.json();
|
|
234
|
+
return data.features;
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
ttlSeconds: 60,
|
|
238
|
+
loaderTimeoutSeconds: 5,
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
export default async function policy(
|
|
243
|
+
request: ZuploRequest,
|
|
244
|
+
context: ZuploContext,
|
|
245
|
+
) {
|
|
246
|
+
// Returns cached definitions immediately in the steady state;
|
|
247
|
+
// the only blocking case is an empty cache on a cold start.
|
|
248
|
+
const features = await featureLoader.get("features");
|
|
249
|
+
|
|
250
|
+
if (features["api-maintenance-mode"]?.defaultValue === true) {
|
|
251
|
+
return HttpProblems.serviceUnavailable(request, context, {
|
|
252
|
+
detail: "This API is temporarily unavailable for maintenance",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return request;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Register this module as a `custom-code-inbound` policy in `config/policies.json`
|
|
261
|
+
and add it to the routes you want to gate, exactly as in pattern 1.
|
|
262
|
+
|
|
263
|
+
The same pattern works for any provider payload that isn't user-specific — for
|
|
264
|
+
example a Statsig gate evaluated against a fixed global user, or the Flagsmith
|
|
265
|
+
environment document. One BackgroundLoader per provider keeps each payload on
|
|
266
|
+
its own refresh cadence.
|
|
267
|
+
|
|
268
|
+
:::note
|
|
269
|
+
|
|
270
|
+
The `defaultValue` shortcut covers simple on/off flags. Flags with targeting
|
|
271
|
+
rules or percentage rollouts need an evaluator — either evaluate remotely
|
|
272
|
+
(pattern 1) or pass the definitions to the provider's JavaScript SDK.
|
|
273
|
+
|
|
274
|
+
:::
|
|
275
|
+
|
|
276
|
+
## More providers
|
|
277
|
+
|
|
278
|
+
The live-evaluation pattern is the same everywhere; only the endpoint, headers,
|
|
279
|
+
and response shape change. Swap the fetch call inside the cached helper — and,
|
|
280
|
+
for providers like PostHog that evaluate all flags in one call, cache the whole
|
|
281
|
+
map keyed by user instead of a boolean keyed by gate and user.
|
|
282
|
+
|
|
283
|
+
### PostHog
|
|
284
|
+
|
|
285
|
+
PostHog evaluates **all** flags for a user in one call, so cache the whole map
|
|
286
|
+
per `distinct_id`. Use your project token — it's safe for this public endpoint,
|
|
287
|
+
though you should still store it in an environment variable.
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
const response = await fetch("https://us.i.posthog.com/flags?v=2", {
|
|
291
|
+
method: "POST",
|
|
292
|
+
headers: { "Content-Type": "application/json" },
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
api_key: environment.POSTHOG_PROJECT_TOKEN,
|
|
295
|
+
distinct_id: userId,
|
|
296
|
+
person_properties: { plan: request.user?.data?.plan },
|
|
297
|
+
}),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const data = await response.json();
|
|
301
|
+
// Cache the whole map: cache.put(`posthog:${userId}`, data.flags, 60)
|
|
302
|
+
// data.flags["new-checkout"] = { key, enabled, variant, metadata: { payload } }
|
|
303
|
+
const enabled = data.flags?.["new-checkout"]?.enabled === true;
|
|
304
|
+
const variant = data.flags?.["new-checkout"]?.variant; // multivariate flags
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Use `https://eu.i.posthog.com` if your PostHog project is hosted in the EU.
|
|
308
|
+
|
|
309
|
+
### Flagsmith
|
|
310
|
+
|
|
311
|
+
Flagsmith's edge API evaluates flags for an identity with a simple GET. Use the
|
|
312
|
+
client-side environment key (not a server-side SDK key) — it's designed to be
|
|
313
|
+
public.
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
const response = await fetch(
|
|
317
|
+
`https://edge.api.flagsmith.com/api/v1/identities/?identifier=${encodeURIComponent(userId)}`,
|
|
318
|
+
{
|
|
319
|
+
headers: { "X-Environment-Key": environment.FLAGSMITH_ENVIRONMENT_KEY },
|
|
320
|
+
},
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const data = await response.json();
|
|
324
|
+
const flag = data.flags?.find((f) => f.feature.name === "new-checkout");
|
|
325
|
+
const enabled = flag?.enabled === true;
|
|
326
|
+
const value = flag?.feature_state_value; // remote config value
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Statsig, fully local
|
|
330
|
+
|
|
331
|
+
If you need many gates evaluated per request with zero provider calls, use
|
|
332
|
+
Statsig's `@statsig/serverless-client` SDK, which is built for short-lived
|
|
333
|
+
worker processes. Unlike the HTTP API examples above (which use the server
|
|
334
|
+
secret key), the serverless client evaluates on-device and authenticates with
|
|
335
|
+
the publishable client SDK key.
|
|
336
|
+
|
|
337
|
+
Zuplo runs a custom JavaScript engine rather than Node.js, so third-party
|
|
338
|
+
packages must be bundled into your project before you can import them — see
|
|
339
|
+
[Node Modules](../programmable-api/node-modules.mdx). Bundle the SDK once, then
|
|
340
|
+
import it from the bundled path:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
npm install @statsig/serverless-client
|
|
344
|
+
npx tsdown ./node_modules/@statsig/serverless-client --format esm --platform browser --out-dir ./modules/third-party/statsig
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Initialize the client once at module level so the configuration download happens
|
|
348
|
+
on cold start, not per request. Evaluate gates locally, and flush exposure
|
|
349
|
+
events with
|
|
350
|
+
[`context.waitUntil`](../programmable-api/zuplo-context.mdx#waituntil) so
|
|
351
|
+
analytics never add latency to a response:
|
|
352
|
+
|
|
353
|
+
```ts title="modules/statsig-local-policy.ts"
|
|
354
|
+
import {
|
|
355
|
+
HttpProblems,
|
|
356
|
+
ZuploContext,
|
|
357
|
+
ZuploRequest,
|
|
358
|
+
environment,
|
|
359
|
+
} from "@zuplo/runtime";
|
|
360
|
+
import { StatsigServerlessClient } from "./third-party/statsig/index.mjs";
|
|
361
|
+
|
|
362
|
+
const client = new StatsigServerlessClient(environment.STATSIG_CLIENT_KEY);
|
|
363
|
+
// Kicks off the config download once per worker process, not per request
|
|
364
|
+
const ready = client.initializeAsync();
|
|
365
|
+
|
|
366
|
+
export default async function policy(
|
|
367
|
+
request: ZuploRequest,
|
|
368
|
+
context: ZuploContext,
|
|
369
|
+
) {
|
|
370
|
+
await ready;
|
|
371
|
+
const enabled = client.checkGate("new-beta-endpoint", {
|
|
372
|
+
userID: request.user?.sub,
|
|
373
|
+
});
|
|
374
|
+
context.waitUntil(client.flush());
|
|
375
|
+
|
|
376
|
+
if (!enabled) {
|
|
377
|
+
return HttpProblems.forbidden(request, context, {
|
|
378
|
+
detail: "This feature is not enabled for your account",
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return request;
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### What about LaunchDarkly?
|
|
386
|
+
|
|
387
|
+
LaunchDarkly is the least HTTP-friendly option. Its server-side SDK holds a
|
|
388
|
+
persistent streaming connection open and relies on Node.js APIs that don't run
|
|
389
|
+
in Zuplo's engine, and LaunchDarkly doesn't document a plain-HTTP endpoint for
|
|
390
|
+
server-side evaluation. Its
|
|
391
|
+
[edge SDKs](https://launchdarkly.com/docs/sdk/server-side) evaluate locally but
|
|
392
|
+
assume platform-specific storage bindings (Cloudflare KV, Vercel Edge Config,
|
|
393
|
+
and similar), so they don't drop directly into a Zuplo project either. If
|
|
394
|
+
LaunchDarkly is your provider, the practical paths are evaluating flags in your
|
|
395
|
+
backend behind the gateway, or syncing flag state into a store your Zuplo code
|
|
396
|
+
reads with the patterns above.
|
|
397
|
+
|
|
398
|
+
## Gate an MCP server with flags
|
|
399
|
+
|
|
400
|
+
If you're fronting an MCP server with the
|
|
401
|
+
[MCP Gateway](../mcp-gateway/introduction.mdx), the same policies apply — MCP
|
|
402
|
+
routes run the standard inbound policy chain before the handler. Add your
|
|
403
|
+
feature-gate policy to the MCP route to control access per user or plan, for
|
|
404
|
+
example to make MCP access a paid-tier feature or to turn off a beta server.
|
|
405
|
+
|
|
406
|
+
For curating _which tools_ a server exposes, start with the static
|
|
407
|
+
[capability filtering](../mcp-gateway/capability-filtering.mdx) policy, which
|
|
408
|
+
needs no code. Feature flags complement it when the availability of the server
|
|
409
|
+
itself must change dynamically per user without a redeploy.
|
|
410
|
+
|
|
411
|
+
## Best practices
|
|
412
|
+
|
|
413
|
+
1. **Decide how to fail.** If the provider is unreachable, fail closed (deny the
|
|
414
|
+
feature) for rollouts of risky functionality, or fail open (allow) for
|
|
415
|
+
cosmetic features. The examples above fail closed and log the error.
|
|
416
|
+
2. **Keep per-user TTLs short.** 30–120 seconds balances provider traffic
|
|
417
|
+
against flag-change propagation. Global definitions can tolerate longer.
|
|
418
|
+
3. **Cache the smallest useful value.** A boolean per gate per user stays well
|
|
419
|
+
within memory limits; don't cache whole provider responses you don't need.
|
|
420
|
+
Remember a Zuplo process has roughly 120 MB of memory — see
|
|
421
|
+
[Lazy Load Configuration](./lazy-load-configuration-into-cache.mdx) for the
|
|
422
|
+
caching trade-offs.
|
|
423
|
+
4. **Store keys in environment variables.** Use secret
|
|
424
|
+
[environment variables](./environment-variables.mdx) for server keys like
|
|
425
|
+
Statsig's server secret; never hardcode them.
|
|
426
|
+
5. **Log exposures in the background.** Providers use exposure events for
|
|
427
|
+
analytics and experiments. Send them with
|
|
428
|
+
[`context.waitUntil`](../programmable-api/zuplo-context.mdx#waituntil) (as in
|
|
429
|
+
the Statsig SDK example) so analytics never add latency to a request.
|
|
430
|
+
|
|
431
|
+
## Next steps
|
|
432
|
+
|
|
433
|
+
- [BackgroundLoader](../programmable-api/background-loader.mdx) — the primitive
|
|
434
|
+
behind pattern 2
|
|
435
|
+
- [MemoryZoneReadThroughCache](../programmable-api/memory-zone-read-through-cache.mdx)
|
|
436
|
+
and [ZoneCache](../programmable-api/zone-cache.mdx) — the caching layers
|
|
437
|
+
- [Custom code policies](../policies/custom-code-inbound.mdx) — how inbound
|
|
438
|
+
policies are wired and run
|
|
439
|
+
- [Lazy Load Configuration](./lazy-load-configuration-into-cache.mdx) — the
|
|
440
|
+
general config-caching pattern this guide builds on
|
|
441
|
+
- [Curate the tools an upstream exposes](../mcp-gateway/how-to/curate-tools.mdx)
|
|
442
|
+
— static MCP tool curation to combine with flags
|
|
@@ -3,12 +3,12 @@ title: Secure a GCP Backend with Zuplo Upstream Auth
|
|
|
3
3
|
sidebar_label: GCP Backend
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
When using any
|
|
7
|
-
traffic that originates from the
|
|
6
|
+
When using any gateway as your API's entry point, it's critical that only
|
|
7
|
+
traffic that originates from the gateway is allowed to call your backend. There
|
|
8
8
|
are [many way to this](./securing-your-backend.mdx) depending on your
|
|
9
9
|
requirements and how your backend is hosted. This article will explain how to
|
|
10
10
|
use Google IAM to secure a GKE cluster so that only requests made through your
|
|
11
|
-
Zuplo
|
|
11
|
+
Zuplo Gateway will be allowed to call your GKE ingress.
|
|
12
12
|
|
|
13
13
|
Using GCP IAM to authorize your Zuplo Gateway to make requests to your backend
|
|
14
14
|
utilizes Googles core IAM system known as
|
|
@@ -33,11 +33,11 @@ set up behind an HTTP Load Balancer can be used.
|
|
|
33
33
|
The diagram below shows how the end-to-end system interacts. The important steps
|
|
34
34
|
to this process are:
|
|
35
35
|
|
|
36
|
-
1. The client makes a request to your Zuplo
|
|
37
|
-
1. The Zuplo
|
|
36
|
+
1. The client makes a request to your Zuplo Gateway
|
|
37
|
+
1. The Zuplo Gateway enforces any policies (for example authentication,
|
|
38
38
|
authorization, rate limiting)
|
|
39
|
-
1. The Zuplo
|
|
40
|
-
|
|
39
|
+
1. The Zuplo Gateway proxies the request to the public IP address of your GCP
|
|
40
|
+
Load Balancer. Zuplo adds authorization information to the request that
|
|
41
41
|
identify the request as coming from Zuplo
|
|
42
42
|
1. GCP Identity Aware proxy validates that the request comes from an authorized
|
|
43
43
|
Service Account identity. Unauthorized requests are rejected.
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
title: Health Check Handler
|
|
3
3
|
sidebar_label: Health Checks
|
|
4
4
|
description:
|
|
5
|
-
Learn how to set up health check endpoints to monitor your
|
|
5
|
+
Learn how to set up health check endpoints to monitor your Zuplo Gateway and
|
|
6
6
|
backend services.
|
|
7
7
|
tags:
|
|
8
8
|
- observability
|
|
9
9
|
- backends
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
Part of running a reliable
|
|
12
|
+
Part of running a reliable Zuplo Gateway is ensuring that the service is healthy
|
|
13
13
|
and available. With Zuplo, it's easy to set up a health check endpoint that can
|
|
14
14
|
be used to ensure the health of both your Zuplo Gateway and your backend (as
|
|
15
15
|
well as the connectivity between them).
|
|
16
16
|
|
|
17
|
-
A typical health check endpoint on your
|
|
17
|
+
A typical health check endpoint on your Zuplo Gateway will exercise some of the
|
|
18
18
|
most important policies (such as authentication) and then make a simple request
|
|
19
19
|
to your backend to ensure that it's reachable and functioning.
|
|
20
20
|
|
|
@@ -32,7 +32,7 @@ OpenAPI file this would look like:
|
|
|
32
32
|
"/health": {
|
|
33
33
|
"get": {
|
|
34
34
|
"summary": "Health Check",
|
|
35
|
-
"description": "Checks the health of the
|
|
35
|
+
"description": "Checks the health of the Zuplo Gateway and backend.",
|
|
36
36
|
"responses": {
|
|
37
37
|
"200": {
|
|
38
38
|
"description": "OK"
|
|
@@ -32,7 +32,7 @@ your service hosted to meet any sovereignty or data residency concerns.
|
|
|
32
32
|
|
|
33
33
|
Managed Dedicated is ideal for organizations that need:
|
|
34
34
|
|
|
35
|
-
- To run your
|
|
35
|
+
- To run your Zuplo Gateway on a specific cloud provider
|
|
36
36
|
- Custom networking configurations, such as restricting access to the public
|
|
37
37
|
internet
|
|
38
38
|
- Geographical deployment requirements where Managed Edge isn't feasible
|
|
@@ -50,7 +50,7 @@ Run Zuplo on your own infrastructure in any cloud or private data center. Zuplo
|
|
|
50
50
|
Self-Hosted runs exclusively on Kubernetes and is installed with a single Helm
|
|
51
51
|
chart into your cluster.
|
|
52
52
|
|
|
53
|
-
In the standard hybrid deployment model, you run the Zuplo
|
|
53
|
+
In the standard hybrid deployment model, you run the Zuplo Gateway and its
|
|
54
54
|
management plane on your Kubernetes cluster, while a small set of Zuplo cloud
|
|
55
55
|
services provides supporting features such as deployment configuration and API
|
|
56
56
|
key management. All API traffic to your gateways stays on your infrastructure.
|
|
@@ -5,7 +5,7 @@ sidebar_label: Environment variables
|
|
|
5
5
|
|
|
6
6
|
For security reasons, your local development doesn't have access to the
|
|
7
7
|
environment variables that you have configured on the Zuplo Portal. Instead,
|
|
8
|
-
your local Zuplo
|
|
8
|
+
your local Zuplo Gateway will load environment variables from a .env file.
|
|
9
9
|
|
|
10
10
|
1. Create a .env file in the root of your project.
|
|
11
11
|
2. Follow the following format
|
|
@@ -28,7 +28,7 @@ Zuplo supports the following metrics:
|
|
|
28
28
|
|
|
29
29
|
- request latency
|
|
30
30
|
- This measures the total time (in milliseconds) that a request takes once it
|
|
31
|
-
has entered the
|
|
31
|
+
has entered the Zuplo Gateway. It includes any outbound calls from the
|
|
32
32
|
gateway.
|
|
33
33
|
- request content length
|
|
34
34
|
- The content length of the request as reported by the content-length header.
|