Aggiunto un log migliore

This commit is contained in:
2026-07-01 15:57:16 +02:00
parent d740485522
commit 4b47a65fbc
2 changed files with 112 additions and 29 deletions

View File

@@ -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: