diff --git a/homeassistant/components/http/forwarded.py b/homeassistant/components/http/forwarded.py index 5c62a469924..18bc51af1d1 100644 --- a/homeassistant/components/http/forwarded.py +++ b/homeassistant/components/http/forwarded.py @@ -47,7 +47,8 @@ def async_setup_forwarded( Additionally: - If no X-Forwarded-For header is found, the processing of all headers is skipped. - - Log a warning when untrusted connected peer provides X-Forwarded-For headers. + - Throw HTTP 400 status when untrusted connected peer provides + X-Forwarded-For headers. - If multiple instances of X-Forwarded-For, X-Forwarded-Proto or X-Forwarded-Host are found, an HTTP 400 status code is thrown. - If malformed or invalid (IP) data in X-Forwarded-For header is found, @@ -87,26 +88,20 @@ def async_setup_forwarded( # We have X-Forwarded-For, but config does not agree if not use_x_forwarded_for: - _LOGGER.warning( + _LOGGER.error( "A request from a reverse proxy was received from %s, but your " - "HTTP integration is not set-up for reverse proxies; " - "This request will be blocked in Home Assistant 2021.7 unless " - "you configure your HTTP integration to allow this header", + "HTTP integration is not set-up for reverse proxies", connected_ip, ) - # Block this request in the future, for now we pass. - return await handler(request) + raise HTTPBadRequest # Ensure the IP of the connected peer is trusted if not any(connected_ip in trusted_proxy for trusted_proxy in trusted_proxies): - _LOGGER.warning( - "Received X-Forwarded-For header from untrusted proxy %s, headers not processed; " - "This request will be blocked in Home Assistant 2021.7 unless you configure " - "your HTTP integration to allow this proxy to reverse your Home Assistant instance", + _LOGGER.error( + "Received X-Forwarded-For header from an untrusted proxy %s", connected_ip, ) - # Not trusted, Block this request in the future, continue as normal - return await handler(request) + raise HTTPBadRequest # Multiple X-Forwarded-For headers if len(forwarded_for_headers) > 1: diff --git a/tests/components/http/test_forwarded.py b/tests/components/http/test_forwarded.py index 4b7a3421b0a..400a1f32729 100644 --- a/tests/components/http/test_forwarded.py +++ b/tests/components/http/test_forwarded.py @@ -33,9 +33,9 @@ async def test_x_forwarded_for_without_trusted_proxy(aiohttp_client, caplog): mock_api_client = await aiohttp_client(app) resp = await mock_api_client.get("/", headers={X_FORWARDED_FOR: "255.255.255.255"}) - assert resp.status == 200 + assert resp.status == 400 assert ( - "Received X-Forwarded-For header from untrusted proxy 127.0.0.1, headers not processed" + "Received X-Forwarded-For header from an untrusted proxy 127.0.0.1" in caplog.text ) @@ -103,35 +103,13 @@ async def test_x_forwarded_for_disabled_with_proxy(aiohttp_client, caplog): mock_api_client = await aiohttp_client(app) resp = await mock_api_client.get("/", headers={X_FORWARDED_FOR: "255.255.255.255"}) - assert resp.status == 200 + assert resp.status == 400 assert ( "A request from a reverse proxy was received from 127.0.0.1, but your HTTP " "integration is not set-up for reverse proxies" in caplog.text ) -async def test_x_forwarded_for_with_untrusted_proxy(aiohttp_client): - """Test that we get the IP from transport with untrusted proxy.""" - - async def handler(request): - url = mock_api_client.make_url("/") - assert request.host == f"{url.host}:{url.port}" - assert request.scheme == "http" - assert not request.secure - assert request.remote == "127.0.0.1" - - return web.Response() - - app = web.Application() - app.router.add_get("/", handler) - async_setup_forwarded(app, True, [ip_network("1.1.1.1")]) - - mock_api_client = await aiohttp_client(app) - resp = await mock_api_client.get("/", headers={X_FORWARDED_FOR: "255.255.255.255"}) - - assert resp.status == 200 - - async def test_x_forwarded_for_with_spoofed_header(aiohttp_client): """Test that we get the IP from the transport with a spoofed header.""" @@ -205,31 +183,6 @@ async def test_x_forwarded_for_with_multiple_headers(aiohttp_client, caplog): assert "Too many headers for X-Forwarded-For" in caplog.text -async def test_x_forwarded_proto_without_trusted_proxy(aiohttp_client): - """Test that proto header is ignored when untrusted.""" - - async def handler(request): - url = mock_api_client.make_url("/") - assert request.host == f"{url.host}:{url.port}" - assert request.scheme == "http" - assert not request.secure - assert request.remote == "127.0.0.1" - - return web.Response() - - app = web.Application() - app.router.add_get("/", handler) - - async_setup_forwarded(app, True, []) - - mock_api_client = await aiohttp_client(app) - resp = await mock_api_client.get( - "/", headers={X_FORWARDED_FOR: "255.255.255.255", X_FORWARDED_PROTO: "https"} - ) - - assert resp.status == 200 - - @pytest.mark.parametrize( "x_forwarded_for,remote,x_forwarded_proto,secure", [ @@ -409,32 +362,6 @@ async def test_x_forwarded_proto_incorrect_number_of_elements( ) -async def test_x_forwarded_host_without_trusted_proxy(aiohttp_client): - """Test that host header is ignored when untrusted.""" - - async def handler(request): - url = mock_api_client.make_url("/") - assert request.host == f"{url.host}:{url.port}" - assert request.scheme == "http" - assert not request.secure - assert request.remote == "127.0.0.1" - - return web.Response() - - app = web.Application() - app.router.add_get("/", handler) - - async_setup_forwarded(app, True, []) - - mock_api_client = await aiohttp_client(app) - resp = await mock_api_client.get( - "/", - headers={X_FORWARDED_FOR: "255.255.255.255", X_FORWARDED_HOST: "example.com"}, - ) - - assert resp.status == 200 - - async def test_x_forwarded_host_with_trusted_proxy(aiohttp_client): """Test that we get the host header if proxy is trusted."""