zuplo 6.72.2 → 6.72.7
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/analytics/access-and-entitlements.md +27 -34
- package/docs/analytics/overview.md +7 -6
- package/docs/analytics/reference/url-parameters.md +2 -10
- package/docs/analytics/shared-controls.md +3 -10
- package/docs/analytics/tabs/agents.md +3 -3
- package/docs/analytics/tabs/requests.md +2 -4
- package/docs/articles/accounts/audit-logs.mdx +4 -4
- package/docs/articles/audit-logging.mdx +65 -0
- package/docs/articles/audit-logs.mdx +222 -0
- package/docs/articles/connect-to-aws-alb-with-mtls.mdx +89 -13
- package/docs/articles/{custom-audit-log-policy.mdx → custom-audit-logs.mdx} +26 -22
- package/docs/articles/graphql-security.mdx +2 -1
- package/docs/articles/graphql.mdx +8 -0
- package/docs/articles/opentelemetry.mdx +7 -5
- package/docs/articles/securing-backend-mtls.mdx +92 -3
- package/docs/articles/troubleshooting-slow-responses.mdx +4 -3
- package/docs/articles/troubleshooting.md +3 -1
- package/docs/policies/_index.md +3 -0
- package/docs/policies/audit-log-inbound/doc.md +16 -11
- package/docs/policies/authzen-inbound/schema.json +1 -1
- package/docs/policies/axiomatics-authz-inbound/schema.json +1 -1
- package/docs/policies/clerk-jwt-auth-inbound/schema.json +3 -3
- package/docs/policies/mcp-auth0-oauth-inbound/schema.json +8 -5
- package/docs/policies/mcp-clerk-oauth-inbound/schema.json +78 -0
- package/docs/policies/mcp-cognito-oauth-inbound/schema.json +78 -0
- package/docs/policies/mcp-entra-oauth-inbound/schema.json +78 -0
- package/docs/policies/mcp-google-oauth-inbound/schema.json +78 -0
- package/docs/policies/mcp-keycloak-oauth-inbound/schema.json +78 -0
- package/docs/policies/mcp-logto-oauth-inbound/schema.json +78 -0
- package/docs/policies/mcp-oauth-inbound/schema.json +8 -5
- package/docs/policies/mcp-okta-oauth-inbound/schema.json +78 -0
- package/docs/policies/mcp-onelogin-oauth-inbound/schema.json +78 -0
- package/docs/policies/mcp-ping-oauth-inbound/schema.json +78 -0
- package/docs/policies/mcp-token-exchange-inbound/doc.md +72 -4
- package/docs/policies/mcp-token-exchange-inbound/intro.md +5 -4
- package/docs/policies/mcp-token-exchange-inbound/schema.json +277 -2
- package/docs/policies/mcp-workos-oauth-inbound/schema.json +78 -0
- package/docs/policies/mtls-auth-inbound/intro.md +52 -5
- package/docs/policies/okta-fga-authz-inbound/schema.json +1 -1
- package/docs/policies/open-id-jwt-auth-inbound/schema.json +3 -3
- package/docs/policies/openfga-authz-inbound/schema.json +1 -1
- package/docs/policies/propel-auth-jwt-inbound/schema.json +3 -3
- package/docs/policies/require-user-claims-inbound/doc.md +120 -0
- package/docs/policies/require-user-claims-inbound/intro.md +9 -0
- package/docs/policies/require-user-claims-inbound/schema.json +1323 -0
- package/docs/policies/semantic-cache-inbound/schema.json +1 -1
- package/docs/policies/upstream-aws-federated-auth-inbound/doc.md +78 -0
- package/docs/policies/upstream-aws-federated-auth-inbound/intro.md +20 -0
- package/docs/policies/upstream-aws-federated-auth-inbound/schema.json +98 -0
- package/docs/policies/upstream-aws-service-auth-inbound/doc.md +89 -0
- package/docs/policies/upstream-aws-service-auth-inbound/intro.md +19 -0
- package/docs/policies/upstream-aws-service-auth-inbound/schema.json +113 -0
- package/docs/policies/upstream-azure-ad-service-auth-inbound/schema.json +4 -4
- package/docs/policies/upstream-gcp-federated-auth-inbound/doc.md +2 -2
- package/docs/policies/upstream-gcp-federated-auth-inbound/schema.json +5 -5
- package/docs/policies/upstream-gcp-service-auth-inbound/doc.md +3 -3
- package/docs/policies/upstream-gcp-service-auth-inbound/schema.json +3 -3
- package/docs/policies/upstream-zuplo-jwt-auth-inbound/schema.json +19 -0
- package/docs/programmable-api/overview.mdx +0 -7
- package/package.json +4 -4
- package/docs/programmable-api/audit-log.mdx +0 -74
|
@@ -7,56 +7,49 @@ sidebar_label: "Access & Entitlements"
|
|
|
7
7
|
|
|
8
8
|
- Confirm whether your account can see analytics.
|
|
9
9
|
- Find out how many days of history you have access to.
|
|
10
|
-
- Understand the trial banner
|
|
10
|
+
- Understand the trial banner.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Availability
|
|
13
13
|
|
|
14
|
-
Analytics
|
|
15
|
-
|
|
14
|
+
Analytics is included on every plan. Your plan determines how much history you
|
|
15
|
+
can query — see [Data retention](#data-retention). If the Analytics page shows
|
|
16
|
+
an upsell view with a **Contact Sales** call-to-action instead of charts, your
|
|
17
|
+
account has analytics disabled — contact your Zuplo account team.
|
|
16
18
|
|
|
17
19
|
## Free trial
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
Every new account gets an automatic 14-day observability trial. During the
|
|
22
|
+
trial:
|
|
20
23
|
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
- You get longer history windows for analytics, logs, and traces than your plan
|
|
25
|
+
includes. Accounts on self-serve plans get the Builder/Business windows (7
|
|
26
|
+
days of analytics, 1 day of logs and traces); Enterprise trial accounts get
|
|
27
|
+
the Enterprise windows. See [Data retention](#data-retention).
|
|
28
|
+
- A banner appears across the top of the Observability tabs: "You're on a 14-day
|
|
29
|
+
trial of Zuplo Observability — {N} days left", with a **Contact sales** link.
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
continue to use the previous experience.
|
|
31
|
+
When the trial ends, your plan's own history windows apply.
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
The trial banner notes that the charts may look sparse if your account hasn't
|
|
33
|
-
yet generated much traffic. Use **View demo →** to see what a fully populated
|
|
34
|
-
dashboard looks like.
|
|
33
|
+
## Data retention
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
Each account has history windows for analytics, logs, and traces. The windows
|
|
36
|
+
depend on your plan:
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
| Plan | Analytics history | Log & trace history |
|
|
39
|
+
| ------------------ | ----------------- | ------------------- |
|
|
40
|
+
| Free | 1 day | Live (15 minutes) |
|
|
41
|
+
| Builder / Business | 7 days | 1 day |
|
|
42
|
+
| Enterprise | 30 days | 3 days |
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
controls:
|
|
44
|
+
The analytics window controls:
|
|
42
45
|
|
|
43
46
|
- How far back you can scroll using the time-range picker.
|
|
44
47
|
- Which presets in the picker are available. Presets longer than your window are
|
|
45
|
-
locked
|
|
48
|
+
locked and show an **Upgrade** badge; hover one for details.
|
|
46
49
|
- The maximum start and end values when you pick a custom range.
|
|
47
50
|
|
|
48
|
-
If you need a longer window, contact your Zuplo account team
|
|
49
|
-
|
|
50
|
-
## Demo mode
|
|
51
|
-
|
|
52
|
-
Append `?demo=true` to the Analytics URL, or click **View demo →** in the trial
|
|
53
|
-
banner, to switch into demo mode. In demo mode:
|
|
54
|
-
|
|
55
|
-
- Charts and tables are populated with synthetic sample data.
|
|
56
|
-
- A persistent banner reads: "You're viewing the Analytics demo with sample
|
|
57
|
-
data. Your real analytics aren't shown here."
|
|
58
|
-
|
|
59
|
-
Remove the `demo` parameter from the URL to return to your real data.
|
|
51
|
+
If you need a longer window, contact your Zuplo account team — custom retention
|
|
52
|
+
is available on Enterprise plans.
|
|
60
53
|
|
|
61
54
|
## Scope: account vs project
|
|
62
55
|
|
|
@@ -32,7 +32,7 @@ Analytics lives in the **Observability** tab of the Zuplo Portal, alongside
|
|
|
32
32
|
## What's in this section
|
|
33
33
|
|
|
34
34
|
- [Access and entitlements](./access-and-entitlements.md): plans, free trial,
|
|
35
|
-
|
|
35
|
+
retention.
|
|
36
36
|
- [Shared controls](./shared-controls.md): time range, filters, environment
|
|
37
37
|
selector, banners, URL state.
|
|
38
38
|
- Sections:
|
|
@@ -56,12 +56,13 @@ You'll see a subset of sections depending on your plan and project setup:
|
|
|
56
56
|
|
|
57
57
|
| Section | When it appears |
|
|
58
58
|
| --------- | ---------------------------------------------------------- |
|
|
59
|
-
| Requests | All accounts
|
|
59
|
+
| Requests | All accounts. |
|
|
60
60
|
| Origins | The project uses managed-edge origins. |
|
|
61
|
-
| Consumers | All accounts
|
|
62
|
-
| Agents | All accounts
|
|
61
|
+
| Consumers | All accounts. |
|
|
62
|
+
| Agents | All accounts. |
|
|
63
63
|
| MCP | The project type is **standard** and the project uses MCP. |
|
|
64
64
|
| GraphQL | The project proxies a GraphQL API. |
|
|
65
65
|
|
|
66
|
-
If you don't see Analytics at all, your
|
|
67
|
-
|
|
66
|
+
Analytics is included on every plan. If you don't see Analytics at all, your
|
|
67
|
+
account may have it disabled — see
|
|
68
|
+
[Access and entitlements](./access-and-entitlements.md).
|
|
@@ -8,7 +8,7 @@ view.
|
|
|
8
8
|
|
|
9
9
|
## When to use this
|
|
10
10
|
|
|
11
|
-
- Build a permalink to a specific time window
|
|
11
|
+
- Build a permalink to a specific time window or filter set.
|
|
12
12
|
- Embed an Analytics link in a runbook, postmortem, or dashboard.
|
|
13
13
|
- Understand what each query parameter does.
|
|
14
14
|
|
|
@@ -16,11 +16,9 @@ view.
|
|
|
16
16
|
|
|
17
17
|
| Parameter | Example | Effect |
|
|
18
18
|
| -------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
|
|
19
|
-
| `time` | `?time=7d` | Apply a preset. Values: `1h`, `6h`, `24h`, `3d`, `7d`, `14d`, `28d`, `60d`, `90d`.
|
|
19
|
+
| `time` | `?time=7d` | Apply a preset. Values: `1h`, `6h`, `24h`, `3d`, `7d`, `14d`, `28d`, `30d`, `60d`, `90d`. |
|
|
20
20
|
| `start`, `end` | `?start=2026-05-01T00:00:00Z&end=2026-05-15T00:00:00Z` | Custom range as ISO-8601 datetimes. Overrides `time` when both are present. |
|
|
21
21
|
| `filter` | `?filter=httpStatus:class:5xx` | Add a filter as `<field>:<matchmode>:<value>`. Repeat the parameter for multiple filters. |
|
|
22
|
-
| `demo` | `?demo=true` | Demo mode (sample data instead of your real analytics). |
|
|
23
|
-
| `preview` | `?preview=1` | Legacy preview mode. |
|
|
24
22
|
|
|
25
23
|
## Match modes for `filter`
|
|
26
24
|
|
|
@@ -48,12 +46,6 @@ Custom range with two filters:
|
|
|
48
46
|
?start=2026-05-01T00:00:00Z&end=2026-05-08T00:00:00Z&filter=country:equals:US&filter=httpMethod:equals:POST
|
|
49
47
|
```
|
|
50
48
|
|
|
51
|
-
Open the demo:
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
?demo=true
|
|
55
|
-
```
|
|
56
|
-
|
|
57
49
|
## Sharing
|
|
58
50
|
|
|
59
51
|
The recipient sees the same view, provided they have access to the project or
|
|
@@ -18,7 +18,7 @@ State persists to the URL so you can share or bookmark any view.
|
|
|
18
18
|
The time range picker controls every chart, table, and KPI in the active
|
|
19
19
|
section.
|
|
20
20
|
|
|
21
|
-
**Presets.** Last 1h, 6h, 24h, 3d, 7d, 14d, 28d, 60d, 90d.
|
|
21
|
+
**Presets.** Last 1h, 6h, 24h, 3d, 7d, 14d, 28d, 30d, 60d, 90d.
|
|
22
22
|
|
|
23
23
|
**Custom range.** Use the datetime-local inputs for **Start** and **End**. Both
|
|
24
24
|
fields are clamped to your account's retention window.
|
|
@@ -80,8 +80,6 @@ There's no separate share button.
|
|
|
80
80
|
| `time` | `?time=7d` | Apply a preset. |
|
|
81
81
|
| `start`, `end` | `?start=2026-05-01T00:00:00Z&end=2026-05-15T00:00:00Z` | Custom range. Overrides `time`. |
|
|
82
82
|
| `filter` | `?filter=httpStatus:class:5xx` | Add a filter. Repeat the parameter for multiple values. |
|
|
83
|
-
| `demo` | `?demo=true` | Demo mode (sample data). |
|
|
84
|
-
| `preview` | `?preview=1` | Legacy preview mode. |
|
|
85
83
|
|
|
86
84
|
See [URL parameters](./reference/url-parameters.md) for the full reference.
|
|
87
85
|
|
|
@@ -94,13 +92,8 @@ a refetch.
|
|
|
94
92
|
|
|
95
93
|
## Banners
|
|
96
94
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
1. **Preview banner**: when `preview=1` is set. Indicates legacy preview mode.
|
|
100
|
-
2. **Demo banner**: when `demo=true` is set. Reminds you sample data is shown
|
|
101
|
-
instead of your real analytics.
|
|
102
|
-
3. **Trial banner**: for new accounts with analytics. Shows days remaining and
|
|
103
|
-
offers **View demo →** and **Contact Sales**.
|
|
95
|
+
During the 14-day observability trial, a banner appears at the top of the page
|
|
96
|
+
showing the days remaining, with a **Contact sales** link.
|
|
104
97
|
|
|
105
98
|
## Loading and empty states
|
|
106
99
|
|
|
@@ -74,9 +74,9 @@ The filter bar applies. `originHost` is not applicable here. See
|
|
|
74
74
|
## Troubleshooting
|
|
75
75
|
|
|
76
76
|
**The Agents section is empty.** Either no classified agents called your gateway
|
|
77
|
-
in the window, or your
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
in the window, or your
|
|
78
|
+
[retention window](../access-and-entitlements.md#data-retention) doesn't yet
|
|
79
|
+
include any agent traffic.
|
|
80
80
|
|
|
81
81
|
**I see a known agent in my logs but not here.** The classifier is conservative;
|
|
82
82
|
it labels traffic that clearly matches a known agent fingerprint. Generic SDK
|
|
@@ -91,7 +91,5 @@ not include any geolocated requests.
|
|
|
91
91
|
that breakdown. Top-10 plus 50 covers up to 60 distinct values; beyond that,
|
|
92
92
|
narrow the time range or add a filter.
|
|
93
93
|
|
|
94
|
-
**My charts look sparse.**
|
|
95
|
-
|
|
96
|
-
populated dashboard looks like. See
|
|
97
|
-
[Access and entitlements](../access-and-entitlements.md).
|
|
94
|
+
**My charts look sparse.** New accounts may not have generated much traffic yet.
|
|
95
|
+
Charts fill in as requests flow through your gateway.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Audit Logs
|
|
2
|
+
title: Account Audit Logs
|
|
3
3
|
sidebar_label: Audit Logs
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -211,8 +211,8 @@ exceed 30 days. The start date must be before the end date.
|
|
|
211
211
|
|
|
212
212
|
:::tip
|
|
213
213
|
|
|
214
|
-
Account audit logs track administrative activities
|
|
215
|
-
|
|
216
|
-
[Audit
|
|
214
|
+
Account audit logs track administrative activities in your Zuplo account. To
|
|
215
|
+
record an audit trail of the requests flowing through your gateway, see
|
|
216
|
+
[Audit Logging](../audit-logging.mdx).
|
|
217
217
|
|
|
218
218
|
:::
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Audit Logging
|
|
3
|
+
sidebar_label: Overview
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Audit logs answer the question "who did what through your API, and when?" —
|
|
7
|
+
providing a structured, searchable record of every request and business event
|
|
8
|
+
that flows through your gateway. This page explains why audit logging matters
|
|
9
|
+
and the options Zuplo gives you for capturing an audit trail.
|
|
10
|
+
|
|
11
|
+
## Why audit logging?
|
|
12
|
+
|
|
13
|
+
Audit logging plays a critical role in API security: it lets you detect and
|
|
14
|
+
investigate issues such as unauthorized access or permission elevation, and
|
|
15
|
+
reconstruct exactly what happened to a resource after the fact. It's also a hard
|
|
16
|
+
requirement for many compliance certifications (SOC 2, ISO 27001, HIPAA, PCI
|
|
17
|
+
DSS) and a common buying criterion for enterprise customers of your API.
|
|
18
|
+
|
|
19
|
+
Unlike request logs — which are optimized for debugging and operational
|
|
20
|
+
visibility — audit logs are structured business records: each event identifies
|
|
21
|
+
an actor, an action, and the resources affected, in a stable format you can
|
|
22
|
+
retain, search, and hand to an auditor.
|
|
23
|
+
|
|
24
|
+
## Options for audit logging
|
|
25
|
+
|
|
26
|
+
Zuplo supports two approaches, depending on where you want your audit trail to
|
|
27
|
+
live:
|
|
28
|
+
|
|
29
|
+
1. **[Zuplo Audit Logs](./audit-logs.mdx)** — the recommended approach for most
|
|
30
|
+
APIs. Add a single policy and Zuplo records, stores, and indexes a structured
|
|
31
|
+
audit event for every request. You can search the events in the Zuplo Portal,
|
|
32
|
+
query them through the Zuplo API, and export them to a SIEM. No code or
|
|
33
|
+
infrastructure required.
|
|
34
|
+
2. **[Custom audit logs](./custom-audit-logs.mdx)** — write a small custom
|
|
35
|
+
policy that sends audit events to any external audit provider (for example,
|
|
36
|
+
WorkOS Audit Logs). Full control over the event format and destination.
|
|
37
|
+
|
|
38
|
+
| | Zuplo Audit Logs | Custom audit logs |
|
|
39
|
+
| ------------ | ------------------------- | --------------------- |
|
|
40
|
+
| Setup | Add a policy | Write policy code |
|
|
41
|
+
| Storage | Managed by Zuplo | Your provider |
|
|
42
|
+
| Viewing | Zuplo Portal + Zuplo API | Your provider's tools |
|
|
43
|
+
| Availability | Enterprise (free to test) | All plans |
|
|
44
|
+
|
|
45
|
+
Zuplo Audit Logs is an enterprise feature, but anyone can use it for free for
|
|
46
|
+
development and testing purposes.
|
|
47
|
+
|
|
48
|
+
## Gateway audit logs versus account audit logs
|
|
49
|
+
|
|
50
|
+
Zuplo has two separate audit trails — the options above all concern the first:
|
|
51
|
+
|
|
52
|
+
- **Gateway audit logs** record traffic through _your API_: requests from your
|
|
53
|
+
API's consumers and custom events emitted by your gateway code.
|
|
54
|
+
- **[Account audit logs](./accounts/audit-logs.mdx)** record administrative
|
|
55
|
+
activity in _your Zuplo account_: project changes, team member management,
|
|
56
|
+
deployments, and other portal and API operations.
|
|
57
|
+
|
|
58
|
+
## Next steps
|
|
59
|
+
|
|
60
|
+
- [Zuplo Audit Logs](./audit-logs.mdx) — enable the built-in feature and start
|
|
61
|
+
recording events
|
|
62
|
+
- [Custom audit logs](./custom-audit-logs.mdx) — send audit events to an
|
|
63
|
+
external provider
|
|
64
|
+
- [Audit Logs policy reference](../policies/audit-log-inbound.mdx) — all
|
|
65
|
+
configuration options and the full event shape
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Zuplo Audit Logs
|
|
3
|
+
sidebar_label: Zuplo Audit Logs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Zuplo's Audit Logs feature gives you a complete
|
|
7
|
+
[audit trail](./audit-logging.mdx) of your API with a single policy: the gateway
|
|
8
|
+
records a structured event for every request, stores the events for you, and
|
|
9
|
+
makes them available in the Zuplo Portal and through the Zuplo API.
|
|
10
|
+
|
|
11
|
+
<EnterpriseFeature name="Audit Logs" />
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
<Diagram height="h-56">
|
|
16
|
+
<DiagramNode id="client">Client</DiagramNode>
|
|
17
|
+
<DiagramGroup id="zuplo" label="Zuplo">
|
|
18
|
+
<DiagramNode id="gateway" variant="zuplo">
|
|
19
|
+
Gateway (Audit Logs policy)
|
|
20
|
+
</DiagramNode>
|
|
21
|
+
<DiagramNode id="store">Audit log storage</DiagramNode>
|
|
22
|
+
</DiagramGroup>
|
|
23
|
+
<DiagramNode id="consumers">Portal / API / SIEM</DiagramNode>
|
|
24
|
+
<DiagramEdge from="client" to="gateway" />
|
|
25
|
+
<DiagramEdge from="gateway" to="store" label="events" />
|
|
26
|
+
<DiagramEdge from="store" to="consumers" />
|
|
27
|
+
</Diagram>
|
|
28
|
+
|
|
29
|
+
Add the [Audit Logs policy](../policies/audit-log-inbound.mdx) to any route and
|
|
30
|
+
the gateway records one structured audit event per request — who made the
|
|
31
|
+
request, what they called, and the outcome. You can also emit your own custom
|
|
32
|
+
events (for example, "account deleted") from handlers and custom policies. Every
|
|
33
|
+
event follows the [CloudEvents](https://cloudevents.io/) 1.0 format.
|
|
34
|
+
|
|
35
|
+
Zuplo stores the events for you, grouped by environment — production, preview,
|
|
36
|
+
and working copy environments each write to their own bucket. You don't need a
|
|
37
|
+
database, logging service, or any other infrastructure of your own.
|
|
38
|
+
|
|
39
|
+
## Recording audit events
|
|
40
|
+
|
|
41
|
+
### Automatic request events
|
|
42
|
+
|
|
43
|
+
Add the policy to the routes you want audited. Typically that's any route that
|
|
44
|
+
modifies data, though depending on your API you may want it on sensitive read
|
|
45
|
+
operations as well (for example, retrieving a secret).
|
|
46
|
+
|
|
47
|
+
```json title="config/policies.json"
|
|
48
|
+
{
|
|
49
|
+
"name": "audit-logs",
|
|
50
|
+
"policyType": "audit-log-inbound",
|
|
51
|
+
"handler": {
|
|
52
|
+
"export": "AuditLogInboundPolicy",
|
|
53
|
+
"module": "$import(@zuplo/runtime)"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Each request through the policy produces an event of type
|
|
59
|
+
`com.zuplo.api.request` capturing the actor, HTTP method and path, response
|
|
60
|
+
status, caller IP address, and geolocation.
|
|
61
|
+
|
|
62
|
+
Audit events can contain personal or sensitive data. The policy's `include`
|
|
63
|
+
options let you turn off individual fields (query parameters, user identity, IP
|
|
64
|
+
address, geolocation) to satisfy your own data-handling and personally
|
|
65
|
+
identifiable information (PII) policies, and `samplingRate` lets you capture
|
|
66
|
+
only a fraction of requests on high-volume routes. See the
|
|
67
|
+
[policy reference](../policies/audit-log-inbound.mdx) for all options.
|
|
68
|
+
|
|
69
|
+
### Custom events
|
|
70
|
+
|
|
71
|
+
Beyond the automatic per-request event, record your own domain events from a
|
|
72
|
+
handler or custom policy with the static `AuditLogInboundPolicy.log()` method.
|
|
73
|
+
Only `type` is required — Zuplo fills in the actor, timestamp, request ID, and
|
|
74
|
+
other context fields automatically from the request:
|
|
75
|
+
|
|
76
|
+
```ts title="modules/handlers.ts"
|
|
77
|
+
import {
|
|
78
|
+
AuditLogInboundPolicy,
|
|
79
|
+
ZuploContext,
|
|
80
|
+
ZuploRequest,
|
|
81
|
+
} from "@zuplo/runtime";
|
|
82
|
+
|
|
83
|
+
export async function deleteAccount(
|
|
84
|
+
request: ZuploRequest,
|
|
85
|
+
context: ZuploContext,
|
|
86
|
+
) {
|
|
87
|
+
const accountId = request.params.accountId;
|
|
88
|
+
|
|
89
|
+
// ...perform the delete...
|
|
90
|
+
|
|
91
|
+
AuditLogInboundPolicy.log(context, {
|
|
92
|
+
type: "com.acme.account.deleted",
|
|
93
|
+
subject: `account|${accountId}`,
|
|
94
|
+
resources: [{ type: "account", id: accountId }],
|
|
95
|
+
success: true,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return new Response(null, { status: 204 });
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Audit logging is best-effort by design: `log()` never throws and never blocks or
|
|
103
|
+
fails the request, even if an event can't be recorded.
|
|
104
|
+
|
|
105
|
+
## Viewing audit logs in the portal
|
|
106
|
+
|
|
107
|
+
To browse and search your audit logs, open your project's
|
|
108
|
+
[**Services**](https://portal.zuplo.com/+/account/project/services) page in the
|
|
109
|
+
Zuplo Portal and select the **Audit Logs** tile for the environment you want to
|
|
110
|
+
inspect.
|
|
111
|
+
|
|
112
|
+
The events view lets you:
|
|
113
|
+
|
|
114
|
+
- Filter events by time range, event type, actor, and subject
|
|
115
|
+
- See the top event types and most active actors for the selected window
|
|
116
|
+
- Select any event to inspect the full event payload
|
|
117
|
+
|
|
118
|
+
## Querying audit logs with the API
|
|
119
|
+
|
|
120
|
+
Everything in the portal is backed by the Zuplo API, so you can query audit logs
|
|
121
|
+
programmatically — for scripting, custom dashboards, or exporting to other
|
|
122
|
+
systems. Authenticate with a [Zuplo API key](./accounts/zuplo-api-keys.mdx) and
|
|
123
|
+
query events by bucket name. Your bucket name is shown on the **Services** page
|
|
124
|
+
in the portal (it's the same bucket used by the API Key Service).
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
curl "https://dev.zuplo.com/v1/audit-logs/$BUCKET_NAME/events?type=com.zuplo.api.request&startDate=2026-07-01T00:00:00Z&endDate=2026-07-07T00:00:00Z" \
|
|
128
|
+
-H "Authorization: Bearer $ZUPLO_API_KEY"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"data": [
|
|
134
|
+
{
|
|
135
|
+
"specversion": "1.0",
|
|
136
|
+
"id": "e6c8b1f2-2c3d-4a5b-9e10-1f2a3b4c5d6e",
|
|
137
|
+
"type": "com.zuplo.api.request",
|
|
138
|
+
"time": "2026-07-05T18:12:04.123Z",
|
|
139
|
+
"actorsub": "user|12356",
|
|
140
|
+
"actortype": "user",
|
|
141
|
+
"requestid": "b1a2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
142
|
+
"httpmethod": "GET",
|
|
143
|
+
"httpurl": "/customers/12345?expand=orders",
|
|
144
|
+
"httpstatus": 200,
|
|
145
|
+
"success": true,
|
|
146
|
+
"ipaddress": "203.0.113.7",
|
|
147
|
+
"useragent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...",
|
|
148
|
+
"country": "US",
|
|
149
|
+
"region": "Washington",
|
|
150
|
+
"city": "Seattle"
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"pagination": { "limit": 20, "offset": 0, "total": 1, "hasMore": false }
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Two endpoints are available:
|
|
158
|
+
|
|
159
|
+
- **Query events** — `GET /v1/audit-logs/{bucketId}/events` returns matching
|
|
160
|
+
events, filterable by `type`, `actorSub`, `subject`, and time range.
|
|
161
|
+
- **Aggregated stats** — `GET /v1/audit-logs/{bucketId}/stats` returns top-N
|
|
162
|
+
event counts grouped by event type or actor.
|
|
163
|
+
|
|
164
|
+
The `bucketId` path parameter accepts either the bucket ID or the bucket name.
|
|
165
|
+
Each query is limited to a 30-day window between `startDate` and `endDate`. See
|
|
166
|
+
the [Audit Logs API reference](../api/audit-logs) for all parameters and
|
|
167
|
+
response schemas.
|
|
168
|
+
|
|
169
|
+
## Exporting audit logs to a SIEM
|
|
170
|
+
|
|
171
|
+
Many organizations centralize audit trails in a SIEM (Splunk, Microsoft
|
|
172
|
+
Sentinel, Datadog Cloud SIEM, and so on) to correlate API activity with other
|
|
173
|
+
security signals, drive alerting, and meet long-term retention requirements. The
|
|
174
|
+
events API is designed for this: run a scheduled job that pulls new events since
|
|
175
|
+
its last checkpoint and forwards them to your SIEM.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const BASE_URL = "https://dev.zuplo.com/v1/audit-logs";
|
|
179
|
+
|
|
180
|
+
// Run on a schedule (e.g. every 5 minutes). `startDate` is the checkpoint
|
|
181
|
+
// saved by the previous run; `endDate` is the current time.
|
|
182
|
+
async function exportAuditLogs(
|
|
183
|
+
bucketName: string,
|
|
184
|
+
startDate: string,
|
|
185
|
+
endDate: string,
|
|
186
|
+
) {
|
|
187
|
+
let offset = 0;
|
|
188
|
+
let hasMore = true;
|
|
189
|
+
while (hasMore) {
|
|
190
|
+
const url = new URL(`${BASE_URL}/${bucketName}/events`);
|
|
191
|
+
url.searchParams.set("startDate", startDate);
|
|
192
|
+
url.searchParams.set("endDate", endDate);
|
|
193
|
+
url.searchParams.set("limit", "100");
|
|
194
|
+
url.searchParams.set("offset", String(offset));
|
|
195
|
+
const response = await fetch(url, {
|
|
196
|
+
headers: { Authorization: `Bearer ${process.env.ZUPLO_API_KEY}` },
|
|
197
|
+
});
|
|
198
|
+
const { data, pagination } = await response.json();
|
|
199
|
+
await sendToSiem(data); // e.g. Splunk HEC, Datadog Logs API, S3
|
|
200
|
+
offset += data.length;
|
|
201
|
+
hasMore = pagination.hasMore;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Because every event is a CloudEvents document, most SIEMs can ingest the payload
|
|
207
|
+
directly without transformation.
|
|
208
|
+
|
|
209
|
+
Alternatively, if you'd rather send audit events to an external audit provider
|
|
210
|
+
directly from the gateway instead of storing them in Zuplo, see
|
|
211
|
+
[Custom Audit Logs](./custom-audit-logs.mdx).
|
|
212
|
+
|
|
213
|
+
## Related resources
|
|
214
|
+
|
|
215
|
+
- [Audit logging overview](./audit-logging.mdx) — why audit logging matters and
|
|
216
|
+
how the options compare
|
|
217
|
+
- [Audit Logs policy reference](../policies/audit-log-inbound.mdx) — all
|
|
218
|
+
configuration options, custom event fields, and the full event shape
|
|
219
|
+
- [Audit Logs API reference](../api/audit-logs) — query events and stats
|
|
220
|
+
programmatically
|
|
221
|
+
- [Account audit logs](./accounts/audit-logs.mdx) — the audit trail of your
|
|
222
|
+
Zuplo account itself
|
|
@@ -20,9 +20,9 @@ present an X.509 certificate that chains to a Certificate Authority (CA) in the
|
|
|
20
20
|
ALB's trust store. Zuplo presents that client certificate on each outbound
|
|
21
21
|
request, and the ALB rejects anything that can't.
|
|
22
22
|
|
|
23
|
-
This guide covers
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
This guide covers uploading a client certificate to Zuplo and presenting it on
|
|
24
|
+
requests to the ALB. It does **not** cover configuring the ALB itself — for
|
|
25
|
+
that, follow the AWS documentation linked in
|
|
26
26
|
[Configure the ALB](#1-configure-the-alb).
|
|
27
27
|
|
|
28
28
|
## How it works
|
|
@@ -64,10 +64,13 @@ For background on the gateway-to-origin direction in general, see
|
|
|
64
64
|
|
|
65
65
|
Before you begin, you need:
|
|
66
66
|
|
|
67
|
-
- A client certificate
|
|
68
|
-
|
|
67
|
+
- A PEM-encoded client certificate (`client-cert.pem`), matching private key
|
|
68
|
+
(`client-key.pem`), and public CA certificate or bundle (`client-ca.pem`). The
|
|
69
|
+
same CA must be in the ALB's trust store.
|
|
69
70
|
- An AWS Application Load Balancer with an HTTPS listener you can configure for
|
|
70
71
|
mTLS.
|
|
72
|
+
- The AWS CLI installed and authenticated with permission to inspect the ALB's
|
|
73
|
+
listener and trust store.
|
|
71
74
|
- The [Zuplo CLI](../cli/overview.mdx) installed and authenticated.
|
|
72
75
|
|
|
73
76
|
## 1/ Configure the ALB
|
|
@@ -93,7 +96,79 @@ edge.
|
|
|
93
96
|
|
|
94
97
|
:::
|
|
95
98
|
|
|
96
|
-
## 2/
|
|
99
|
+
## 2/ Validate the certificate and ALB trust
|
|
100
|
+
|
|
101
|
+
Confirm that the listener uses verify mode and capture its trust store ARN:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
aws elbv2 describe-listeners \
|
|
105
|
+
--listener-arns <HTTPS_LISTENER_ARN> \
|
|
106
|
+
--query 'Listeners[0].MutualAuthentication' \
|
|
107
|
+
--output json
|
|
108
|
+
|
|
109
|
+
TRUST_STORE_ARN=$(aws elbv2 describe-listeners \
|
|
110
|
+
--listener-arns <HTTPS_LISTENER_ARN> \
|
|
111
|
+
--query 'Listeners[0].MutualAuthentication.TrustStoreArn' \
|
|
112
|
+
--output text)
|
|
113
|
+
|
|
114
|
+
aws elbv2 describe-trust-stores \
|
|
115
|
+
--trust-store-arns "$TRUST_STORE_ARN" \
|
|
116
|
+
--query 'TrustStores[0].{Name:Name,Status:Status,CACertificates:NumberOfCaCertificates}' \
|
|
117
|
+
--output table
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The listener mode should be `verify`, and the trust store status should be
|
|
121
|
+
`ACTIVE`.
|
|
122
|
+
|
|
123
|
+
Inspect the client certificate and verify its path against the CA certificate:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
openssl x509 \
|
|
127
|
+
-in client-cert.pem \
|
|
128
|
+
-noout \
|
|
129
|
+
-subject \
|
|
130
|
+
-issuer \
|
|
131
|
+
-dates \
|
|
132
|
+
-purpose
|
|
133
|
+
|
|
134
|
+
openssl verify \
|
|
135
|
+
-verbose \
|
|
136
|
+
-purpose sslclient \
|
|
137
|
+
-x509_strict \
|
|
138
|
+
-show_chain \
|
|
139
|
+
-CAfile client-ca.pem \
|
|
140
|
+
client-cert.pem
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`SSL client` should report `Yes`, and verification should return
|
|
144
|
+
`client-cert.pem: OK`. Also confirm that the certificate and private key match:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
CERT_KEY_HASH=$(openssl x509 -in client-cert.pem -noout -pubkey | openssl sha256)
|
|
148
|
+
PRIVATE_KEY_HASH=$(openssl pkey -in client-key.pem -pubout | openssl sha256)
|
|
149
|
+
|
|
150
|
+
test "$CERT_KEY_HASH" = "$PRIVATE_KEY_HASH" &&
|
|
151
|
+
echo "Certificate and private key match" ||
|
|
152
|
+
echo "ERROR: Certificate and private key do not match"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For the strongest offline check, run `openssl verify` with the exact CA bundle
|
|
156
|
+
uploaded to the ALB trust store instead of `client-ca.pem`.
|
|
157
|
+
|
|
158
|
+
If the ALB is reachable, test the TLS handshake directly:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
curl --verbose \
|
|
162
|
+
--cert client-cert.pem \
|
|
163
|
+
--key client-key.pem \
|
|
164
|
+
"https://<ALB_HOSTNAME>/<TEST_PATH>"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Use a hostname covered by the ALB's server certificate. Any HTTP response,
|
|
168
|
+
including a `401`, `403`, or `404`, shows that the TLS handshake completed; a
|
|
169
|
+
client certificate failure occurs before an HTTP response is produced.
|
|
170
|
+
|
|
171
|
+
## 3/ Upload your client certificate to Zuplo
|
|
97
172
|
|
|
98
173
|
Use the Zuplo CLI to upload the client certificate and private key to your
|
|
99
174
|
project. The CA that issued this certificate must already be in the ALB's trust
|
|
@@ -119,7 +194,7 @@ when you create the certificate.
|
|
|
119
194
|
|
|
120
195
|
:::
|
|
121
196
|
|
|
122
|
-
##
|
|
197
|
+
## 4/ Present the certificate on requests to the ALB
|
|
123
198
|
|
|
124
199
|
Reference the uploaded certificate by name when the gateway forwards requests to
|
|
125
200
|
the ALB. Use the ALB's DNS name (or a custom domain pointed at it) as the
|
|
@@ -162,7 +237,7 @@ export default async function (request: ZuploRequest, context: ZuploContext) {
|
|
|
162
237
|
}
|
|
163
238
|
```
|
|
164
239
|
|
|
165
|
-
##
|
|
240
|
+
## 5/ Use environment variables across environments
|
|
166
241
|
|
|
167
242
|
To use a different certificate per environment, store the certificate name in an
|
|
168
243
|
[environment variable](./environment-variables.mdx) and reference it with the
|
|
@@ -226,11 +301,12 @@ received — usually a TLS handshake problem. Confirm that:
|
|
|
226
301
|
|
|
227
302
|
### The ALB rejects the certificate
|
|
228
303
|
|
|
229
|
-
If the handshake
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
304
|
+
If the TLS handshake fails, re-check the ALB trust store and confirm the
|
|
305
|
+
listener is in **verify** mode, not passthrough. If your client certificate is
|
|
306
|
+
issued by an intermediate CA, make sure the trust store contains the CA chain
|
|
307
|
+
required to validate the certificate. ALB connection logs can identify errors
|
|
308
|
+
such as `ClientCertUntrusted`, `ClientCertPurposeInvalid`, `ClientCertExpired`,
|
|
309
|
+
or `ClientCertNotYetValid`.
|
|
234
310
|
|
|
235
311
|
### No certificate appears to be sent
|
|
236
312
|
|