mcp-proxy-adapter 6.9.43__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 (242) hide show
  1. mcp_proxy_adapter/__init__.py +47 -0
  2. mcp_proxy_adapter/__main__.py +13 -0
  3. mcp_proxy_adapter/api/__init__.py +0 -0
  4. mcp_proxy_adapter/api/app.py +66 -0
  5. mcp_proxy_adapter/api/core/__init__.py +18 -0
  6. mcp_proxy_adapter/api/core/app_factory.py +355 -0
  7. mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
  8. mcp_proxy_adapter/api/core/registration_context.py +356 -0
  9. mcp_proxy_adapter/api/core/registration_manager.py +266 -0
  10. mcp_proxy_adapter/api/core/registration_tasks.py +84 -0
  11. mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
  12. mcp_proxy_adapter/api/handlers.py +181 -0
  13. mcp_proxy_adapter/api/middleware/__init__.py +21 -0
  14. mcp_proxy_adapter/api/middleware/base.py +54 -0
  15. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +73 -0
  16. mcp_proxy_adapter/api/middleware/error_handling.py +76 -0
  17. mcp_proxy_adapter/api/middleware/factory.py +147 -0
  18. mcp_proxy_adapter/api/middleware/logging.py +31 -0
  19. mcp_proxy_adapter/api/middleware/performance.py +51 -0
  20. mcp_proxy_adapter/api/middleware/protocol_middleware.py +140 -0
  21. mcp_proxy_adapter/api/middleware/transport_middleware.py +87 -0
  22. mcp_proxy_adapter/api/middleware/unified_security.py +223 -0
  23. mcp_proxy_adapter/api/middleware/user_info_middleware.py +132 -0
  24. mcp_proxy_adapter/api/openapi/__init__.py +21 -0
  25. mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
  26. mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
  27. mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
  28. mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
  29. mcp_proxy_adapter/api/schemas.py +270 -0
  30. mcp_proxy_adapter/api/tool_integration.py +131 -0
  31. mcp_proxy_adapter/api/tools.py +163 -0
  32. mcp_proxy_adapter/cli/__init__.py +12 -0
  33. mcp_proxy_adapter/cli/commands/__init__.py +15 -0
  34. mcp_proxy_adapter/cli/commands/client.py +100 -0
  35. mcp_proxy_adapter/cli/commands/config_generate.py +35 -0
  36. mcp_proxy_adapter/cli/commands/config_validate.py +74 -0
  37. mcp_proxy_adapter/cli/commands/generate.py +259 -0
  38. mcp_proxy_adapter/cli/commands/server.py +174 -0
  39. mcp_proxy_adapter/cli/commands/sets.py +128 -0
  40. mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
  41. mcp_proxy_adapter/cli/examples/__init__.py +8 -0
  42. mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
  43. mcp_proxy_adapter/cli/examples/https_token.py +96 -0
  44. mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
  45. mcp_proxy_adapter/cli/main.py +63 -0
  46. mcp_proxy_adapter/cli/parser.py +338 -0
  47. mcp_proxy_adapter/cli/validators.py +231 -0
  48. mcp_proxy_adapter/client/jsonrpc_client/__init__.py +9 -0
  49. mcp_proxy_adapter/client/jsonrpc_client/client.py +42 -0
  50. mcp_proxy_adapter/client/jsonrpc_client/command_api.py +45 -0
  51. mcp_proxy_adapter/client/jsonrpc_client/proxy_api.py +224 -0
  52. mcp_proxy_adapter/client/jsonrpc_client/queue_api.py +60 -0
  53. mcp_proxy_adapter/client/jsonrpc_client/transport.py +108 -0
  54. mcp_proxy_adapter/client/proxy.py +123 -0
  55. mcp_proxy_adapter/commands/__init__.py +66 -0
  56. mcp_proxy_adapter/commands/auth_validation_command.py +69 -0
  57. mcp_proxy_adapter/commands/base.py +389 -0
  58. mcp_proxy_adapter/commands/builtin_commands.py +30 -0
  59. mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
  60. mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
  61. mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
  62. mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
  63. mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
  64. mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
  65. mcp_proxy_adapter/commands/catalog_manager.py +97 -0
  66. mcp_proxy_adapter/commands/cert_monitor_command.py +552 -0
  67. mcp_proxy_adapter/commands/certificate_management_command.py +562 -0
  68. mcp_proxy_adapter/commands/command_registry.py +298 -0
  69. mcp_proxy_adapter/commands/config_command.py +102 -0
  70. mcp_proxy_adapter/commands/dependency_container.py +40 -0
  71. mcp_proxy_adapter/commands/dependency_manager.py +143 -0
  72. mcp_proxy_adapter/commands/echo_command.py +48 -0
  73. mcp_proxy_adapter/commands/health_command.py +142 -0
  74. mcp_proxy_adapter/commands/help_command.py +175 -0
  75. mcp_proxy_adapter/commands/hooks.py +172 -0
  76. mcp_proxy_adapter/commands/key_management_command.py +484 -0
  77. mcp_proxy_adapter/commands/load_command.py +123 -0
  78. mcp_proxy_adapter/commands/plugins_command.py +246 -0
  79. mcp_proxy_adapter/commands/protocol_management_command.py +216 -0
  80. mcp_proxy_adapter/commands/proxy_registration_command.py +319 -0
  81. mcp_proxy_adapter/commands/queue_commands.py +750 -0
  82. mcp_proxy_adapter/commands/registration_status_command.py +76 -0
  83. mcp_proxy_adapter/commands/registry/__init__.py +18 -0
  84. mcp_proxy_adapter/commands/registry/command_info.py +103 -0
  85. mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
  86. mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
  87. mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
  88. mcp_proxy_adapter/commands/reload_command.py +136 -0
  89. mcp_proxy_adapter/commands/result.py +157 -0
  90. mcp_proxy_adapter/commands/role_test_command.py +99 -0
  91. mcp_proxy_adapter/commands/roles_management_command.py +502 -0
  92. mcp_proxy_adapter/commands/security_command.py +472 -0
  93. mcp_proxy_adapter/commands/settings_command.py +113 -0
  94. mcp_proxy_adapter/commands/ssl_setup_command.py +306 -0
  95. mcp_proxy_adapter/commands/token_management_command.py +500 -0
  96. mcp_proxy_adapter/commands/transport_management_command.py +129 -0
  97. mcp_proxy_adapter/commands/unload_command.py +92 -0
  98. mcp_proxy_adapter/config.py +32 -0
  99. mcp_proxy_adapter/core/__init__.py +8 -0
  100. mcp_proxy_adapter/core/app_factory.py +560 -0
  101. mcp_proxy_adapter/core/app_runner.py +318 -0
  102. mcp_proxy_adapter/core/auth_validator.py +508 -0
  103. mcp_proxy_adapter/core/certificate/__init__.py +20 -0
  104. mcp_proxy_adapter/core/certificate/certificate_creator.py +372 -0
  105. mcp_proxy_adapter/core/certificate/certificate_extractor.py +185 -0
  106. mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
  107. mcp_proxy_adapter/core/certificate/certificate_validator.py +388 -0
  108. mcp_proxy_adapter/core/certificate/ssl_context_manager.py +65 -0
  109. mcp_proxy_adapter/core/certificate_utils.py +249 -0
  110. mcp_proxy_adapter/core/client.py +608 -0
  111. mcp_proxy_adapter/core/client_manager.py +271 -0
  112. mcp_proxy_adapter/core/client_security.py +411 -0
  113. mcp_proxy_adapter/core/config/__init__.py +18 -0
  114. mcp_proxy_adapter/core/config/config.py +237 -0
  115. mcp_proxy_adapter/core/config/config_factory.py +22 -0
  116. mcp_proxy_adapter/core/config/config_loader.py +66 -0
  117. mcp_proxy_adapter/core/config/feature_manager.py +31 -0
  118. mcp_proxy_adapter/core/config/simple_config.py +116 -0
  119. mcp_proxy_adapter/core/config/simple_config_generator.py +100 -0
  120. mcp_proxy_adapter/core/config/simple_config_validator.py +380 -0
  121. mcp_proxy_adapter/core/config_converter.py +252 -0
  122. mcp_proxy_adapter/core/config_validator.py +211 -0
  123. mcp_proxy_adapter/core/crl_utils.py +362 -0
  124. mcp_proxy_adapter/core/errors.py +276 -0
  125. mcp_proxy_adapter/core/job_manager.py +54 -0
  126. mcp_proxy_adapter/core/logging.py +250 -0
  127. mcp_proxy_adapter/core/mtls_asgi.py +140 -0
  128. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  129. mcp_proxy_adapter/core/mtls_proxy.py +229 -0
  130. mcp_proxy_adapter/core/mtls_server.py +154 -0
  131. mcp_proxy_adapter/core/protocol_manager.py +232 -0
  132. mcp_proxy_adapter/core/proxy/__init__.py +19 -0
  133. mcp_proxy_adapter/core/proxy/auth_manager.py +26 -0
  134. mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +160 -0
  135. mcp_proxy_adapter/core/proxy/registration_client.py +186 -0
  136. mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
  137. mcp_proxy_adapter/core/proxy_client.py +184 -0
  138. mcp_proxy_adapter/core/proxy_registration.py +80 -0
  139. mcp_proxy_adapter/core/role_utils.py +103 -0
  140. mcp_proxy_adapter/core/security_adapter.py +343 -0
  141. mcp_proxy_adapter/core/security_factory.py +96 -0
  142. mcp_proxy_adapter/core/security_integration.py +342 -0
  143. mcp_proxy_adapter/core/server_adapter.py +251 -0
  144. mcp_proxy_adapter/core/server_engine.py +217 -0
  145. mcp_proxy_adapter/core/settings.py +260 -0
  146. mcp_proxy_adapter/core/signal_handler.py +107 -0
  147. mcp_proxy_adapter/core/ssl_utils.py +161 -0
  148. mcp_proxy_adapter/core/transport_manager.py +153 -0
  149. mcp_proxy_adapter/core/unified_config_adapter.py +471 -0
  150. mcp_proxy_adapter/core/utils.py +101 -0
  151. mcp_proxy_adapter/core/validation/__init__.py +21 -0
  152. mcp_proxy_adapter/core/validation/config_validator.py +219 -0
  153. mcp_proxy_adapter/core/validation/file_validator.py +131 -0
  154. mcp_proxy_adapter/core/validation/protocol_validator.py +190 -0
  155. mcp_proxy_adapter/core/validation/security_validator.py +140 -0
  156. mcp_proxy_adapter/core/validation/validation_result.py +27 -0
  157. mcp_proxy_adapter/custom_openapi.py +58 -0
  158. mcp_proxy_adapter/examples/__init__.py +16 -0
  159. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  160. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  161. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  162. mcp_proxy_adapter/examples/basic_framework/main.py +52 -0
  163. mcp_proxy_adapter/examples/bugfix_certificate_config.py +261 -0
  164. mcp_proxy_adapter/examples/cert_manager_bugfix.py +203 -0
  165. mcp_proxy_adapter/examples/check_config.py +413 -0
  166. mcp_proxy_adapter/examples/client_usage_example.py +164 -0
  167. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  168. mcp_proxy_adapter/examples/config_builder.py +234 -0
  169. mcp_proxy_adapter/examples/config_cli.py +282 -0
  170. mcp_proxy_adapter/examples/create_test_configs.py +174 -0
  171. mcp_proxy_adapter/examples/debug_request_state.py +130 -0
  172. mcp_proxy_adapter/examples/debug_role_chain.py +191 -0
  173. mcp_proxy_adapter/examples/demo_client.py +287 -0
  174. mcp_proxy_adapter/examples/full_application/__init__.py +13 -0
  175. mcp_proxy_adapter/examples/full_application/commands/__init__.py +8 -0
  176. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +45 -0
  177. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +52 -0
  178. mcp_proxy_adapter/examples/full_application/commands/echo_command.py +32 -0
  179. mcp_proxy_adapter/examples/full_application/commands/help_command.py +54 -0
  180. mcp_proxy_adapter/examples/full_application/commands/list_command.py +57 -0
  181. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +5 -0
  182. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +29 -0
  183. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +27 -0
  184. mcp_proxy_adapter/examples/full_application/main.py +264 -0
  185. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +81 -0
  186. mcp_proxy_adapter/examples/full_application/run_mtls.py +252 -0
  187. mcp_proxy_adapter/examples/full_application/run_simple.py +152 -0
  188. mcp_proxy_adapter/examples/full_application/test_minimal_server.py +45 -0
  189. mcp_proxy_adapter/examples/full_application/test_server.py +163 -0
  190. mcp_proxy_adapter/examples/full_application/test_simple_server.py +62 -0
  191. mcp_proxy_adapter/examples/generate_config.py +502 -0
  192. mcp_proxy_adapter/examples/proxy_registration_example.py +335 -0
  193. mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
  194. mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
  195. mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
  196. mcp_proxy_adapter/examples/queue_server_example.py +85 -0
  197. mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
  198. mcp_proxy_adapter/examples/required_certificates.py +208 -0
  199. mcp_proxy_adapter/examples/run_example.py +77 -0
  200. mcp_proxy_adapter/examples/run_full_test_suite.py +619 -0
  201. mcp_proxy_adapter/examples/run_proxy_server.py +153 -0
  202. mcp_proxy_adapter/examples/run_security_tests_fixed.py +435 -0
  203. mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
  204. mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
  205. mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
  206. mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
  207. mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
  208. mcp_proxy_adapter/examples/security_test_client.py +72 -0
  209. mcp_proxy_adapter/examples/setup/__init__.py +24 -0
  210. mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
  211. mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
  212. mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
  213. mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
  214. mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
  215. mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
  216. mcp_proxy_adapter/examples/setup_test_environment.py +235 -0
  217. mcp_proxy_adapter/examples/simple_protocol_test.py +125 -0
  218. mcp_proxy_adapter/examples/test_chk_hostname_automated.py +211 -0
  219. mcp_proxy_adapter/examples/test_config.py +205 -0
  220. mcp_proxy_adapter/examples/test_config_builder.py +110 -0
  221. mcp_proxy_adapter/examples/test_examples.py +308 -0
  222. mcp_proxy_adapter/examples/test_framework_complete.py +267 -0
  223. mcp_proxy_adapter/examples/test_mcp_server.py +187 -0
  224. mcp_proxy_adapter/examples/test_protocol_examples.py +337 -0
  225. mcp_proxy_adapter/examples/universal_client.py +674 -0
  226. mcp_proxy_adapter/examples/update_config_certificates.py +135 -0
  227. mcp_proxy_adapter/examples/validate_generator_compatibility.py +385 -0
  228. mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +61 -0
  229. mcp_proxy_adapter/integrations/__init__.py +25 -0
  230. mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
  231. mcp_proxy_adapter/main.py +313 -0
  232. mcp_proxy_adapter/openapi.py +375 -0
  233. mcp_proxy_adapter/schemas/base_schema.json +114 -0
  234. mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
  235. mcp_proxy_adapter/schemas/roles.json +37 -0
  236. mcp_proxy_adapter/schemas/roles_schema.json +162 -0
  237. mcp_proxy_adapter/version.py +5 -0
  238. mcp_proxy_adapter-6.9.43.dist-info/METADATA +739 -0
  239. mcp_proxy_adapter-6.9.43.dist-info/RECORD +242 -0
  240. mcp_proxy_adapter-6.9.43.dist-info/WHEEL +5 -0
  241. mcp_proxy_adapter-6.9.43.dist-info/entry_points.txt +12 -0
  242. mcp_proxy_adapter-6.9.43.dist-info/top_level.txt +1 -0
