62 lines
2.4 KiB
Markdown
62 lines
2.4 KiB
Markdown
# kaya-forwarded
|
|
|
|
Trusted-proxy forwarded header handling for the Kaya web framework.
|
|
|
|
Without this package, Kaya exposes the raw socket peer address as
|
|
`ctx.client` / `ws.client` and ignores `Forwarded` / `X-Forwarded-*` headers
|
|
entirely (they are client-controllable and trivially spoofable when the app is
|
|
directly exposed).
|
|
|
|
`ForwardedHeadersMixin` opts the application into honoring those headers, but
|
|
only when the direct socket peer is a trusted proxy, identified by a list of
|
|
trusted CIDRs/IPs.
|
|
|
|
## Usage
|
|
|
|
```python
|
|
from kaya.core import KayaApp, HttpContext
|
|
from kaya.forwarded import ForwardedHeadersMixin
|
|
|
|
app = KayaApp(mixins=[
|
|
ForwardedHeadersMixin(trusted_proxies=['127.0.0.1', '::1', '10.0.0.0/8'])
|
|
])
|
|
|
|
@app.GET('/whoami')
|
|
async def whoami(ctx: HttpContext):
|
|
host, port = ctx.client
|
|
await ctx.send_str(200, f'{host}:{port}')
|
|
```
|
|
|
|
## How it works
|
|
|
|
When a request arrives:
|
|
|
|
1. If the socket peer IP does not belong to any trusted CIDR (or there is no
|
|
peer address), the mixin leaves the context untouched — `client` remains
|
|
the socket peer and all proxy headers are ignored.
|
|
2. Otherwise the client address is resolved from the headers, in order:
|
|
- `Forwarded` (RFC 7239): the `for=` entries are walked **from right to
|
|
left**, skipping entries that are themselves trusted proxies (and
|
|
`unknown`); the first untrusted entry is the client. This defeats
|
|
spoofing when the edge proxy *appends* to the header (e.g. nginx with
|
|
`$proxy_add_x_forwarded_for`), because attacker-supplied leftmost entries
|
|
are never selected. A `:port` in the selected `for=` value also
|
|
populates the port.
|
|
- `X-Forwarded-For`: same right-to-left trusted-proxy walk; the port comes
|
|
from `X-Forwarded-Port` when present and valid.
|
|
- `X-Forwarded-Host`: first entry; port from `X-Forwarded-Port` as above.
|
|
3. If none of the headers are present or usable, the socket peer is kept.
|
|
|
|
If every entry in the chain is a trusted proxy, the leftmost entry is used
|
|
(the whole chain is trusted, so the leftmost is the original client).
|
|
|
|
The resolved address is exposed by wrapping the request context /
|
|
websocket (the same pattern as `kaya-session`), so both ASGI and RSGI keep
|
|
working and `ctx.session` from other mixins is preserved.
|
|
|
|
## Note
|
|
|
|
Even with this mixin, the edge proxy should still strip or overwrite inbound
|
|
`Forwarded` / `X-Forwarded-*` headers from clients — the mixin protects the
|
|
application, the proxy protects the chain.
|