Expand description
The client’s address: resolved once per request, before any guard, and forwarded to the upstream by an explicit policy.
§Resolution
The connection’s peer is the client, unless it is listed in
trusted_proxies. Forwarding
information is not trustworthy by itself (RFC 7239 §8.1), so only then is
the header the trusted proxy reports in read:
X-Forwarded-For(the default): every field line, in order, as one list (RFC 9110 §5.3), walked from the right through the trusted proxies to the first address outside them. What lies left of that address is the client’s own claim and is never read. When every hop is trusted, the leftmost is the client.X-Real-IP, when chosen: one address, the trusted proxy’s own peer.
The result is a ClientAddress on the request, and every consumer reads
that one value: the rate limits, the auth decider, extra routes, the
fallback and an upstream in process. When nothing can be resolved (no
connection information, or a trusted proxy’s report that cannot be read)
the Resolution says why, and no address is ever made up: a trusted
proxy’s own address never stands in for its client’s.
§What the upstream receives
ForwardConfig decides, independently of
the resolution:
x_forwarded_for | X-Forwarded-For upstream | Its first element |
|---|---|---|
verified (default) | the client, the trusted proxies after it, this proxy’s peer | the client |
resolved | the client alone | the client |
append | the list as it arrived, then this proxy’s peer | whatever the client wrote |
preserve | the list as it arrived (X-Real-IP and Forwarded too) | whatever the client wrote |
remove | nothing | none |
Next to it, client_header (X-Real-IP by default) carries the resolved
address alone, and audit_header (off by default) the list exactly as it
arrived, for logs. These headers are the proxy’s: the request cannot set
them, and neither can a guard (JWT claim headers, ext_authz, the auth
decider). Native gRPC, gRPC-Web and transcoded calls receive the same
values, remote or in process, and so does the fallback.
With append and preserve the first element of X-Forwarded-For is
whatever the client chose to write: an upstream should then read the
client header, or walk the list from the right trusting this proxy.
§Examples
Behind a load balancer in 10.0.0.0/8, forwarding as nginx does with
$proxy_add_x_forwarded_for, and keeping what arrived for the access log:
client_address:
trusted_proxies: ["10.0.0.0/8"]
forward:
x_forwarded_for: append
client_header: x-real-ip
audit_header: x-original-forwarded-forThe same in code, with the address read back in a tonic handler:
use structured_proxy::client_address::Resolution;
use structured_proxy::config::{ClientAddressConfig, XForwardedFor};
use structured_proxy::{ClientAddress, ProxyServer};
let mut client_address = ClientAddressConfig::default();
client_address.trusted_proxies = vec!["10.0.0.0/8".into()];
client_address.forward.x_forwarded_for = XForwardedFor::Append;
client_address.forward.audit_header = Some("x-original-forwarded-for".into());
let service = ProxyServer::new()
.with_client_address(client_address)
.service(tonic::service::Routes::default())?;
fn caller(request: &tonic::Request<()>) -> String {
match request.extensions().get::<ClientAddress>().map(ClientAddress::resolution) {
Some(Resolution::Peer(ip) | Resolution::Forwarded(ip)) => ip.to_string(),
_ => "unknown".into(),
}
}Routers of your own that mount proxy parts such as the
shield middleware get the same resolution from
ClientAddressLayer.
Structs§
- Client
Address - The client address the proxy resolved for a request.
- Client
Address Layer - Resolves the client address of every request before the service it wraps
sees it: puts the
ClientAddresson the request and rewrites its forwarding headers, as the proxy does before its own guards. - Client
Address Service - The service of
ClientAddressLayer.
Enums§
- Invalid
Forwarding - Why a trusted proxy’s forwarded address cannot be read.
- Resolution
- How the client address was resolved, or why it was not.