@@ -0,0 +1,211 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Main configuration validator for MCP Proxy Adapter.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Dict, List, Any, Optional
12
+
13
+ from .file_validator import FileValidator
14
+ from .security_validator import SecurityValidator
15
+ from .protocol_validator import ProtocolValidator
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class ConfigValidator:
21
+ """
22
+ Comprehensive configuration validator for MCP Proxy Adapter.
23
+
24
+ Validates:
25
+ - Required sections and keys
26
+ - File existence for referenced files
27
+ - Feature flag dependencies
28
+ - Protocol-specific requirements
29
+ - Security configuration consistency
30
+ """
31
+
32
+ def __init__(self, config_path: Optional[str] = None):
33
+ """
34
+ Initialize configuration validator.
35
+
36
+ Args:
37
+ config_path: Path to configuration file (optional)
38
+ """
39
+ self.config_path = config_path
40
+ self.config_data: Dict[str, Any] = {}
41
+ self.validation_results: List[ValidationResult] = []
42
+
43
+ def load_config(self, config_path: Optional[str] = None) -> None:
44
+ """
45
+ Load configuration from file.
46
+
47
+ Args:
48
+ config_path: Path to configuration file
49
+ """
50
+ if config_path:
51
+ self.config_path = config_path
52
+
53
+ if not self.config_path:
54
+ raise ValueError("No configuration path provided")
55
+
56
+ try:
57
+ with open(self.config_path, 'r', encoding='utf-8') as f:
58
+ self.config_data = json.load(f)
59
+ logger.info(f"Configuration loaded from {self.config_path}")
60
+ except FileNotFoundError:
61
+ raise FileNotFoundError(f"Configuration file not found: {self.config_path}")
62
+ except json.JSONDecodeError as e:
63
+ raise ValueError(f"Invalid JSON in configuration file: {e}")
64
+ except Exception as e:
65
+ raise RuntimeError(f"Error loading configuration: {e}")
66
+
67
+ def validate_config(self, config_data: Optional[Dict[str, Any]] = None) -> List[ValidationResult]:
68
+ """
69
+ Validate configuration data.
70
+
71
+ Args:
72
+ config_data: Configuration data to validate (optional)
73
+
74
+ Returns:
75
+ List of validation results
76
+ """
77
+ if config_data is not None:
78
+ self.config_data = config_data
79
+
80
+ if not self.config_data:
81
+ raise ValueError("No configuration data to validate")
82
+
83
+ self.validation_results = []
84
+
85
+ # Initialize validators
86
+ file_validator = FileValidator(self.config_data)
87
+ security_validator = SecurityValidator(self.config_data)
88
+ protocol_validator = ProtocolValidator(self.config_data)
89
+
90
+ # Run all validations
91
+ self.validation_results.extend(protocol_validator.validate_required_sections())
92
+ self.validation_results.extend(protocol_validator.validate_protocol_requirements())
93
+ self.validation_results.extend(file_validator.validate_file_existence())
94
+ self.validation_results.extend(security_validator.validate_security_consistency())
95
+ self.validation_results.extend(security_validator.validate_ssl_configuration())
96
+ self.validation_results.extend(security_validator.validate_roles_configuration())
97
+ self.validation_results.extend(security_validator.validate_proxy_registration())
98
+
99
+ # Additional validations
100
+ self._validate_unknown_fields()
101
+ self._validate_uuid_format()
102
+
103
+ return self.validation_results
104
+
105
+ def validate_all(self, config_data: Optional[Dict[str, Any]] = None) -> List[ValidationResult]:
106
+ """
107
+ Validate all aspects of the configuration.
108
+
109
+ Args:
110
+ config_data: Configuration data to validate (optional)
111
+
112
+ Returns:
113
+ List of validation results
114
+ """
115
+ return self.validate_config(config_data)
116
+
117
+ def _validate_unknown_fields(self) -> None:
118
+ """Validate for unknown configuration fields."""
119
+ known_sections = {
120
+ "server", "protocols", "security", "ssl", "auth", "roles",
121
+ "logging", "commands", "proxy_registration", "transport"
122
+ }
123
+
124
+ for section in self.config_data.keys():
125
+ if section not in known_sections:
126
+ self.validation_results.append(ValidationResult(
127
+ level="warning",
128
+ message=f"Unknown configuration section: {section}",
129
+ section=section,
130
+ suggestion="Check if this section is needed or if it's a typo"
131
+ ))
132
+
133
+ def _validate_uuid_format(self) -> None:
134
+ """Validate UUID format in configuration."""
135
+ uuid_fields = ["server.server_id", "proxy_registration.server_id"]
136
+
137
+ for field in uuid_fields:
138
+ value = self._get_nested_value_safe(field)
139
+ if value and not self._is_valid_uuid4(str(value)):
140
+ self.validation_results.append(ValidationResult(
141
+ level="warning",
142
+ message=f"Invalid UUID format in {field}: {value}",
143
+ section=field.split(".")[0],
144
+ key=field.split(".")[1],
145
+ suggestion="Use a valid UUID4 format"
146
+ ))
147
+
148
+ def _is_valid_uuid4(self, uuid_str: str) -> bool:
149
+ """Check if string is a valid UUID4."""
150
+ import re
151
+ uuid_pattern = r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
152
+ return bool(re.match(uuid_pattern, uuid_str, re.IGNORECASE))
153
+
154
+ def _get_nested_value_safe(self, key: str, default: Any = None) -> Any:
155
+ """Safely get a nested value from configuration."""
156
+ keys = key.split('.')
157
+ value = self.config_data
158
+
159
+ for k in keys:
160
+ if isinstance(value, dict) and k in value:
161
+ value = value[k]
162
+ else:
163
+ return default
164
+
165
+ return value
166
+
167
+ def get_validation_summary(self) -> Dict[str, Any]:
168
+ """
169
+ Get a summary of validation results.
170
+
171
+ Returns:
172
+ Dictionary with validation summary
173
+ """
174
+ error_count = sum(1 for r in self.validation_results if r.level == "error")
175
+ warning_count = sum(1 for r in self.validation_results if r.level == "warning")
176
+ info_count = sum(1 for r in self.validation_results if r.level == "info")
177
+
178
+ return {
179
+ "total_issues": len(self.validation_results),
180
+ "errors": error_count,
181
+ "warnings": warning_count,
182
+ "info": info_count,
183
+ "is_valid": error_count == 0
184
+ }
185
+
186
+ def print_validation_report(self) -> None:
187
+ """Print a formatted validation report."""
188
+ summary = self.get_validation_summary()
189
+
190
+ print(f"\\n📋 Configuration Validation Report")
191
+ print(f"{'=' * 40}")
192
+ print(f"Total issues: {summary['total_issues']}")
193
+ print(f"Errors: {summary['errors']}")
194
+ print(f"Warnings: {summary['warnings']}")
195
+ print(f"Info: {summary['info']}")
196
+ print(f"Valid: {'✅ Yes' if summary['is_valid'] else '❌ No'}")
197
+
198
+ if self.validation_results:
199
+ print(f"\\n📝 Issues:")
200
+ for i, result in enumerate(self.validation_results, 1):
201
+ level_icon = {"error": "❌", "warning": "⚠️", "info": "ℹ️"}[result.level]
202
+ print(f"{i:2d}. {level_icon} {result.message}")
203
+ if result.section:
204
+ print(f" Section: {result.section}")
205
+ if result.key:
206
+ print(f" Key: {result.key}")
207
+ if result.suggestion:
208
+ print(f" Suggestion: {result.suggestion}")
209
+ print()
210
+
211
+
@@ -0,0 +1,362 @@
1
+ """
2
+ CRL Utilities Module
3
+
4
+ This module provides utilities for working with Certificate Revocation Lists (CRL).
5
+ Supports both file-based and URL-based CRL sources.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ Version: 1.0.0
10
+ """
11
+
12
+ import logging
13
+ import os
14
+ import tempfile
15
+ from pathlib import Path
16
+ from typing import Optional, Union, Dict, Any
17
+ import requests
18
+ from requests.adapters import HTTPAdapter
19
+ from urllib3.util.retry import Retry
20
+
21
+ # Import mcp_security_framework CRL utilities
22
+ try:
23
+ from mcp_security_framework.utils.cert_utils import (
24
+ is_certificate_revoked,
25
+ validate_certificate_against_crl,
26
+ is_crl_valid,
27
+ get_crl_info,
28
+ )
29
+
30
+ SECURITY_FRAMEWORK_AVAILABLE = True
31
+ except ImportError:
32
+ SECURITY_FRAMEWORK_AVAILABLE = False
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ class CRLManager:
38
+ """
39
+ Manager for Certificate Revocation Lists (CRL).
40
+
41
+ Supports both file-based and URL-based CRL sources.
42
+ Automatically downloads CRL from URLs and caches them locally.
43
+ """
44
+
45
+ def __init__(self, config: Dict[str, Any]):
46
+ """
47
+ Initialize CRL manager.
48
+
49
+ Args:
50
+ config: Configuration dictionary containing CRL settings
51
+ """
52
+ self.config = config
53
+ self.crl_enabled = config.get("crl_enabled", False)
54
+
55
+ # Only analyze CRL paths if certificates are enabled
56
+ certificates_enabled = config.get("certificates_enabled", True)
57
+ if certificates_enabled and self.crl_enabled:
58
+ self.crl_path = config.get("crl_path")
59
+ self.crl_url = config.get("crl_url")
60
+ self.crl_validity_days = config.get("crl_validity_days", 30)
61
+ else:
62
+ # Don't analyze CRL paths if certificates are disabled
63
+ self.crl_path = None
64
+ self.crl_url = None
65
+ self.crl_validity_days = 30
66
+
67
+ # Cache for downloaded CRL files
68
+ self._crl_cache: Dict[str, str] = {}
69
+
70
+ # Setup HTTP session with retry strategy
71
+ self._setup_http_session()
72
+
73
+ get_global_logger().info(
74
+ f"CRL Manager initialized - enabled: {self.crl_enabled}, certificates_enabled: {certificates_enabled}"
75
+ )
76
+
77
+ def _setup_http_session(self):
78
+ """Setup HTTP session with retry strategy for CRL downloads."""
79
+ self.session = requests.Session()
80
+
81
+ # Configure retry strategy
82
+ retry_strategy = Retry(
83
+ total=3,
84
+ backoff_factor=1,
85
+ status_forcelist=[429, 500, 502, 503, 504],
86
+ )
87
+
88
+ adapter = HTTPAdapter(max_retries=retry_strategy)
89
+ self.session.mount("http://", adapter)
90
+ self.session.mount("https://", adapter)
91
+
92
+ # Set timeout
93
+ self.session.timeout = 30
94
+
95
+ def get_crl_data(self) -> Optional[Union[str, bytes, Path]]:
96
+ """
97
+ Get CRL data from configured source.
98
+
99
+ Returns:
100
+ CRL data as string, bytes, or Path, or None if not available
101
+
102
+ Raises:
103
+ ValueError: If CRL is enabled but no source is configured
104
+ FileNotFoundError: If CRL file is not found
105
+ requests.RequestException: If CRL download fails
106
+ """
107
+ if not self.crl_enabled:
108
+ get_global_logger().debug("CRL is disabled, skipping CRL check")
109
+ return None
110
+
111
+ # Check if CRL URL is configured
112
+ if self.crl_url:
113
+ return self._get_crl_from_url()
114
+
115
+ # Check if CRL file path is configured
116
+ if self.crl_path:
117
+ return self._get_crl_from_file()
118
+
119
+ # If CRL is enabled but no source is configured, this is an error
120
+ if self.crl_enabled:
121
+ raise ValueError(
122
+ "CRL is enabled but neither crl_path nor crl_url is configured"
123
+ )
124
+
125
+ return None
126
+
127
+ def _get_crl_from_url(self) -> str:
128
+ """
129
+ Download CRL from URL.
130
+
131
+ Returns:
132
+ Path to downloaded CRL file
133
+
134
+ Raises:
135
+ requests.RequestException: If download fails
136
+ ValueError: If downloaded data is not valid CRL
137
+ """
138
+ try:
139
+ get_global_logger().info(f"Downloading CRL from URL: {self.crl_url}")
140
+
141
+ # Download CRL
142
+ response = self.session.get(self.crl_url)
143
+ response.raise_for_status()
144
+
145
+ # Validate content type
146
+ content_type = response.headers.get("content-type", "").lower()
147
+ if (
148
+ "application/pkix-crl" not in content_type
149
+ and "application/x-pkcs7-crl" not in content_type
150
+ ):
151
+ get_global_logger().warning(f"Unexpected content type for CRL: {content_type}")
152
+
153
+ # Save to temporary file
154
+ with tempfile.NamedTemporaryFile(
155
+ mode="wb", suffix=".crl", delete=False
156
+ ) as temp_file:
157
+ temp_file.write(response.content)
158
+ temp_file_path = temp_file.name
159
+
160
+ # Validate CRL format
161
+ if SECURITY_FRAMEWORK_AVAILABLE:
162
+ try:
163
+ is_crl_valid(temp_file_path)
164
+ get_global_logger().info(
165
+ f"CRL downloaded and validated successfully from {self.crl_url}"
166
+ )
167
+ except Exception as e:
168
+ os.unlink(temp_file_path)
169
+ raise ValueError(f"Downloaded CRL is not valid: {e}")
170
+ else:
171
+ get_global_logger().warning(
172
+ "mcp_security_framework not available, skipping CRL validation"
173
+ )
174
+
175
+ # Cache the file path
176
+ self._crl_cache[self.crl_url] = temp_file_path
177
+
178
+ return temp_file_path
179
+
180
+ except requests.RequestException as e:
181
+ get_global_logger().error(f"Failed to download CRL from {self.crl_url}: {e}")
182
+ raise
183
+ except Exception as e:
184
+ get_global_logger().error(f"CRL download failed: {e}")
185
+ raise
186
+
187
+ def _get_crl_from_file(self) -> str:
188
+ """
189
+ Get CRL from file path.
190
+
191
+ Returns:
192
+ Path to CRL file
193
+
194
+ Raises:
195
+ FileNotFoundError: If CRL file is not found
196
+ ValueError: If CRL file is not valid
197
+ """
198
+ if not os.path.exists(self.crl_path):
199
+ raise FileNotFoundError(f"CRL file not found: {self.crl_path}")
200
+
201
+ # Validate CRL format
202
+ if SECURITY_FRAMEWORK_AVAILABLE:
203
+ try:
204
+ is_crl_valid(self.crl_path)
205
+ get_global_logger().info(f"CRL file validated successfully: {self.crl_path}")
206
+ except Exception as e:
207
+ raise ValueError(f"CRL file is not valid: {e}")
208
+ else:
209
+ get_global_logger().warning(
210
+ "mcp_security_framework not available, skipping CRL validation"
211
+ )
212
+
213
+ return self.crl_path
214
+
215
+ def is_certificate_revoked(self, cert_path: str) -> bool:
216
+ """
217
+ Check if certificate is revoked according to CRL.
218
+
219
+ Args:
220
+ cert_path: Path to certificate file
221
+
222
+ Returns:
223
+ True if certificate is revoked, False otherwise
224
+
225
+ Raises:
226
+ ValueError: If CRL is enabled but not available
227
+ FileNotFoundError: If certificate file is not found
228
+ """
229
+ if not self.crl_enabled:
230
+ return False
231
+
232
+ if not SECURITY_FRAMEWORK_AVAILABLE:
233
+ get_global_logger().warning("mcp_security_framework not available, skipping CRL check")
234
+ return False
235
+
236
+ try:
237
+ crl_data = self.get_crl_data()
238
+ if not crl_data:
239
+ get_global_logger().warning("CRL is enabled but no CRL data is available")
240
+ return False
241
+
242
+ is_revoked = is_certificate_revoked(cert_path, crl_data)
243
+
244
+ if is_revoked:
245
+ get_global_logger().warning(f"Certificate is revoked according to CRL: {cert_path}")
246
+ else:
247
+ get_global_logger().debug(
248
+ f"Certificate is not revoked according to CRL: {cert_path}"
249
+ )
250
+
251
+ return is_revoked
252
+
253
+ except Exception as e:
254
+ get_global_logger().error(f"CRL check failed for certificate {cert_path}: {e}")
255
+ # For security, consider certificate invalid if CRL check fails
256
+ return True
257
+
258
+ def validate_certificate_against_crl(self, cert_path: str) -> Dict[str, Any]:
259
+ """
260
+ Validate certificate against CRL and return detailed status.
261
+
262
+ Args:
263
+ cert_path: Path to certificate file
264
+
265
+ Returns:
266
+ Dictionary containing validation results
267
+
268
+ Raises:
269
+ ValueError: If CRL is enabled but not available
270
+ FileNotFoundError: If certificate file is not found
271
+ """
272
+ if not self.crl_enabled:
273
+ return {
274
+ "is_revoked": False,
275
+ "crl_checked": False,
276
+ "crl_source": None,
277
+ "message": "CRL check is disabled",
278
+ }
279
+
280
+ if not SECURITY_FRAMEWORK_AVAILABLE:
281
+ get_global_logger().warning(
282
+ "mcp_security_framework not available, skipping CRL validation"
283
+ )
284
+ return {
285
+ "is_revoked": False,
286
+ "crl_checked": False,
287
+ "crl_source": None,
288
+ "message": "mcp_security_framework not available",
289
+ }
290
+
291
+ try:
292
+ crl_data = self.get_crl_data()
293
+ if not crl_data:
294
+ get_global_logger().warning("CRL is enabled but no CRL data is available")
295
+ return {
296
+ "is_revoked": True, # For security, consider invalid if CRL unavailable
297
+ "crl_checked": False,
298
+ "crl_source": None,
299
+ "message": "CRL is enabled but not available",
300
+ }
301
+
302
+ # Get CRL source info
303
+ crl_source = self.crl_url if self.crl_url else self.crl_path
304
+
305
+ # Validate certificate against CRL
306
+ result = validate_certificate_against_crl(cert_path, crl_data)
307
+
308
+ result["crl_checked"] = True
309
+ result["crl_source"] = crl_source
310
+
311
+ return result
312
+
313
+ except Exception as e:
314
+ get_global_logger().error(f"CRL validation failed for certificate {cert_path}: {e}")
315
+ # For security, consider certificate invalid if CRL validation fails
316
+ return {
317
+ "is_revoked": True,
318
+ "crl_checked": False,
319
+ "crl_source": self.crl_url if self.crl_url else self.crl_path,
320
+ "message": f"CRL validation failed: {e}",
321
+ }
322
+
323
+ def get_crl_info(self) -> Optional[Dict[str, Any]]:
324
+ """
325
+ Get information about the configured CRL.
326
+
327
+ Returns:
328
+ Dictionary containing CRL information, or None if CRL is not available
329
+ """
330
+ if not self.crl_enabled:
331
+ return None
332
+
333
+ if not SECURITY_FRAMEWORK_AVAILABLE:
334
+ get_global_logger().warning("mcp_security_framework not available, cannot get CRL info")
335
+ return None
336
+
337
+ try:
338
+ crl_data = self.get_crl_data()
339
+ if not crl_data:
340
+ return None
341
+
342
+ return get_crl_info(crl_data)
343
+
344
+ except Exception as e:
345
+ get_global_logger().error(f"Failed to get CRL info: {e}")
346
+ return None
347
+
348
+ def cleanup_cache(self):
349
+ """Clean up temporary CRL files."""
350
+ for url, temp_path in self._crl_cache.items():
351
+ try:
352
+ if os.path.exists(temp_path):
353
+ os.unlink(temp_path)
354
+ get_global_logger().debug(f"Cleaned up temporary CRL file: {temp_path}")
355
+ except Exception as e:
356
+ get_global_logger().warning(f"Failed to cleanup temporary CRL file {temp_path}: {e}")
357
+
358
+ self._crl_cache.clear()
359
+
360
+ def __del__(self):
361
+ """Cleanup when object is destroyed."""
362
+ self.cleanup_cache()