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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
13
|
-
the
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
23
|
-
"@zuplo/core": "6.
|
|
24
|
-
"@zuplo/runtime": "6.
|
|
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
|
}
|