From 4b47a65fbc796386b1014e4755f6982c55b86eda Mon Sep 17 00:00:00 2001 From: tommal Date: Wed, 1 Jul 2026 15:57:16 +0200 Subject: [PATCH] Aggiunto un log migliore --- manager-solution/manager/manager.py | 112 ++++++++++++++++++----- manager-solution/manager/test_manager.py | 29 +++++- 2 files changed, 112 insertions(+), 29 deletions(-) diff --git a/manager-solution/manager/manager.py b/manager-solution/manager/manager.py index 303fbd0..e9d26e3 100644 --- a/manager-solution/manager/manager.py +++ b/manager-solution/manager/manager.py @@ -29,6 +29,7 @@ TCP_CONNECT_TIMEOUT_SECONDS = float(os.getenv("TCP_CONNECT_TIMEOUT_SECONDS", "1. INITIAL_REQUEST_TIMEOUT_SECONDS = float(os.getenv("INITIAL_REQUEST_TIMEOUT_SECONDS", "3")) MAX_REQUEST_HEADER_BYTES = int(os.getenv("MAX_REQUEST_HEADER_BYTES", "65536")) GATEWAY_ROUTE_TTL_SECONDS = int(os.getenv("GATEWAY_ROUTE_TTL_SECONDS", "3600")) +GATEWAY_ROUTE_COOKIE_NAME = "fis_user_route" DEFAULTS: Dict[str, Any] = { "container_prefix": "fis-", @@ -221,15 +222,15 @@ def build_http_error(status_code: int, reason: str, message: str) -> bytes: return "\r\n".join(headers).encode("utf-8") + body -def build_http_redirect(location: str) -> bytes: +def build_http_redirect(location: str, extra_headers: Optional[Iterable[str]] = None) -> bytes: headers = [ "HTTP/1.1 302 Found", f"Location: {location}", "Content-Length: 0", "Connection: close", - "", - "", ] + headers.extend(extra_headers or []) + headers.extend(["", ""]) return "\r\n".join(headers).encode("utf-8") @@ -248,6 +249,59 @@ def rewrite_http_request_target(data: bytes, target: str) -> bytes: return rewritten_line + data[header_end:] +def parse_http_request(data: bytes): + if not data: + return None + + header_end = data.find(b"\r\n\r\n") + if header_end == -1: + return None + + header_block = data[:header_end].decode("iso-8859-1", errors="replace") + lines = header_block.split("\r\n") + if not lines or len(lines[0].split(" ")) < 3: + return None + + method, target, version = lines[0].split(" ", 2) + headers = {} + for line in lines[1:]: + if ":" not in line: + continue + key, value = line.split(":", 1) + headers[key.strip().lower()] = value.strip() + + return { + "method": method, + "target": target, + "version": version, + "headers": headers, + } + + +def extract_cookie_user(headers: Dict[str, str], known_users: Dict[str, Dict[str, Any]]) -> Optional[Dict[str, Any]]: + cookie_header = headers.get("cookie", "") + if not cookie_header: + return None + + for part in cookie_header.split(";"): + key, separator, value = part.strip().partition("=") + if separator and key == GATEWAY_ROUTE_COOKIE_NAME: + user_id = value.strip() + return known_users.get(user_id) + return None + + +def is_websocket_request(headers: Dict[str, str]) -> bool: + return headers.get("upgrade", "").lower() == "websocket" + + +def extract_request_target(data: bytes) -> Optional[str]: + parsed = parse_http_request(data) + if not parsed: + return None + return parsed["target"] + + def remember_gateway_route(client_ip: str, user_id: str) -> None: if not client_ip: return @@ -271,25 +325,12 @@ def get_remembered_gateway_user(client_ip: str) -> Optional[Dict[str, Any]]: def extract_user_from_http_request(data: bytes, known_users: Dict[str, Dict[str, Any]]): - if not data: - return None, data, None + parsed = parse_http_request(data) + if not parsed: + return None, data, None, False - header_end = data.find(b"\r\n\r\n") - if header_end == -1: - return None, data, None - - header_block = data[:header_end].decode("iso-8859-1", errors="replace") - lines = header_block.split("\r\n") - if not lines or len(lines[0].split(" ")) < 3: - return None, data, None - - method, target, version = lines[0].split(" ", 2) - headers = {} - for line in lines[1:]: - if ":" not in line: - continue - key, value = line.split(":", 1) - headers[key.strip().lower()] = value.strip() + target = parsed["target"] + headers = parsed["headers"] host = headers.get("host", "").split(":", 1)[0].strip().lower() if host: @@ -297,7 +338,7 @@ def extract_user_from_http_request(data: bytes, known_users: Dict[str, Dict[str, if labels: candidate = labels[0] if candidate in known_users and candidate not in {"localhost", "127", "0"}: - return known_users[candidate], data, None + return known_users[candidate], data, None, False parsed_target = urlsplit(target) if parsed_target.scheme and parsed_target.netloc: @@ -314,9 +355,13 @@ def extract_user_from_http_request(data: bytes, known_users: Dict[str, Dict[str, rewritten_path = "/" + "/".join(segments[1:]) if len(segments) > 1 else "/" rewritten_target = f"{rewritten_path}?{query}" if separator else rewritten_path rewritten_data = rewrite_http_request_target(data, rewritten_target) - return known_users[candidate], rewritten_data, None + return known_users[candidate], rewritten_data, None, True - return None, data, None + cookie_user = extract_cookie_user(headers, known_users) + if cookie_user: + return cookie_user, data, None, False + + return None, data, None, False def wait_until_ready(container_name: str) -> bool: @@ -500,7 +545,8 @@ async def handle_gateway_connection(reader, writer): await writer.wait_closed() return - user, rewritten_data, redirect_target = extract_user_from_http_request(initial_data, users_by_id) + parsed_request = parse_http_request(initial_data) + user, rewritten_data, redirect_target, selected_via_path = extract_user_from_http_request(initial_data, users_by_id) if user: remember_gateway_route(client_ip, user["id"]) @@ -511,6 +557,22 @@ async def handle_gateway_connection(reader, writer): await writer.wait_closed() return + if user and selected_via_path and parsed_request and not is_websocket_request(parsed_request["headers"]): + normalized_target = extract_request_target(rewritten_data) or "/" + writer.write( + build_http_redirect( + normalized_target, + extra_headers=[ + f"Set-Cookie: {GATEWAY_ROUTE_COOKIE_NAME}={user['id']}; Path=/; HttpOnly; SameSite=Lax" + ], + ) + ) + await writer.drain() + writer.close() + await writer.wait_closed() + log(f"[gateway] Switched {client_ip} to user {user['id']} via path route") + return + if not user: remembered_user = get_remembered_gateway_user(client_ip) if remembered_user: diff --git a/manager-solution/manager/test_manager.py b/manager-solution/manager/test_manager.py index 16563f3..b7a9f22 100644 --- a/manager-solution/manager/test_manager.py +++ b/manager-solution/manager/test_manager.py @@ -192,11 +192,12 @@ class ManagerConfigTests(unittest.TestCase): ) users = {"user2": {"id": "user2"}} - user, rewritten, redirect_target = self.manager.extract_user_from_http_request(request, users) + user, rewritten, redirect_target, selected_via_path = self.manager.extract_user_from_http_request(request, users) self.assertEqual(user["id"], "user2") self.assertEqual(rewritten, request) self.assertIsNone(redirect_target) + self.assertFalse(selected_via_path) def test_extract_user_from_path_rewrites_request_for_direct_proxying(self): request = ( @@ -207,7 +208,7 @@ class ManagerConfigTests(unittest.TestCase): ) users = {"user1": {"id": "user1"}} - user, rewritten, redirect_target = self.manager.extract_user_from_http_request(request, users) + user, rewritten, redirect_target, selected_via_path = self.manager.extract_user_from_http_request(request, users) self.assertEqual(user["id"], "user1") self.assertEqual( @@ -220,6 +221,7 @@ class ManagerConfigTests(unittest.TestCase): ), ) self.assertIsNone(redirect_target) + self.assertTrue(selected_via_path) def test_extract_user_from_bare_user_path_rewrites_to_root(self): request = ( @@ -230,7 +232,7 @@ class ManagerConfigTests(unittest.TestCase): ) users = {"user1": {"id": "user1"}} - user, rewritten, redirect_target = self.manager.extract_user_from_http_request(request, users) + user, rewritten, redirect_target, selected_via_path = self.manager.extract_user_from_http_request(request, users) self.assertEqual(user["id"], "user1") self.assertEqual( @@ -243,6 +245,7 @@ class ManagerConfigTests(unittest.TestCase): ), ) self.assertIsNone(redirect_target) + self.assertTrue(selected_via_path) def test_extract_user_from_absolute_form_request_rewrites_to_root(self): request = ( @@ -253,7 +256,7 @@ class ManagerConfigTests(unittest.TestCase): ) users = {"user1": {"id": "user1"}} - user, rewritten, redirect_target = self.manager.extract_user_from_http_request(request, users) + user, rewritten, redirect_target, selected_via_path = self.manager.extract_user_from_http_request(request, users) self.assertEqual(user["id"], "user1") self.assertEqual( @@ -266,6 +269,24 @@ class ManagerConfigTests(unittest.TestCase): ), ) self.assertIsNone(redirect_target) + self.assertTrue(selected_via_path) + + def test_extract_user_from_cookie_route(self): + request = ( + b"GET / HTTP/1.1\r\n" + b"Host: 10.200.200.113:8080\r\n" + b"Cookie: fis_user_route=user2\r\n" + b"Connection: keep-alive\r\n" + b"\r\n" + ) + users = {"user2": {"id": "user2"}} + + user, rewritten, redirect_target, selected_via_path = self.manager.extract_user_from_http_request(request, users) + + self.assertEqual(user["id"], "user2") + self.assertEqual(rewritten, request) + self.assertIsNone(redirect_target) + self.assertFalse(selected_via_path) def test_remembered_gateway_route_returns_user_within_ttl(self): self.manager.users_by_id = {"user1": {"id": "user1"}}