4.1 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| proxy/headers-and-forwarding | Implement proxy header injection, hop-by-hop removal, and request forwarding with hyper Client | completed |
|
moderate | medium | component | implementation |
Description
Implement the core reverse proxy logic: inject proxy headers, remove hop-by-hop headers, and forward requests to the upstream via a shared hyper::Client.
Proxy Header Injection
The proxy is an edge proxy — it sits directly in front of the internet with no trusted proxies upstream. This means existing X-Forwarded-For headers from the client cannot be trusted.
| Header | Value Source | Behavior |
|---|---|---|
Host |
Original request Host header |
Preserved as-is |
X-Real-IP |
ConnectInfo<SocketAddr> remote IP |
Set to client's IP address |
X-Forwarded-For |
ConnectInfo<SocketAddr> remote IP |
Replaced, not appended |
X-Forwarded-Proto |
Determined by listener port | https for https_port, http for http_port |
Hop-by-Hop Header Removal
Remove these headers before forwarding to upstream (RFC 2616 §13.5.1):
Connection,Keep-Alive,Proxy-Authorization,Proxy-AuthenticateTE,Trailers,Transfer-Encoding,Upgrade
Also remove these from upstream responses before sending to client.
Request Forwarding
- Build the upstream URI:
{upstream_scheme}://{upstream}{path}?{query} - Copy request method, headers (with proxy headers injected, hop-by-hop removed), and body
- Send via shared
hyper::Clientwith per-site timeout overrides - Stream response back to client (chunk-by-chunk, not buffered)
- Handle client disconnect (log at debug, close upstream connection)
- Handle upstream disconnect (send whatever was already sent, close connection)
hyper Client Configuration
- Created once at startup, shared via axum State
- HTTP/1.1 only for upstream connections
- No redirect following (proxies should not follow redirects)
- Connection pooling (hyper default behavior)
- Per-site timeout overrides:
upstream_connect_timeout_secs(default 5s),upstream_request_timeout_secs(default 60s)
Upstream Scheme
Default is http://. When upstream_scheme is "https", validate the upstream's TLS certificate using the system's native TLS root certificates. Certificate validation failures result in 502 Bad Gateway.
Acceptance Criteria
X-Real-IPset fromConnectInfo<SocketAddr>remote IPX-Forwarded-Forreplaced (not appended) with client IPX-Forwarded-Protoset tohttpsorhttpbased on listener portHostheader preserved as-is- Hop-by-hop headers removed before forwarding to upstream
- Hop-by-hop headers removed from upstream response before sending to client
- No
Serverheader added to responses - No
Viaheader added in Phase 1 - Request body streamed (not buffered) to upstream
- Response body streamed (not buffered) to client
- Client disconnect logged at debug level, upstream connection closed
- Upstream disconnect: client receives whatever was already sent
- Per-site timeout overrides applied to hyper Client requests
upstream_scheme: "https"validates upstream TLS certificate with system roots- Shared
hyper::Clientinstance via axum State - Unit tests for header injection and removal
- Integration test: proxy request to upstream, verify headers and response
References
- docs/architecture/proxy.md — header injection, request forwarding, error handling
- docs/architecture/decisions/002-custom-proxy-handler.md — custom handler rationale
- docs/architecture/decisions/017-upstream-connection-defaults.md — HTTP/1.1, no redirects
- docs/architecture/decisions/021-x-forwarded-for-edge-proxy.md — edge proxy model
Notes
The
X-Forwarded-For: replace, don't appendbehavior is critical. The proxy is the edge — there are no trusted proxies upstream. ExistingX-Forwarded-Forvalues from the client could be spoofed and must not be trusted.
Summary
To be filled on completion