Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 19 additions & 18 deletions pyproxy/handlers/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,24 +90,25 @@ def handle_client(self, client_socket):
Args:
client_socket (socket): The socket object for the client connection.
"""
request = client_socket.recv(4096)
try:
client_socket.settimeout(10)
request = client_socket.recv(4096)

if not request:
self.console_logger.debug("No request received, closing connection.")
if not request:
return

first_line = request.decode(errors="ignore").split("\n")[0]
if first_line.startswith("CONNECT"):
https_handler = self._create_handler(
HttpsHandler,
ssl_config=self.ssl_config,
cancel_inspect_queue=self.cancel_inspect_queue,
cancel_inspect_result_queue=self.cancel_inspect_result_queue,
)
https_handler.handle_https_connection(client_socket, first_line)
else:
http_handler = self._create_handler(HttpHandler)
http_handler.handle_http_request(client_socket, request)
finally:
client_socket.close()
self.active_connections.pop(threading.get_ident(), None)
return

first_line = request.decode(errors="ignore").split("\n")[0]

if first_line.startswith("CONNECT"):
https_handler = self._create_handler(
HttpsHandler,
ssl_config=self.ssl_config,
cancel_inspect_queue=self.cancel_inspect_queue,
cancel_inspect_result_queue=self.cancel_inspect_result_queue,
)
https_handler.handle_https_connection(client_socket, first_line)
else:
http_handler = self._create_handler(HttpHandler)
http_handler.handle_http_request(client_socket, request)
21 changes: 15 additions & 6 deletions pyproxy/handlers/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,15 +202,24 @@ def forward_request_to_server(self, client_socket, request, url, first_line):
server_port = parsed_url.port or (
443 if parsed_url.scheme == "https" else 80
)

try:
ip_address = socket.gethostbyname(server_host)
except socket.gaierror:
ip_address = server_host

thread_id = threading.get_ident()

if thread_id in self.active_connections:
self.active_connections[thread_id] = {
"target_ip": server_host,
"target_port": server_port,
"bytes_sent": 0,
"bytes_received": 0,
}
self.active_connections[thread_id].update(
{
"target_ip": ip_address,
"target_domain": server_host,
"target_port": server_port,
"bytes_sent": 0,
"bytes_received": 0,
}
)

try:
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Expand Down
31 changes: 25 additions & 6 deletions pyproxy/handlers/https.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,21 @@ def _process_first_ssl_request(self, ssl_client_socket, server_host, first_line)
if self._is_blocked(f"{server_host}{path}"):
return None, full_url, True

if not self.logger_config.no_logging_access:
method, domain_port, protocol = first_line.split(" ")
domain, port = domain_port.split(":")
self.logger_config.access_logger.info(
"",
extra={
"ip_src": ssl_client_socket.getpeername()[0],
"url": full_url,
"method": method,
"domain": server_host,
"port": port,
"protocol": protocol,
},
)

return first_request, full_url, False
except Exception as e:
self.logger_config.error_logger.error(f"SSL request processing error : {e}")
Expand All @@ -231,10 +246,13 @@ def handle_https_connection(self, client_socket, first_line):
not_inspect = self._should_skip_inspection(server_host)

thread_id = threading.get_ident()
self.active_connections[thread_id] = {
"bytes_sent": 0,
"bytes_received": 0,
}
self.active_connections[thread_id].update(
{
"target_domain": server_host,
"bytes_sent": 0,
"bytes_received": 0,
}
)

if self.ssl_config.ssl_inspect and not not_inspect:
try:
Expand Down Expand Up @@ -317,13 +335,13 @@ def handle_https_connection(self, client_socket, first_line):
)

if not self.logger_config.no_logging_access:
_, _, protocol = first_line.split(" ")
method, _, protocol = first_line.split(" ")
self.logger_config.access_logger.info(
"",
extra={
"ip_src": client_ip,
"url": target,
"method": "CONNECT",
"method": method,
"domain": server_host,
"port": server_port,
"protocol": protocol,
Expand Down Expand Up @@ -397,6 +415,7 @@ def transfer_data_between_sockets(self, client_socket, server_socket):
data
)
except (socket.error, OSError):
self.logger_config.console_logger("error")
client_socket.close()
server_socket.close()
self.active_connections.pop(threading.get_ident(), None)
38 changes: 38 additions & 0 deletions pyproxy/monitoring/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
pyproxy.monitoring.__init__.py

Provides a monitoring system for the ProxyServer, exposing information about
processes, threads, active connections, and subprocesses. Implements an HTTP
server using Flask to provide monitoring endpoints.
"""

import logging
from flask import Flask
from .monitor import ProxyMonitor
from .auth import create_basic_auth
from .routes import register_routes


def start_flask_server(proxy_server, flask_port, flask_pass, debug) -> None:
"""
Launches a Flask HTTP server to monitor the ProxyServer.

The server exposes endpoints that provide status information about
the proxy server, including process details, thread information,
subprocess statuses, and active connections.

Args:
proxy_server (ProxyServer): The ProxyServer instance to monitor.
flask_port (int): The port number on which the Flask server will listen.
flask_pass (str): The password used for basic HTTP authentication.
debug (bool): Flag to enable or disable Flask debug mode.
"""
auth = create_basic_auth(flask_pass)

app = Flask(__name__, static_folder="static")
if not debug:
log = logging.getLogger("werkzeug")
log.setLevel(logging.ERROR)

register_routes(app, auth, proxy_server, ProxyMonitor)
app.run(host="0.0.0.0", port=flask_port) # nosec
31 changes: 31 additions & 0 deletions pyproxy/monitoring/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
pyproxy.monitoring.auth.py

Provides HTTP Basic Authentication setup for the monitoring Flask server,
using a single hardcoded user 'admin' with a hashed password.
"""

from flask_httpauth import HTTPBasicAuth
from werkzeug.security import check_password_hash, generate_password_hash


def create_basic_auth(password: str):
"""
Creates and configures an HTTPBasicAuth instance with a single user.

Args:
password (str): The password for the 'admin' user.

Returns:
HTTPBasicAuth: Configured HTTPBasicAuth instance for use in Flask.
"""
auth = HTTPBasicAuth()
users = {"admin": generate_password_hash(password)}

@auth.verify_password
def verify_password(username, passwd):
if username in users and check_password_hash(users.get(username), passwd):
return username
return None

return auth
Loading