mcp-proxy-adapter 6.9.28__py3-none-any.whl → 6.9.30__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.
Potentially problematic release.
This version of mcp-proxy-adapter might be problematic. Click here for more details.
- mcp_proxy_adapter/__init__.py +10 -0
- mcp_proxy_adapter/__main__.py +8 -21
- mcp_proxy_adapter/api/app.py +10 -913
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +243 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_manager.py +166 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +78 -199
- mcp_proxy_adapter/api/middleware/__init__.py +1 -44
- mcp_proxy_adapter/api/middleware/base.py +0 -42
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
- mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
- mcp_proxy_adapter/api/middleware/factory.py +0 -94
- mcp_proxy_adapter/api/middleware/logging.py +0 -112
- mcp_proxy_adapter/api/middleware/performance.py +0 -35
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
- mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
- mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
- mcp_proxy_adapter/api/openapi/__init__.py +21 -0
- mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
- mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
- mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
- mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
- mcp_proxy_adapter/api/schemas.py +0 -61
- mcp_proxy_adapter/api/tool_integration.py +0 -117
- mcp_proxy_adapter/api/tools.py +0 -46
- mcp_proxy_adapter/cli/__init__.py +12 -0
- mcp_proxy_adapter/cli/commands/__init__.py +15 -0
- mcp_proxy_adapter/cli/commands/client.py +100 -0
- mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
- mcp_proxy_adapter/cli/commands/generate.py +259 -0
- mcp_proxy_adapter/cli/commands/server.py +174 -0
- mcp_proxy_adapter/cli/commands/sets.py +128 -0
- mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
- mcp_proxy_adapter/cli/examples/__init__.py +8 -0
- mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
- mcp_proxy_adapter/cli/examples/https_token.py +96 -0
- mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
- mcp_proxy_adapter/cli/main.py +63 -0
- mcp_proxy_adapter/cli/parser.py +324 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
- mcp_proxy_adapter/client/proxy.py +45 -0
- mcp_proxy_adapter/commands/__init__.py +44 -28
- mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
- mcp_proxy_adapter/commands/base.py +19 -43
- mcp_proxy_adapter/commands/builtin_commands.py +0 -75
- mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
- mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
- mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
- mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
- mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
- mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
- mcp_proxy_adapter/commands/catalog_manager.py +58 -928
- mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
- mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
- mcp_proxy_adapter/commands/command_registry.py +172 -904
- mcp_proxy_adapter/commands/config_command.py +0 -28
- mcp_proxy_adapter/commands/dependency_container.py +1 -70
- mcp_proxy_adapter/commands/dependency_manager.py +0 -128
- mcp_proxy_adapter/commands/echo_command.py +0 -34
- mcp_proxy_adapter/commands/health_command.py +0 -3
- mcp_proxy_adapter/commands/help_command.py +0 -159
- mcp_proxy_adapter/commands/hooks.py +0 -137
- mcp_proxy_adapter/commands/key_management_command.py +0 -25
- mcp_proxy_adapter/commands/load_command.py +7 -78
- mcp_proxy_adapter/commands/plugins_command.py +0 -16
- mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
- mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +0 -43
- mcp_proxy_adapter/commands/registry/__init__.py +18 -0
- mcp_proxy_adapter/commands/registry/command_info.py +103 -0
- mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
- mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
- mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
- mcp_proxy_adapter/commands/reload_command.py +0 -80
- mcp_proxy_adapter/commands/result.py +25 -77
- mcp_proxy_adapter/commands/role_test_command.py +0 -44
- mcp_proxy_adapter/commands/roles_management_command.py +0 -199
- mcp_proxy_adapter/commands/security_command.py +0 -30
- mcp_proxy_adapter/commands/settings_command.py +0 -68
- mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
- mcp_proxy_adapter/commands/token_management_command.py +0 -1
- mcp_proxy_adapter/commands/transport_management_command.py +0 -20
- mcp_proxy_adapter/commands/unload_command.py +0 -71
- mcp_proxy_adapter/config.py +15 -626
- mcp_proxy_adapter/core/__init__.py +5 -39
- mcp_proxy_adapter/core/app_factory.py +14 -36
- mcp_proxy_adapter/core/app_runner.py +0 -27
- mcp_proxy_adapter/core/auth_validator.py +1 -93
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
- mcp_proxy_adapter/core/certificate_utils.py +64 -903
- mcp_proxy_adapter/core/client.py +10 -9
- mcp_proxy_adapter/core/client_manager.py +0 -19
- mcp_proxy_adapter/core/client_security.py +0 -2
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +195 -0
- mcp_proxy_adapter/core/config/config_factory.py +22 -0
- mcp_proxy_adapter/core/config/config_loader.py +66 -0
- mcp_proxy_adapter/core/config/feature_manager.py +31 -0
- mcp_proxy_adapter/core/config/simple_config.py +112 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
- mcp_proxy_adapter/core/config_converter.py +0 -186
- mcp_proxy_adapter/core/config_validator.py +96 -1238
- mcp_proxy_adapter/core/errors.py +7 -42
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +2 -22
- mcp_proxy_adapter/core/mtls_asgi.py +0 -20
- mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
- mcp_proxy_adapter/core/mtls_proxy.py +0 -80
- mcp_proxy_adapter/core/mtls_server.py +3 -173
- mcp_proxy_adapter/core/protocol_manager.py +1 -191
- mcp_proxy_adapter/core/proxy/__init__.py +22 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +0 -1
- mcp_proxy_adapter/core/proxy_registration.py +36 -913
- mcp_proxy_adapter/core/role_utils.py +0 -308
- mcp_proxy_adapter/core/security_adapter.py +1 -36
- mcp_proxy_adapter/core/security_factory.py +1 -150
- mcp_proxy_adapter/core/security_integration.py +0 -33
- mcp_proxy_adapter/core/server_adapter.py +1 -40
- mcp_proxy_adapter/core/server_engine.py +2 -173
- mcp_proxy_adapter/core/settings.py +0 -127
- mcp_proxy_adapter/core/signal_handler.py +0 -65
- mcp_proxy_adapter/core/ssl_utils.py +19 -137
- mcp_proxy_adapter/core/transport_manager.py +0 -151
- mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
- mcp_proxy_adapter/core/utils.py +1 -182
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +211 -0
- mcp_proxy_adapter/core/validation/file_validator.py +73 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
- mcp_proxy_adapter/core/validation/security_validator.py +58 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +33 -652
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
- mcp_proxy_adapter/examples/check_config.py +0 -2
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/config_builder.py +13 -2
- mcp_proxy_adapter/examples/config_cli.py +0 -1
- mcp_proxy_adapter/examples/create_test_configs.py +0 -46
- mcp_proxy_adapter/examples/debug_request_state.py +0 -1
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
- mcp_proxy_adapter/examples/full_application/main.py +186 -150
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
- mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
- mcp_proxy_adapter/examples/generate_config.py +65 -11
- mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
- mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
- mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
- mcp_proxy_adapter/examples/queue_server_example.py +85 -0
- mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
- mcp_proxy_adapter/examples/required_certificates.py +0 -2
- mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
- mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
- mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
- mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
- mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
- mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
- mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
- mcp_proxy_adapter/examples/security_test_client.py +24 -1075
- mcp_proxy_adapter/examples/setup/__init__.py +24 -0
- mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
- mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
- mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
- mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
- mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
- mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
- mcp_proxy_adapter/examples/test_config.py +0 -3
- mcp_proxy_adapter/examples/test_config_builder.py +25 -405
- mcp_proxy_adapter/examples/test_examples.py +0 -1
- mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
- mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
- mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
- mcp_proxy_adapter/examples/universal_client.py +0 -6
- mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +70 -62
- mcp_proxy_adapter/openapi.py +0 -22
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.9.30.dist-info/RECORD +235 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/entry_points.txt +1 -1
- mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/top_level.txt +0 -0
|
@@ -1,42 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Core
|
|
3
|
-
"""
|
|
2
|
+
Core package marker for MCP Proxy Adapter.
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
Intentionally lightweight: avoids importing submodules at package import time
|
|
5
|
+
to prevent heavy side effects and circular imports.
|
|
6
|
+
"""
|
|
8
7
|
|
|
9
|
-
__all__ = [
|
|
10
|
-
# Errors
|
|
11
|
-
"NotFoundError",
|
|
12
|
-
"InvalidParamsError",
|
|
13
|
-
"CommandExecutionError",
|
|
14
|
-
"ConfigurationError",
|
|
15
|
-
# Logging
|
|
16
|
-
"setup_logging",
|
|
17
|
-
"get_logger",
|
|
18
|
-
"get_global_logger()",
|
|
19
|
-
"RequestLogger",
|
|
20
|
-
"CustomFormatter",
|
|
21
|
-
"RequestContextFilter",
|
|
22
|
-
# Settings
|
|
23
|
-
"Settings",
|
|
24
|
-
"ServerSettings",
|
|
25
|
-
"LoggingSettings",
|
|
26
|
-
"CommandsSettings",
|
|
27
|
-
"get_server_host",
|
|
28
|
-
"get_server_port",
|
|
29
|
-
"get_server_debug",
|
|
30
|
-
"get_logging_level",
|
|
31
|
-
"get_logging_dir",
|
|
32
|
-
"get_auto_discovery",
|
|
33
|
-
"get_discovery_path",
|
|
34
|
-
"get_setting",
|
|
35
|
-
"set_setting",
|
|
36
|
-
"reload_settings",
|
|
37
|
-
"add_custom_settings",
|
|
38
|
-
"get_custom_settings",
|
|
39
|
-
"get_custom_setting_value",
|
|
40
|
-
"set_custom_setting_value",
|
|
41
|
-
"clear_custom_settings",
|
|
42
|
-
]
|
|
8
|
+
__all__ = []
|
|
@@ -14,18 +14,11 @@ from typing import Optional, Dict, Any
|
|
|
14
14
|
|
|
15
15
|
from fastapi import FastAPI
|
|
16
16
|
from mcp_proxy_adapter.api.app import create_app
|
|
17
|
-
from mcp_proxy_adapter.core.logging import setup_logging,
|
|
17
|
+
from mcp_proxy_adapter.core.logging import setup_logging, get_global_logger
|
|
18
18
|
from mcp_proxy_adapter.config import config
|
|
19
|
-
|
|
20
|
-
register_builtin_commands,
|
|
21
|
-
)
|
|
22
|
-
from mcp_proxy_adapter.core.utils import (
|
|
23
|
-
check_port_availability,
|
|
24
|
-
handle_port_conflict,
|
|
25
|
-
find_port_for_internal_server,
|
|
26
|
-
)
|
|
19
|
+
# Built-in command registration is temporarily disabled in this startup path
|
|
27
20
|
|
|
28
|
-
logger =
|
|
21
|
+
logger = get_global_logger()
|
|
29
22
|
|
|
30
23
|
|
|
31
24
|
async def create_and_run_server(
|
|
@@ -253,13 +246,8 @@ async def create_and_run_server(
|
|
|
253
246
|
print(f"❌ Failed to setup logging: {e}")
|
|
254
247
|
sys.exit(1)
|
|
255
248
|
|
|
256
|
-
# 3. Register built-in commands
|
|
257
|
-
|
|
258
|
-
builtin_count = register_builtin_commands()
|
|
259
|
-
print(f"✅ Registered {builtin_count} built-in commands")
|
|
260
|
-
except Exception as e:
|
|
261
|
-
print(f"❌ Failed to register built-in commands: {e}")
|
|
262
|
-
sys.exit(1)
|
|
249
|
+
# 3. Register built-in commands (disabled)
|
|
250
|
+
print("⚠️ Built-in command registration disabled for simplified startup")
|
|
263
251
|
|
|
264
252
|
# 4. Create FastAPI application with configuration
|
|
265
253
|
try:
|
|
@@ -449,13 +437,7 @@ async def create_and_run_server(
|
|
|
449
437
|
else:
|
|
450
438
|
print(f"🌐 Starting HTTP server with hypercorn...")
|
|
451
439
|
|
|
452
|
-
# Final port check
|
|
453
|
-
print(f"🔍 Final port check before starting hypercorn: {host}:{server_port}")
|
|
454
|
-
if not check_port_availability(host, server_port):
|
|
455
|
-
print(f"❌ CRITICAL: Port {server_port} is occupied during final check")
|
|
456
|
-
handle_port_conflict(host, server_port)
|
|
457
|
-
return # Exit immediately
|
|
458
|
-
print(f"✅ Final port check passed: {host}:{server_port}")
|
|
440
|
+
# Final port check disabled in this refactor; rely on OS errors
|
|
459
441
|
|
|
460
442
|
# Run the server
|
|
461
443
|
# hypercorn.asyncio.serve() should be run with asyncio.run(), not awaited
|
|
@@ -469,18 +451,14 @@ async def create_and_run_server(
|
|
|
469
451
|
print("🛑 Stopping internal mTLS server...")
|
|
470
452
|
mtls_server.stop()
|
|
471
453
|
except OSError as e:
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
mtls_server.stop()
|
|
481
|
-
import traceback
|
|
482
|
-
traceback.print_exc()
|
|
483
|
-
sys.exit(1)
|
|
454
|
+
print(f"\n❌ Failed to start server: {e}")
|
|
455
|
+
# Stop mTLS server if running
|
|
456
|
+
if mtls_server:
|
|
457
|
+
print("🛑 Stopping mTLS server...")
|
|
458
|
+
mtls_server.stop()
|
|
459
|
+
import traceback
|
|
460
|
+
traceback.print_exc()
|
|
461
|
+
sys.exit(1)
|
|
484
462
|
except Exception as e:
|
|
485
463
|
print(f"\n❌ Failed to start server: {e}")
|
|
486
464
|
# Stop internal mTLS server if running
|
|
@@ -11,7 +11,6 @@ with full configuration validation and error handling.
|
|
|
11
11
|
import socket
|
|
12
12
|
import sys
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import Dict, Any, List, Optional
|
|
15
14
|
|
|
16
15
|
from fastapi import FastAPI
|
|
17
16
|
|
|
@@ -227,16 +226,9 @@ class ApplicationRunner:
|
|
|
227
226
|
|
|
228
227
|
# Add startup event
|
|
229
228
|
@self.app.on_event("startup")
|
|
230
|
-
async def startup_event():
|
|
231
|
-
get_global_logger().info("Application starting up")
|
|
232
|
-
get_global_logger().info(
|
|
233
|
-
f"Configuration validation passed with {len(self.errors)} errors"
|
|
234
|
-
)
|
|
235
229
|
|
|
236
230
|
# Add shutdown event
|
|
237
231
|
@self.app.on_event("shutdown")
|
|
238
|
-
async def shutdown_event():
|
|
239
|
-
get_global_logger().info("Application shutting down")
|
|
240
232
|
|
|
241
233
|
def run(self) -> None:
|
|
242
234
|
"""
|
|
@@ -252,25 +244,6 @@ class ApplicationRunner:
|
|
|
252
244
|
sys.exit(1)
|
|
253
245
|
|
|
254
246
|
# Setup signal handling for graceful shutdown
|
|
255
|
-
def shutdown_callback():
|
|
256
|
-
"""Callback for graceful shutdown with proxy unregistration."""
|
|
257
|
-
print("\n🛑 Graceful shutdown initiated...")
|
|
258
|
-
try:
|
|
259
|
-
from mcp_proxy_adapter.core.async_proxy_registration import (
|
|
260
|
-
stop_async_registration,
|
|
261
|
-
get_registration_status,
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
# Get final status
|
|
265
|
-
final_status = get_registration_status()
|
|
266
|
-
print(f"📊 Final registration status: {final_status}")
|
|
267
|
-
|
|
268
|
-
# Stop async registration (this will unregister from proxy)
|
|
269
|
-
stop_async_registration()
|
|
270
|
-
print("✅ Proxy unregistration completed")
|
|
271
|
-
|
|
272
|
-
except Exception as e:
|
|
273
|
-
print(f"❌ Error during shutdown: {e}")
|
|
274
247
|
|
|
275
248
|
setup_signal_handling(shutdown_callback)
|
|
276
249
|
print("🔧 Signal handling configured for graceful shutdown")
|
|
@@ -12,12 +12,10 @@ import json
|
|
|
12
12
|
import logging
|
|
13
13
|
import os
|
|
14
14
|
from datetime import datetime
|
|
15
|
-
from typing import Dict, List, Optional, Any, Union
|
|
16
15
|
from pathlib import Path
|
|
16
|
+
from typing import Optional, Dict, Any, List
|
|
17
17
|
|
|
18
18
|
from cryptography import x509
|
|
19
|
-
from cryptography.hazmat.primitives import hashes
|
|
20
|
-
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
# Standard JSON-RPC error codes
|
|
@@ -71,36 +69,7 @@ class AuthValidationResult:
|
|
|
71
69
|
self.error_message = error_message
|
|
72
70
|
self.roles = roles or []
|
|
73
71
|
|
|
74
|
-
def to_json_rpc_error(self) -> Dict[str, Any]:
|
|
75
|
-
"""
|
|
76
|
-
Convert to JSON-RPC error format.
|
|
77
|
-
|
|
78
|
-
Returns:
|
|
79
|
-
JSON-RPC error dictionary
|
|
80
|
-
"""
|
|
81
|
-
if self.is_valid:
|
|
82
|
-
return {}
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
"code": self.error_code or -32603,
|
|
86
|
-
"message": self.error_message
|
|
87
|
-
or JSON_RPC_ERRORS.get(self.error_code, "Unknown error"),
|
|
88
|
-
"data": {"validation_type": "authentication", "roles": self.roles},
|
|
89
|
-
}
|
|
90
72
|
|
|
91
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
92
|
-
"""
|
|
93
|
-
Convert to dictionary format.
|
|
94
|
-
|
|
95
|
-
Returns:
|
|
96
|
-
Dictionary representation
|
|
97
|
-
"""
|
|
98
|
-
return {
|
|
99
|
-
"is_valid": self.is_valid,
|
|
100
|
-
"error_code": self.error_code,
|
|
101
|
-
"error_message": self.error_message,
|
|
102
|
-
"roles": self.roles,
|
|
103
|
-
}
|
|
104
73
|
|
|
105
74
|
|
|
106
75
|
class AuthValidator:
|
|
@@ -127,67 +96,6 @@ class AuthValidator:
|
|
|
127
96
|
# Custom OID for roles
|
|
128
97
|
self.role_oid = "1.3.6.1.4.1.99999.1"
|
|
129
98
|
|
|
130
|
-
def validate_auth(
|
|
131
|
-
self, auth_data: Dict[str, Any], auth_type: str = "auto"
|
|
132
|
-
) -> AuthValidationResult:
|
|
133
|
-
"""
|
|
134
|
-
Universal authentication validation.
|
|
135
|
-
|
|
136
|
-
Logic:
|
|
137
|
-
1. Check SSL configuration
|
|
138
|
-
2. If SSL disabled - return success
|
|
139
|
-
3. Determine validation type (auto/ssl/mtls/token)
|
|
140
|
-
4. Check for required data
|
|
141
|
-
5. Perform validation
|
|
142
|
-
6. Return result with JSON-RPC error code
|
|
143
|
-
|
|
144
|
-
Args:
|
|
145
|
-
auth_data: Authentication data dictionary
|
|
146
|
-
auth_type: Type of authentication to validate
|
|
147
|
-
|
|
148
|
-
Returns:
|
|
149
|
-
Authentication validation result
|
|
150
|
-
"""
|
|
151
|
-
try:
|
|
152
|
-
# Determine validation type
|
|
153
|
-
if auth_type == "auto":
|
|
154
|
-
auth_type = self._get_validation_mode()
|
|
155
|
-
|
|
156
|
-
# Check for unsupported types first
|
|
157
|
-
if auth_type not in ["certificate", "token", "mtls", "ssl"]:
|
|
158
|
-
return AuthValidationResult(
|
|
159
|
-
is_valid=False,
|
|
160
|
-
error_code=-32602,
|
|
161
|
-
error_message=f"Unsupported authentication type: {auth_type}",
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
# Check if authentication is enabled for the determined type
|
|
165
|
-
if not self._check_config(auth_type):
|
|
166
|
-
return AuthValidationResult(is_valid=True, roles=[])
|
|
167
|
-
|
|
168
|
-
# Validate based on type
|
|
169
|
-
if auth_type == "certificate":
|
|
170
|
-
return self.validate_certificate(
|
|
171
|
-
auth_data.get("cert_path"), auth_data.get("cert_type", "server")
|
|
172
|
-
)
|
|
173
|
-
elif auth_type == "token":
|
|
174
|
-
return self.validate_token(
|
|
175
|
-
auth_data.get("token"), auth_data.get("token_type", "jwt")
|
|
176
|
-
)
|
|
177
|
-
elif auth_type == "mtls":
|
|
178
|
-
return self.validate_mtls(
|
|
179
|
-
auth_data.get("client_cert"), auth_data.get("ca_cert")
|
|
180
|
-
)
|
|
181
|
-
elif auth_type == "ssl":
|
|
182
|
-
return self.validate_ssl(auth_data.get("server_cert"))
|
|
183
|
-
|
|
184
|
-
except Exception as e:
|
|
185
|
-
self.get_global_logger().error(f"Authentication validation error: {e}")
|
|
186
|
-
return AuthValidationResult(
|
|
187
|
-
is_valid=False,
|
|
188
|
-
error_code=-32603,
|
|
189
|
-
error_message=f"Internal validation error: {str(e)}",
|
|
190
|
-
)
|
|
191
99
|
|
|
192
100
|
def validate_certificate(
|
|
193
101
|
self, cert_path: Optional[str], cert_type: str = "server"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Certificate utilities package for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .certificate_utils import CertificateUtils
|
|
9
|
+
from .certificate_creator import CertificateCreator
|
|
10
|
+
from .certificate_validator import CertificateValidator
|
|
11
|
+
from .certificate_extractor import CertificateExtractor
|
|
12
|
+
from .ssl_context_manager import SSLContextManager
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"CertificateUtils",
|
|
16
|
+
"CertificateCreator",
|
|
17
|
+
"CertificateValidator",
|
|
18
|
+
"CertificateExtractor",
|
|
19
|
+
"SSLContextManager",
|
|
20
|
+
]
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Certificate creation utilities for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime, timedelta, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
# Import mcp_security_framework
|
|
14
|
+
try:
|
|
15
|
+
from mcp_security_framework.core.cert_manager import CertificateManager
|
|
16
|
+
CertificateConfig,
|
|
17
|
+
CAConfig,
|
|
18
|
+
ClientCertConfig,
|
|
19
|
+
ServerCertConfig,
|
|
20
|
+
)
|
|
21
|
+
SECURITY_FRAMEWORK_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
SECURITY_FRAMEWORK_AVAILABLE = False
|
|
24
|
+
# Fallback to cryptography if mcp_security_framework is not available
|
|
25
|
+
from cryptography import x509
|
|
26
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
27
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
28
|
+
from cryptography.x509.oid import NameOID
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CertificateCreator:
|
|
34
|
+
"""Creator for various types of certificates."""
|
|
35
|
+
|
|
36
|
+
# Default certificate validity period (1 year)
|
|
37
|
+
DEFAULT_VALIDITY_DAYS = 365
|
|
38
|
+
|
|
39
|
+
# Default key size
|
|
40
|
+
DEFAULT_KEY_SIZE = 2048
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def create_ca_certificate(
|
|
44
|
+
common_name: str,
|
|
45
|
+
output_dir: str,
|
|
46
|
+
validity_days: int = DEFAULT_VALIDITY_DAYS,
|
|
47
|
+
key_size: int = DEFAULT_KEY_SIZE,
|
|
48
|
+
) -> Dict[str, str]:
|
|
49
|
+
"""
|
|
50
|
+
Create a CA certificate and private key using mcp_security_framework.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
common_name: Common name for the CA certificate
|
|
54
|
+
output_dir: Directory to save certificate and key files
|
|
55
|
+
validity_days: Certificate validity period in days
|
|
56
|
+
key_size: RSA key size in bits
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dictionary with paths to created files
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: If parameters are invalid
|
|
63
|
+
OSError: If files cannot be created
|
|
64
|
+
"""
|
|
65
|
+
if not SECURITY_FRAMEWORK_AVAILABLE:
|
|
66
|
+
logger.warning("mcp_security_framework not available, using fallback method")
|
|
67
|
+
return CertificateCreator._create_ca_certificate_fallback(
|
|
68
|
+
common_name, output_dir, validity_days, key_size
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
# Validate parameters
|
|
73
|
+
if not common_name or not common_name.strip():
|
|
74
|
+
raise ValueError("Common name cannot be empty")
|
|
75
|
+
|
|
76
|
+
if validity_days <= 0:
|
|
77
|
+
raise ValueError("Validity days must be positive")
|
|
78
|
+
|
|
79
|
+
if key_size < 1024:
|
|
80
|
+
raise ValueError("Key size must be at least 1024 bits")
|
|
81
|
+
|
|
82
|
+
# Create output directory if it doesn't exist
|
|
83
|
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
84
|
+
|
|
85
|
+
# Configure CA using mcp_security_framework
|
|
86
|
+
ca_config = CAConfig(
|
|
87
|
+
common_name=common_name,
|
|
88
|
+
organization="MCP Proxy Adapter CA",
|
|
89
|
+
organizational_unit="Certificate Authority",
|
|
90
|
+
country="US",
|
|
91
|
+
state="Default State",
|
|
92
|
+
locality="Default City",
|
|
93
|
+
validity_days=validity_days,
|
|
94
|
+
key_size=key_size,
|
|
95
|
+
key_type="RSA",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Create certificate manager
|
|
99
|
+
cert_config = CertificateConfig(
|
|
100
|
+
output_dir=output_dir,
|
|
101
|
+
ca_cert_path=str(Path(output_dir) / f"{common_name}.crt"),
|
|
102
|
+
ca_key_path=str(Path(output_dir) / f"{common_name}.key"),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
cert_manager = CertificateManager(cert_config)
|
|
106
|
+
|
|
107
|
+
# Generate CA certificate
|
|
108
|
+
ca_pair = cert_manager.create_ca_certificate(ca_config)
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
"cert_path": str(ca_pair.cert_path),
|
|
112
|
+
"key_path": str(ca_pair.key_path),
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.error(f"Failed to create CA certificate: {e}")
|
|
117
|
+
raise
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def _create_ca_certificate_fallback(
|
|
121
|
+
common_name: str, output_dir: str, validity_days: int, key_size: int
|
|
122
|
+
) -> Dict[str, str]:
|
|
123
|
+
"""Fallback CA certificate creation using cryptography."""
|
|
124
|
+
try:
|
|
125
|
+
# Create output directory if it doesn't exist
|
|
126
|
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
127
|
+
|
|
128
|
+
# Generate private key
|
|
129
|
+
private_key = rsa.generate_private_key(
|
|
130
|
+
public_exponent=65537,
|
|
131
|
+
key_size=key_size,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Create certificate
|
|
135
|
+
subject = issuer = x509.Name([
|
|
136
|
+
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
|
|
137
|
+
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Default State"),
|
|
138
|
+
x509.NameAttribute(NameOID.LOCALITY_NAME, "Default City"),
|
|
139
|
+
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "MCP Proxy Adapter CA"),
|
|
140
|
+
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Certificate Authority"),
|
|
141
|
+
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
|
142
|
+
])
|
|
143
|
+
|
|
144
|
+
cert = x509.CertificateBuilder().subject_name(
|
|
145
|
+
subject
|
|
146
|
+
).issuer_name(
|
|
147
|
+
issuer
|
|
148
|
+
).public_key(
|
|
149
|
+
private_key.public_key()
|
|
150
|
+
).serial_number(
|
|
151
|
+
x509.random_serial_number()
|
|
152
|
+
).not_valid_before(
|
|
153
|
+
datetime.now(timezone.utc)
|
|
154
|
+
).not_valid_after(
|
|
155
|
+
datetime.now(timezone.utc) + timedelta(days=validity_days)
|
|
156
|
+
).add_extension(
|
|
157
|
+
x509.BasicConstraints(ca=True, path_length=None),
|
|
158
|
+
critical=True,
|
|
159
|
+
).add_extension(
|
|
160
|
+
x509.KeyUsage(
|
|
161
|
+
key_cert_sign=True,
|
|
162
|
+
crl_sign=True,
|
|
163
|
+
digital_signature=True,
|
|
164
|
+
key_encipherment=False,
|
|
165
|
+
data_encipherment=False,
|
|
166
|
+
key_agreement=False,
|
|
167
|
+
encipher_only=False,
|
|
168
|
+
decipher_only=False,
|
|
169
|
+
content_commitment=False,
|
|
170
|
+
),
|
|
171
|
+
critical=True,
|
|
172
|
+
).sign(private_key, hashes.SHA256())
|
|
173
|
+
|
|
174
|
+
# Save certificate and key
|
|
175
|
+
cert_path = Path(output_dir) / f"{common_name}.crt"
|
|
176
|
+
key_path = Path(output_dir) / f"{common_name}.key"
|
|
177
|
+
|
|
178
|
+
with open(cert_path, "wb") as f:
|
|
179
|
+
f.write(cert.public_bytes(serialization.Encoding.PEM))
|
|
180
|
+
|
|
181
|
+
with open(key_path, "wb") as f:
|
|
182
|
+
f.write(private_key.private_bytes(
|
|
183
|
+
encoding=serialization.Encoding.PEM,
|
|
184
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
185
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
186
|
+
))
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
"cert_path": str(cert_path),
|
|
190
|
+
"key_path": str(key_path),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logger.error(f"Failed to create CA certificate (fallback): {e}")
|
|
195
|
+
raise
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def create_server_certificate(
|
|
199
|
+
common_name: str,
|
|
200
|
+
output_dir: str,
|
|
201
|
+
ca_cert_path: str,
|
|
202
|
+
ca_key_path: str,
|
|
203
|
+
validity_days: int = DEFAULT_VALIDITY_DAYS,
|
|
204
|
+
key_size: int = DEFAULT_KEY_SIZE,
|
|
205
|
+
san_dns: Optional[List[str]] = None,
|
|
206
|
+
san_ip: Optional[List[str]] = None,
|
|
207
|
+
) -> Dict[str, str]:
|
|
208
|
+
"""
|
|
209
|
+
Create a server certificate signed by CA.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
common_name: Common name for the server certificate
|
|
213
|
+
output_dir: Directory to save certificate and key files
|
|
214
|
+
ca_cert_path: Path to CA certificate
|
|
215
|
+
ca_key_path: Path to CA private key
|
|
216
|
+
validity_days: Certificate validity period in days
|
|
217
|
+
key_size: RSA key size in bits
|
|
218
|
+
san_dns: List of DNS names for SAN extension
|
|
219
|
+
san_ip: List of IP addresses for SAN extension
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Dictionary with paths to created files
|
|
223
|
+
"""
|
|
224
|
+
if not SECURITY_FRAMEWORK_AVAILABLE:
|
|
225
|
+
logger.warning("mcp_security_framework not available, using fallback method")
|
|
226
|
+
return CertificateCreator._create_server_certificate_fallback(
|
|
227
|
+
common_name, output_dir, ca_cert_path, ca_key_path,
|
|
228
|
+
validity_days, key_size, san_dns, san_ip
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
# Validate parameters
|
|
233
|
+
if not common_name or not common_name.strip():
|
|
234
|
+
raise ValueError("Common name cannot be empty")
|
|
235
|
+
|
|
236
|
+
if not Path(ca_cert_path).exists():
|
|
237
|
+
raise FileNotFoundError(f"CA certificate not found: {ca_cert_path}")
|
|
238
|
+
|
|
239
|
+
if not Path(ca_key_path).exists():
|
|
240
|
+
raise FileNotFoundError(f"CA key not found: {ca_key_path}")
|
|
241
|
+
|
|
242
|
+
# Create output directory if it doesn't exist
|
|
243
|
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
244
|
+
|
|
245
|
+
# Configure server certificate using mcp_security_framework
|
|
246
|
+
server_config = ServerCertConfig(
|
|
247
|
+
common_name=common_name,
|
|
248
|
+
organization="MCP Proxy Adapter",
|
|
249
|
+
organizational_unit="Server",
|
|
250
|
+
country="US",
|
|
251
|
+
state="Default State",
|
|
252
|
+
locality="Default City",
|
|
253
|
+
validity_days=validity_days,
|
|
254
|
+
key_size=key_size,
|
|
255
|
+
key_type="RSA",
|
|
256
|
+
san_dns=san_dns or [],
|
|
257
|
+
san_ip=san_ip or [],
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Create certificate manager
|
|
261
|
+
cert_config = CertificateConfig(
|
|
262
|
+
output_dir=output_dir,
|
|
263
|
+
ca_cert_path=ca_cert_path,
|
|
264
|
+
ca_key_path=ca_key_path,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
cert_manager = CertificateManager(cert_config)
|
|
268
|
+
|
|
269
|
+
# Generate server certificate
|
|
270
|
+
server_pair = cert_manager.create_server_certificate(server_config)
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
"cert_path": str(server_pair.cert_path),
|
|
274
|
+
"key_path": str(server_pair.key_path),
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logger.error(f"Failed to create server certificate: {e}")
|
|
279
|
+
raise
|
|
280
|
+
|
|
281
|
+
@staticmethod
|
|
282
|
+
def _create_server_certificate_fallback(
|
|
283
|
+
common_name: str,
|
|
284
|
+
output_dir: str,
|
|
285
|
+
ca_cert_path: str,
|
|
286
|
+
ca_key_path: str,
|
|
287
|
+
validity_days: int,
|
|
288
|
+
key_size: int,
|
|
289
|
+
san_dns: Optional[List[str]],
|
|
290
|
+
san_ip: Optional[List[str]],
|
|
291
|
+
) -> Dict[str, str]:
|
|
292
|
+
"""Fallback server certificate creation using cryptography."""
|
|
293
|
+
try:
|
|
294
|
+
# Create output directory if it doesn't exist
|
|
295
|
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
296
|
+
|
|
297
|
+
# Load CA certificate and key
|
|
298
|
+
with open(ca_cert_path, "rb") as f:
|
|
299
|
+
ca_cert = x509.load_pem_x509_certificate(f.read())
|
|
300
|
+
|
|
301
|
+
with open(ca_key_path, "rb") as f:
|
|
302
|
+
ca_key = serialization.load_pem_private_key(f.read(), password=None)
|
|
303
|
+
|
|
304
|
+
# Generate server private key
|
|
305
|
+
private_key = rsa.generate_private_key(
|
|
306
|
+
public_exponent=65537,
|
|
307
|
+
key_size=key_size,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Create certificate
|
|
311
|
+
subject = x509.Name([
|
|
312
|
+
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
|
|
313
|
+
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Default State"),
|
|
314
|
+
x509.NameAttribute(NameOID.LOCALITY_NAME, "Default City"),
|
|
315
|
+
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "MCP Proxy Adapter"),
|
|
316
|
+
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Server"),
|
|
317
|
+
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
|
318
|
+
])
|
|
319
|
+
|
|
320
|
+
# Build certificate
|
|
321
|
+
cert_builder = x509.CertificateBuilder().subject_name(
|
|
322
|
+
subject
|
|
323
|
+
).issuer_name(
|
|
324
|
+
ca_cert.subject
|
|
325
|
+
).public_key(
|
|
326
|
+
private_key.public_key()
|
|
327
|
+
).serial_number(
|
|
328
|
+
x509.random_serial_number()
|
|
329
|
+
).not_valid_before(
|
|
330
|
+
datetime.now(timezone.utc)
|
|
331
|
+
).not_valid_after(
|
|
332
|
+
datetime.now(timezone.utc) + timedelta(days=validity_days)
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Add SAN extension if provided
|
|
336
|
+
if san_dns or san_ip:
|
|
337
|
+
san_list = []
|
|
338
|
+
if san_dns:
|
|
339
|
+
san_list.extend([x509.DNSName(name) for name in san_dns])
|
|
340
|
+
if san_ip:
|
|
341
|
+
san_list.extend([x509.IPAddress(ip) for ip in san_ip])
|
|
342
|
+
|
|
343
|
+
cert_builder = cert_builder.add_extension(
|
|
344
|
+
x509.SubjectAlternativeName(san_list),
|
|
345
|
+
critical=False,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
cert = cert_builder.sign(ca_key, hashes.SHA256())
|
|
349
|
+
|
|
350
|
+
# Save certificate and key
|
|
351
|
+
cert_path = Path(output_dir) / f"{common_name}_server.crt"
|
|
352
|
+
key_path = Path(output_dir) / f"{common_name}_server.key"
|
|
353
|
+
|
|
354
|
+
with open(cert_path, "wb") as f:
|
|
355
|
+
f.write(cert.public_bytes(serialization.Encoding.PEM))
|
|
356
|
+
|
|
357
|
+
with open(key_path, "wb") as f:
|
|
358
|
+
f.write(private_key.private_bytes(
|
|
359
|
+
encoding=serialization.Encoding.PEM,
|
|
360
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
361
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
362
|
+
))
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
"cert_path": str(cert_path),
|
|
366
|
+
"key_path": str(key_path),
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.error(f"Failed to create server certificate (fallback): {e}")
|
|
371
|
+
raise
|