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,187 @@
1
+ """
2
+ MTLS ASGI Application Wrapper
3
+
4
+ This module provides an ASGI application wrapper that extracts client certificates
5
+ from the SSL context and makes them available to FastAPI middleware.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ Version: 1.0.0
10
+ """
11
+
12
+ import logging
13
+ import ssl
14
+ from typing import Dict, Any, Optional
15
+ from cryptography import x509
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class MTLSASGIApp:
21
+ """
22
+ ASGI application wrapper for mTLS support.
23
+
24
+ Extracts client certificates from SSL context and stores them in ASGI scope
25
+ for access by FastAPI middleware.
26
+ """
27
+
28
+ def __init__(self, app, ssl_config: Dict[str, Any]):
29
+ """
30
+ Initialize MTLS ASGI app.
31
+
32
+ Args:
33
+ app: The underlying ASGI application
34
+ ssl_config: SSL configuration dictionary
35
+ """
36
+ self.app = app
37
+ self.ssl_config = ssl_config
38
+ self.client_cert_required = ssl_config.get("client_cert_required", True)
39
+
40
+ logger.info(f"MTLS ASGI app initialized: client_cert_required={self.client_cert_required}")
41
+
42
+ async def __call__(self, scope: Dict[str, Any], receive, send):
43
+ """
44
+ Handle ASGI request with mTLS support.
45
+
46
+ Args:
47
+ scope: ASGI scope dictionary
48
+ receive: ASGI receive callable
49
+ send: ASGI send callable
50
+ """
51
+ try:
52
+ # Extract client certificate from SSL context
53
+ if scope["type"] == "http" and "ssl" in scope:
54
+ client_cert = self._extract_client_certificate(scope)
55
+ if client_cert:
56
+ # Store certificate in scope for middleware access
57
+ scope["client_certificate"] = client_cert
58
+ logger.debug(f"Client certificate extracted: {client_cert.get('subject', {})}")
59
+ elif self.client_cert_required:
60
+ logger.warning("Client certificate required but not provided")
61
+ # Return 401 Unauthorized
62
+ await self._send_unauthorized_response(send)
63
+ return
64
+
65
+ # Call the underlying application
66
+ await self.app(scope, receive, send)
67
+
68
+ except Exception as e:
69
+ logger.error(f"Error in MTLS ASGI app: {e}")
70
+ await self._send_error_response(send, str(e))
71
+
72
+ def _extract_client_certificate(self, scope: Dict[str, Any]) -> Optional[Dict[str, Any]]:
73
+ """
74
+ Extract client certificate from SSL context.
75
+
76
+ Args:
77
+ scope: ASGI scope dictionary
78
+
79
+ Returns:
80
+ Certificate dictionary or None if not found
81
+ """
82
+ try:
83
+ ssl_context = scope.get("ssl")
84
+ if not ssl_context:
85
+ logger.debug("No SSL context found in scope")
86
+ return None
87
+
88
+ # Try to get peer certificate
89
+ if hasattr(ssl_context, 'getpeercert'):
90
+ cert_data = ssl_context.getpeercert(binary_form=True)
91
+ if cert_data:
92
+ # Parse certificate
93
+ cert = x509.load_der_x509_certificate(cert_data)
94
+ return self._cert_to_dict(cert)
95
+ else:
96
+ logger.debug("No certificate data in SSL context")
97
+ return None
98
+ else:
99
+ logger.debug("SSL context has no getpeercert method")
100
+ return None
101
+
102
+ except Exception as e:
103
+ logger.error(f"Failed to extract client certificate: {e}")
104
+ return None
105
+
106
+ def _cert_to_dict(self, cert: x509.Certificate) -> Dict[str, Any]:
107
+ """
108
+ Convert x509 certificate to dictionary.
109
+
110
+ Args:
111
+ cert: x509 certificate object
112
+
113
+ Returns:
114
+ Certificate dictionary
115
+ """
116
+ try:
117
+ # Extract subject
118
+ subject = {}
119
+ for name in cert.subject:
120
+ subject[name.oid._name] = name.value
121
+
122
+ # Extract issuer
123
+ issuer = {}
124
+ for name in cert.issuer:
125
+ issuer[name.oid._name] = name.value
126
+
127
+ return {
128
+ "subject": subject,
129
+ "issuer": issuer,
130
+ "serial_number": str(cert.serial_number),
131
+ "not_valid_before": cert.not_valid_before.isoformat(),
132
+ "not_valid_after": cert.not_valid_after.isoformat(),
133
+ "version": cert.version.value,
134
+ "signature_algorithm_oid": cert.signature_algorithm_oid._name,
135
+ "public_key": {
136
+ "key_size": cert.public_key().key_size if hasattr(cert.public_key(), 'key_size') else None,
137
+ "public_numbers": str(cert.public_key().public_numbers()) if hasattr(cert.public_key(), 'public_numbers') else None
138
+ }
139
+ }
140
+ except Exception as e:
141
+ logger.error(f"Failed to convert certificate to dict: {e}")
142
+ return {"error": str(e)}
143
+
144
+ async def _send_unauthorized_response(self, send):
145
+ """Send 401 Unauthorized response."""
146
+ await send({
147
+ "type": "http.response.start",
148
+ "status": 401,
149
+ "headers": [
150
+ (b"content-type", b"application/json"),
151
+ (b"content-length", b"0")
152
+ ]
153
+ })
154
+ await send({
155
+ "type": "http.response.body",
156
+ "body": b""
157
+ })
158
+
159
+ async def _send_error_response(self, send, error_message: str):
160
+ """Send error response."""
161
+ body = f'{{"error": "{error_message}"}}'.encode('utf-8')
162
+ await send({
163
+ "type": "http.response.start",
164
+ "status": 500,
165
+ "headers": [
166
+ (b"content-type", b"application/json"),
167
+ (b"content-length", str(len(body)).encode())
168
+ ]
169
+ })
170
+ await send({
171
+ "type": "http.response.body",
172
+ "body": body
173
+ })
174
+
175
+
176
+ def create_mtls_asgi_app(app, ssl_config: Dict[str, Any]):
177
+ """
178
+ Create MTLS ASGI application wrapper.
179
+
180
+ Args:
181
+ app: The underlying ASGI application
182
+ ssl_config: SSL configuration dictionary
183
+
184
+ Returns:
185
+ MTLS ASGI app wrapper
186
+ """
187
+ return MTLSASGIApp(app, ssl_config)
@@ -21,11 +21,85 @@ class ProtocolManager:
21
21
  ensuring that only configured protocols are accessible.
