mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.1.0__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.
Files changed (259) hide show
  1. mcp_proxy_adapter/api/app.py +174 -80
  2. mcp_proxy_adapter/api/handlers.py +16 -5
  3. mcp_proxy_adapter/api/middleware/__init__.py +7 -2
  4. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  5. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  6. mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
  7. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  8. mcp_proxy_adapter/commands/__init__.py +7 -1
  9. mcp_proxy_adapter/commands/base.py +7 -4
  10. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  11. mcp_proxy_adapter/commands/command_registry.py +8 -0
  12. mcp_proxy_adapter/commands/echo_command.py +81 -0
  13. mcp_proxy_adapter/commands/help_command.py +21 -14
  14. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  15. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  16. mcp_proxy_adapter/commands/security_command.py +488 -0
  17. mcp_proxy_adapter/commands/ssl_setup_command.py +2 -2
  18. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  19. mcp_proxy_adapter/config.py +81 -21
  20. mcp_proxy_adapter/core/app_factory.py +326 -0
  21. mcp_proxy_adapter/core/client_security.py +384 -0
  22. mcp_proxy_adapter/core/logging.py +8 -3
  23. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  24. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  25. mcp_proxy_adapter/core/protocol_manager.py +9 -0
  26. mcp_proxy_adapter/core/proxy_client.py +602 -0
  27. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  28. mcp_proxy_adapter/core/security_adapter.py +12 -15
  29. mcp_proxy_adapter/core/security_integration.py +277 -0
  30. mcp_proxy_adapter/core/server_adapter.py +345 -0
  31. mcp_proxy_adapter/core/server_engine.py +364 -0
  32. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  33. mcp_proxy_adapter/examples/README.md +230 -97
  34. mcp_proxy_adapter/examples/README_EN.md +258 -0
  35. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  36. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  37. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  38. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  39. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  40. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
  41. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
  42. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  43. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  44. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  45. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  46. mcp_proxy_adapter/examples/cert_config.json +9 -0
  47. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  48. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  49. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  50. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  51. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  52. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  53. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  54. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  55. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  56. mcp_proxy_adapter/examples/certs/client.key +52 -0
  57. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  58. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  59. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  60. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  61. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  62. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  63. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  64. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  65. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  66. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  67. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  68. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  69. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  70. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  71. mcp_proxy_adapter/examples/certs/server.key +52 -0
  72. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  73. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  74. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  75. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  76. mcp_proxy_adapter/examples/certs/user.key +52 -0
  77. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  78. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  79. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  80. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  81. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  82. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  83. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  84. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  85. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  86. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  87. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  88. mcp_proxy_adapter/examples/demo_client.py +341 -0
  89. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  90. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  91. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  92. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  93. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  94. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  95. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  96. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  97. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  98. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  99. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  100. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  101. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  102. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  103. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  104. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  105. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  106. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  107. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  108. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  109. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  110. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  111. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  112. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  113. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  114. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  115. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  124. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  125. mcp_proxy_adapter/examples/roles.json +38 -0
  126. mcp_proxy_adapter/examples/run_example.py +81 -0
  127. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  128. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  129. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  130. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  131. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  132. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  133. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  134. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  135. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  136. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  137. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  138. mcp_proxy_adapter/examples/test_examples.py +344 -0
  139. mcp_proxy_adapter/examples/universal_client.py +628 -0
  140. mcp_proxy_adapter/main.py +21 -10
  141. mcp_proxy_adapter/utils/config_generator.py +639 -0
  142. mcp_proxy_adapter/version.py +2 -1
  143. mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
  144. mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
  145. mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
  146. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
  147. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  148. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  149. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  150. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  151. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  152. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  153. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  154. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  155. mcp_proxy_adapter/api/middleware/security.py +0 -376
  156. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  157. mcp_proxy_adapter/examples/__init__.py +0 -7
  158. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  159. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  160. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  161. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  162. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  163. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  164. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  165. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  166. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  167. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  168. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  169. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  170. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  171. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  172. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  173. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  174. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  175. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  176. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  177. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  178. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  179. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  180. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  181. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  182. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  183. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  184. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  185. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  186. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  187. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  188. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  189. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  190. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  191. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  192. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  193. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  194. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  195. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  196. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  197. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  198. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  199. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  200. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  201. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  202. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  203. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  204. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  205. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  206. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  207. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  208. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  209. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  210. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  211. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  212. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  213. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  214. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  215. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  216. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  217. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  218. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  219. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  220. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  221. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  222. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  223. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  224. mcp_proxy_adapter/tests/__init__.py +0 -0
  225. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  226. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  227. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  228. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  229. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  230. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  231. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  232. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  233. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  234. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  235. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  236. mcp_proxy_adapter/tests/conftest.py +0 -131
  237. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  238. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  239. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  240. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  241. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  242. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  243. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  244. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  245. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  246. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  247. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  248. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  249. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  250. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  251. mcp_proxy_adapter/tests/test_config.py +0 -127
  252. mcp_proxy_adapter/tests/test_utils.py +0 -65
  253. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  254. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  255. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  256. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  257. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  258. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
  259. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,384 @@
