castrum 0.9.2 → 0.9.4
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/Cargo.lock +2259 -0
- package/Cargo.toml +138 -0
- package/README.md +6 -0
- package/build.rs +5 -0
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +1348 -1295
- package/dist/index.js +4307 -3776
- package/docs/API.md +1 -1
- package/docs/ARCHITECTURE.md +3 -2
- package/docs/CASE_STUDY.md +2 -2
- package/docs/ENVIRONMENT.md +16 -0
- package/docs/FFI_BUN_GUIDE.md +89 -6
- package/docs/REPO_MAP.md +2 -2
- package/index.ts +19 -25
- package/package.json +9 -1
- package/rust/crypto/aead.rs +378 -0
- package/rust/crypto/argon2.rs +265 -0
- package/rust/crypto/base64.rs +420 -0
- package/rust/crypto/bcrypt.rs +89 -0
- package/rust/crypto/cookie_sign.rs +169 -0
- package/rust/crypto/csrf.rs +106 -0
- package/rust/crypto/ed25519.rs +215 -0
- package/rust/crypto/hashing.rs +140 -0
- package/rust/crypto/hmac_sha256.rs +88 -0
- package/rust/crypto/jwt/api.rs +286 -0
- package/rust/crypto/jwt/mod.rs +22 -0
- package/rust/crypto/jwt/tests.rs +312 -0
- package/rust/crypto/jwt/token.rs +360 -0
- package/rust/crypto/mod.rs +27 -0
- package/rust/crypto/pbkdf2.rs +96 -0
- package/rust/crypto/random_token.rs +85 -0
- package/rust/crypto/session.rs +222 -0
- package/rust/ffi/crypto.rs +708 -0
- package/rust/ffi/hashing.rs +285 -0
- package/rust/ffi/http.rs +752 -0
- package/rust/ffi/ingress.rs +303 -0
- package/rust/ffi/json.rs +517 -0
- package/rust/ffi/jwt.rs +419 -0
- package/rust/ffi/metrics.rs +373 -0
- package/rust/ffi/mod.rs +68 -0
- package/rust/ffi/payload.rs +275 -0
- package/rust/ffi/probe.rs +60 -0
- package/rust/ffi/rate_limit.rs +77 -0
- package/rust/ffi/route.rs +85 -0
- package/rust/ffi/tests.rs +2333 -0
- package/rust/ffi/util.rs +152 -0
- package/rust/ffi/validators.rs +123 -0
- package/rust/http/accept.rs +606 -0
- package/rust/http/cookie_parser.rs +140 -0
- package/rust/http/etag.rs +289 -0
- package/rust/http/form.rs +132 -0
- package/rust/http/headers.rs +267 -0
- package/rust/http/http_date.rs +286 -0
- package/rust/http/http_parser.rs +252 -0
- package/rust/http/media_type.rs +355 -0
- package/rust/http/method.rs +200 -0
- package/rust/http/mime_lookup.rs +130 -0
- package/rust/http/mod.rs +32 -0
- package/rust/http/multipart.rs +685 -0
- package/rust/http/query_parser.rs +180 -0
- package/rust/http/url_codec.rs +296 -0
- package/rust/http/url_join.rs +324 -0
- package/rust/ingress/api.rs +355 -0
- package/rust/ingress/cors.rs +403 -0
- package/rust/ingress/ingress_constants.rs +209 -0
- package/rust/ingress/ip_trust.rs +312 -0
- package/rust/ingress/mod.rs +48 -0
- package/rust/ingress/native_route.rs +779 -0
- package/rust/ingress/options.rs +176 -0
- package/rust/ingress/output.rs +406 -0
- package/rust/ingress/packed.rs +191 -0
- package/rust/ingress/pipeline.rs +534 -0
- package/rust/ingress/proxy.rs +138 -0
- package/rust/ingress/rate_limit.rs +538 -0
- package/rust/ingress/terminal.rs +114 -0
- package/rust/ingress/tests.rs +566 -0
- package/rust/ingress/time.rs +70 -0
- package/rust/json/fast_schema/capture.rs +336 -0
- package/rust/json/fast_schema/compile.rs +556 -0
- package/rust/json/fast_schema/cursor.rs +368 -0
- package/rust/json/fast_schema/email.rs +282 -0
- package/rust/json/fast_schema/errors.rs +247 -0
- package/rust/json/fast_schema/mod.rs +57 -0
- package/rust/json/fast_schema/tests.rs +1143 -0
- package/rust/json/fast_schema/types.rs +140 -0
- package/rust/json/fast_schema/validate.rs +931 -0
- package/rust/json/json_ops.rs +121 -0
- package/rust/json/json_schema.rs +837 -0
- package/rust/json/json_ser.rs +881 -0
- package/rust/json/mod.rs +19 -0
- package/rust/json/napi_marshal.rs +318 -0
- package/rust/json/patch/api.rs +60 -0
- package/rust/json/patch/engine.rs +153 -0
- package/rust/json/patch/mod.rs +47 -0
- package/rust/json/patch/ops.rs +270 -0
- package/rust/json/patch/pointer.rs +49 -0
- package/rust/json/patch/tests.rs +499 -0
- package/rust/lib.rs +72 -0
- package/rust/metrics/api.rs +148 -0
- package/rust/metrics/mod.rs +21 -0
- package/rust/metrics/registry.rs +961 -0
- package/rust/panic_safety.rs +40 -0
- package/rust/payload/compress.rs +512 -0
- package/rust/payload/mod.rs +13 -0
- package/rust/payload/sse.rs +214 -0
- package/rust/payload/template.rs +253 -0
- package/rust/payload/websocket.rs +69 -0
- package/rust/payload/ws_frames.rs +354 -0
- package/rust/proptest_suite.rs +139 -0
- package/rust/selection.rs +95 -0
- package/rust/src/bin/native-bench.rs +495 -0
- package/rust/test_support.rs +71 -0
- package/rust/util/batch/api.rs +266 -0
- package/rust/util/batch/core.rs +473 -0
- package/rust/util/batch/mod.rs +23 -0
- package/rust/util/batch/tests.rs +445 -0
- package/rust/util/batch_core.rs +184 -0
- package/rust/util/bytes.rs +407 -0
- package/rust/util/mod.rs +34 -0
- package/rust/util/packed.rs +473 -0
- package/rust/util/text.rs +130 -0
- package/rust/util/threadpool.rs +128 -0
- package/rust/util/validation.rs +419 -0
- package/rust-toolchain.toml +17 -0
- package/scripts/postinstall.mjs +199 -0
- package/src/ingress/constants.ts +1 -1
- package/src/ingress/context.ts +2 -2
- package/src/ingress/decode/baked-result.ts +13 -13
- package/src/ingress/decode/fast-result.ts +17 -17
- package/src/ingress/errors.ts +7 -7
- package/src/ingress/headers/baked-templates.ts +1 -1
- package/src/ingress/headers/fast-templates.ts +3 -3
- package/src/ingress/index.ts +1 -1
- package/src/ingress/native-route.ts +9 -5
- package/src/ingress/options.ts +1 -1
- package/src/ingress/packing/gather-raw-headers.ts +1 -1
- package/src/ingress/packing/route-wire.ts +1 -5
- package/src/ingress/response/error-bodies.ts +5 -5
- package/src/ingress/response/terminal.ts +1 -1
- package/src/ingress/routes/echo.ts +4 -4
- package/src/ingress/routes/fallback.ts +2 -2
- package/src/ingress/routes/head.ts +2 -2
- package/src/ingress/routes/native.ts +2 -1
- package/src/ingress/routes/options.ts +1 -1
- package/src/ingress/routes/read.ts +2 -2
- package/src/ingress/server.ts +10 -6
- package/src/ingress/status.ts +7 -7
- package/src/integration/batch.ts +1 -1
- package/src/integration/index.ts +9 -9
- package/src/integration/pipeline.ts +6 -6
- package/src/integration/websocket.ts +2 -2
- package/src/loader/index.ts +1 -5
- package/src/loader/ops.ts +1 -1
- package/src/native/ffi/build/codecs.ts +104 -8
- package/src/native/ffi/build/compress.ts +14 -10
- package/src/native/ffi/build/instances.ts +40 -3
- package/src/native/ffi/build/metrics.ts +224 -0
- package/src/native/ffi/build/parse.ts +59 -23
- package/src/native/ffi/build/util.ts +14 -1
- package/src/native/ffi/build.ts +2 -0
- package/src/native/ffi/selftest.ts +10 -3
- package/src/native/ffi/types.ts +111 -9
- package/src/native/ffi.ts +87 -1
- package/src/native/index.ts +1 -1
- package/src/native/loader.ts +2 -2
- package/src/native/types/instances.ts +34 -0
- package/src/native/types.ts +11 -0
- package/src/runtime/codec.ts +53 -9
- package/src/runtime/types.ts +7 -0
- package/src/rust-ffi/batch/build.ts +16 -9
- package/src/rust-ffi/index.ts +7 -6
- package/src/rust-ffi/options.ts +1 -3
- package/src/rust-ffi/proven.ts +3 -3
- package/src/rust-ffi/scalar/crypto.ts +1 -1
- package/src/rust-ffi/scalar/factories.ts +73 -0
- package/src/rust-ffi/scalar/hashing.ts +1 -1
- package/src/rust-ffi/scalar/http.ts +17 -11
- package/src/rust-ffi/scalar/index.ts +2 -0
- package/src/rust-ffi/scalar/interface.ts +20 -0
- package/src/rust-ffi/scalar/payload.ts +11 -3
- package/src/rust-ffi/scalar/validation.ts +121 -0
- package/src/selection.ts +1 -1
- package/src/shared/bytes.ts +5 -4
- package/src/shared/codec.ts +18 -3
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// rust/ingress/ip_trust.rs — trusted-proxy client-IP resolution.
|
|
2
|
+
|
|
3
|
+
use crate::util::trim_ascii_whitespace;
|
|
4
|
+
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
|
|
5
|
+
use std::net::IpAddr;
|
|
6
|
+
use xxhash_rust::xxh3::Xxh3;
|
|
7
|
+
|
|
8
|
+
#[derive(Clone)]
|
|
9
|
+
pub enum ProxyTrustMode {
|
|
10
|
+
None,
|
|
11
|
+
All,
|
|
12
|
+
Networks(Vec<IpNet>),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl ProxyTrustMode {
|
|
16
|
+
/// Build the trust mode from user config. Pure core: errors are `String`
|
|
17
|
+
/// messages; the napi boundary (`mod.rs`) maps them to JS errors.
|
|
18
|
+
pub fn from_config(
|
|
19
|
+
enabled: bool,
|
|
20
|
+
networks: Option<Vec<String>>,
|
|
21
|
+
) -> std::result::Result<Self, String> {
|
|
22
|
+
if !enabled {
|
|
23
|
+
return Ok(Self::None);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Safety: an enabled trust-proxy mode with NO explicit networks must
|
|
27
|
+
// never fall back to ProxyTrustMode::All (which trusts every hop and
|
|
28
|
+
// lets clients spoof X-Forwarded-For). Treat it as "trust nothing"
|
|
29
|
+
// unless the operator explicitly lists trusted networks.
|
|
30
|
+
let Some(networks) = networks else {
|
|
31
|
+
return Ok(Self::Networks(Vec::new()));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if networks.is_empty() {
|
|
35
|
+
return Ok(Self::Networks(Vec::new()));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let mut out = Vec::with_capacity(networks.len());
|
|
39
|
+
|
|
40
|
+
for raw in networks {
|
|
41
|
+
let raw = raw.trim();
|
|
42
|
+
if raw.is_empty() {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if let Ok(ip) = raw.parse::<IpAddr>() {
|
|
47
|
+
out.push(ip_to_net(ip)?);
|
|
48
|
+
} else {
|
|
49
|
+
let net: IpNet = raw
|
|
50
|
+
.parse()
|
|
51
|
+
.map_err(|_| format!("invalid trusted proxy network: {raw}"))?;
|
|
52
|
+
out.push(net);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Networks that filtered down to nothing -> trust nothing (see above).
|
|
57
|
+
Ok(Self::Networks(out))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[inline(always)]
|
|
61
|
+
pub fn is_none(&self) -> bool {
|
|
62
|
+
matches!(self, Self::None)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
pub fn is_trusted(&self, ip: IpAddr) -> bool {
|
|
66
|
+
match self {
|
|
67
|
+
Self::None => false,
|
|
68
|
+
Self::All => true,
|
|
69
|
+
Self::Networks(nets) => nets.iter().any(|n| n.contains(&ip)),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pub enum ResolvedIp<'a> {
|
|
75
|
+
V4([u8; 4]),
|
|
76
|
+
V6([u8; 16]),
|
|
77
|
+
Raw(&'a [u8]),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
impl<'a> From<IpAddr> for ResolvedIp<'a> {
|
|
81
|
+
fn from(ip: IpAddr) -> Self {
|
|
82
|
+
match ip {
|
|
83
|
+
IpAddr::V4(v4) => ResolvedIp::V4(v4.octets()),
|
|
84
|
+
IpAddr::V6(v6) => ResolvedIp::V6(v6.octets()),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
impl ResolvedIp<'_> {
|
|
90
|
+
pub fn rate_key(&self, seed: u64) -> u64 {
|
|
91
|
+
let mut h = Xxh3::with_seed(seed);
|
|
92
|
+
|
|
93
|
+
match self {
|
|
94
|
+
ResolvedIp::V4(octets) => {
|
|
95
|
+
h.update(&[1]);
|
|
96
|
+
h.update(octets);
|
|
97
|
+
}
|
|
98
|
+
ResolvedIp::V6(octets) => {
|
|
99
|
+
h.update(&[2]);
|
|
100
|
+
h.update(octets);
|
|
101
|
+
}
|
|
102
|
+
ResolvedIp::Raw(bytes) => {
|
|
103
|
+
h.update(&[0]);
|
|
104
|
+
h.update(bytes);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
h.digest()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn ip_to_net(ip: IpAddr) -> std::result::Result<IpNet, String> {
|
|
113
|
+
match ip {
|
|
114
|
+
// Prefix 32 (v4) / 128 (v6) is always valid, but return a proper error
|
|
115
|
+
// instead of panicking so a failed conversion surfaces as a JS error
|
|
116
|
+
// rather than unwinding through the constructor.
|
|
117
|
+
IpAddr::V4(v4) => Ipv4Net::new(v4, 32)
|
|
118
|
+
.map(IpNet::V4)
|
|
119
|
+
.map_err(|_| "invalid IPv4 /32 trusted network".to_string()),
|
|
120
|
+
IpAddr::V6(v6) => Ipv6Net::new(v6, 128)
|
|
121
|
+
.map(IpNet::V6)
|
|
122
|
+
.map_err(|_| "invalid IPv6 /128 trusted network".to_string()),
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fn parse_ip_bytes(bytes: &[u8]) -> Option<IpAddr> {
|
|
127
|
+
let s = std::str::from_utf8(trim_ascii_whitespace(bytes)).ok()?;
|
|
128
|
+
s.parse::<IpAddr>().ok()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Cheap "is the socket peer a trusted proxy" check for the common case where
|
|
132
|
+
/// the caller only needs `peer_trusted` and not the resolved client IP (i.e.
|
|
133
|
+
/// rate limiting is disabled). Skips the `IpAddr` parse entirely when no
|
|
134
|
+
/// trusted-proxy mode is configured (where `peer_trusted` is always false).
|
|
135
|
+
#[inline]
|
|
136
|
+
pub fn socket_is_trusted(mode: &ProxyTrustMode, socket_ip: &[u8]) -> bool {
|
|
137
|
+
if mode.is_none() {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
socket_parsed_is_trusted(mode, parse_ip_bytes(socket_ip))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[inline]
|
|
144
|
+
fn socket_parsed_is_trusted(mode: &ProxyTrustMode, parsed: Option<IpAddr>) -> bool {
|
|
145
|
+
parsed.map(|ip| mode.is_trusted(ip)).unwrap_or(false)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
pub fn resolve_client_ip<'a>(
|
|
149
|
+
mode: &ProxyTrustMode,
|
|
150
|
+
socket_ip: &'a [u8],
|
|
151
|
+
xff: Option<&'a [u8]>,
|
|
152
|
+
x_real_ip: Option<&'a [u8]>,
|
|
153
|
+
) -> (ResolvedIp<'a>, bool) {
|
|
154
|
+
let socket_trim = trim_ascii_whitespace(socket_ip);
|
|
155
|
+
let socket_parsed = parse_ip_bytes(socket_trim);
|
|
156
|
+
|
|
157
|
+
let peer_trusted = socket_parsed_is_trusted(mode, socket_parsed);
|
|
158
|
+
|
|
159
|
+
if mode.is_none() || !peer_trusted {
|
|
160
|
+
return (
|
|
161
|
+
socket_parsed
|
|
162
|
+
.map(Into::into)
|
|
163
|
+
.unwrap_or(ResolvedIp::Raw(socket_trim)),
|
|
164
|
+
peer_trusted,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if let Some(xff) = xff {
|
|
169
|
+
let mut last_valid: Option<ResolvedIp> = None;
|
|
170
|
+
|
|
171
|
+
for part in xff.split(|&b| b == b',').rev() {
|
|
172
|
+
let part = trim_ascii_whitespace(part);
|
|
173
|
+
if part.is_empty() {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
match parse_ip_bytes(part) {
|
|
178
|
+
Some(ip) => {
|
|
179
|
+
if mode.is_trusted(ip) {
|
|
180
|
+
last_valid = Some(ip.into());
|
|
181
|
+
} else {
|
|
182
|
+
// First hop past the trusted edge: use it directly.
|
|
183
|
+
return (ip.into(), peer_trusted);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
None => break,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if let Some(ip) = last_valid {
|
|
191
|
+
return (ip, peer_trusted);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if let Some(xri) = x_real_ip {
|
|
196
|
+
let xri_trim = trim_ascii_whitespace(xri);
|
|
197
|
+
let first = xri_trim.split(|&b| b == b',').next().unwrap_or(xri_trim);
|
|
198
|
+
|
|
199
|
+
if let Some(ip) = parse_ip_bytes(first) {
|
|
200
|
+
return (ip.into(), peer_trusted);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
(
|
|
205
|
+
socket_parsed
|
|
206
|
+
.map(Into::into)
|
|
207
|
+
.unwrap_or(ResolvedIp::Raw(socket_trim)),
|
|
208
|
+
peer_trusted,
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[cfg(test)]
|
|
213
|
+
mod tests {
|
|
214
|
+
use super::{resolve_client_ip, ProxyTrustMode};
|
|
215
|
+
|
|
216
|
+
#[test]
|
|
217
|
+
fn ip_trust_disabled_mode_is_none() {
|
|
218
|
+
let mode = ProxyTrustMode::from_config(false, None).unwrap();
|
|
219
|
+
assert!(mode.is_none());
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#[test]
|
|
223
|
+
fn ip_trust_enabled_without_networks_trusts_nothing() {
|
|
224
|
+
// Regression: this must NOT become ProxyTrustMode::All (spoofing vector).
|
|
225
|
+
let mode = ProxyTrustMode::from_config(true, None).unwrap();
|
|
226
|
+
assert!(!mode.is_none());
|
|
227
|
+
assert!(!mode.is_trusted("10.0.0.1".parse().unwrap()));
|
|
228
|
+
assert!(!mode.is_trusted("8.8.8.8".parse().unwrap()));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#[test]
|
|
232
|
+
fn ip_trust_enabled_with_empty_networks_trusts_nothing() {
|
|
233
|
+
let mode = ProxyTrustMode::from_config(true, Some(vec![])).unwrap();
|
|
234
|
+
assert!(!mode.is_trusted("10.0.0.1".parse().unwrap()));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#[test]
|
|
238
|
+
fn ip_trust_network_list_matches() {
|
|
239
|
+
let mode = ProxyTrustMode::from_config(true, Some(vec!["10.0.0.0/8".to_string()])).unwrap();
|
|
240
|
+
assert!(mode.is_trusted("10.1.2.3".parse().unwrap()));
|
|
241
|
+
assert!(!mode.is_trusted("8.8.8.8".parse().unwrap()));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#[test]
|
|
245
|
+
fn ip_trust_single_ip_network() {
|
|
246
|
+
let mode =
|
|
247
|
+
ProxyTrustMode::from_config(true, Some(vec!["192.168.1.1".to_string()])).unwrap();
|
|
248
|
+
assert!(mode.is_trusted("192.168.1.1".parse().unwrap()));
|
|
249
|
+
assert!(!mode.is_trusted("192.168.1.2".parse().unwrap()));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#[test]
|
|
253
|
+
fn ip_trust_invalid_network_is_error() {
|
|
254
|
+
let res = ProxyTrustMode::from_config(true, Some(vec!["not-an-ip".to_string()]));
|
|
255
|
+
assert!(res.is_err());
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#[test]
|
|
259
|
+
fn ip_trust_resolves_socket_ip_when_not_trusting_proxy() {
|
|
260
|
+
let mode = ProxyTrustMode::from_config(false, None).unwrap();
|
|
261
|
+
let (resolved, peer_trusted) =
|
|
262
|
+
resolve_client_ip(&mode, b"203.0.113.5", Some(b"6.6.6.6"), None);
|
|
263
|
+
assert!(!peer_trusted);
|
|
264
|
+
// Socket IP must win; XFF must be ignored.
|
|
265
|
+
match resolved {
|
|
266
|
+
super::ResolvedIp::V4(o) => assert_eq!(o, [203, 0, 113, 5]),
|
|
267
|
+
_ => panic!("expected V4 socket IP"),
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#[test]
|
|
272
|
+
fn ip_trust_untrusted_socket_ignores_xff() {
|
|
273
|
+
// Socket is NOT in the trusted networks -> XFF cannot spoof the client IP.
|
|
274
|
+
let mode = ProxyTrustMode::from_config(true, Some(vec!["10.0.0.0/8".to_string()])).unwrap();
|
|
275
|
+
let (resolved, peer_trusted) =
|
|
276
|
+
resolve_client_ip(&mode, b"203.0.113.9", Some(b"6.6.6.6"), None);
|
|
277
|
+
assert!(!peer_trusted);
|
|
278
|
+
match resolved {
|
|
279
|
+
super::ResolvedIp::V4(o) => assert_eq!(o, [203, 0, 113, 9]),
|
|
280
|
+
_ => panic!("expected V4 socket IP"),
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#[test]
|
|
285
|
+
fn ip_trust_trusted_socket_uses_leftmost_untrusted_xff() {
|
|
286
|
+
let mode = ProxyTrustMode::from_config(true, Some(vec!["10.0.0.0/8".to_string()])).unwrap();
|
|
287
|
+
let (resolved, peer_trusted) = resolve_client_ip(
|
|
288
|
+
&mode,
|
|
289
|
+
b"10.0.0.5",
|
|
290
|
+
Some(b"8.8.8.8, 10.0.0.1, 10.0.0.2"),
|
|
291
|
+
None,
|
|
292
|
+
);
|
|
293
|
+
assert!(peer_trusted);
|
|
294
|
+
// Right-to-left: 10.0.0.2 trusted, 10.0.0.1 trusted, 8.8.8.8 NOT trusted -> client = 8.8.8.8.
|
|
295
|
+
match resolved {
|
|
296
|
+
super::ResolvedIp::V4(o) => assert_eq!(o, [8, 8, 8, 8]),
|
|
297
|
+
_ => panic!("expected V4 8.8.8.8"),
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#[test]
|
|
302
|
+
fn ip_trust_all_trusted_xff_returns_last_entry() {
|
|
303
|
+
let mode = ProxyTrustMode::from_config(true, Some(vec!["10.0.0.0/8".to_string()])).unwrap();
|
|
304
|
+
let (resolved, peer_trusted) =
|
|
305
|
+
resolve_client_ip(&mode, b"10.0.0.5", Some(b"10.0.0.1, 10.0.0.2"), None);
|
|
306
|
+
assert!(peer_trusted);
|
|
307
|
+
match resolved {
|
|
308
|
+
super::ResolvedIp::V4(o) => assert_eq!(o, [10, 0, 0, 1]),
|
|
309
|
+
_ => panic!("expected V4 10.0.0.1"),
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//! The ingress HTTP pipeline (napi boundary): the `Ingress` class + entry
|
|
2
|
+
//! points (`handle_request_packed`, `handle_request_full_sync{,_into}`) live in
|
|
3
|
+
//! the `api` submodule. The core pipeline logic lives in the pure-Rust
|
|
4
|
+
//! `pipeline` submodule; see `docs/INGRESS.md` and `docs/REPO_MAP.md` for the
|
|
5
|
+
//! two JS-side paths.
|
|
6
|
+
|
|
7
|
+
// ── Ingress submodules (task-focused split of the former single-file module) ──
|
|
8
|
+
// - api: the `Ingress` napi class + `IngressInner` state (api.rs)
|
|
9
|
+
// - pipeline: the core request pipeline — `IngressInner::handle_packed`
|
|
10
|
+
// + `write_body_sections` + `BodySections` (pipeline.rs)
|
|
11
|
+
// - tests: unit tests (tests.rs)
|
|
12
|
+
// - options: JS-facing option structs + Limits
|
|
13
|
+
// - time: monotonic/wall-clock helpers
|
|
14
|
+
// - packed: packed-input readers + builder
|
|
15
|
+
// - cors, proxy, ip_trust, rate_limit, terminal, output, ingress_constants:
|
|
16
|
+
// the ingress support modules (moved into this folder from the crate root).
|
|
17
|
+
mod api;
|
|
18
|
+
mod pipeline;
|
|
19
|
+
|
|
20
|
+
#[cfg(test)]
|
|
21
|
+
mod tests;
|
|
22
|
+
|
|
23
|
+
pub(crate) mod options;
|
|
24
|
+
pub(crate) mod packed;
|
|
25
|
+
pub(crate) mod time;
|
|
26
|
+
|
|
27
|
+
pub(crate) mod cors;
|
|
28
|
+
pub mod ingress_constants;
|
|
29
|
+
pub(crate) mod ip_trust;
|
|
30
|
+
// Per-route native stack (`castrum_route_*` C-ABI + napi `Route` class) —
|
|
31
|
+
// the LIVE external wire consumed by `@ignex/native` (route-wire.ts v3).
|
|
32
|
+
// Supersedes the deleted `rust/route.rs` (dead external-project wire); see
|
|
33
|
+
// the module doc for the contract and the lenient-parse parity rules.
|
|
34
|
+
pub mod native_route;
|
|
35
|
+
pub(crate) mod output;
|
|
36
|
+
pub(crate) mod proxy;
|
|
37
|
+
pub(crate) mod rate_limit;
|
|
38
|
+
pub(crate) mod terminal;
|
|
39
|
+
|
|
40
|
+
#[cfg(test)]
|
|
41
|
+
pub(crate) use self::api::clamp_output_size;
|
|
42
|
+
pub use self::api::Ingress;
|
|
43
|
+
pub(crate) use self::api::IngressInner;
|
|
44
|
+
pub(crate) use self::native_route::NativeRoute;
|
|
45
|
+
pub(crate) use self::options::{IngressOptions, Limits};
|
|
46
|
+
#[cfg(test)]
|
|
47
|
+
pub(crate) use self::packed::build_packed_input_sync;
|
|
48
|
+
pub(crate) use self::pipeline::IngressSchema;
|