mcp-proxy-adapter 4.1.0__py3-none-any.whl → 6.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_proxy_adapter/__main__.py +12 -0
- mcp_proxy_adapter/api/app.py +138 -11
- mcp_proxy_adapter/api/handlers.py +16 -1
- mcp_proxy_adapter/api/middleware/__init__.py +30 -29
- mcp_proxy_adapter/api/middleware/auth_adapter.py +235 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
- mcp_proxy_adapter/api/middleware/factory.py +219 -0
- mcp_proxy_adapter/api/middleware/logging.py +32 -6
- mcp_proxy_adapter/api/middleware/mtls_adapter.py +305 -0
- mcp_proxy_adapter/api/middleware/mtls_middleware.py +296 -0
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
- mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +241 -0
- mcp_proxy_adapter/api/middleware/roles_adapter.py +365 -0
- mcp_proxy_adapter/api/middleware/roles_middleware.py +381 -0
- mcp_proxy_adapter/api/middleware/security.py +376 -0
- mcp_proxy_adapter/api/middleware/token_auth_middleware.py +261 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/commands/__init__.py +13 -4
- mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
- mcp_proxy_adapter/commands/base.py +61 -30
- mcp_proxy_adapter/commands/builtin_commands.py +89 -0
- mcp_proxy_adapter/commands/catalog_manager.py +838 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
- mcp_proxy_adapter/commands/command_registry.py +705 -345
- mcp_proxy_adapter/commands/dependency_manager.py +245 -0
- mcp_proxy_adapter/commands/health_command.py +7 -0
- mcp_proxy_adapter/commands/hooks.py +200 -167
- mcp_proxy_adapter/commands/key_management_command.py +506 -0
- mcp_proxy_adapter/commands/load_command.py +176 -0
- mcp_proxy_adapter/commands/plugins_command.py +235 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +268 -0
- mcp_proxy_adapter/commands/reload_command.py +48 -50
- mcp_proxy_adapter/commands/result.py +1 -0
- mcp_proxy_adapter/commands/roles_management_command.py +697 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
- mcp_proxy_adapter/commands/token_management_command.py +529 -0
- mcp_proxy_adapter/commands/transport_management_command.py +144 -0
- mcp_proxy_adapter/commands/unload_command.py +158 -0
- mcp_proxy_adapter/config.py +99 -2
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +827 -0
- mcp_proxy_adapter/core/config_converter.py +405 -0
- mcp_proxy_adapter/core/config_validator.py +218 -0
- mcp_proxy_adapter/core/logging.py +11 -0
- mcp_proxy_adapter/core/protocol_manager.py +226 -0
- mcp_proxy_adapter/core/proxy_registration.py +270 -0
- mcp_proxy_adapter/core/role_utils.py +426 -0
- mcp_proxy_adapter/core/security_adapter.py +373 -0
- mcp_proxy_adapter/core/security_factory.py +239 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +233 -0
- mcp_proxy_adapter/core/transport_manager.py +292 -0
- mcp_proxy_adapter/custom_openapi.py +22 -11
- mcp_proxy_adapter/examples/basic_server/config.json +58 -23
- mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +54 -0
- mcp_proxy_adapter/examples/basic_server/config_http.json +70 -0
- mcp_proxy_adapter/examples/basic_server/config_http_only.json +52 -0
- mcp_proxy_adapter/examples/basic_server/config_https.json +58 -0
- mcp_proxy_adapter/examples/basic_server/config_mtls.json +58 -0
- mcp_proxy_adapter/examples/basic_server/config_ssl.json +46 -0
- mcp_proxy_adapter/examples/basic_server/server.py +17 -1
- mcp_proxy_adapter/examples/custom_commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +339 -23
- mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +105 -0
- mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +129 -0
- mcp_proxy_adapter/examples/custom_commands/config.json +97 -41
- mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_https_only.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/full_help_response.json +1 -0
- mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +629 -0
- mcp_proxy_adapter/examples/custom_commands/get_openapi.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +129 -0
- mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +278 -0
- mcp_proxy_adapter/examples/custom_commands/server.py +92 -63
- mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +75 -0
- mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +299 -0
- mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +278 -0
- mcp_proxy_adapter/examples/custom_commands/test_openapi.py +27 -0
- mcp_proxy_adapter/examples/custom_commands/test_registry.py +23 -0
- mcp_proxy_adapter/examples/custom_commands/test_simple.py +19 -0
- mcp_proxy_adapter/examples/custom_project_example/README.md +103 -0
- mcp_proxy_adapter/examples/custom_project_example/README_EN.md +103 -0
- mcp_proxy_adapter/examples/simple_custom_commands/README.md +149 -0
- mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +149 -0
- mcp_proxy_adapter/main.py +175 -0
- mcp_proxy_adapter/schemas/roles_schema.json +162 -0
- mcp_proxy_adapter/tests/unit/test_config.py +53 -0
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.0.0.dist-info/RECORD +179 -0
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- mcp_proxy_adapter-4.1.0.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,373 @@
|
|
1
|
+
"""
|
2
|
+
Security Adapter for mcp_security_framework integration.
|
3
|
+
|
4
|
+
This module provides an adapter layer between mcp_proxy_adapter and mcp_security_framework,
|
5
|
+
handling configuration conversion and request validation.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
import logging
|
10
|
+
from typing import Dict, Any, List, Optional
|
11
|
+
from pathlib import Path
|
12
|
+
|
13
|
+
# Import mcp_security_framework components
|
14
|
+
try:
|
15
|
+
from mcp_security_framework import SecurityManager, SecurityConfig
|
16
|
+
from mcp_security_framework.schemas.config import (
|
17
|
+
AuthConfig, SSLConfig, PermissionConfig, RateLimitConfig
|
18
|
+
)
|
19
|
+
from mcp_security_framework.schemas.request import SecurityRequest
|
20
|
+
from mcp_security_framework.schemas.response import SecurityResult
|
21
|
+
SECURITY_FRAMEWORK_AVAILABLE = True
|
22
|
+
except ImportError:
|
23
|
+
# Fallback for when mcp_security_framework is not available
|
24
|
+
SECURITY_FRAMEWORK_AVAILABLE = False
|
25
|
+
SecurityManager = None
|
26
|
+
SecurityConfig = None
|
27
|
+
AuthConfig = None
|
28
|
+
SSLConfig = None
|
29
|
+
PermissionConfig = None
|
30
|
+
RateLimitConfig = None
|
31
|
+
SecurityRequest = None
|
32
|
+
SecurityResult = None
|
33
|
+
|
34
|
+
from mcp_proxy_adapter.core.logging import logger
|
35
|
+
|
36
|
+
|
37
|
+
class SecurityAdapter:
|
38
|
+
"""
|
39
|
+
Adapter for integrating with mcp_security_framework.
|
40
|
+
|
41
|
+
Provides methods to convert mcp_proxy_adapter configuration to SecurityConfig
|
42
|
+
and handle request validation through the security framework.
|
43
|
+
"""
|
44
|
+
|
45
|
+
def __init__(self, config: Dict[str, Any]):
|
46
|
+
"""
|
47
|
+
Initialize security adapter.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
config: mcp_proxy_adapter configuration dictionary
|
51
|
+
"""
|
52
|
+
self.config = config
|
53
|
+
self.security_manager = None
|
54
|
+
|
55
|
+
if SECURITY_FRAMEWORK_AVAILABLE:
|
56
|
+
self.security_manager = self._create_security_manager()
|
57
|
+
logger.info("Security adapter initialized with mcp_security_framework")
|
58
|
+
else:
|
59
|
+
logger.warning("mcp_security_framework not available, using fallback mode")
|
60
|
+
|
61
|
+
def _create_security_manager(self) -> Optional[SecurityManager]:
|
62
|
+
"""
|
63
|
+
Create SecurityManager from mcp_proxy_adapter configuration.
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
SecurityManager instance or None if framework not available
|
67
|
+
"""
|
68
|
+
if not SECURITY_FRAMEWORK_AVAILABLE:
|
69
|
+
return None
|
70
|
+
|
71
|
+
try:
|
72
|
+
security_config = self._convert_config()
|
73
|
+
return SecurityManager(security_config)
|
74
|
+
except Exception as e:
|
75
|
+
logger.error(f"Failed to create SecurityManager: {e}")
|
76
|
+
return None
|
77
|
+
|
78
|
+
def _convert_config(self) -> SecurityConfig:
|
79
|
+
"""
|
80
|
+
Convert mcp_proxy_adapter configuration to SecurityConfig.
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
SecurityConfig instance
|
84
|
+
"""
|
85
|
+
# Get security configuration section
|
86
|
+
security_config = self.config.get("security", {})
|
87
|
+
|
88
|
+
# Convert auth configuration
|
89
|
+
auth_config = self._convert_auth_config(security_config)
|
90
|
+
|
91
|
+
# Convert SSL configuration
|
92
|
+
ssl_config = self._convert_ssl_config(security_config)
|
93
|
+
|
94
|
+
# Convert permissions configuration
|
95
|
+
permission_config = self._convert_permission_config(security_config)
|
96
|
+
|
97
|
+
# Convert rate limit configuration
|
98
|
+
rate_limit_config = self._convert_rate_limit_config(security_config)
|
99
|
+
|
100
|
+
return SecurityConfig(
|
101
|
+
auth=auth_config,
|
102
|
+
ssl=ssl_config,
|
103
|
+
permissions=permission_config,
|
104
|
+
rate_limit=rate_limit_config
|
105
|
+
)
|
106
|
+
|
107
|
+
def _convert_auth_config(self, security_config: Dict[str, Any]) -> AuthConfig:
|
108
|
+
"""
|
109
|
+
Convert authentication configuration.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
security_config: Security configuration section
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
AuthConfig instance
|
116
|
+
"""
|
117
|
+
auth_config = security_config.get("auth", {})
|
118
|
+
|
119
|
+
# Get authentication methods
|
120
|
+
methods = auth_config.get("methods", ["api_key"])
|
121
|
+
|
122
|
+
# Get API keys from legacy config if not in security section
|
123
|
+
api_keys = auth_config.get("api_keys", {})
|
124
|
+
if not api_keys:
|
125
|
+
# Try to get from legacy SSL config
|
126
|
+
legacy_ssl = self.config.get("ssl", {})
|
127
|
+
if "api_keys" in legacy_ssl:
|
128
|
+
api_keys = legacy_ssl["api_keys"]
|
129
|
+
|
130
|
+
return AuthConfig(
|
131
|
+
enabled=auth_config.get("enabled", True),
|
132
|
+
methods=methods,
|
133
|
+
api_keys=api_keys,
|
134
|
+
jwt_secret=auth_config.get("jwt_secret", ""),
|
135
|
+
jwt_algorithm=auth_config.get("jwt_algorithm", "HS256")
|
136
|
+
)
|
137
|
+
|
138
|
+
def _convert_ssl_config(self, security_config: Dict[str, Any]) -> SSLConfig:
|
139
|
+
"""
|
140
|
+
Convert SSL configuration.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
security_config: Security configuration section
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
SSLConfig instance
|
147
|
+
"""
|
148
|
+
ssl_config = security_config.get("ssl", {})
|
149
|
+
|
150
|
+
# Fallback to legacy SSL config if not in security section
|
151
|
+
if not ssl_config:
|
152
|
+
ssl_config = self.config.get("ssl", {})
|
153
|
+
|
154
|
+
return SSLConfig(
|
155
|
+
enabled=ssl_config.get("enabled", False),
|
156
|
+
cert_file=ssl_config.get("cert_file"),
|
157
|
+
key_file=ssl_config.get("key_file"),
|
158
|
+
ca_cert=ssl_config.get("ca_cert"),
|
159
|
+
min_tls_version=ssl_config.get("min_tls_version", "TLSv1.2"),
|
160
|
+
verify_client=ssl_config.get("verify_client", False),
|
161
|
+
client_cert_required=ssl_config.get("client_cert_required", False)
|
162
|
+
)
|
163
|
+
|
164
|
+
def _convert_permission_config(self, security_config: Dict[str, Any]) -> PermissionConfig:
|
165
|
+
"""
|
166
|
+
Convert permissions configuration.
|
167
|
+
|
168
|
+
Args:
|
169
|
+
security_config: Security configuration section
|
170
|
+
|
171
|
+
Returns:
|
172
|
+
PermissionConfig instance
|
173
|
+
"""
|
174
|
+
permission_config = security_config.get("permissions", {})
|
175
|
+
|
176
|
+
# Fallback to legacy roles config if not in security section
|
177
|
+
if not permission_config:
|
178
|
+
roles_config = self.config.get("roles", {})
|
179
|
+
permission_config = {
|
180
|
+
"enabled": roles_config.get("enabled", True),
|
181
|
+
"roles_file": roles_config.get("config_file", "roles.json"),
|
182
|
+
"default_role": "user"
|
183
|
+
}
|
184
|
+
|
185
|
+
return PermissionConfig(
|
186
|
+
enabled=permission_config.get("enabled", True),
|
187
|
+
roles_file=permission_config.get("roles_file", "roles.json"),
|
188
|
+
default_role=permission_config.get("default_role", "user"),
|
189
|
+
deny_by_default=permission_config.get("deny_by_default", True)
|
190
|
+
)
|
191
|
+
|
192
|
+
def _convert_rate_limit_config(self, security_config: Dict[str, Any]) -> RateLimitConfig:
|
193
|
+
"""
|
194
|
+
Convert rate limit configuration.
|
195
|
+
|
196
|
+
Args:
|
197
|
+
security_config: Security configuration section
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
RateLimitConfig instance
|
201
|
+
"""
|
202
|
+
rate_limit_config = security_config.get("rate_limit", {})
|
203
|
+
|
204
|
+
return RateLimitConfig(
|
205
|
+
enabled=rate_limit_config.get("enabled", True),
|
206
|
+
requests_per_minute=rate_limit_config.get("requests_per_minute", 60),
|
207
|
+
requests_per_hour=rate_limit_config.get("requests_per_hour", 1000),
|
208
|
+
burst_limit=rate_limit_config.get("burst_limit", 10),
|
209
|
+
by_ip=rate_limit_config.get("by_ip", True),
|
210
|
+
by_user=rate_limit_config.get("by_user", True)
|
211
|
+
)
|
212
|
+
|
213
|
+
def validate_request(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
214
|
+
"""
|
215
|
+
Validate request through mcp_security_framework.
|
216
|
+
|
217
|
+
Args:
|
218
|
+
request_data: Request data dictionary
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
Validation result dictionary
|
222
|
+
"""
|
223
|
+
logger.debug(f"Security manager available: {self.security_manager is not None}")
|
224
|
+
if not self.security_manager:
|
225
|
+
# Fallback validation when framework is not available
|
226
|
+
logger.debug("Using fallback validation")
|
227
|
+
return self._fallback_validation(request_data)
|
228
|
+
|
229
|
+
try:
|
230
|
+
# Convert request data to SecurityRequest
|
231
|
+
security_request = self._create_security_request(request_data)
|
232
|
+
|
233
|
+
# Validate through security framework
|
234
|
+
result = self.security_manager.validate_request(security_request)
|
235
|
+
|
236
|
+
return result.to_dict()
|
237
|
+
|
238
|
+
except Exception as e:
|
239
|
+
logger.error(f"Security validation failed: {e}")
|
240
|
+
return {
|
241
|
+
"is_valid": False,
|
242
|
+
"error_code": -32603,
|
243
|
+
"error_message": f"Security validation error: {str(e)}",
|
244
|
+
"roles": [],
|
245
|
+
"user_id": None
|
246
|
+
}
|
247
|
+
|
248
|
+
def _create_security_request(self, request_data: Dict[str, Any]) -> SecurityRequest:
|
249
|
+
"""
|
250
|
+
Create SecurityRequest from request data.
|
251
|
+
|
252
|
+
Args:
|
253
|
+
request_data: Request data dictionary
|
254
|
+
|
255
|
+
Returns:
|
256
|
+
SecurityRequest instance
|
257
|
+
"""
|
258
|
+
return SecurityRequest(
|
259
|
+
method=request_data.get("method", "GET"),
|
260
|
+
path=request_data.get("path", "/"),
|
261
|
+
headers=request_data.get("headers", {}),
|
262
|
+
query_params=request_data.get("query_params", {}),
|
263
|
+
client_ip=request_data.get("client_ip", "unknown"),
|
264
|
+
body=request_data.get("body", {})
|
265
|
+
)
|
266
|
+
|
267
|
+
def _fallback_validation(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
268
|
+
"""
|
269
|
+
Fallback validation when mcp_security_framework is not available.
|
270
|
+
|
271
|
+
Args:
|
272
|
+
request_data: Request data dictionary
|
273
|
+
|
274
|
+
Returns:
|
275
|
+
Validation result dictionary
|
276
|
+
"""
|
277
|
+
# Simple API key validation as fallback
|
278
|
+
headers = request_data.get("headers", {})
|
279
|
+
query_params = request_data.get("query_params", {})
|
280
|
+
body = request_data.get("body", {})
|
281
|
+
|
282
|
+
# Check for API key in headers (FastAPI converts headers to lowercase)
|
283
|
+
api_key = headers.get("x-api-key") or headers.get("X-API-Key")
|
284
|
+
logger.debug(f"API key from headers: {api_key}")
|
285
|
+
|
286
|
+
# Check for API key in query parameters
|
287
|
+
if not api_key:
|
288
|
+
api_key = query_params.get("api_key")
|
289
|
+
logger.debug(f"API key from query params: {api_key}")
|
290
|
+
|
291
|
+
# Check for API key in JSON-RPC body
|
292
|
+
if not api_key and isinstance(body, dict):
|
293
|
+
api_key = body.get("params", {}).get("api_key")
|
294
|
+
logger.debug(f"API key from body: {api_key}")
|
295
|
+
|
296
|
+
# Get API keys from config
|
297
|
+
api_keys = self._get_api_keys()
|
298
|
+
logger.debug(f"Available API keys: {list(api_keys.keys())}")
|
299
|
+
|
300
|
+
if api_key and api_key in api_keys:
|
301
|
+
return {
|
302
|
+
"is_valid": True,
|
303
|
+
"error_code": None,
|
304
|
+
"error_message": None,
|
305
|
+
"roles": ["user"],
|
306
|
+
"user_id": api_keys[api_key]
|
307
|
+
}
|
308
|
+
else:
|
309
|
+
return {
|
310
|
+
"is_valid": False,
|
311
|
+
"error_code": -32000,
|
312
|
+
"error_message": "API key not provided or invalid",
|
313
|
+
"roles": [],
|
314
|
+
"user_id": None
|
315
|
+
}
|
316
|
+
|
317
|
+
def _get_api_keys(self) -> Dict[str, str]:
|
318
|
+
"""
|
319
|
+
Get API keys from configuration.
|
320
|
+
|
321
|
+
Returns:
|
322
|
+
Dictionary mapping API keys to usernames
|
323
|
+
"""
|
324
|
+
# Try security config first
|
325
|
+
security_config = self.config.get("security", {})
|
326
|
+
auth_config = security_config.get("auth", {})
|
327
|
+
api_keys = auth_config.get("api_keys", {})
|
328
|
+
|
329
|
+
logger.debug(f"Security config: {security_config}")
|
330
|
+
logger.debug(f"Auth config: {auth_config}")
|
331
|
+
logger.debug(f"API keys from security config: {api_keys}")
|
332
|
+
|
333
|
+
# Fallback to legacy SSL config
|
334
|
+
if not api_keys:
|
335
|
+
ssl_config = self.config.get("ssl", {})
|
336
|
+
api_keys = ssl_config.get("api_keys", {})
|
337
|
+
logger.debug(f"API keys from SSL config: {api_keys}")
|
338
|
+
|
339
|
+
logger.info(f"Total API keys loaded: {len(api_keys)}")
|
340
|
+
return api_keys
|
341
|
+
|
342
|
+
def create_middleware(self, framework: str = "fastapi"):
|
343
|
+
"""
|
344
|
+
Create framework-specific middleware.
|
345
|
+
|
346
|
+
Args:
|
347
|
+
framework: Framework type (fastapi, flask, etc.)
|
348
|
+
|
349
|
+
Returns:
|
350
|
+
Middleware instance
|
351
|
+
"""
|
352
|
+
if not self.security_manager:
|
353
|
+
logger.warning("Cannot create middleware: security framework not available")
|
354
|
+
return None
|
355
|
+
|
356
|
+
try:
|
357
|
+
if framework == "fastapi":
|
358
|
+
from mcp_security_framework.middleware import create_fastapi_security_middleware
|
359
|
+
return create_fastapi_security_middleware(self.security_manager)
|
360
|
+
else:
|
361
|
+
raise ValueError(f"Unsupported framework: {framework}")
|
362
|
+
except Exception as e:
|
363
|
+
logger.error(f"Failed to create middleware: {e}")
|
364
|
+
return None
|
365
|
+
|
366
|
+
def is_available(self) -> bool:
|
367
|
+
"""
|
368
|
+
Check if security framework is available.
|
369
|
+
|
370
|
+
Returns:
|
371
|
+
True if framework is available, False otherwise
|
372
|
+
"""
|
373
|
+
return SECURITY_FRAMEWORK_AVAILABLE and self.security_manager is not None
|
@@ -0,0 +1,239 @@
|
|
1
|
+
"""
|
2
|
+
Security Factory for creating security components.
|
3
|
+
|
4
|
+
This module provides factory methods for creating security adapters, managers,
|
5
|
+
and middleware components with proper configuration and error handling.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
from typing import Dict, Any, Optional
|
10
|
+
|
11
|
+
from mcp_proxy_adapter.core.logging import logger
|
12
|
+
from .security_adapter import SecurityAdapter
|
13
|
+
|
14
|
+
|
15
|
+
class SecurityFactory:
|
16
|
+
"""
|
17
|
+
Factory for creating security components.
|
18
|
+
|
19
|
+
Provides static methods to create security adapters, managers,
|
20
|
+
and middleware components with proper configuration handling.
|
21
|
+
"""
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def create_security_adapter(config: Dict[str, Any]) -> SecurityAdapter:
|
25
|
+
"""
|
26
|
+
Create SecurityAdapter from configuration.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
config: mcp_proxy_adapter configuration dictionary
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
SecurityAdapter instance
|
33
|
+
"""
|
34
|
+
try:
|
35
|
+
adapter = SecurityAdapter(config)
|
36
|
+
logger.info("Security adapter created successfully")
|
37
|
+
return adapter
|
38
|
+
except Exception as e:
|
39
|
+
logger.error(f"Failed to create security adapter: {e}")
|
40
|
+
raise
|
41
|
+
|
42
|
+
@staticmethod
|
43
|
+
def create_security_manager(config: Dict[str, Any]):
|
44
|
+
"""
|
45
|
+
Create SecurityManager from configuration.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
config: mcp_proxy_adapter configuration dictionary
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
SecurityManager instance or None if not available
|
52
|
+
"""
|
53
|
+
try:
|
54
|
+
adapter = SecurityFactory.create_security_adapter(config)
|
55
|
+
return adapter.security_manager
|
56
|
+
except Exception as e:
|
57
|
+
logger.error(f"Failed to create security manager: {e}")
|
58
|
+
return None
|
59
|
+
|
60
|
+
@staticmethod
|
61
|
+
def create_middleware(config: Dict[str, Any], framework: str = "fastapi"):
|
62
|
+
"""
|
63
|
+
Create framework-specific security middleware.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
config: mcp_proxy_adapter configuration dictionary
|
67
|
+
framework: Framework type (fastapi, flask, etc.)
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
Middleware instance or None if creation failed
|
71
|
+
"""
|
72
|
+
try:
|
73
|
+
adapter = SecurityFactory.create_security_adapter(config)
|
74
|
+
middleware = adapter.create_middleware(framework)
|
75
|
+
|
76
|
+
if middleware:
|
77
|
+
logger.info(f"Security middleware created for {framework}")
|
78
|
+
else:
|
79
|
+
logger.warning(f"Failed to create security middleware for {framework}")
|
80
|
+
|
81
|
+
return middleware
|
82
|
+
|
83
|
+
except Exception as e:
|
84
|
+
logger.error(f"Failed to create security middleware: {e}")
|
85
|
+
return None
|
86
|
+
|
87
|
+
@staticmethod
|
88
|
+
def create_fastapi_middleware(config: Dict[str, Any]):
|
89
|
+
"""
|
90
|
+
Create FastAPI-specific security middleware.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
config: mcp_proxy_adapter configuration dictionary
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
FastAPI middleware instance or None if creation failed
|
97
|
+
"""
|
98
|
+
return SecurityFactory.create_middleware(config, "fastapi")
|
99
|
+
|
100
|
+
@staticmethod
|
101
|
+
def validate_config(config: Dict[str, Any]) -> bool:
|
102
|
+
"""
|
103
|
+
Validate security configuration.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
config: Configuration dictionary to validate
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
True if configuration is valid, False otherwise
|
110
|
+
"""
|
111
|
+
try:
|
112
|
+
# Check if security section exists
|
113
|
+
security_config = config.get("security", {})
|
114
|
+
|
115
|
+
# Validate required fields
|
116
|
+
if not isinstance(security_config, dict):
|
117
|
+
logger.error("Security configuration must be a dictionary")
|
118
|
+
return False
|
119
|
+
|
120
|
+
# Validate auth configuration
|
121
|
+
auth_config = security_config.get("auth", {})
|
122
|
+
if not isinstance(auth_config, dict):
|
123
|
+
logger.error("Auth configuration must be a dictionary")
|
124
|
+
return False
|
125
|
+
|
126
|
+
# Validate SSL configuration
|
127
|
+
ssl_config = security_config.get("ssl", {})
|
128
|
+
if not isinstance(ssl_config, dict):
|
129
|
+
logger.error("SSL configuration must be a dictionary")
|
130
|
+
return False
|
131
|
+
|
132
|
+
# Validate permissions configuration
|
133
|
+
permissions_config = security_config.get("permissions", {})
|
134
|
+
if not isinstance(permissions_config, dict):
|
135
|
+
logger.error("Permissions configuration must be a dictionary")
|
136
|
+
return False
|
137
|
+
|
138
|
+
# Validate rate limit configuration
|
139
|
+
rate_limit_config = security_config.get("rate_limit", {})
|
140
|
+
if not isinstance(rate_limit_config, dict):
|
141
|
+
logger.error("Rate limit configuration must be a dictionary")
|
142
|
+
return False
|
143
|
+
|
144
|
+
logger.info("Security configuration validation passed")
|
145
|
+
return True
|
146
|
+
|
147
|
+
except Exception as e:
|
148
|
+
logger.error(f"Configuration validation failed: {e}")
|
149
|
+
return False
|
150
|
+
|
151
|
+
@staticmethod
|
152
|
+
def get_default_config() -> Dict[str, Any]:
|
153
|
+
"""
|
154
|
+
Get default security configuration.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
Default security configuration dictionary
|
158
|
+
"""
|
159
|
+
return {
|
160
|
+
"security": {
|
161
|
+
"framework": "mcp_security_framework",
|
162
|
+
"enabled": True,
|
163
|
+
"auth": {
|
164
|
+
"enabled": True,
|
165
|
+
"methods": ["api_key"],
|
166
|
+
"api_keys": {},
|
167
|
+
"jwt_secret": "",
|
168
|
+
"jwt_algorithm": "HS256"
|
169
|
+
},
|
170
|
+
"ssl": {
|
171
|
+
"enabled": False,
|
172
|
+
"cert_file": None,
|
173
|
+
"key_file": None,
|
174
|
+
"ca_cert": None,
|
175
|
+
"min_tls_version": "TLSv1.2",
|
176
|
+
"verify_client": False,
|
177
|
+
"client_cert_required": False
|
178
|
+
},
|
179
|
+
"permissions": {
|
180
|
+
"enabled": True,
|
181
|
+
"roles_file": "roles.json",
|
182
|
+
"default_role": "user",
|
183
|
+
"deny_by_default": True
|
184
|
+
},
|
185
|
+
"rate_limit": {
|
186
|
+
"enabled": True,
|
187
|
+
"requests_per_minute": 60,
|
188
|
+
"requests_per_hour": 1000,
|
189
|
+
"burst_limit": 10,
|
190
|
+
"by_ip": True,
|
191
|
+
"by_user": True
|
192
|
+
}
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
@staticmethod
|
197
|
+
def merge_config(base_config: Dict[str, Any], security_config: Dict[str, Any]) -> Dict[str, Any]:
|
198
|
+
"""
|
199
|
+
Merge security configuration into base configuration.
|
200
|
+
|
201
|
+
Args:
|
202
|
+
base_config: Base configuration dictionary
|
203
|
+
security_config: Security configuration to merge
|
204
|
+
|
205
|
+
Returns:
|
206
|
+
Merged configuration dictionary
|
207
|
+
"""
|
208
|
+
try:
|
209
|
+
# Create a copy of base config
|
210
|
+
merged_config = base_config.copy()
|
211
|
+
|
212
|
+
# Merge security configuration
|
213
|
+
if "security" not in merged_config:
|
214
|
+
merged_config["security"] = {}
|
215
|
+
|
216
|
+
# Deep merge security configuration
|
217
|
+
SecurityFactory._deep_merge(merged_config["security"], security_config)
|
218
|
+
|
219
|
+
logger.info("Security configuration merged successfully")
|
220
|
+
return merged_config
|
221
|
+
|
222
|
+
except Exception as e:
|
223
|
+
logger.error(f"Failed to merge security configuration: {e}")
|
224
|
+
return base_config
|
225
|
+
|
226
|
+
@staticmethod
|
227
|
+
def _deep_merge(base_dict: Dict[str, Any], update_dict: Dict[str, Any]) -> None:
|
228
|
+
"""
|
229
|
+
Deep merge two dictionaries.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
base_dict: Base dictionary to update
|
233
|
+
update_dict: Dictionary with updates
|
234
|
+
"""
|
235
|
+
for key, value in update_dict.items():
|
236
|
+
if key in base_dict and isinstance(base_dict[key], dict) and isinstance(value, dict):
|
237
|
+
SecurityFactory._deep_merge(base_dict[key], value)
|
238
|
+
else:
|
239
|
+
base_dict[key] = value
|
@@ -117,6 +117,7 @@ class Settings:
|
|
117
117
|
return {
|
118
118
|
"auto_discovery": config.get("commands.auto_discovery", True),
|
119
119
|
"discovery_path": config.get("commands.discovery_path", "mcp_proxy_adapter.commands"),
|
120
|
+
"auto_commands_path": config.get("commands.auto_commands_path"),
|
120
121
|
"custom_commands_path": config.get("commands.custom_commands_path")
|
121
122
|
}
|
122
123
|
|