22
22
  """
23
23
 
24
- def __init__(self):
25
- """Initialize the protocol manager."""
26
- self.protocols_config = config.get("protocols", {})
24
+ def __init__(self, app_config: Optional[Dict] = None):
25
+ """
26
+ Initialize the protocol manager.
27
+
28
+ Args:
29
+ app_config: Application configuration dictionary (optional)
30
+ """
31
+ self.app_config = app_config
32
+ self._load_config()
33
+
34
+ def _load_config(self):
35
+ """Load protocol configuration from config."""
36
+ # Use provided config or fallback to global config
37
+ current_config = self.app_config if self.app_config is not None else config.get_all()
38
+
39
+ # Get protocols configuration
40
+ self.protocols_config = current_config.get("protocols", {})
27
41
  self.enabled = self.protocols_config.get("enabled", True)
28
- self.allowed_protocols = self.protocols_config.get("allowed_protocols", ["http"])
42
+
43
+ # Get SSL configuration to determine allowed protocols
44
+ ssl_enabled = self._is_ssl_enabled(current_config)
45
+
46
+ # Set allowed protocols based on SSL configuration
47
+ if ssl_enabled:
48
+ # If SSL is enabled, allow both HTTP and HTTPS
49
+ self.allowed_protocols = self.protocols_config.get("allowed_protocols", ["http", "https"])
50
+ # Ensure HTTPS is in allowed protocols if SSL is enabled
51
+ if "https" not in self.allowed_protocols:
52
+ self.allowed_protocols.append("https")
53
+ else:
54
+ # If SSL is disabled, only allow HTTP
55
+ self.allowed_protocols = self.protocols_config.get("allowed_protocols", ["http"])
56
+ # Remove HTTPS from allowed protocols if SSL is disabled
57
+ if "https" in self.allowed_protocols:
58
+ self.allowed_protocols.remove("https")
59
+
60
+ logger.debug(f"Protocol manager loaded config: enabled={self.enabled}, allowed_protocols={self.allowed_protocols}, ssl_enabled={ssl_enabled}")
61
+
62
+ def _is_ssl_enabled(self, current_config: Dict) -> bool:
63
+ """
64
+ Check if SSL is enabled in configuration.
65
+
66
+ Args:
67
+ current_config: Current configuration dictionary
68
+
69
+ Returns:
70
+ True if SSL is enabled, False otherwise
71
+ """
72
+ # Try security framework SSL config first
73
+ security_config = current_config.get("security", {})
74
+ ssl_config = security_config.get("ssl", {})
75
+
76
+ if ssl_config.get("enabled", False):
77
+ logger.debug("SSL enabled via security.ssl configuration")
78
+ return True
79
+
80
+ # Fallback to legacy SSL config
81
+ legacy_ssl_config = current_config.get("ssl", {})
82
+ if legacy_ssl_config.get("enabled", False):
83
+ logger.debug("SSL enabled via legacy ssl configuration")
84
+ return True
85
+
86
+ logger.debug("SSL is disabled in configuration")
87
+ return False
88
+
89
+ def update_config(self, new_config: Dict):
90
+ """
91
+ Update configuration and reload protocol settings.
92
+
93
+ Args:
94
+ new_config: New configuration dictionary
95
+ """
96
+ self.app_config = new_config
97
+ self._load_config()
98
+ logger.info(f"Protocol manager configuration updated: allowed_protocols={self.allowed_protocols}")
99
+
100
+ def reload_config(self):
101
+ """Reload protocol configuration from global config."""
102
+ self._load_config()
29
103
 
30
104
  def is_protocol_allowed(self, protocol: str) -> bool:
31
105
  """
