mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.1.1__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 (264) 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 +9 -4
  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/protocol_middleware.py +32 -13
  7. mcp_proxy_adapter/api/middleware/unified_security.py +160 -0
  8. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  9. mcp_proxy_adapter/commands/__init__.py +7 -1
  10. mcp_proxy_adapter/commands/base.py +7 -4
  11. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  12. mcp_proxy_adapter/commands/command_registry.py +8 -0
  13. mcp_proxy_adapter/commands/echo_command.py +81 -0
  14. mcp_proxy_adapter/commands/help_command.py +21 -14
  15. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  16. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  17. mcp_proxy_adapter/commands/security_command.py +488 -0
  18. mcp_proxy_adapter/commands/ssl_setup_command.py +2 -2
  19. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  20. mcp_proxy_adapter/config.py +81 -21
  21. mcp_proxy_adapter/core/app_factory.py +326 -0
  22. mcp_proxy_adapter/core/client_security.py +384 -0
  23. mcp_proxy_adapter/core/logging.py +8 -3
  24. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  25. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  26. mcp_proxy_adapter/core/protocol_manager.py +139 -8
  27. mcp_proxy_adapter/core/proxy_client.py +602 -0
  28. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  29. mcp_proxy_adapter/core/security_adapter.py +12 -15
  30. mcp_proxy_adapter/core/security_integration.py +285 -0
  31. mcp_proxy_adapter/core/server_adapter.py +345 -0
  32. mcp_proxy_adapter/core/server_engine.py +364 -0
  33. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  34. mcp_proxy_adapter/docs/EN/TROUBLESHOOTING.md +285 -0
  35. mcp_proxy_adapter/docs/RU/TROUBLESHOOTING.md +285 -0
  36. mcp_proxy_adapter/examples/README.md +230 -97
  37. mcp_proxy_adapter/examples/README_EN.md +258 -0
  38. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  39. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  40. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  41. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +43 -0
  42. mcp_proxy_adapter/examples/basic_framework/configs/https_no_protocol_middleware.json +36 -0
  43. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +29 -0
  44. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_protocol_middleware.json +34 -0
  45. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  46. mcp_proxy_adapter/examples/basic_framework/configs/mtls_simple.json +35 -0
  47. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  48. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  49. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  50. mcp_proxy_adapter/examples/cert_config.json +9 -0
  51. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  52. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  53. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  54. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  55. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  56. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  57. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  58. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  59. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  60. mcp_proxy_adapter/examples/certs/client.key +52 -0
  61. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  62. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  63. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  64. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  65. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  66. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  67. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  68. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  69. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  70. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  71. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  72. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  73. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  74. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  75. mcp_proxy_adapter/examples/certs/server.key +52 -0
  76. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  77. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  78. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  79. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  80. mcp_proxy_adapter/examples/certs/user.key +52 -0
  81. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  82. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  83. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  84. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  85. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  86. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  87. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  88. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  89. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  90. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  91. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  92. mcp_proxy_adapter/examples/demo_client.py +341 -0
  93. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  94. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  95. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  96. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  97. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  98. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  99. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  100. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  101. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  102. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  103. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  104. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  105. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  106. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  107. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  108. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  109. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  110. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  111. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  112. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  113. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  114. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  115. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  124. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  125. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  126. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  127. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  128. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  129. mcp_proxy_adapter/examples/roles.json +38 -0
  130. mcp_proxy_adapter/examples/run_example.py +81 -0
  131. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  132. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  133. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  134. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  135. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  136. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  137. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  138. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  139. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  140. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  141. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  142. mcp_proxy_adapter/examples/test_config_generator.py +110 -0
  143. mcp_proxy_adapter/examples/test_examples.py +344 -0
  144. mcp_proxy_adapter/examples/universal_client.py +628 -0
  145. mcp_proxy_adapter/main.py +21 -10
  146. mcp_proxy_adapter/utils/config_generator.py +727 -0
  147. mcp_proxy_adapter/version.py +5 -2
  148. mcp_proxy_adapter-6.1.1.dist-info/METADATA +205 -0
  149. mcp_proxy_adapter-6.1.1.dist-info/RECORD +197 -0
  150. mcp_proxy_adapter-6.1.1.dist-info/entry_points.txt +2 -0
  151. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/licenses/LICENSE +2 -2
  152. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  153. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  154. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  155. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  156. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  157. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  158. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  159. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  160. mcp_proxy_adapter/api/middleware/security.py +0 -376
  161. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  162. mcp_proxy_adapter/examples/__init__.py +0 -7
  163. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  164. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  166. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  167. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  168. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  169. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  170. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  171. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  172. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  173. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  174. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  175. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  176. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  177. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  178. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  179. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  180. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  181. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  182. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  183. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  184. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  185. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  186. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  187. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  188. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  189. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  190. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  191. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  192. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  193. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  194. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  195. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  196. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  197. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  198. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  199. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  200. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  201. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  202. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  203. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  204. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  205. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  206. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  207. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  208. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  209. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  210. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  211. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  212. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  213. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  214. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  215. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  216. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  217. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  218. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  219. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  220. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  221. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  222. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  223. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  224. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  225. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  226. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  227. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  228. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  229. mcp_proxy_adapter/tests/__init__.py +0 -0
  230. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  231. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  232. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  233. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  234. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  235. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  236. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  237. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  238. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  239. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  240. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  241. mcp_proxy_adapter/tests/conftest.py +0 -131
  242. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  243. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  244. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  245. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  246. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  247. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  248. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  249. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  250. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  251. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  252. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  253. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  254. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  255. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  256. mcp_proxy_adapter/tests/test_config.py +0 -127
  257. mcp_proxy_adapter/tests/test_utils.py +0 -65
  258. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  259. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  260. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  261. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  262. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  263. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/WHEEL +0 -0
  264. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.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