zuplo 6.70.25 → 6.70.26

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.
@@ -0,0 +1,223 @@
1
+ ---
2
+ title: Create Consumers in a Specific Bucket
3
+ sidebar_label: Consumers in a Specific Bucket
4
+ description:
5
+ Learn how to create API key consumers in a specific bucket using the Zuplo
6
+ portal UI, including bucket selection, environment mapping, and
7
+ troubleshooting.
8
+ ---
9
+
10
+ Every API key consumer in Zuplo lives inside a **bucket**, and each bucket is
11
+ scoped to a specific environment. This guide shows how to pick a target bucket
12
+ from the **Services** screen and create a consumer inside it.
13
+
14
+ :::note
15
+
16
+ For general API key management (creating consumers, viewing keys, assigning
17
+ managers), see [Manage Keys in the Portal](./api-key-administration.mdx).
18
+
19
+ :::
20
+
21
+ ## Prerequisites
22
+
23
+ - A Zuplo project with at least one deployed environment (see the
24
+ [getting started tutorial](./step-1-setup-basic-gateway.mdx))
25
+ - The [API Key Authentication policy](../policies/api-key-inbound.mdx)
26
+ configured on your routes
27
+ - Permission to manage API key consumers in your project
28
+
29
+ ## Understanding buckets and environments
30
+
31
+ Zuplo creates three buckets for every project. Each isolates its own consumers
32
+ and keys, so a key created in one bucket only authenticates requests against the
33
+ matching environment.
34
+
35
+ | Bucket | Environment | Git branch |
36
+ | --------------- | ----------- | -------------------- |
37
+ | **Production** | Production | Default branch |
38
+ | **Preview** | Preview | Non-default branches |
39
+ | **Development** | Development | Local development |
40
+
41
+ ![Diagram showing each Zuplo environment (Production, Preview, Development) mapping one-to-one to its matching bucket](../../public/media/api-key-consumer-bucket-portal-ui/bucket-environment-mapping.png)
42
+
43
+ For deeper detail, see [Buckets and Environments](./api-key-buckets.mdx).
44
+
45
+ ### When you need a non-default bucket
46
+
47
+ - **Per-environment isolation.** Keep staging keys out of production.
48
+ - **Custom buckets.** Your team created extra buckets (QA, per-tenant) via the
49
+ [Developer API](./api-key-api.mdx).
50
+ - **Shared buckets across projects.** Enterprise setups where one bucket backs
51
+ several projects.
52
+
53
+ ## Find your buckets in the portal
54
+
55
+ <Stepper>
56
+
57
+ 1. Open your project in the [Zuplo Portal](https://portal.zuplo.com).
58
+
59
+ 1. Navigate to the
60
+ [**Services**](https://portal.zuplo.com/+/account/project/services) page.
61
+
62
+ 1. Locate the **API Key Service** card. Use the **environment dropdown** at the
63
+ top right to filter the visible buckets. Pick **All Environments** to see
64
+ every bucket, or pick a single environment to narrow the list.
65
+
66
+ </Stepper>
67
+
68
+ <ModalScreenshot size="md">
69
+
70
+ ![Services page with the environment dropdown open, showing All Environments, Production, Preview, and Development options](../../public/media/api-key-consumer-bucket-portal-ui/services-page-dropdown.png)
71
+
72
+ </ModalScreenshot>
73
+
74
+ The card's **Connected to** badge shows which environment's bucket is currently
75
+ active.
76
+
77
+ ## Create a consumer in a specific bucket
78
+
79
+ <Stepper>
80
+
81
+ 1. On the **Services** page, choose the target environment from the dropdown.
82
+
83
+ 1. On the API Key Service card, click **Bucket Details**. The bucket's consumer
84
+ list opens.
85
+
86
+ 1. Click **Create new consumer** and fill in the form below.
87
+
88
+ 1. Click **Save consumer**, then confirm it appears in the list.
89
+
90
+ </Stepper>
91
+
92
+ <ModalScreenshot size="md">
93
+
94
+ ![Create new consumer modal showing Subject, Key managers, and Metadata fields plus the Save consumer button](../../public/media/api-key-consumer-bucket-portal-ui/create-consumer-modal.png)
95
+
96
+ </ModalScreenshot>
97
+
98
+ ### Consumer form fields
99
+
100
+ | Field | Required | Runtime value | Notes |
101
+ | ---------------- | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
102
+ | **Subject** | Yes | `request.user.sub` | Unique within the bucket. Identifies the consumer in logs and policy code. |
103
+ | **Key managers** | No | n/a | Comma-separated emails of users who can manage this consumer's keys via the [Developer Portal](../dev-portal/introduction.mdx). |
104
+ | **Metadata** | No | `request.user.data` | Valid JSON object. Plan info, customer IDs, anything your policies need at runtime. |
105
+
106
+ :::tip
107
+
108
+ Once created, the consumer's API key only authenticates requests routed through
109
+ an environment whose API Key Authentication policy resolves to that same bucket.
110
+
111
+ :::
112
+
113
+ ## How bucket selection affects key validation
114
+
115
+ The [API Key Authentication policy](../policies/api-key-inbound.mdx) decides
116
+ which bucket to validate keys against. With no `bucketName` set, the policy
117
+ defaults to the bucket that matches the current environment:
118
+
119
+ | Environment | Default bucket |
120
+ | ----------- | -------------- |
121
+ | Production | Production |
122
+ | Preview | Preview |
123
+ | Development | Development |
124
+
125
+ For a custom bucket, set `bucketName` (or `bucketId`) on the policy so it checks
126
+ the right one:
127
+
128
+ ```json
129
+ {
130
+ "export": "ApiKeyInboundPolicy",
131
+ "module": "$import(@zuplo/runtime)",
132
+ "options": {
133
+ "bucketName": "my-custom-bucket",
134
+ "allowUnauthenticatedRequests": false
135
+ }
136
+ }
137
+ ```
138
+
139
+ :::caution
140
+
141
+ If the consumer lives in one bucket but the policy checks a different bucket,
142
+ the key is not found and the request returns `401 Unauthorized`. Make sure the
143
+ policy's bucket matches the bucket where you created the consumer.
144
+
145
+ :::
146
+
147
+ ## Using the Developer API instead
148
+
149
+ To script consumer creation as part of an onboarding flow or CI/CD pipeline, use
150
+ the [Zuplo Developer API](./api-key-api.mdx):
151
+
152
+ ```bash
153
+ curl \
154
+ https://dev.zuplo.com/v1/accounts/$ACCOUNT_NAME/key-buckets/$BUCKET_NAME/consumers?with-api-key=true \
155
+ --request POST \
156
+ --header "Content-type: application/json" \
157
+ --header "Authorization: Bearer $ZAPI_KEY" \
158
+ --data '{
159
+ "name": "my-consumer",
160
+ "description": "Created via API",
161
+ "metadata": { "plan": "gold" }
162
+ }'
163
+ ```
164
+
165
+ Replace `$ACCOUNT_NAME` with your Zuplo account name, `$BUCKET_NAME` with the
166
+ target bucket name, and `$ZAPI_KEY` with your
167
+ [Zuplo API key](./accounts/zuplo-api-keys.mdx). Full reference at the
168
+ [Developer API documentation](https://dev.zuplo.com/docs).
169
+
170
+ ## Troubleshooting
171
+
172
+ <details>
173
+ <summary>My API key returns 401 Unauthorized</summary>
174
+
175
+ Usually a bucket mismatch. The consumer is in one bucket, but the policy checks
176
+ a different one.
177
+
178
+ 1. In the portal, navigate to **Services** and confirm which bucket holds the
179
+ consumer.
180
+ 2. Open the route's API Key Authentication policy. If `bucketName` or `bucketId`
181
+ is set, verify it matches the consumer's bucket. If neither is set, the
182
+ policy uses the current environment's default bucket.
183
+ 3. Either recreate the consumer in the correct bucket, or update the policy's
184
+ `bucketName` to match.
185
+
186
+ </details>
187
+
188
+ <details>
189
+ <summary>I don't see the bucket I'm looking for</summary>
190
+
191
+ - **Environment filter.** Set the dropdown to **All Environments** to see every
192
+ bucket.
193
+ - **Custom buckets.** Buckets created via the Developer API are account-scoped,
194
+ not environment-scoped. They show under **All Environments**. If still
195
+ missing, confirm the account using the
196
+ [list buckets API endpoint](https://dev.zuplo.com/docs).
197
+ - **Permissions.** Account-level roles control access to the Services page.
198
+ Confirm your role can view and manage API key consumers.
199
+
200
+ </details>
201
+
202
+ <details>
203
+ <summary>I created a consumer but it doesn't appear in the expected environment</summary>
204
+
205
+ Consumers belong to buckets, not environments directly. A consumer created while
206
+ viewing the **Preview** environment sits in the preview bucket and only
207
+ authenticates preview environments. Switch the dropdown to **All Environments**
208
+ or the specific environment to locate it.
209
+
210
+ </details>
211
+
212
+ ## Related documentation
213
+
214
+ - [Buckets and Environments](./api-key-buckets.mdx): How buckets map to
215
+ environments
216
+ - [Manage Keys in the Portal](./api-key-administration.mdx): General portal
217
+ management walkthrough
218
+ - [API Key Authentication policy](../policies/api-key-inbound.mdx): Policy
219
+ configuration reference including `bucketName`
220
+ - [Use the Developer API](./api-key-api.mdx): Programmatic consumer management
221
+ - [Create an API Key Consumer on Login](../dev-portal/dev-portal-create-consumer-on-auth.mdx):
222
+ Automatically create consumers when users sign in
223
+ - [Environments](./environments.mdx): How environments work in Zuplo
@@ -65,7 +65,7 @@ curl \
65
65
 
66
66
  Each bucket has an optional `MonetizationConfiguration` record that holds
67
67
  bucket-wide defaults. The configuration is read by the runtime and the Developer
68
- Portal — it is not stored in OpenMeter.
68
+ Portal.
69
69
 
70
70
  | Method | Path |
71
71
  | -------- | ---------------------------------------------------- |
@@ -105,8 +105,8 @@ the schema defaults (`multipleSubscriptionsEnabled: false`, `planOrder: []`,
105
105
 
106
106
  These endpoints script the Stripe integration that the
107
107
  [Monetization Service UI](./stripe-integration.md#connecting-your-stripe-account)
108
- runs interactively. They live on the Zuplo developer API (not OpenMeter), so the
109
- request shape is documented here.
108
+ runs interactively. They live on the Zuplo developer API, so the request shape
109
+ is documented here.
110
110
 
111
111
  ### Connect a Stripe app
112
112
 
@@ -82,7 +82,7 @@ curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions \
82
82
  ```
83
83
 
84
84
  `plan` references the target plan by its `key` (and optionally `version`).
85
- Provide either `customerId` (the metering customer ULID, format
85
+ Provide either `customerId` (the Zuplo customer ULID, format
86
86
  `^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$`) or `customerKey` (your own
87
87
  identifier). Optional fields include `timing` (`"immediate"` by default,
88
88
  `"next_billing_cycle"`, or an RFC 3339 timestamp), `startingPhase`, `name`,
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: mTLS Authentication
2
+ title: Gateway to Origin mTLS Authentication
3
3
  ---
4
4
 
5
5
  <EnterpriseFeature name="mTLS Client Certificates" />
@@ -0,0 +1,318 @@
1
+ ---
2
+ title: Client mTLS Authentication
3
+ ---
4
+
5
+ <EnterpriseFeature name="Client mTLS" />
6
+
7
+ Client mTLS authentication lets your Zuplo gateway verify the identity of
8
+ clients calling your API using certificates issued by your own Certificate
9
+ Authority (CA). Both the client and the gateway authenticate each other during
10
+ the TLS handshake, so only clients holding a certificate signed by your CA can
11
+ reach your API.
12
+
13
+ ## How Client mTLS Works
14
+
15
+ When a client calls your Zuplo gateway:
16
+
17
+ 1. The client presents a certificate issued by your CA during the TLS handshake.
18
+ 2. Zuplo's edge verifies the certificate against the CA you've uploaded and
19
+ passes the verification result (and parsed certificate) to your gateway
20
+ workers.
21
+ 3. The [`mtls-auth-inbound`](../policies/mtls-auth-inbound.md) policy on your
22
+ route reads the verification result, enforces it, and attaches the parsed
23
+ certificate metadata to `request.user.data.mtlsAuth` for use in your handlers
24
+ and downstream policies.
25
+
26
+ CA certificates are scoped to your Zuplo **account**, not a single project or
27
+ deployment. Once a CA is uploaded, every gateway domain on the account will
28
+ verify presented client certificates against it. The policy on each route
29
+ controls whether unverified traffic is rejected or allowed through.
30
+
31
+ ## Prerequisites
32
+
33
+ Before you begin, you need:
34
+
35
+ - A public CA certificate (PEM-encoded) that issued — or will issue — the client
36
+ certificates you want to accept
37
+ - The [Zuplo CLI](../cli/overview.mdx) installed and authenticated
38
+ - A Zuplo project where you can add the `mtls-auth-inbound` policy to a route
39
+
40
+ :::note
41
+
42
+ You only upload your **public CA certificate** to Zuplo. Private keys and issued
43
+ client certificates stay with you and your clients.
44
+
45
+ :::
46
+
47
+ ## 1/ Upload Your CA Certificate
48
+
49
+ Use the Zuplo CLI to upload your CA certificate. The CA is registered against
50
+ your account and is automatically made available on all of your gateway domains.
51
+
52
+ First, authenticate your client:
53
+
54
+ ```bash
55
+ zuplo login
56
+ ```
57
+
58
+ Then you can create a CA by running:
59
+
60
+ ```bash
61
+ zuplo ca-certificate create \
62
+ --name my_ca \
63
+ --cert ./ca.pem \
64
+ --account your-account
65
+ ```
66
+
67
+ **Parameters:**
68
+
69
+ - `--name`: A unique identifier for the CA. Must be a valid JavaScript
70
+ identifier (letters, digits, `_`, `$`; cannot start with a digit).
71
+ - `--cert`: Path to the PEM-encoded CA certificate
72
+ (`-----BEGIN CERTIFICATE-----` ...). DER is not supported.
73
+ - `--account`: Your Zuplo account name.
74
+
75
+ The command returns the new CA's ID (prefixed with `mtlsca_`). You can list all
76
+ CAs on the account at any time:
77
+
78
+ ```bash
79
+ zuplo ca-certificate list --account your-account
80
+ ```
81
+
82
+ See the [`ca-certificate` CLI reference](../cli/ca-certificate-create.md) for
83
+ all available subcommands (`create`, `list`, `describe`, `update`, `delete`).
84
+
85
+ :::tip{title="Using an intermediate CA"}
86
+
87
+ If your client certificates are issued by an intermediate CA (rather than
88
+ directly by your root), upload the **intermediate** itself as the CA — not the
89
+ root. The client certs used must be directly signed by the CA certificate you
90
+ provide to Zuplo.
91
+
92
+ :::
93
+
94
+ ## 2/ Add the mTLS Auth Inbound Policy
95
+
96
+ Add the [`mtls-auth-inbound`](../policies/mtls-auth-inbound.md) policy to any
97
+ route that should require a verified client certificate. The policy reads the
98
+ verification result that Zuplo's edge attached to the request and either rejects
99
+ unverified traffic or allows it through, depending on configuration.
100
+
101
+ ```json title="config/policies.json"
102
+ {
103
+ "name": "my-mtls-auth-inbound-policy",
104
+ "policyType": "mtls-auth-inbound",
105
+ "handler": {
106
+ "export": "MTLSAuthInboundPolicy",
107
+ "module": "$import(@zuplo/runtime)",
108
+ "options": {
109
+ "allowUnauthenticatedRequests": false,
110
+ "certIssuerDN": "CN=example-ca, O=Example, C=US"
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ **Key options:**
117
+
118
+ - `allowUnauthenticatedRequests` (default `false`): When `false`, the policy
119
+ rejects requests that don't present a valid client certificate signed by a CA
120
+ on your account. When `true`, the policy lets traffic through but still
121
+ attaches certificate metadata when a parseable client certificate is present —
122
+ useful for staged rollouts or logging-only modes.
123
+ - `certIssuerDN`: The fully qualified issuer distinguished name that the client
124
+ certificate must be signed by.
125
+
126
+ See the full [policy reference](../policies/mtls-auth-inbound.md) for all
127
+ options.
128
+
129
+ :::tip{title="Finding your certIssuerDN value"}
130
+
131
+ The issuer DN of a client certificate is the subject DN of the CA that signed
132
+ it. Read it directly from your CA's PEM file with `openssl`:
133
+
134
+ ```bash
135
+ openssl x509 -in ca.pem -noout -subject -nameopt RFC2253
136
+ ```
137
+
138
+ This prints something like `subject=CN=example-ca,O=Example,C=US`. Copy the part
139
+ after `subject=` into `certIssuerDN`. The policy tolerates casing and whitespace
140
+ differences, but not RDN reordering, so keep the order produced by `openssl`
141
+ as-is.
142
+
143
+ :::
144
+
145
+ ## 3/ Read Certificate Metadata in Your Handler
146
+
147
+ When verification succeeds, the policy attaches parsed certificate metadata to
148
+ `request.user.data.mtlsAuth`. If `request.user` does not already exist, the
149
+ policy also sets `request.user.sub` to the certificate subject.
150
+
151
+ ```ts
152
+ import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
153
+
154
+ export default async function (request: ZuploRequest, context: ZuploContext) {
155
+ const mtls = request.user?.data?.mtlsAuth;
156
+
157
+ if (!mtls) {
158
+ return new Response("No client certificate", { status: 401 });
159
+ }
160
+
161
+ context.log.info("Authenticated client", {
162
+ subject: mtls.subject,
163
+ issuer: mtls.issuer,
164
+ fingerprint: mtls.sha256Fingerprint,
165
+ });
166
+
167
+ // Authorize based on certificate subject, fingerprint, etc.
168
+ return new Response(`Hello, ${mtls.subject}`);
169
+ }
170
+ ```
171
+
172
+ The metadata object includes:
173
+
174
+ - `subject` — the client certificate subject DN
175
+ - `issuer` — the issuer DN (the CA that signed the certificate)
176
+ - `notBefore` / `notAfter` — validity window in ISO 8601 format
177
+ - `sha256Fingerprint` — SHA-256 digest of the DER-encoded certificate, uppercase
178
+ hex with colon separators (e.g. `AB:CD:EF:...`). Useful for pinning specific
179
+ client certificates.
180
+
181
+ The raw client certificate is also available on
182
+ `context.incomingRequestProperties.clientCert` in
183
+ [RFC 9440](https://datatracker.ietf.org/doc/html/rfc9440#section-2.2) format
184
+ (Base64-encoded DER, colon-wrapped) if you need to perform custom parsing or
185
+ forward it to a backend.
186
+
187
+ ## 4/ Test with curl
188
+
189
+ Once your CA is uploaded and the policy is on the route, you can verify the
190
+ end-to-end flow with `curl`. You'll need a client certificate and private key
191
+ issued by the CA you uploaded.
192
+
193
+ Send the certificate and key with `--cert` and `--key`:
194
+
195
+ ```bash
196
+ curl --cert ./client.pem --key ./client.key \
197
+ https://your-gateway.zuplo.app/v1/example
198
+ ```
199
+
200
+ Confirm that:
201
+
202
+ - A request **without** `--cert` is rejected with `401` when
203
+ `allowUnauthenticatedRequests` is `false`.
204
+ - A request with a certificate signed by your uploaded CA succeeds and your
205
+ handler sees the parsed certificate on `request.user.data.mtlsAuth`.
206
+ - A request with a certificate signed by a different CA is rejected.
207
+
208
+ ## Managing CA Certificates
209
+
210
+ ### Listing CAs
211
+
212
+ ```bash
213
+ zuplo ca-certificate list --account your-account
214
+ ```
215
+
216
+ ### Inspecting a CA
217
+
218
+ ```bash
219
+ zuplo ca-certificate describe \
220
+ --cert-id mtlsca_abc123 \
221
+ --account your-account
222
+ ```
223
+
224
+ ### Renaming a CA
225
+
226
+ Only the name can be updated; to replace the certificate body, delete the CA and
227
+ create a new one.
228
+
229
+ ```bash
230
+ zuplo ca-certificate update \
231
+ --cert-id mtlsca_abc123 \
232
+ --name renamed_ca \
233
+ --account your-account
234
+ ```
235
+
236
+ ### Deleting a CA
237
+
238
+ ```bash
239
+ zuplo ca-certificate delete \
240
+ --cert-id mtlsca_abc123 \
241
+ --account your-account
242
+ ```
243
+
244
+ :::caution
245
+
246
+ Deleting a CA stops verification for client certificates issued by it on all of
247
+ your gateway domains. Routes that use `mtls-auth-inbound` with
248
+ `allowUnauthenticatedRequests: false` will start rejecting those clients
249
+ immediately. Rotate to a new CA and update your clients before deleting the old
250
+ CA.
251
+
252
+ :::
253
+
254
+ ### Rotating a CA
255
+
256
+ To rotate the CA without downtime:
257
+
258
+ 1. Upload the new CA alongside the existing one with
259
+ `zuplo ca-certificate create`.
260
+ 2. Reissue client certificates from the new CA and distribute them to your
261
+ clients.
262
+ 3. Once all clients have moved to the new CA, delete the old CA with
263
+ `zuplo ca-certificate delete`.
264
+
265
+ If you've followed the common practice of preserving the CA's subject DN across
266
+ the rotation (only the key, serial, and validity dates change), the issuer DN on
267
+ newly issued client certificates is identical to the previous one and
268
+ **`certIssuerDN` does not need to change**. If the rotation deliberately changes
269
+ the CA's subject DN, update `certIssuerDN` to match the new value before cutting
270
+ clients over — or temporarily set `allowUnauthenticatedRequests: true` to allow
271
+ both issuers during the transition.
272
+
273
+ ## Local Development
274
+
275
+ The `mtls-auth-inbound` policy relies on verification metadata supplied by
276
+ Zuplo's edge proxy and does not work in local development with `zuplo dev`. Test
277
+ the policy in a working-copy or preview environment.
278
+
279
+ ## Troubleshooting
280
+
281
+ ### Requests are rejected with 401
282
+
283
+ - Confirm the client is presenting a certificate signed by a CA that's been
284
+ uploaded with `zuplo ca-certificate list`.
285
+ - If you've set `certIssuerDN`, verify it matches
286
+ `request.user.data.mtlsAuth.issuer` exactly (casing and whitespace are
287
+ tolerated, but RDN order is not).
288
+ - Temporarily set `allowUnauthenticatedRequests: true` and log
289
+ `context.incomingRequestProperties.clientMtlsVerificationStatus` and
290
+ `context.incomingRequestProperties.clientMtlsVerificationReason` to see why
291
+ verification failed.
292
+
293
+ ### `request.user.data.mtlsAuth` is missing
294
+
295
+ - The policy only attaches metadata when a parseable client certificate is
296
+ present on the request. Confirm the client is sending one.
297
+ - Verify the route includes the `mtls-auth-inbound` policy.
298
+
299
+ ### Custom domains
300
+
301
+ When a CA is uploaded, it's automatically associated with Zuplo's managed
302
+ gateway domains. A custom domain must be _active_ in the dashboard (check the
303
+ Settings/Custom Domains sidebar) before CA verification will become active on
304
+ that custom domain.
305
+
306
+ If you add a custom domain later and your clients aren't being verified against
307
+ it, contact [support@zuplo.com](mailto:support@zuplo.com).
308
+
309
+ ## Additional Resources
310
+
311
+ - [`mtls-auth-inbound` policy reference](../policies/mtls-auth-inbound.md)
312
+ - [`ca-certificate` CLI reference](../cli/ca-certificate-create.md)
313
+ - [Gateway to Origin mTLS Authentication](./securing-backend-mtls.mdx) — the
314
+ reverse direction, where Zuplo authenticates to your backend with a client
315
+ certificate
316
+
317
+ If you need help configuring client mTLS for your account, contact us at
318
+ [support@zuplo.com](mailto:support@zuplo.com).
@@ -76,8 +76,8 @@ users already authenticate with an identity provider.
76
76
 
77
77
  - [Basic Auth](../policies/basic-auth-inbound.mdx) - Username/password
78
78
  authentication
79
- - [mTLS](../policies/mtls-auth-inbound.mdx) - Mutual TLS certificate
80
- authentication
79
+ - [mTLS](../articles/securing-the-gateway-with-client-mtls.mdx) - Mutual TLS
80
+ certificate authentication
81
81
  - [LDAP](../policies/ldap-auth-inbound.mdx) - LDAP directory authentication
82
82
  - [HMAC](../policies/hmac-auth-inbound.mdx) - Hash-based message authentication
83
83
 
@@ -15,9 +15,13 @@ certificate is present. If a parseable certificate is present, the policy still
15
15
  sets `request.user.data.mtlsAuth`; otherwise it leaves the request unchanged.
16
16
 
17
17
  Set `certIssuerDN` to the fully qualified issuer distinguished name to require
18
- on the client certificate. When set and enforcement is enabled, the policy
19
- rejects certificates whose parsed issuer DN does not match. Comparison is
20
- order-sensitive on RDNs (e.g. `"CN=foo, O=bar"` does not match
18
+ on the client certificate. The policy rejects certificates whose parsed issuer
19
+ DN does not match. `certIssuerDN` is required whenever enforcement is enabled
20
+ (i.e. when `allowUnauthenticatedRequests` is not `true`); the policy fails to
21
+ load otherwise. This guarantees that requests are pinned to a specific CA and is
22
+ especially important when an account has multiple CAs configured.
23
+
24
+ Comparison is order-sensitive on RDNs (e.g. `"CN=foo, O=bar"` does not match
21
25
  `"O=bar, CN=foo"`, which matches RFC 4514 §2.1 semantics) but tolerant of
22
26
  casing and whitespace, so `"CN=example-ca, O=Example, C=US"` matches
23
27
  `"cn=Example-CA,o=example,c=us"`. Multi-valued RDNs (`+`) and hex-encoded
@@ -25,8 +29,5 @@ values (`#...`) are not normalized. The simplest way to obtain the expected
25
29
  value is to inspect `request.user.data.mtlsAuth.issuer` from a request signed
26
30
  by the desired CA.
27
31
 
28
- The `certIssuerDN` is useful when you want to distinguish between client certs from
29
- different CAs if you have multiple set on your account. It is recommended to set this by default.
30
-
31
32
  Note: this policy does not work with local development since it relies on metadata from the upstream reverse proxy,
32
33
  it is recommended to test this using a working-copy or preview environment.
@@ -41,10 +41,23 @@
41
41
  },
42
42
  "certIssuerDN": {
43
43
  "type": "string",
44
- "description": "Optional fully qualified issuer distinguished name to require on the client certificate. When set, the policy rejects certificates whose parsed issuer DN does not match this string exactly. The expected format matches the parsed metadata issuer, e.g. \"CN=example-ca, O=Example, C=US\".",
44
+ "description": "Fully qualified issuer distinguished name to require on the client certificate. The policy rejects certificates whose parsed issuer DN does not match this string exactly. Required unless `allowUnauthenticatedRequests` is `true`. The expected format matches the parsed metadata issuer, e.g. \"CN=example-ca, O=Example, C=US\".",
45
45
  "examples": ["CN=example-ca, O=Example, C=US"]
46
46
  }
47
- }
47
+ },
48
+ "anyOf": [
49
+ {
50
+ "required": ["certIssuerDN"]
51
+ },
52
+ {
53
+ "properties": {
54
+ "allowUnauthenticatedRequests": {
55
+ "const": true
56
+ }
57
+ },
58
+ "required": ["allowUnauthenticatedRequests"]
59
+ }
60
+ ]
48
61
  }
49
62
  },
50
63
  "examples": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "6.70.25",
3
+ "version": "6.70.26",
4
4
  "type": "module",
5
5
  "description": "The programmable API Gateway",
6
6
  "author": "Zuplo, Inc.",
@@ -19,9 +19,9 @@
19
19
  "zuplo": "zuplo.js"
20
20
  },
21
21
  "dependencies": {
22
- "@zuplo/cli": "6.70.25",
23
- "@zuplo/core": "6.70.25",
24
- "@zuplo/runtime": "6.70.25",
22
+ "@zuplo/cli": "6.70.26",
23
+ "@zuplo/core": "6.70.26",
24
+ "@zuplo/runtime": "6.70.26",
25
25
  "@zuplo/test": "1.4.0"
26
26
  }
27
27
  }