zuplo 6.73.31 → 6.74.2

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.
@@ -2,16 +2,175 @@
2
2
  title: How to check an incoming IP address
3
3
  sidebar_label: "Check IP Address"
4
4
  description:
5
- Learn how to access the true client IP address of requests using the
6
- true-client-ip header.
5
+ Read the client IP address of an incoming request, and understand when that
6
+ address can be trusted on each deployment type.
7
7
  tags:
8
8
  - request-handling
9
9
  - deployment
10
10
  ---
11
11
 
12
- Sometimes you want to access the true IP address of the gateway's client making
13
- the current request. To do this you can read the `true-client-ip` header:
12
+ To get the IP address of the client that made the current request, read it from
13
+ the request context:
14
14
 
15
15
  ```ts
16
- const ip = request.headers.get("true-client-ip");
16
+ export default async function (request: ZuploRequest, context: ZuploContext) {
17
+ const ip = context.incomingRequestProperties.ip;
18
+ return new Response(ip ?? "unknown");
19
+ }
17
20
  ```
21
+
22
+ This is the official way to read the client IP. Zuplo resolves the address from
23
+ the layer in front of your gateway, so you get the same property regardless of
24
+ where the gateway runs.
25
+
26
+ ## Handle an unknown address
27
+
28
+ The value is `undefined` when nothing identified the caller. Handle that case
29
+ explicitly:
30
+
31
+ ```ts
32
+ const ip = context.incomingRequestProperties.ip;
33
+ if (!ip) {
34
+ return HttpProblems.badRequest(request, context, {
35
+ detail: "Could not determine the client IP address",
36
+ });
37
+ }
38
+ ```
39
+
40
+ Don't substitute a placeholder such as `"unknown"` or `0.0.0.0` and then key on
41
+ it. Every unidentified caller collapses into that single value, so a rate limit
42
+ keyed on it throttles them as one client, and an allow list keyed on it either
43
+ admits all of them or none.
44
+
45
+ ## When the address can be trusted
46
+
47
+ An IP address is only as trustworthy as the layer that determined it. Zuplo
48
+ reports an address only where that layer establishes it, so the value is either
49
+ trustworthy or `undefined` — never one the caller chose.
50
+
51
+ | Deployment | Determined by | Trustworthy |
52
+ | ----------------- | ------------------------------------- | ---------------------------------------- |
53
+ | Zuplo Cloud | The Zuplo edge | Yes |
54
+ | Zuplo Dedicated | Your environment, configured by Zuplo | Yes |
55
+ | Self-hosted | The proxy you run | Only if that proxy overwrites the header |
56
+ | Local development | Nothing — the caller is loopback | Reports `127.0.0.1` |
57
+
58
+ Zuplo Cloud and Zuplo Dedicated need no configuration. Self-hosted deployments
59
+ depend on the proxy you run, covered in
60
+ [Self-hosted deployments](#self-hosted-deployments).
61
+
62
+ :::caution
63
+
64
+ Don't read an IP header from `request.headers` yourself. Which header carries
65
+ the caller's address depends on where the gateway runs, so a header that holds
66
+ it on one deployment may be absent on another — or may hold whatever the caller
67
+ put there. Use `context.incomingRequestProperties.ip`, which resolves the right
68
+ one for the deployment.
69
+
70
+ :::
71
+
72
+ ## The `x-forwarded-for` header
73
+
74
+ `x-forwarded-for` carries a client address through a chain of proxies. Each
75
+ proxy appends the address it received the request from, producing a list:
76
+
77
+ ```
78
+ X-Forwarded-For: 203.0.113.7, 198.51.100.4
79
+ ```
80
+
81
+ The header reaches your code and your backend unchanged, so you can read it
82
+ directly:
83
+
84
+ ```ts
85
+ const chain = (request.headers.get("x-forwarded-for") ?? "")
86
+ .split(",")
87
+ .map((entry) => entry.trim())
88
+ .filter(Boolean);
89
+ ```
90
+
91
+ To use it correctly, understand what the list contains. A caller can send an
92
+ `X-Forwarded-For` of its own, and every proxy after that appends to whatever
93
+ arrived. So the front of the list is whatever the caller wrote, and only entries
94
+ added by a hop you operate mean anything.
95
+
96
+ That leads to a few rules:
97
+
98
+ - Count positions from the **end**. The last entry was added by the hop nearest
99
+ your gateway; the first came from the caller.
100
+ - Decide in advance how many hops you operate, and ignore anything beyond them.
101
+ A caller can pad the front with as many entries as it likes.
102
+ - Don't search the list for an address you recognize — a caller can put one
103
+ there.
104
+ - Expect IPv6 entries, which may be bracketed and carry a port, such as
105
+ `[2001:db8::1]:8080`.
106
+
107
+ :::danger
108
+
109
+ An address is only as reliable as the hop that established it. If you key a rate
110
+ limit on a value the caller can choose, they escape the limit by rotating it.
111
+ The same value in an allow list can be used to impersonate an allowed client,
112
+ and in an audit record it produces entries that didn't happen.
113
+
114
+ :::
115
+
116
+ If you only need the caller's address, `context.incomingRequestProperties.ip`
117
+ already gives it to you without any of this.
118
+
119
+ ## Running your own CDN or proxy in front of Zuplo
120
+
121
+ If your own CDN, load balancer, or WAF sits in front of your gateway, then that
122
+ hop — not the end user — is the client Zuplo sees.
123
+ `incomingRequestProperties.ip` returns its address.
124
+
125
+ To identify the end user, have your CDN write the address into a header of your
126
+ own and read that header in your code:
127
+
128
+ ```ts
129
+ const endUserIp = request.headers.get("x-acme-client-ip");
130
+ ```
131
+
132
+ This works only if your CDN **overwrites** that header on every request. If it
133
+ merely adds the header when absent, a caller can supply their own value and it
134
+ passes straight through to your code.
135
+
136
+ ## Zuplo Dedicated
137
+
138
+ Zuplo configures your Dedicated environment to determine the client address, so
139
+ there's nothing for you to set up. `context.incomingRequestProperties.ip`
140
+ returns the caller's address the same way it does on Zuplo Cloud.
141
+
142
+ Dedicated environments aren't identical to one another — they differ by cloud,
143
+ region, and what sits in front of the gateway, such as a CDN or WAF you already
144
+ operate. If you need to know exactly how the address is determined for your
145
+ environment, or you're adding a network layer in front of it, contact your
146
+ account team or [support](mailto:support@zuplo.com). Adding a hop in front of
147
+ the gateway changes which address the gateway sees, as described in
148
+ [Running your own CDN or proxy in front of Zuplo](#running-your-own-cdn-or-proxy-in-front-of-zuplo).
149
+
150
+ ## Self-hosted deployments
151
+
152
+ On a self-hosted deployment you run the proxy in front of the gateway, so
153
+ determining the client address is yours to configure. The gateway reads it from
154
+ the `x-real-ip` request header.
155
+
156
+ Your proxy must **overwrite** that header rather than pass one through. If a
157
+ client-supplied `x-real-ip` reaches the gateway untouched, any caller can choose
158
+ its own address by sending it. NGINX's `real_ip` module does this correctly by
159
+ default. Confirm the behavior against your own configuration before relying on
160
+ the address for anything security-sensitive.
161
+
162
+ :::warning
163
+
164
+ A gateway reachable directly, with no proxy in front of it, has nothing
165
+ determining the client address. Don't expose a self-hosted gateway to the
166
+ internet directly if you rely on the client IP.
167
+
168
+ :::
169
+
170
+ ## Geolocation
171
+
172
+ To act on where a caller is rather than their address,
173
+ `incomingRequestProperties` already carries resolved geolocation — `country`,
174
+ `city`, `region`, `latitude`, `longitude`, `asn`, and more. There's no need to
175
+ geolocate the IP address yourself. See
176
+ [ZuploContext](../programmable-api/zuplo-context.mdx#incomingrequestproperties).
@@ -48,9 +48,15 @@ const elapsed = Date.now() - context.custom.startTime;
48
48
 
49
49
  ### `incomingRequestProperties`
50
50
 
51
- Information about the incoming request such as geolocation data. This is a
52
- read-only object with the following properties:
53
-
51
+ Information about the incoming request such as the client IP address and
52
+ geolocation data. This is a read-only object with the following properties:
53
+
54
+ - `ip` - The IP address of the client that made the request, for example,
55
+ "203.0.113.7". This is the only supported way to read the client IP; don't
56
+ read an IP header from `request.headers` yourself. The value is `undefined`
57
+ when nothing identified the caller. See
58
+ [how to check an incoming IP address](../articles/check-ip-address.mdx) for
59
+ when the address can be trusted on each deployment type.
54
60
  - `asn` - ASN of the incoming request, for example, 395747.
55
61
  - `asOrganization` - The organization which owns the ASN of the incoming
56
62
  request, for example, Google Cloud.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "6.73.31",
3
+ "version": "6.74.2",
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.73.31",
23
- "@zuplo/core": "6.73.31",
24
- "@zuplo/runtime": "6.73.31",
22
+ "@zuplo/cli": "6.74.2",
23
+ "@zuplo/core": "6.74.2",
24
+ "@zuplo/runtime": "6.74.2",
25
25
  "@zuplo/test": "1.4.4"
26
26
  }
27
27
  }