1
+ """
2
+ Client Security Module
3
+
4
+ This module provides client-side security integration for MCP Proxy Adapter,
5
+ using mcp_security_framework utilities for secure connections to servers.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ """
10
+
11
+ import logging
12
+ import ssl
13
+ from typing import Dict, Any, Optional, List, Tuple
14
+ from pathlib import Path
15
+
16
+ # Import framework utilities
17
+ try:
18
+ from mcp_security_framework.utils.crypto_utils import (
19
+ generate_api_key, create_jwt_token, verify_jwt_token
20
+ )
21
+ from mcp_security_framework.utils.cert_utils import (
22
+ parse_certificate, extract_roles_from_certificate,
23
+ validate_certificate_chain, validate_certificate_format
24
+ )
25
+ from mcp_security_framework.core.ssl_manager import SSLManager
26
+ from mcp_security_framework.schemas.config import SSLConfig, AuthConfig
27
+ from mcp_security_framework.schemas.models import AuthResult, ValidationResult
28
+ SECURITY_FRAMEWORK_AVAILABLE = True
29
+ except ImportError:
30
+ SECURITY_FRAMEWORK_AVAILABLE = False
31
+ SSLManager = None
32
+ SSLConfig = None
33
+ AuthConfig = None
34
+ AuthResult = None
35
+ ValidationResult = None
36
+
37
+ from mcp_proxy_adapter.core.logging import logger
38
+
39
+
40
+ class ClientSecurityManager:
41
+ """
42
+ Client-side security manager for MCP Proxy Adapter.
43
+
44
+ Provides secure client connections using mcp_security_framework utilities.
45
+ Handles authentication, certificate management, and SSL/TLS for client connections.
46
+ """
47
+
48
+ def __init__(self, config: Dict[str, Any]):
49
+ """
50
+ Initialize client security manager.
51
+
52
+ Args:
53
+ config: Security configuration
54
+ """
55
+ if not SECURITY_FRAMEWORK_AVAILABLE:
56
+ raise ImportError("mcp_security_framework is not available")
57
+
58
+ self.config = config
59
+ self.security_config = config.get("security", {})
60
+
61
+ # Initialize SSL manager if needed
62
+ self.ssl_manager = None
63
+ if self.security_config.get("ssl", {}).get("enabled", False):
64
+ ssl_config = self._create_ssl_config()
65
+ self.ssl_manager = SSLManager(ssl_config)
66
+
67
+ logger.info("Client security manager initialized")
68
+
69
+ def _create_ssl_config(self) -> SSLConfig:
70
+ """Create SSL configuration for client connections."""
71
+ ssl_section = self.security_config.get("ssl", {})
72
+
73
+ return SSLConfig(
74
+ enabled=ssl_section.get("enabled", False),
75
+ cert_file=ssl_section.get("client_cert_file"),
76
+ key_file=ssl_section.get("client_key_file"),
77
+ ca_cert_file=ssl_section.get("ca_cert_file"),
78
+ verify_mode=ssl_section.get("verify_mode", "CERT_REQUIRED"),
79
+ min_tls_version=ssl_section.get("min_tls_version", "TLSv1.2"),
80
+ check_hostname=ssl_section.get("check_hostname", True),
81
+ check_expiry=ssl_section.get("check_expiry", True)
82
+ )
83
+
84
+ def create_client_ssl_context(self, server_hostname: Optional[str] = None) -> Optional[ssl.SSLContext]:
85
+ """
86
+ Create SSL context for client connections.
87
+
88
+ Args:
89
+ server_hostname: Server hostname for SNI
90
+
91
+ Returns:
92
+ SSL context or None if SSL not enabled
93
+ """
94
+ if not self.ssl_manager:
95
+ return None
96
+
97
+ try:
98
+ # Create client SSL context
99
+ context = self.ssl_manager.create_client_context()
100
+
101
+ if server_hostname:
102
+ # Set server hostname for SNI
103
+ context.check_hostname = True
104
+ context.verify_mode = ssl.CERT_REQUIRED
105
+
106
+ logger.info(f"Client SSL context created for {server_hostname or 'unknown server'}")
107
+ return context
108
+
109
+ except Exception as e:
110
+ logger.error(f"Failed to create client SSL context: {e}")
111
+ return None
112
+
113
+ def generate_client_api_key(self, user_id: str, prefix: str = "mcp_proxy") -> str:
114
+ """
115
+ Generate API key for client authentication.
116
+
117
+ Args:
118
+ user_id: User identifier
119
+ prefix: Key prefix
120
+
121
+ Returns:
122
+ Generated API key
123
+ """
124
+ try:
125
+ api_key = generate_api_key(prefix=prefix)
126
+ logger.info(f"Generated API key for user: {user_id}")
127
+ return api_key
128
+ except Exception as e:
129
+ logger.error(f"Failed to generate API key: {e}")
130
+ raise
131
+
132
+ def create_client_jwt_token(self, user_id: str, roles: List[str],
133
+ secret: str, algorithm: str = "HS256",
134
+ expiry_hours: int = 24) -> str:
135
+ """
136
+ Create JWT token for client authentication.
137
+
138
+ Args:
139
+ user_id: User identifier
140
+ roles: User roles
141
+ secret: JWT secret
142
+ algorithm: JWT algorithm
143
+ expiry_hours: Token expiry in hours
144
+
145
+ Returns:
146
+ JWT token
147
+ """
148
+ try:
149
+ payload = {
150
+ "user_id": user_id,
151
+ "roles": roles,
152
+ "type": "client_proxy"
153
+ }
154
+
155
+ token = create_jwt_token(
156
+ payload=payload,
157
+ secret=secret,
158
+ algorithm=algorithm,
159
+ expiry_hours=expiry_hours
160
+ )
161
+
162
+ logger.info(f"Created JWT token for user: {user_id}")
163
+ return token
164
+
165
+ except Exception as e:
166
+ logger.error(f"Failed to create JWT token: {e}")
167
+ raise
168
+
169
+ def validate_server_certificate(self, cert_path: str, ca_cert_path: Optional[str] = None) -> bool:
170
+ """
171
+ Validate server certificate before connection.
172
+
173
+ Args:
174
+ cert_path: Path to server certificate
175
+ ca_cert_path: Path to CA certificate
176
+
177
+ Returns:
178
+ True if certificate is valid
179
+ """
180
+ try:
181
+ # Validate certificate format
182
+ if not validate_certificate_format(cert_path):
183
+ logger.error(f"Invalid certificate format: {cert_path}")
184
+ return False
185
+
186
+ # Validate certificate chain if CA provided
187
+ if ca_cert_path:
188
+ if not validate_certificate_chain(cert_path, ca_cert_path):
189
+ logger.error(f"Invalid certificate chain: {cert_path}")
190
+ return False
191
+
192
+ # Parse certificate and check basic properties
193
+ cert_info = parse_certificate(cert_path)
194
+ if not cert_info:
195
+ logger.error(f"Failed to parse certificate: {cert_path}")
196
+ return False
197
+
198
+ logger.info(f"Server certificate validated: {cert_path}")
199
+ return True
200
+
201
+ except Exception as e:
202
+ logger.error(f"Failed to validate server certificate: {e}")
203
+ return False
204
+
205
+ def extract_server_roles(self, cert_path: str) -> List[str]:
206
+ """
207
+ Extract roles from server certificate.
208
+
209
+ Args:
210
+ cert_path: Path to server certificate
211
+
212
+ Returns:
213
+ List of roles extracted from certificate
214
+ """
215
+ try:
216
+ roles = extract_roles_from_certificate(cert_path)
217
+ logger.info(f"Extracted roles from server certificate: {roles}")
218
+ return roles
219
+ except Exception as e:
220
+ logger.error(f"Failed to extract roles from certificate: {e}")
221
+ return []
222
+
223
+ def get_client_auth_headers(self, auth_method: str = "api_key", **kwargs) -> Dict[str, str]:
224
+ """
225
+ Get authentication headers for client requests.
226
+
227
+ Args:
228
+ auth_method: Authentication method (api_key, jwt, certificate)
229
+ **kwargs: Additional parameters
230
+
231
+ Returns:
232
+ Dictionary of authentication headers
233
+ """
234
+ headers = {}
235
+
236
+ try:
237
+ if auth_method == "api_key":
238
+ api_key = kwargs.get("api_key")
239
+ if api_key:
240
+ headers["X-API-Key"] = api_key
241
+ headers["Authorization"] = f"Bearer {api_key}"
242
+
243
+ elif auth_method == "jwt":
244
+ token = kwargs.get("token")
245
+ if token:
246
+ headers["Authorization"] = f"Bearer {token}"
247
+
248
+ elif auth_method == "certificate":
249
+ # Certificate authentication is handled at SSL level
250
+ headers["X-Auth-Method"] = "certificate"
251
+
252
+ # Add common proxy headers
253
+ headers["X-Proxy-Type"] = "mcp_proxy_adapter"
254
+ headers["X-Client-Type"] = "proxy_client"
255
+
256
+ logger.debug(f"Created auth headers for method: {auth_method}")
257
+ return headers
258
+
259
+ except Exception as e:
260
+ logger.error(f"Failed to create auth headers: {e}")
261
+ return {}
262
+
263
+ def prepare_client_connection(self, server_config: Dict[str, Any]) -> Tuple[Optional[ssl.SSLContext], Dict[str, str]]:
264
+ """
265
+ Prepare secure client connection to server.
266
+
267
+ Args:
268
+ server_config: Server connection configuration
269
+
270
+ Returns:
271
+ Tuple of (SSL context, auth headers)
272
+ """
273
+ ssl_context = None
274
+ auth_headers = {}
275
+
276
+ try:
277
+ # Create SSL context if needed
278
+ if server_config.get("ssl", False):
279
+ server_hostname = server_config.get("hostname")
280
+ ssl_context = self.create_client_ssl_context(server_hostname)
281
+
282
+ # Create authentication headers
283
+ auth_method = server_config.get("auth_method", "api_key")
284
+ auth_headers = self.get_client_auth_headers(
285
+ auth_method=auth_method,
286
+ api_key=server_config.get("api_key"),
287
+ token=server_config.get("token")
288
+ )
289
+
290
+ logger.info(f"Prepared client connection for {server_config.get('hostname', 'unknown')}")
291
+ return ssl_context, auth_headers
292
+
293
+ except Exception as e:
294
+ logger.error(f"Failed to prepare client connection: {e}")
295
+ return None, {}
296
+
297
+ def validate_server_response(self, response_headers: Dict[str, str]) -> bool:
298
+ """
299
+ Validate server response for security compliance.
300
+
301
+ Args:
302
+ response_headers: Server response headers
303
+
304
+ Returns:
305
+ True if response is valid
306
+ """
307
+ try:
308
+ # Check for required security headers
309
+ required_headers = ["Content-Type"]
310
+ for header in required_headers:
311
+ if header not in response_headers:
312
+ logger.warning(f"Missing required header: {header}")
313
+
314
+ # Check for security headers
315
+ security_headers = ["X-Frame-Options", "X-Content-Type-Options"]
316
+ for header in security_headers:
317
+ if header in response_headers:
318
+ logger.debug(f"Found security header: {header}")
319
+
320
+ # Validate content type
321
+ content_type = response_headers.get("Content-Type", "")
322
+ if "application/json" not in content_type:
323
+ logger.warning(f"Unexpected content type: {content_type}")
324
+
325
+ return True
326
+
327
+ except Exception as e:
328
+ logger.error(f"Failed to validate server response: {e}")
329
+ return False
330
+
331
+ def get_client_certificate_info(self) -> Optional[Dict[str, Any]]:
332
+ """
333
+ Get information about client certificate.
334
+
335
+ Returns:
336
+ Certificate information or None
337
+ """
338
+ try:
339
+ ssl_config = self.security_config.get("ssl", {})
340
+ cert_path = ssl_config.get("client_cert_file")
341
+
342
+ if not cert_path or not Path(cert_path).exists():
343
+ return None
344
+
345
+ cert_info = parse_certificate(cert_path)
346
+ if cert_info:
347
+ roles = extract_roles_from_certificate(cert_path)
348
+ cert_info["roles"] = roles
349
+ return cert_info
350
+
351
+ return None
352
+
353
+ except Exception as e:
354
+ logger.error(f"Failed to get client certificate info: {e}")
355
+ return None
356
+
357
+ def is_ssl_enabled(self) -> bool:
358
+ """Check if SSL is enabled for client connections."""
359
+ return self.security_config.get("ssl", {}).get("enabled", False)
360
+
361
+ def get_supported_auth_methods(self) -> List[str]:
362
+ """Get list of supported authentication methods."""
363
+ return ["api_key", "jwt", "certificate"]
364
+
365
+
366
+ # Factory function for easy integration
367
+ def create_client_security_manager(config: Dict[str, Any]) -> Optional[ClientSecurityManager]:
368
+ """
369
+ Create client security manager instance.
370
+
371
+ Args:
372
+ config: Configuration dictionary
373
+
374
+ Returns:
375
+ ClientSecurityManager instance or None if framework not available
376
+ """
377
+ try:
378
+ return ClientSecurityManager(config)
379
+ except ImportError:
380
+ logger.warning("mcp_security_framework not available, client security disabled")
381
+ return None
382
+ except Exception as e:
383
+ logger.error(f"Failed to create client security manager: {e}")
384
+ return None
@@ -230,17 +230,22 @@ def setup_logging(
230
230
  return logger
231
231
 
232
232
 
233
- def _parse_file_size(size_str: str) -> int:
233
+ def _parse_file_size(size_str) -> int:
234
234
  """
235
235
  Parse file size string to bytes.
236
236
 
237
237
  Args:
238
- size_str: Size string (e.g., "10MB", "1GB", "100KB")
238
+ size_str: Size string (e.g., "10MB", "1GB", "100KB") or int
239
239
 
240
240
  Returns:
241
241
  Size in bytes
242
242
  """
243
- size_str = size_str.upper()
243
+ # If it's already an int, return it
244
+ if isinstance(size_str, int):
245
+ return size_str
246
+
247
+ # Convert to string and parse
248
+ size_str = str(size_str).upper()
244
249
  if size_str.endswith("KB"):
245
250
  return int(size_str[:-2]) * 1024
246
251
  elif size_str.endswith("MB"):
@@ -0,0 +1,156 @@
1
+ """
2
+ Custom ASGI application for mTLS support.
3
+
4
+ This module provides a custom ASGI application that properly handles
5
+ client certificates in mTLS connections.
6
+ """
7
+
8
+ import ssl
9
+ import logging
10
+ from typing import Dict, Any, Optional
11
+ from starlette.applications import Starlette
12
+ from starlette.requests import Request
13
+ from starlette.responses import Response
14
+ from starlette.types import ASGIApp, Receive, Send, Scope
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class MTLSASGIApp:
20
+ """
21
+ Custom ASGI application that properly handles mTLS client certificates.
22
+
23
+ This wrapper ensures that client certificates are properly extracted
24
+ and made available to the FastAPI application.
25
+ """
26
+
27
+ def __init__(self, app: ASGIApp, ssl_config: Dict[str, Any]):
28
+ """
29
+ Initialize MTLS ASGI application.
30
+
31
+ Args:
32
+ app: The underlying ASGI application (FastAPI)
33
+ ssl_config: SSL configuration for mTLS
34
+ """
35
+ self.app = app
36
+ self.ssl_config = ssl_config
37
+ self.verify_client = ssl_config.get("verify_client", False)
38
+ self.client_cert_required = ssl_config.get("client_cert_required", False)
39
+
40
+ logger.info(f"MTLS ASGI app initialized: verify_client={self.verify_client}, "
41
+ f"client_cert_required={self.client_cert_required}")
42
+
43
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
44
+ """
45
+ Handle ASGI request with mTLS support.
46
+
47
+ Args:
48
+ scope: ASGI scope
49
+ receive: ASGI receive callable
50
+ send: ASGI send callable
51
+ """
52
+ try:
53
+ # Extract client certificate from SSL context
54
+ if scope["type"] == "http" and "ssl" in scope:
55
+ client_cert = self._extract_client_certificate(scope)
56
+ if client_cert:
57
+ # Store certificate in scope for middleware access
58
+ scope["client_certificate"] = client_cert
59
+ logger.debug(f"Client certificate extracted: {client_cert.get('subject', {})}")
60
+ elif self.client_cert_required:
61
+ logger.warning("Client certificate required but not provided")
62
+ # Return 401 Unauthorized
63
+ await self._send_unauthorized_response(send)
64
+ return
65
+
66
+ # Call the underlying application
67
+ await self.app(scope, receive, send)
68
+
69
+ except Exception as e:
70
+ logger.error(f"Error in MTLS ASGI app: {e}")
71
+ await self._send_error_response(send, str(e))
72
+
73
+ def _extract_client_certificate(self, scope: Scope) -> Optional[Dict[str, Any]]:
74
+ """
75
+ Extract client certificate from SSL context.
76
+
77
+ Args:
78
+ scope: ASGI scope
79
+
80
+ Returns:
81
+ Client certificate data or None
82
+ """
83
+ try:
84
+ ssl_context = scope.get("ssl")
85
+ if not ssl_context:
86
+ return None
87
+
88
+ # Get peer certificate
89
+ cert = ssl_context.getpeercert()
90
+ if cert:
91
+ return cert
92
+
93
+ return None
94
+
95
+ except Exception as e:
96
+ logger.error(f"Failed to extract client certificate: {e}")
97
+ return None
98
+
99
+ async def _send_unauthorized_response(self, send: Send) -> None:
100
+ """
101
+ Send 401 Unauthorized response.
102
+
103
+ Args:
104
+ send: ASGI send callable
105
+ """
106
+ response = {
107
+ "type": "http.response.start",
108
+ "status": 401,
109
+ "headers": [
110
+ (b"content-type", b"application/json"),
111
+ (b"content-length", b"163"),
112
+ ],
113
+ }
114
+ await send(response)
115
+
116
+ body = b'{"jsonrpc": "2.0", "error": {"code": -32001, "message": "Unauthorized: Client certificate required"}, "id": null}'
117
+ await send({"type": "http.response.body", "body": body})
118
+
119
+ async def _send_error_response(self, send: Send, error_message: str) -> None:
120
+ """
121
+ Send error response.
122
+
123
+ Args:
124
+ send: ASGI send callable
125
+ error_message: Error message
126
+ """
127
+ response = {
128
+ "type": "http.response.start",
129
+ "status": 500,
130
+ "headers": [
131
+ (b"content-type", b"application/json"),
132
+ ],
133
+ }
134
+ await send(response)
135
+
136
+ body = f'{{"jsonrpc": "2.0", "error": {{"code": -32603, "message": "Internal error: {error_message}"}}, "id": null}}'.encode()
137
+ await send({"type": "http.response.body", "body": body})
138
+
139
+
140
+ def create_mtls_asgi_app(app: ASGIApp, ssl_config: Dict[str, Any]) -> ASGIApp:
141
+ """
142
+ Create MTLS-enabled ASGI application.
143
+
144
+ Args:
145
+ app: The underlying ASGI application (FastAPI)
146
+ ssl_config: SSL configuration for mTLS
147
+
148
+ Returns:
149
+ MTLS-enabled ASGI application
150
+ """
151
+ if ssl_config.get("mode") == "mtls" or ssl_config.get("verify_client", False):
152
+ logger.info("Creating MTLS-enabled ASGI application")
153
+ return MTLSASGIApp(app, ssl_config)
154
+ else:
155
+ logger.info("Creating standard ASGI application (no mTLS)")
156
+ return app