@@ -128,7 +202,11 @@ class ProtocolManager:
128
202
  if protocol.lower() not in ["https", "mtls"]:
129
203
  return None
130
204
 
131
- ssl_config = config.get("ssl", {})
205
+ # Use provided config or fallback to global config
206
+ current_config = self.app_config if self.app_config is not None else config.get_all()
207
+
208
+ # Get SSL configuration
209
+ ssl_config = self._get_ssl_config(current_config)
132
210
 
133
211
  if not ssl_config.get("enabled", False):
134
212
  logger.warning(f"SSL required for protocol '{protocol}' but SSL is disabled")
@@ -161,6 +239,33 @@ class ProtocolManager:
161
239
  logger.error(f"Failed to create SSL context for protocol '{protocol}': {e}")
162
240
  return None
163
241
 
242
+ def _get_ssl_config(self, current_config: Dict) -> Dict:
243
+ """
244
+ Get SSL configuration from config.
245
+
246
+ Args:
247
+ current_config: Current configuration dictionary
248
+
249
+ Returns:
250
+ SSL configuration dictionary
251
+ """
252
+ # Try security framework SSL config first
253
+ security_config = current_config.get("security", {})
254
+ ssl_config = security_config.get("ssl", {})
255
+
256
+ if ssl_config.get("enabled", False):
257
+ logger.debug("Using security.ssl configuration")
258
+ return ssl_config
259
+
260
+ # Fallback to legacy SSL config
261
+ legacy_ssl_config = current_config.get("ssl", {})
262
+ if legacy_ssl_config.get("enabled", False):
263
+ logger.debug("Using legacy ssl configuration")
264
+ return legacy_ssl_config
265
+
266
+ # Return empty config if SSL is disabled
267
+ return {"enabled": False}
268
+
164
269
  def get_protocol_info(self) -> Dict[str, Dict]:
165
270
  """
166
271
  Get information about all configured protocols.
@@ -213,7 +318,10 @@ class ProtocolManager:
213
318
 
214
319
  # Check SSL requirements
215
320
  if protocol in ["https", "mtls"]:
216
- ssl_config = config.get("ssl", {})
321
+ # Use provided config or fallback to global config
322
+ current_config = self.app_config if self.app_config is not None else config.get_all()
323
+ ssl_config = self._get_ssl_config(current_config)
324
+
217
325
  if not ssl_config.get("enabled", False):
218
326
  errors.append(f"Protocol '{protocol}' requires SSL but SSL is disabled")
219
327
  elif not ssl_config.get("cert_file") or not ssl_config.get("key_file"):
@@ -222,5 +330,28 @@ class ProtocolManager:
222
330
  return errors
223
331
 
224
332
 
225
- # Global protocol manager instance
226
- protocol_manager = ProtocolManager()
333
+ # Global protocol manager instance - will be updated with config when needed
334
+ protocol_manager = None
335
+
336
+ def get_protocol_manager(app_config: Optional[Dict] = None) -> ProtocolManager:
337
+ """
338
+ Get protocol manager instance with current configuration.
339
+
340
+ Args:
341
+ app_config: Application configuration dictionary (optional)
342
+
343
+ Returns:
344
+ ProtocolManager instance
345
+ """
346
+ global protocol_manager
347
+
348
+ # If no app_config provided, use global config
349
+ if app_config is None:
350
+ app_config = config.get_all()
351
+
352
+ # Create new instance if none exists or config changed
353
+ if protocol_manager is None or protocol_manager.app_config != app_config:
354
+ protocol_manager = ProtocolManager(app_config)
355
+ logger.info("Protocol manager created with new configuration")
356
+
357
+ return protocol_manager