Upgrade to kaya 0.0.3 with trusted-proxy forwarded header support
- bump kaya-core/kaya-rsgi to >= 0.0.3 and add kaya-forwarded: forwarded header handling is no longer built into core, it is opt-in via ForwardedHeadersMixin and gated on trusted proxy CIDRs - add TRUSTED_PROXY_CIDRS setting (comma-separated CIDRs, validated at startup; empty means no proxy is trusted) and wire the mixin in app.py - cover trusted/untrusted peers, all-trusted chains and the RFC 7239 Forwarded header with port in the test suite - document the new variable in README, .env.example and docker-compose.yml
This commit is contained in:
+29
-3
@@ -12,9 +12,10 @@ from pyfconfig.app import app
|
||||
# httpx's ASGITransport populates the scope with this client tuple.
|
||||
CLIENT_IP = "127.0.0.1"
|
||||
CLIENT_PORT = "123"
|
||||
# First entry of the X-Forwarded-For header in ALL_HEADERS; since kaya 0.0.2
|
||||
# ctx.client honors forwarded headers, ip_addr resolves to this instead of
|
||||
# the socket peer address.
|
||||
# First entry of the X-Forwarded-For header in ALL_HEADERS. With
|
||||
# kaya-forwarded (see tests/__init__.py for the trusted CIDRs), the chain is
|
||||
# walked right-to-left skipping trusted proxies: 10.0.0.1 is trusted, so
|
||||
# ip_addr resolves to this instead of the socket peer address.
|
||||
FORWARDED_IP = "203.0.113.7"
|
||||
|
||||
ALL_HEADERS = {
|
||||
@@ -58,6 +59,31 @@ class RoutesTest(unittest.TestCase):
|
||||
r = await client.get("/ip", headers={"X-Forwarded-For": "203.0.113.7"})
|
||||
self.assertEqual("203.0.113.7", r.text.strip())
|
||||
|
||||
@async_test
|
||||
async def test_ip_ignores_x_forwarded_for_from_untrusted_peer(self) -> None:
|
||||
# The socket peer is not in TRUSTED_PROXY_CIDRS, so proxy headers
|
||||
# are ignored and the peer address itself is reported.
|
||||
transport = ASGITransport(app=app, client=("192.0.2.10", 5555))
|
||||
async with AsyncClient(transport=transport, base_url="http://192.0.2.10") as client:
|
||||
r = await client.get("/ip", headers={"X-Forwarded-For": "203.0.113.7"})
|
||||
self.assertEqual("192.0.2.10", r.text.strip())
|
||||
|
||||
@async_test
|
||||
async def test_ip_all_trusted_chain_uses_leftmost_entry(self) -> None:
|
||||
# 10.1.2.3 is inside the trusted 10.0.0.0/8, so the whole chain is
|
||||
# trusted and the leftmost entry is the original client.
|
||||
async with self.client() as client:
|
||||
r = await client.get("/ip", headers={"X-Forwarded-For": "10.1.2.3"})
|
||||
self.assertEqual("10.1.2.3", r.text.strip())
|
||||
|
||||
@async_test
|
||||
async def test_forwarded_header_with_port(self) -> None:
|
||||
async with self.client() as client:
|
||||
r = await client.get("/all.json", headers={"Forwarded": "for=203.0.113.7:4455"})
|
||||
data = json.loads(r.text)
|
||||
self.assertEqual("203.0.113.7", data["ip_addr"])
|
||||
self.assertEqual("4455", data["port"])
|
||||
|
||||
@async_test
|
||||
async def test_ua(self) -> None:
|
||||
async with self.client() as client:
|
||||
|
||||
Reference in New Issue
Block a user