diff --git a/CHANGELOG.md b/CHANGELOG.md index 258ca26..8711361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 headers that will be sent with every request from a connection. This setting accepts a mapping of header names to `connection.header.Header` objects which can retrieve values from get params, cookies etc. +- The `X-Real-IP` header of the connection is now forwarded to the backend by +default. Without it the backend attributes every request to the host High +Templar dials it from, rather than to the client that opened the websocket. +- An `API_SOCKET` setting was added that makes requests to the backend go over +the given unix socket rather than over TCP. This is for deployments where the +backend is otherwise only reachable back through a proxy, which overwrites +`X-Real-IP` with the address of the machine dialling it. `API_URL` is still +required, and must be an `http://` url: the socket does not speak TLS, the path +prefix is still prepended to every request, and the host, while it no longer +decides where the connection goes, is still sent as the `Host` header and so +must be one the backend accepts. + +### Removed +- The `FORWARD_IP` setting has been removed. It read the value of the given key +from the WSGI request environment and sent it on as `X-Forwarded-For`. The +request environment has not existed since the rewrite to Quart, which dropped +the code that read the setting but left the setting itself in place, so it has +had no effect since. `X-Real-IP` is now forwarded by default instead; note that +this is a different header, chosen because a proxy overwrites it rather than +appending to it, and so it cannot be set by the client. ### Fixed - The message consumer queue is now declared `exclusive` instead of diff --git a/high_templar/asgi.py b/high_templar/asgi.py index 912d9c6..5eb26bf 100644 --- a/high_templar/asgi.py +++ b/high_templar/asgi.py @@ -5,8 +5,8 @@ class Settings: API_URL = os.environ.get('CY_BINDER_INTERNAL', 'http://wiremock:8080/api/') + API_SOCKET = os.environ.get('CY_BINDER_SOCKET') USER_ID_PATH = ['user', 'data', 'id'] - FORWARD_IP = 'HTTP_X_REAL_IP' CONNECTION_HEADERS = { # 'X-Session-Token': header.Param('session_token'), } diff --git a/high_templar/backend_adapter/binder.py b/high_templar/backend_adapter/binder.py index f0b0b42..19f6edf 100644 --- a/high_templar/backend_adapter/binder.py +++ b/high_templar/backend_adapter/binder.py @@ -1,5 +1,5 @@ # from aiohttp_requests import requests -from aiohttp import ClientSession +from aiohttp import ClientSession, UnixConnector from .interface import NoBackendConnectionException, BackendAdapter, UnparsableBackendPermissionsException from high_templar.authentication import Authentication, Permission from . import header @@ -8,6 +8,9 @@ DEFAULT_HEADERS = { 'cookie': header.Key('Cookie'), 'user-agent': header.Key('User-Agent'), + # Without this the backend attributes the connection to whichever host we + # dial it from, rather than to the client that opened the websocket. + 'x-real-ip': header.Key('X-Real-IP'), 'x-csrftoken': header.Cookie('csrftoken'), 'authorization': header.Param('token').map('Token {}'.format), } @@ -20,10 +23,15 @@ class BinderAdapter(BackendAdapter, ClientSession): ''' def __init__(self, app): - super().__init__() + # API_SOCKET reaches the backend over a unix socket, so the request does + # not pass back through the proxy that fronts API_URL. API_URL is still + # prepended to every request and must be http:// -- the socket does not + # speak TLS -- and its host, though no longer dialled, is still sent as + # the Host header. + socket = app.config.get('API_SOCKET') + super().__init__(connector=UnixConnector(path=socket) if socket else None) self.app = app self.base_url = app.config['API_URL'] - self.forward_ip = app.config.get('FORWARD_IP') self.header_definition = {**DEFAULT_HEADERS, **app.config.get('CONNECTION_HEADERS', {})} async def get_authentication(self, websocket) -> Authentication: diff --git a/install/serve.py b/install/serve.py index 356e236..5304787 100644 --- a/install/serve.py +++ b/install/serve.py @@ -19,9 +19,11 @@ class Settings: - API_URL = os.environ.get('BINDER_INTERNAL', app_url) + API_SOCKET = os.environ.get('BINDER_SOCKET') + API_URL = os.environ.get('BINDER_INTERNAL') or ( + 'http://localhost/api/' if API_SOCKET else app_url + ) USER_ID_PATH = ['user', 'data', 'id'] - FORWARD_IP = 'HTTP_X_REAL_IP' CONNECTION_HEADERS = { 'X-Session-Token': header.Param('session_token'), 'Referer': header.Fixed(app_url), # Needed in production for online/offline notification (see T25664)