Skip to content
Snippets Groups Projects
Unverified Commit 81fea97b authored by Andrej Shadura's avatar Andrej Shadura
Browse files

Use ASGI scope instead of Host header to reliably detect server port


Host header can be spoofed, making it possible to bypass authentication.
Since proxy exposes one port per worker for the backend to connect to,
it cannot require authentication on these ports. The port exposed to the
outside though, needs to be authenticated to prevent information leaks,
so the proxy server needs to be able to reliably distinguish clients
connecting to the proxy from the outside and the backend services trying
to talk to (emulated) workers.

Signed-off-by: default avatarAndrej Shadura <andrew.shadura@collabora.co.uk>
parent 39574ac1
No related branches found
No related tags found
1 merge request!18Use ASGI scope instead of Host header to reliably detect server port
Pipeline #345667 passed
......@@ -42,7 +42,7 @@ from .async_jsonrpc import AsyncJSONRPCResponseManager
from .config import config, HTTP_TIMEOUT
from .rpcqueue import RPCQueue
from .utils import filterdict, run_multiserver, print_ws_message, get_port_by_host, job_trace, job_alias, open_cache
from .utils import filterdict, run_multiserver, print_ws_message, job_trace, job_alias, open_cache
from .worker import Worker, ProxiedWorker
logger = logging.getLogger(__name__)
......@@ -66,7 +66,7 @@ def require_auth(f):
@functools.wraps(f)
async def wrapper(*args, **kwargs):
ctx = request if has_request_context() else websocket
port = get_port_by_host(ctx.scheme, ctx.host)
_, port = ctx.scope['server']
if port == config.server.port:
auth_header = ctx.headers.get("Authorization")
if not auth_header or not (
......@@ -105,7 +105,7 @@ def handout_port(worker_id):
def find_worker(worker_id):
if worker_id:
return worker_id
port = get_port_by_host(request.scheme, request.host)
_, port = request.scope['server']
if not worker_id and ports.get(port):
worker_id = ports[int(port)].workerid
debug(f"detected {worker_id = }")
......
......@@ -126,58 +126,6 @@ def filterdict(d, keep=None, remove=None):
}
RE_HOST_PORT = re.compile(r"^(?P<host>[^[:]+|\[[0-9a-f:]+]*)(?::(?P<port>[0-9]+))?$")
def get_port_by_host(scheme: str, host: str) -> int:
"""
Extract the port number from the host header and scheme
>>> get_port_by_host('http', 'localhost')
80
>>> get_port_by_host('http', 'localhost:8000')
8000
>>> get_port_by_host('https', 'localhost')
443
>>> get_port_by_host('https', 'localhost:8443')
8443
>>> get_port_by_host('http', 'example.com')
80
>>> get_port_by_host('http', 'example.com:8000')
8000
>>> get_port_by_host('https', 'example.com')
443
>>> get_port_by_host('https', 'example.com:8443')
8443
>>> get_port_by_host('http', '127.0.0.1')
80
>>> get_port_by_host('http', '127.0.0.1:8000')
8000
>>> get_port_by_host('https', '127.0.0.1')
443
>>> get_port_by_host('https', '127.0.0.1:8443')
8443
>>> get_port_by_host('http', '[::1]')
80
>>> get_port_by_host('http', '[::1]:8000')
8000
>>> get_port_by_host('https', '[::1]')
443
>>> get_port_by_host('https', '[::1]:8443')
8443
"""
m = RE_HOST_PORT.match(host)
if not m:
raise ValueError
port = m.group("port")
if port is None:
port = 443 if scheme == 'https' else 80
else:
port = int(port)
return port
job_traces = dict()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment