mcp-proxy-adapter 6.4.12__py3-none-any.whl → 6.4.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_proxy_adapter/core/app_factory.py +105 -32
- mcp_proxy_adapter/core/mtls_server.py +314 -0
- mcp_proxy_adapter/core/server_engine.py +1 -0
- mcp_proxy_adapter/examples/run_full_test_suite.py +1 -5
- mcp_proxy_adapter/examples/setup_test_environment.py +150 -19
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.4.12.dist-info → mcp_proxy_adapter-6.4.15.dist-info}/METADATA +2 -1
- {mcp_proxy_adapter-6.4.12.dist-info → mcp_proxy_adapter-6.4.15.dist-info}/RECORD +11 -23
- mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +0 -9
- mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +0 -4
- mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +0 -4
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +0 -52
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +0 -13
- mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +0 -7
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +0 -92
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +0 -97
- mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +0 -7
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +0 -88
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +0 -81
- mcp_proxy_adapter/examples/examples/full_application/main.py +0 -61
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +0 -188
- {mcp_proxy_adapter-6.4.12.dist-info → mcp_proxy_adapter-6.4.15.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.4.12.dist-info → mcp_proxy_adapter-6.4.15.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.4.12.dist-info → mcp_proxy_adapter-6.4.15.dist-info}/top_level.txt +0 -0
@@ -16,7 +16,9 @@ from fastapi import FastAPI
|
|
16
16
|
from mcp_proxy_adapter.api.app import create_app
|
17
17
|
from mcp_proxy_adapter.core.logging import setup_logging, get_logger
|
18
18
|
from mcp_proxy_adapter.config import config
|
19
|
-
from mcp_proxy_adapter.commands.builtin_commands import
|
19
|
+
from mcp_proxy_adapter.commands.builtin_commands import (
|
20
|
+
register_builtin_commands,
|
21
|
+
)
|
20
22
|
|
21
23
|
logger = get_logger("app_factory")
|
22
24
|
|
@@ -87,10 +89,14 @@ async def create_and_run_server(
|
|
87
89
|
print("✅ Configuration validation passed")
|
88
90
|
|
89
91
|
# Debug: Check what config.get_all() actually returns
|
90
|
-
print(
|
92
|
+
print(
|
93
|
+
f"🔍 Debug: config.get_all() keys: {list(app_config.keys())}"
|
94
|
+
)
|
91
95
|
if "security" in app_config:
|
92
96
|
security_ssl = app_config["security"].get("ssl", {})
|
93
|
-
print(
|
97
|
+
print(
|
98
|
+
f"🔍 Debug: config.get_all() security.ssl: {security_ssl}"
|
99
|
+
)
|
94
100
|
|
95
101
|
# Debug: Check if root ssl section exists after loading
|
96
102
|
if "ssl" in app_config:
|
@@ -152,7 +158,9 @@ async def create_and_run_server(
|
|
152
158
|
# Validate security framework configuration only if enabled
|
153
159
|
security_config = app_config.get("security", {})
|
154
160
|
if security_config.get("enabled", False):
|
155
|
-
framework = security_config.get(
|
161
|
+
framework = security_config.get(
|
162
|
+
"framework", "mcp_security_framework"
|
163
|
+
)
|
156
164
|
print(f"🔒 Security framework: {framework}")
|
157
165
|
|
158
166
|
# Debug: Check SSL config before validation
|
@@ -202,7 +210,9 @@ async def create_and_run_server(
|
|
202
210
|
if log_config_path:
|
203
211
|
log_config_file = Path(log_config_path)
|
204
212
|
if not log_config_file.exists():
|
205
|
-
print(
|
213
|
+
print(
|
214
|
+
f"❌ Log configuration file not found: {log_config_path}"
|
215
|
+
)
|
206
216
|
sys.exit(1)
|
207
217
|
setup_logging(log_config_path=str(log_config_file))
|
208
218
|
print(f"✅ Logging configured from: {log_config_path}")
|
@@ -250,7 +260,9 @@ async def create_and_run_server(
|
|
250
260
|
|
251
261
|
# 5. Create server configuration
|
252
262
|
# Get port from config if available, otherwise use default
|
253
|
-
server_port =
|
263
|
+
server_port = (
|
264
|
+
app_config.get("server", {}).get("port", 8000) if app_config else 8000
|
265
|
+
)
|
254
266
|
print(f"🔌 Port: {server_port}")
|
255
267
|
|
256
268
|
server_config = {
|
@@ -268,19 +280,20 @@ async def create_and_run_server(
|
|
268
280
|
# Check for SSL config in root section first (higher priority)
|
269
281
|
if app_config and "ssl" in app_config:
|
270
282
|
print(f"🔍 Debug: SSL config found in root: {app_config['ssl']}")
|
271
|
-
print(
|
283
|
+
print(
|
284
|
+
f"🔍 Debug: SSL enabled: {app_config['ssl'].get('enabled', False)}"
|
285
|
+
)
|
272
286
|
if app_config["ssl"].get("enabled", False):
|
273
287
|
ssl_config = app_config["ssl"]
|
274
288
|
# Add SSL config directly to server_config for Hypercorn
|
275
289
|
server_config["certfile"] = ssl_config.get("cert_file")
|
276
290
|
server_config["keyfile"] = ssl_config.get("key_file")
|
277
|
-
server_config["ca_certs"] = ssl_config.get(
|
278
|
-
|
291
|
+
server_config["ca_certs"] = ssl_config.get(
|
292
|
+
"ca_cert_file", ssl_config.get("ca_cert")
|
293
|
+
)
|
294
|
+
# Set verify_client based on verify_client setting
|
279
295
|
verify_client = ssl_config.get("verify_client", False)
|
280
|
-
|
281
|
-
server_config["verify_mode"] = "CERT_REQUIRED"
|
282
|
-
else:
|
283
|
-
server_config["verify_mode"] = "CERT_NONE"
|
296
|
+
server_config["verify_client"] = verify_client
|
284
297
|
print(f"🔒 SSL enabled: {ssl_config.get('cert_file', 'N/A')}")
|
285
298
|
print(
|
286
299
|
f"🔒 SSL enabled: cert={ssl_config.get('cert_file')}, key={ssl_config.get('key_file')}"
|
@@ -292,9 +305,13 @@ async def create_and_run_server(
|
|
292
305
|
# Check for SSL config in security section (fallback)
|
293
306
|
if app_config and "security" in app_config:
|
294
307
|
security_config = app_config["security"]
|
295
|
-
print(
|
308
|
+
print(
|
309
|
+
f"🔍 Debug: security_config keys: {list(security_config.keys())}"
|
310
|
+
)
|
296
311
|
if "ssl" in security_config:
|
297
|
-
print(
|
312
|
+
print(
|
313
|
+
f"🔍 Debug: SSL config found in security: {security_config['ssl']}"
|
314
|
+
)
|
298
315
|
print(
|
299
316
|
f"🔍 Debug: SSL enabled: {security_config['ssl'].get('enabled', False)}"
|
300
317
|
)
|
@@ -303,7 +320,9 @@ async def create_and_run_server(
|
|
303
320
|
# Add SSL config directly to server_config for Hypercorn
|
304
321
|
server_config["certfile"] = ssl_config.get("cert_file")
|
305
322
|
server_config["keyfile"] = ssl_config.get("key_file")
|
306
|
-
server_config["ca_certs"] = ssl_config.get(
|
323
|
+
server_config["ca_certs"] = ssl_config.get(
|
324
|
+
"ca_cert_file", ssl_config.get("ca_cert")
|
325
|
+
)
|
307
326
|
server_config["verify_mode"] = ssl_config.get("verify_mode")
|
308
327
|
print(f"🔒 SSL enabled: {ssl_config.get('cert_file', 'N/A')}")
|
309
328
|
print(
|
@@ -313,19 +332,20 @@ async def create_and_run_server(
|
|
313
332
|
f"🔒 Server config SSL: certfile={server_config.get('certfile')}, keyfile={server_config.get('keyfile')}, ca_certs={server_config.get('ca_certs')}, verify_mode={server_config.get('verify_mode')}"
|
314
333
|
)
|
315
334
|
print(f"🔍 Debug: SSL config found in root: {app_config['ssl']}")
|
316
|
-
print(
|
335
|
+
print(
|
336
|
+
f"🔍 Debug: SSL enabled: {app_config['ssl'].get('enabled', False)}"
|
337
|
+
)
|
317
338
|
if app_config["ssl"].get("enabled", False):
|
318
339
|
ssl_config = app_config["ssl"]
|
319
340
|
# Add SSL config directly to server_config for Hypercorn
|
320
341
|
server_config["certfile"] = ssl_config.get("cert_file")
|
321
342
|
server_config["keyfile"] = ssl_config.get("key_file")
|
322
|
-
server_config["ca_certs"] = ssl_config.get(
|
323
|
-
|
343
|
+
server_config["ca_certs"] = ssl_config.get(
|
344
|
+
"ca_cert_file", ssl_config.get("ca_cert")
|
345
|
+
)
|
346
|
+
# Set verify_client based on verify_client setting
|
324
347
|
verify_client = ssl_config.get("verify_client", False)
|
325
|
-
|
326
|
-
server_config["verify_mode"] = "CERT_REQUIRED"
|
327
|
-
else:
|
328
|
-
server_config["verify_mode"] = "CERT_NONE"
|
348
|
+
server_config["verify_client"] = verify_client
|
329
349
|
print(f"🔒 SSL enabled: {ssl_config.get('cert_file', 'N/A')}")
|
330
350
|
print(
|
331
351
|
f"🔒 SSL enabled: cert={ssl_config.get('cert_file')}, key={ssl_config.get('key_file')}"
|
@@ -334,20 +354,50 @@ async def create_and_run_server(
|
|
334
354
|
f"🔒 Server config SSL: certfile={server_config.get('certfile')}, keyfile={server_config.get('keyfile')}, ca_certs={server_config.get('ca_certs')}, verify_mode={server_config.get('verify_mode')}"
|
335
355
|
)
|
336
356
|
|
337
|
-
# 6. Start server
|
357
|
+
# 6. Start mTLS server if needed
|
358
|
+
mtls_server = None
|
359
|
+
try:
|
360
|
+
# Check if mTLS is enabled
|
361
|
+
ssl_config = app_config.get("ssl", {}) if app_config else {}
|
362
|
+
verify_client = ssl_config.get("verify_client", False)
|
363
|
+
|
364
|
+
if verify_client:
|
365
|
+
print("🔐 mTLS enabled - starting separate mTLS server...")
|
366
|
+
from mcp_proxy_adapter.core.mtls_server import (
|
367
|
+
start_mtls_server_thread,
|
368
|
+
)
|
369
|
+
|
370
|
+
# Start mTLS server in separate thread
|
371
|
+
mtls_server = start_mtls_server_thread(app_config, main_app=app)
|
372
|
+
if mtls_server:
|
373
|
+
print(f"✅ mTLS server started on port {mtls_server.port}")
|
374
|
+
else:
|
375
|
+
print(
|
376
|
+
"⚠️ Failed to start mTLS server, continuing with regular HTTPS"
|
377
|
+
)
|
378
|
+
else:
|
379
|
+
print("🔓 mTLS disabled - using regular HTTPS")
|
380
|
+
except Exception as e:
|
381
|
+
print(f"⚠️ Error starting mTLS server: {e}")
|
382
|
+
print(" Continuing with regular HTTPS server")
|
383
|
+
|
384
|
+
# 7. Start main server
|
338
385
|
try:
|
339
|
-
print("🚀 Starting server...")
|
386
|
+
print("🚀 Starting main server...")
|
340
387
|
print(" Use Ctrl+C to stop the server")
|
341
388
|
print("=" * 60)
|
342
389
|
|
343
390
|
# Use hypercorn directly
|
344
391
|
import hypercorn.asyncio
|
345
392
|
import hypercorn.config
|
346
|
-
|
393
|
+
|
394
|
+
# import asyncio # Unused import
|
347
395
|
|
348
396
|
# Configure hypercorn
|
349
397
|
config_hypercorn = hypercorn.config.Config()
|
350
|
-
config_hypercorn.bind = [
|
398
|
+
config_hypercorn.bind = [
|
399
|
+
f"{server_config['host']}:{server_config['port']}"
|
400
|
+
]
|
351
401
|
config_hypercorn.loglevel = server_config.get("log_level", "info")
|
352
402
|
|
353
403
|
# Add SSL configuration if present
|
@@ -359,15 +409,25 @@ async def create_and_run_server(
|
|
359
409
|
config_hypercorn.ca_certs = server_config["ca_certs"]
|
360
410
|
if "verify_mode" in server_config:
|
361
411
|
import ssl
|
412
|
+
|
362
413
|
# Use the verify_mode from configuration, default to CERT_NONE
|
363
|
-
verify_mode = getattr(
|
414
|
+
verify_mode = getattr(
|
415
|
+
ssl, server_config["verify_mode"], ssl.CERT_NONE
|
416
|
+
)
|
364
417
|
config_hypercorn.verify_mode = verify_mode
|
365
418
|
|
366
419
|
# Determine if SSL is enabled
|
367
|
-
ssl_enabled = any(
|
420
|
+
ssl_enabled = any(
|
421
|
+
key in server_config for key in ["certfile", "keyfile"]
|
422
|
+
)
|
368
423
|
|
369
424
|
if ssl_enabled:
|
370
|
-
|
425
|
+
if verify_client:
|
426
|
+
print(
|
427
|
+
f"🔐 Starting HTTPS server with hypercorn (mTLS on separate port)..."
|
428
|
+
)
|
429
|
+
else:
|
430
|
+
print(f"🔐 Starting HTTPS server with hypercorn...")
|
371
431
|
else:
|
372
432
|
print(f"🌐 Starting HTTP server with hypercorn...")
|
373
433
|
|
@@ -378,8 +438,16 @@ async def create_and_run_server(
|
|
378
438
|
|
379
439
|
except KeyboardInterrupt:
|
380
440
|
print("\n🛑 Server stopped by user")
|
441
|
+
# Stop mTLS server if running
|
442
|
+
if mtls_server:
|
443
|
+
print("🛑 Stopping mTLS server...")
|
444
|
+
mtls_server.stop()
|
381
445
|
except Exception as e:
|
382
446
|
print(f"\n❌ Failed to start server: {e}")
|
447
|
+
# Stop mTLS server if running
|
448
|
+
if mtls_server:
|
449
|
+
print("🛑 Stopping mTLS server...")
|
450
|
+
mtls_server.stop()
|
383
451
|
import traceback
|
384
452
|
|
385
453
|
traceback.print_exc()
|
@@ -454,7 +522,9 @@ def create_application(
|
|
454
522
|
from fastapi.middleware.cors import CORSMiddleware
|
455
523
|
from mcp_proxy_adapter.api.app import create_app
|
456
524
|
from mcp_proxy_adapter.core.logging import setup_logging
|
457
|
-
from mcp_proxy_adapter.commands.builtin_commands import
|
525
|
+
from mcp_proxy_adapter.commands.builtin_commands import (
|
526
|
+
register_builtin_commands,
|
527
|
+
)
|
458
528
|
|
459
529
|
# Setup logging
|
460
530
|
setup_logging()
|
@@ -464,7 +534,10 @@ def create_application(
|
|
464
534
|
|
465
535
|
# Create FastAPI application using existing create_app function
|
466
536
|
app = create_app(
|
467
|
-
title=title,
|
537
|
+
title=title,
|
538
|
+
description=description,
|
539
|
+
version=version,
|
540
|
+
app_config=config,
|
468
541
|
)
|
469
542
|
|
470
543
|
# Add CORS middleware
|
@@ -0,0 +1,314 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
mTLS Server implementation using built-in http.server.
|
4
|
+
|
5
|
+
Author: Vasiliy Zdanovskiy
|
6
|
+
email: vasilyvz@gmail.com
|
7
|
+
"""
|
8
|
+
|
9
|
+
import threading
|
10
|
+
import ssl
|
11
|
+
import json
|
12
|
+
import logging
|
13
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
14
|
+
from typing import Optional, Dict, Any
|
15
|
+
import os
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
class mTLSHandler(BaseHTTPRequestHandler):
|
21
|
+
"""Handler for mTLS connections."""
|
22
|
+
|
23
|
+
def __init__(self, *args, main_app=None, **kwargs):
|
24
|
+
self.main_app = main_app
|
25
|
+
super().__init__(*args, **kwargs)
|
26
|
+
|
27
|
+
def log_message(self, format, *args):
|
28
|
+
"""Override to use our logger."""
|
29
|
+
logger.info(f"mTLS Server: {format % args}")
|
30
|
+
|
31
|
+
def do_GET(self):
|
32
|
+
"""Handle GET requests."""
|
33
|
+
try:
|
34
|
+
# Get client certificate
|
35
|
+
client_cert = None
|
36
|
+
if hasattr(self.connection, "getpeercert"):
|
37
|
+
client_cert = self.connection.getpeercert()
|
38
|
+
|
39
|
+
# Process request through main app if available
|
40
|
+
if self.main_app:
|
41
|
+
# Forward to main FastAPI app
|
42
|
+
response_data = self._forward_to_main_app(
|
43
|
+
"GET", self.path, client_cert
|
44
|
+
)
|
45
|
+
else:
|
46
|
+
# Simple response
|
47
|
+
response_data = {
|
48
|
+
"status": "ok",
|
49
|
+
"message": "mTLS connection successful",
|
50
|
+
"client_cert": client_cert,
|
51
|
+
"path": self.path,
|
52
|
+
}
|
53
|
+
|
54
|
+
# Send response
|
55
|
+
self.send_response(200)
|
56
|
+
self.send_header("Content-type", "application/json")
|
57
|
+
self.end_headers()
|
58
|
+
self.wfile.write(json.dumps(response_data).encode())
|
59
|
+
|
60
|
+
except Exception as e:
|
61
|
+
logger.error(f"Error in mTLS GET handler: {e}")
|
62
|
+
self.send_error(500, str(e))
|
63
|
+
|
64
|
+
def do_POST(self):
|
65
|
+
"""Handle POST requests."""
|
66
|
+
try:
|
67
|
+
# Get content length
|
68
|
+
content_length = int(self.headers.get("Content-Length", 0))
|
69
|
+
|
70
|
+
# Read request body
|
71
|
+
post_data = None
|
72
|
+
if content_length > 0:
|
73
|
+
post_data = self.rfile.read(content_length)
|
74
|
+
|
75
|
+
# Get client certificate
|
76
|
+
client_cert = None
|
77
|
+
if hasattr(self.connection, "getpeercert"):
|
78
|
+
client_cert = self.connection.getpeercert()
|
79
|
+
|
80
|
+
# Process request through main app if available
|
81
|
+
if self.main_app:
|
82
|
+
# Forward to main FastAPI app
|
83
|
+
response_data = self._forward_to_main_app(
|
84
|
+
"POST", self.path, client_cert, post_data
|
85
|
+
)
|
86
|
+
else:
|
87
|
+
# Simple response
|
88
|
+
response_data = {
|
89
|
+
"status": "ok",
|
90
|
+
"message": "mTLS POST successful",
|
91
|
+
"client_cert": client_cert,
|
92
|
+
"path": self.path,
|
93
|
+
"data_received": len(post_data) if post_data else 0,
|
94
|
+
}
|
95
|
+
|
96
|
+
# Send response
|
97
|
+
self.send_response(200)
|
98
|
+
self.send_header("Content-type", "application/json")
|
99
|
+
self.end_headers()
|
100
|
+
self.wfile.write(json.dumps(response_data).encode())
|
101
|
+
|
102
|
+
except Exception as e:
|
103
|
+
logger.error(f"Error in mTLS POST handler: {e}")
|
104
|
+
self.send_error(500, str(e))
|
105
|
+
|
106
|
+
def _forward_to_main_app(
|
107
|
+
self,
|
108
|
+
method: str,
|
109
|
+
path: str,
|
110
|
+
client_cert: Optional[Dict],
|
111
|
+
post_data: Optional[bytes] = None,
|
112
|
+
) -> Dict[str, Any]:
|
113
|
+
"""Forward request to main FastAPI application."""
|
114
|
+
try:
|
115
|
+
# This is a simplified forwarding - in real implementation
|
116
|
+
# you would use httpx or similar to make internal HTTP calls
|
117
|
+
# to the main FastAPI app running on different port
|
118
|
+
|
119
|
+
return {
|
120
|
+
"status": "ok",
|
121
|
+
"message": f"mTLS {method} forwarded to main app",
|
122
|
+
"client_cert": client_cert,
|
123
|
+
"path": path,
|
124
|
+
"forwarded": True,
|
125
|
+
}
|
126
|
+
except Exception as e:
|
127
|
+
logger.error(f"Error forwarding to main app: {e}")
|
128
|
+
return {
|
129
|
+
"status": "error",
|
130
|
+
"message": f"Forwarding failed: {e}",
|
131
|
+
"client_cert": client_cert,
|
132
|
+
"path": path,
|
133
|
+
}
|
134
|
+
|
135
|
+
|
136
|
+
class mTLSServer:
|
137
|
+
"""mTLS Server using built-in http.server."""
|
138
|
+
|
139
|
+
def __init__(
|
140
|
+
self,
|
141
|
+
host: str = "127.0.0.1",
|
142
|
+
port: int = 8443,
|
143
|
+
cert_file: str = "certs/localhost_server.crt",
|
144
|
+
key_file: str = "keys/localhost_server.key",
|
145
|
+
ca_cert_file: str = "certs/test_ca_ca.crt",
|
146
|
+
main_app=None,
|
147
|
+
):
|
148
|
+
"""
|
149
|
+
Initialize mTLS server.
|
150
|
+
|
151
|
+
Args:
|
152
|
+
host: Server host
|
153
|
+
port: Server port
|
154
|
+
cert_file: Server certificate file
|
155
|
+
key_file: Server private key file
|
156
|
+
ca_cert_file: CA certificate file for client verification
|
157
|
+
main_app: Main FastAPI application for forwarding requests
|
158
|
+
"""
|
159
|
+
self.host = host
|
160
|
+
self.port = port
|
161
|
+
self.cert_file = cert_file
|
162
|
+
self.key_file = key_file
|
163
|
+
self.ca_cert_file = ca_cert_file
|
164
|
+
self.main_app = main_app
|
165
|
+
|
166
|
+
self.server: Optional[HTTPServer] = None
|
167
|
+
self.server_thread: Optional[threading.Thread] = None
|
168
|
+
self.running = False
|
169
|
+
|
170
|
+
logger.info(f"mTLS Server initialized: {host}:{port}")
|
171
|
+
|
172
|
+
def _create_handler(self):
|
173
|
+
"""Create handler with main app reference."""
|
174
|
+
|
175
|
+
def handler(*args, **kwargs):
|
176
|
+
return mTLSHandler(*args, main_app=self.main_app, **kwargs)
|
177
|
+
|
178
|
+
return handler
|
179
|
+
|
180
|
+
def start(self) -> bool:
|
181
|
+
"""Start mTLS server in separate thread."""
|
182
|
+
try:
|
183
|
+
# Check if certificate files exist
|
184
|
+
if not os.path.exists(self.cert_file):
|
185
|
+
logger.error(f"Certificate file not found: {self.cert_file}")
|
186
|
+
return False
|
187
|
+
|
188
|
+
if not os.path.exists(self.key_file):
|
189
|
+
logger.error(f"Key file not found: {self.key_file}")
|
190
|
+
return False
|
191
|
+
|
192
|
+
if not os.path.exists(self.ca_cert_file):
|
193
|
+
logger.error(
|
194
|
+
f"CA certificate file not found: {self.ca_cert_file}"
|
195
|
+
)
|
196
|
+
return False
|
197
|
+
|
198
|
+
# Create server
|
199
|
+
handler_class = self._create_handler()
|
200
|
+
self.server = HTTPServer((self.host, self.port), handler_class)
|
201
|
+
|
202
|
+
# Configure SSL context
|
203
|
+
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
204
|
+
context.load_cert_chain(self.cert_file, self.key_file)
|
205
|
+
context.load_verify_locations(self.ca_cert_file)
|
206
|
+
context.verify_mode = ssl.CERT_REQUIRED
|
207
|
+
|
208
|
+
# Wrap socket with SSL
|
209
|
+
self.server.socket = context.wrap_socket(
|
210
|
+
self.server.socket, server_side=True
|
211
|
+
)
|
212
|
+
|
213
|
+
# Start server in separate thread
|
214
|
+
self.server_thread = threading.Thread(
|
215
|
+
target=self._run_server, daemon=True
|
216
|
+
)
|
217
|
+
self.server_thread.start()
|
218
|
+
|
219
|
+
self.running = True
|
220
|
+
logger.info(
|
221
|
+
f"✅ mTLS Server started on https://{self.host}:{self.port}"
|
222
|
+
)
|
223
|
+
return True
|
224
|
+
|
225
|
+
except Exception as e:
|
226
|
+
logger.error(f"Failed to start mTLS server: {e}")
|
227
|
+
return False
|
228
|
+
|
229
|
+
def _run_server(self):
|
230
|
+
"""Run the server (blocking)."""
|
231
|
+
try:
|
232
|
+
logger.info(
|
233
|
+
f"mTLS Server listening on https://{self.host}:{self.port}"
|
234
|
+
)
|
235
|
+
self.server.serve_forever()
|
236
|
+
except Exception as e:
|
237
|
+
logger.error(f"mTLS Server error: {e}")
|
238
|
+
finally:
|
239
|
+
self.running = False
|
240
|
+
|
241
|
+
def stop(self):
|
242
|
+
"""Stop mTLS server."""
|
243
|
+
if self.server and self.running:
|
244
|
+
try:
|
245
|
+
self.server.shutdown()
|
246
|
+
self.server.server_close()
|
247
|
+
self.running = False
|
248
|
+
logger.info("✅ mTLS Server stopped")
|
249
|
+
except Exception as e:
|
250
|
+
logger.error(f"Error stopping mTLS server: {e}")
|
251
|
+
|
252
|
+
def is_running(self) -> bool:
|
253
|
+
"""Check if server is running."""
|
254
|
+
return (
|
255
|
+
self.running
|
256
|
+
and self.server_thread
|
257
|
+
and self.server_thread.is_alive()
|
258
|
+
)
|
259
|
+
|
260
|
+
|
261
|
+
def start_mtls_server_thread(
|
262
|
+
config: Dict[str, Any], main_app=None
|
263
|
+
) -> Optional[mTLSServer]:
|
264
|
+
"""
|
265
|
+
Start mTLS server in separate thread.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
config: Configuration dictionary
|
269
|
+
main_app: Main FastAPI application
|
270
|
+
|
271
|
+
Returns:
|
272
|
+
mTLSServer instance or None if failed
|
273
|
+
"""
|
274
|
+
try:
|
275
|
+
# Extract SSL configuration
|
276
|
+
ssl_config = config.get("ssl", {})
|
277
|
+
|
278
|
+
# Check if mTLS is enabled
|
279
|
+
verify_client = ssl_config.get("verify_client", False)
|
280
|
+
if not verify_client:
|
281
|
+
logger.info(
|
282
|
+
"mTLS not enabled (verify_client=False), skipping mTLS server"
|
283
|
+
)
|
284
|
+
return None
|
285
|
+
|
286
|
+
# Get server configuration
|
287
|
+
server_config = config.get("server", {})
|
288
|
+
host = server_config.get("host", "127.0.0.1")
|
289
|
+
port = ssl_config.get("mtls_port", 8443) # Different port for mTLS
|
290
|
+
|
291
|
+
# Get certificate paths
|
292
|
+
cert_file = ssl_config.get("cert_file", "certs/localhost_server.crt")
|
293
|
+
key_file = ssl_config.get("key_file", "keys/localhost_server.key")
|
294
|
+
ca_cert_file = ssl_config.get("ca_cert", "certs/test_ca_ca.crt")
|
295
|
+
|
296
|
+
# Create and start mTLS server
|
297
|
+
mtls_server = mTLSServer(
|
298
|
+
host=host,
|
299
|
+
port=port,
|
300
|
+
cert_file=cert_file,
|
301
|
+
key_file=key_file,
|
302
|
+
ca_cert_file=ca_cert_file,
|
303
|
+
main_app=main_app,
|
304
|
+
)
|
305
|
+
|
306
|
+
if mtls_server.start():
|
307
|
+
return mtls_server
|
308
|
+
else:
|
309
|
+
logger.error("Failed to start mTLS server")
|
310
|
+
return None
|
311
|
+
|
312
|
+
except Exception as e:
|
313
|
+
logger.error(f"Error starting mTLS server thread: {e}")
|
314
|
+
return None
|
@@ -298,7 +298,7 @@ class FullTestSuiteRunner:
|
|
298
298
|
"enabled": True,
|
299
299
|
"cert_file": "certs/localhost_server.crt",
|
300
300
|
"key_file": "keys/localhost_server.key",
|
301
|
-
"ca_cert": "certs/
|
301
|
+
"ca_cert": "certs/test_ca_ca.crt",
|
302
302
|
"client_cert_file": "certs/admin_cert.pem",
|
303
303
|
"client_key_file": "certs/admin_key.pem",
|
304
304
|
"verify_client": True
|
@@ -436,10 +436,6 @@ class FullTestSuiteRunner:
|
|
436
436
|
print(f"Python executable: {sys.executable}")
|
437
437
|
|
438
438
|
try:
|
439
|
-
# Step 0: Clean up existing directories
|
440
|
-
if not self.cleanup_directories():
|
441
|
-
return False
|
442
|
-
|
443
439
|
# Step 1: Environment validation
|
444
440
|
if not self.check_environment():
|
445
441
|
return False
|